react-native-smooth-clip-view 0.2.8 → 0.3.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 +86 -21
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +14 -2
- package/android/proguard-rules.pro +3 -3
- package/android/src/main/cpp/SmoothClipAndroid.h +10 -0
- package/android/src/main/cpp/SmoothClipBindings.cpp +896 -6
- package/android/src/main/cpp/SmoothClipRegistry.cpp +1067 -46
- package/android/src/main/cpp/SmoothClipTrace.h +25 -0
- package/android/src/main/java/com/smoothclipview/ClipGeometryNormalizer.kt +265 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipBindings.kt +21 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipModule.kt +283 -0
- package/android/src/main/java/com/smoothclipview/SmoothClipView.kt +385 -24
- package/android/src/main/java/com/smoothclipview/SmoothClipViewManager.kt +138 -24
- package/cpp/SmoothClipAnimationCurve.h +151 -122
- package/cpp/SmoothClipRegistry.h +102 -4
- package/cpp/SmoothClipSharedGeometry.h +146 -7
- package/cpp/SmoothClipVelocityTracker.h +13 -9
- package/ios/SmoothClipGeometry.h +184 -6
- package/ios/SmoothClipRegistry.mm +1033 -25
- package/ios/SmoothClipRegistrySnapshot.h +13 -0
- package/ios/SmoothClipTurboModule.cpp +857 -2
- package/ios/SmoothClipTurboModule.h +310 -0
- package/ios/SmoothClipView.mm +714 -183
- package/ios/SmoothClipViewRegistry.h +1 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
- package/ios/reactnativesmoothclipview.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
- package/lib/module/NativeSmoothClipModule.js.map +1 -1
- package/lib/module/SmoothClipView.ios.js +22 -6
- package/lib/module/SmoothClipView.ios.js.map +1 -1
- package/lib/module/SmoothClipView.js +1 -46
- package/lib/module/SmoothClipView.js.map +1 -1
- package/lib/module/SmoothClipViewNativeComponent.ts +27 -1
- package/lib/module/capabilities.js +4 -0
- package/lib/module/capabilities.js.map +1 -0
- package/lib/module/capabilities.native.js +36 -0
- package/lib/module/capabilities.native.js.map +1 -0
- package/lib/module/capabilityTypes.js +11 -0
- package/lib/module/capabilityTypes.js.map +1 -0
- package/lib/module/driverState.js +0 -28
- package/lib/module/driverState.js.map +1 -1
- package/lib/module/drivers.js +1 -281
- package/lib/module/drivers.js.map +1 -1
- package/lib/module/drivers.native.js +340 -75
- package/lib/module/drivers.native.js.map +1 -1
- package/lib/module/geometry.js +147 -17
- package/lib/module/geometry.js.map +1 -1
- package/lib/module/groupDriverTypes.js +4 -0
- package/lib/module/groupDriverTypes.js.map +1 -0
- package/lib/module/groupDrivers.android.js +4 -0
- package/lib/module/groupDrivers.android.js.map +1 -0
- package/lib/module/groupDrivers.ios.js +4 -0
- package/lib/module/groupDrivers.ios.js.map +1 -0
- package/lib/module/groupDrivers.js +4 -0
- package/lib/module/groupDrivers.js.map +1 -0
- package/lib/module/groupDrivers.native.js +799 -0
- package/lib/module/groupDrivers.native.js.map +1 -0
- package/lib/module/index.js +3 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/presentationProtocol.js +15 -0
- package/lib/module/presentationProtocol.js.map +1 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts +25 -0
- package/lib/typescript/src/NativeSmoothClipModule.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.d.ts +2 -10
- package/lib/typescript/src/SmoothClipView.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipView.ios.d.ts +133 -2
- package/lib/typescript/src/SmoothClipView.ios.d.ts.map +1 -1
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/SmoothClipViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/capabilities.d.ts +3 -0
- package/lib/typescript/src/capabilities.d.ts.map +1 -0
- package/lib/typescript/src/capabilities.native.d.ts +3 -0
- package/lib/typescript/src/capabilities.native.d.ts.map +1 -0
- package/lib/typescript/src/capabilityTypes.d.ts +10 -0
- package/lib/typescript/src/capabilityTypes.d.ts.map +1 -0
- package/lib/typescript/src/driverState.d.ts +1 -6
- package/lib/typescript/src/driverState.d.ts.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +39 -11
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.d.ts +2 -4
- package/lib/typescript/src/drivers.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts.map +1 -1
- package/lib/typescript/src/geometry.d.ts +36 -2
- package/lib/typescript/src/geometry.d.ts.map +1 -1
- package/lib/typescript/src/groupDriverTypes.d.ts +66 -0
- package/lib/typescript/src/groupDriverTypes.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.android.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.android.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts +2 -0
- package/lib/typescript/src/groupDrivers.ios.d.ts.map +1 -0
- package/lib/typescript/src/groupDrivers.native.d.ts +4 -0
- package/lib/typescript/src/groupDrivers.native.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/presentationProtocol.d.ts +4 -0
- package/lib/typescript/src/presentationProtocol.d.ts.map +1 -0
- package/package.json +10 -11
- package/src/NativeSmoothClipModule.ts +275 -0
- package/src/SmoothClipView.ios.tsx +30 -11
- package/src/SmoothClipView.tsx +2 -76
- package/src/SmoothClipViewNativeComponent.ts +27 -1
- package/src/capabilities.native.ts +69 -0
- package/src/capabilities.ts +2 -0
- package/src/capabilityTypes.ts +17 -0
- package/src/driverState.ts +1 -41
- package/src/driverTypes.ts +56 -11
- package/src/drivers.native.ts +921 -151
- package/src/drivers.ts +2 -423
- package/src/geometry.ts +231 -27
- package/src/groupDriverTypes.ts +117 -0
- package/src/groupDrivers.android.ts +1 -0
- package/src/groupDrivers.ios.ts +1 -0
- package/src/groupDrivers.native.ts +1189 -0
- package/src/groupDrivers.ts +1 -0
- package/src/index.ts +30 -2
- package/src/presentationProtocol.ts +28 -0
- package/lib/module/SmoothClipView.web.js +0 -54
- package/lib/module/SmoothClipView.web.js.map +0 -1
- package/lib/module/index.web.js +0 -8
- package/lib/module/index.web.js.map +0 -1
- package/lib/module/webClipPath.js +0 -25
- package/lib/module/webClipPath.js.map +0 -1
- package/lib/typescript/src/SmoothClipView.web.d.ts +0 -10
- package/lib/typescript/src/SmoothClipView.web.d.ts.map +0 -1
- package/lib/typescript/src/index.web.d.ts +0 -9
- package/lib/typescript/src/index.web.d.ts.map +0 -1
- package/lib/typescript/src/webClipPath.d.ts +0 -7
- package/lib/typescript/src/webClipPath.d.ts.map +0 -1
- package/src/SmoothClipView.web.tsx +0 -66
- package/src/index.web.ts +0 -23
- package/src/webClipPath.ts +0 -30
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#pragma once
|
|
2
2
|
|
|
3
|
+
#include "SmoothClipRegistry.h"
|
|
4
|
+
|
|
3
5
|
#include <algorithm>
|
|
4
6
|
#include <cmath>
|
|
5
7
|
|
|
@@ -11,23 +13,110 @@ struct NormalizedClip {
|
|
|
11
13
|
double right = 0;
|
|
12
14
|
double bottom = 0;
|
|
13
15
|
double radius = 0;
|
|
16
|
+
double topLeftRadius = 0;
|
|
17
|
+
double topRightRadius = 0;
|
|
18
|
+
double bottomRightRadius = 0;
|
|
19
|
+
double bottomLeftRadius = 0;
|
|
20
|
+
ClipCurve curve = ClipCurve::Circular;
|
|
14
21
|
};
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
inline double SmoothClipResolvedRadius(double overrideValue, double radius) {
|
|
24
|
+
return std::isfinite(overrideValue) ? overrideValue : radius;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns true only when host normalization is the identity for this geometry.
|
|
29
|
+
*
|
|
30
|
+
* Timing/keyframe renderers interpolate already-normalized native values on
|
|
31
|
+
* iOS, while Android interpolates presentation scalars and normalizes each
|
|
32
|
+
* delivered frame. Those operations commute exactly when every anchor stays
|
|
33
|
+
* inside one host and the CSS corner-overlap rule never activates. Because
|
|
34
|
+
* each condition below is linear, it also holds for every convex interpolation
|
|
35
|
+
* between accepted anchors.
|
|
36
|
+
*
|
|
37
|
+
* Static clipping deliberately does not use this gate: out-of-bounds geometry
|
|
38
|
+
* remains valid and is still clipped by SmoothClipNormalizeV2.
|
|
39
|
+
*/
|
|
40
|
+
inline bool SmoothClipGeometryNormalizationIsIdentity(
|
|
41
|
+
const Geometry &geometry,
|
|
42
|
+
double hostWidth,
|
|
43
|
+
double hostHeight) {
|
|
44
|
+
const double topLeft = SmoothClipResolvedRadius(
|
|
45
|
+
geometry.topLeftRadius, geometry.radius);
|
|
46
|
+
const double topRight = SmoothClipResolvedRadius(
|
|
47
|
+
geometry.topRightRadius, geometry.radius);
|
|
48
|
+
const double bottomRight = SmoothClipResolvedRadius(
|
|
49
|
+
geometry.bottomRightRadius, geometry.radius);
|
|
50
|
+
const double bottomLeft = SmoothClipResolvedRadius(
|
|
51
|
+
geometry.bottomLeftRadius, geometry.radius);
|
|
52
|
+
const double right = geometry.x + geometry.width;
|
|
53
|
+
const double bottom = geometry.y + geometry.height;
|
|
54
|
+
const double topRadii = topLeft + topRight;
|
|
55
|
+
const double bottomRadii = bottomLeft + bottomRight;
|
|
56
|
+
const double leftRadii = topLeft + bottomLeft;
|
|
57
|
+
const double rightRadii = topRight + bottomRight;
|
|
58
|
+
const double values[] = {
|
|
59
|
+
geometry.x,
|
|
60
|
+
geometry.y,
|
|
61
|
+
geometry.width,
|
|
62
|
+
geometry.height,
|
|
63
|
+
geometry.radius,
|
|
64
|
+
topLeft,
|
|
65
|
+
topRight,
|
|
66
|
+
bottomRight,
|
|
67
|
+
bottomLeft,
|
|
68
|
+
right,
|
|
69
|
+
bottom,
|
|
70
|
+
topRadii,
|
|
71
|
+
bottomRadii,
|
|
72
|
+
leftRadii,
|
|
73
|
+
rightRadii,
|
|
74
|
+
hostWidth,
|
|
75
|
+
hostHeight,
|
|
76
|
+
};
|
|
77
|
+
for (const double value : values) {
|
|
78
|
+
if (!std::isfinite(value)) return false;
|
|
79
|
+
}
|
|
80
|
+
return hostWidth >= 0 && hostHeight >= 0 &&
|
|
81
|
+
geometry.x >= 0 && geometry.y >= 0 &&
|
|
82
|
+
geometry.width >= 0 && geometry.height >= 0 &&
|
|
83
|
+
right <= hostWidth && bottom <= hostHeight &&
|
|
84
|
+
topLeft >= 0 && topRight >= 0 &&
|
|
85
|
+
bottomRight >= 0 && bottomLeft >= 0 &&
|
|
86
|
+
topRadii <= geometry.width &&
|
|
87
|
+
bottomRadii <= geometry.width &&
|
|
88
|
+
leftRadii <= geometry.height &&
|
|
89
|
+
rightRadii <= geometry.height;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
inline bool SmoothClipNormalizeV2(
|
|
21
93
|
double x,
|
|
22
94
|
double y,
|
|
23
95
|
double width,
|
|
24
96
|
double height,
|
|
25
97
|
double radius,
|
|
98
|
+
double topLeftRadius,
|
|
99
|
+
double topRightRadius,
|
|
100
|
+
double bottomRightRadius,
|
|
101
|
+
double bottomLeftRadius,
|
|
102
|
+
ClipCurve curve,
|
|
26
103
|
double hostWidth,
|
|
27
104
|
double hostHeight,
|
|
28
105
|
NormalizedClip &out) {
|
|
106
|
+
const double resolvedTopLeft =
|
|
107
|
+
SmoothClipResolvedRadius(topLeftRadius, radius);
|
|
108
|
+
const double resolvedTopRight =
|
|
109
|
+
SmoothClipResolvedRadius(topRightRadius, radius);
|
|
110
|
+
const double resolvedBottomRight =
|
|
111
|
+
SmoothClipResolvedRadius(bottomRightRadius, radius);
|
|
112
|
+
const double resolvedBottomLeft =
|
|
113
|
+
SmoothClipResolvedRadius(bottomLeftRadius, radius);
|
|
29
114
|
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(width) ||
|
|
30
115
|
!std::isfinite(height) || !std::isfinite(radius) ||
|
|
116
|
+
!std::isfinite(resolvedTopLeft) ||
|
|
117
|
+
!std::isfinite(resolvedTopRight) ||
|
|
118
|
+
!std::isfinite(resolvedBottomRight) ||
|
|
119
|
+
!std::isfinite(resolvedBottomLeft) ||
|
|
31
120
|
!std::isfinite(hostWidth) || !std::isfinite(hostHeight)) {
|
|
32
121
|
return false;
|
|
33
122
|
}
|
|
@@ -42,9 +131,59 @@ inline bool SmoothClipNormalize(
|
|
|
42
131
|
out.bottom = std::min(boundedHostHeight, std::max(0.0, requestedBottom));
|
|
43
132
|
const double visibleWidth = std::max(0.0, out.right - out.left);
|
|
44
133
|
const double visibleHeight = std::max(0.0, out.bottom - out.top);
|
|
45
|
-
|
|
46
|
-
|
|
134
|
+
|
|
135
|
+
double topLeft = std::max(0.0, resolvedTopLeft);
|
|
136
|
+
double topRight = std::max(0.0, resolvedTopRight);
|
|
137
|
+
double bottomRight = std::max(0.0, resolvedBottomRight);
|
|
138
|
+
double bottomLeft = std::max(0.0, resolvedBottomLeft);
|
|
139
|
+
double scale = 1.0;
|
|
140
|
+
const auto includeLimit = [&scale](double available, double requested) {
|
|
141
|
+
if (requested > 0) scale = std::min(scale, available / requested);
|
|
142
|
+
};
|
|
143
|
+
includeLimit(visibleWidth, topLeft + topRight);
|
|
144
|
+
includeLimit(visibleWidth, bottomLeft + bottomRight);
|
|
145
|
+
includeLimit(visibleHeight, topLeft + bottomLeft);
|
|
146
|
+
includeLimit(visibleHeight, topRight + bottomRight);
|
|
147
|
+
scale = std::max(0.0, std::min(1.0, scale));
|
|
148
|
+
out.topLeftRadius = topLeft * scale;
|
|
149
|
+
out.topRightRadius = topRight * scale;
|
|
150
|
+
out.bottomRightRadius = bottomRight * scale;
|
|
151
|
+
out.bottomLeftRadius = bottomLeft * scale;
|
|
152
|
+
const bool uniform = out.topLeftRadius == out.topRightRadius &&
|
|
153
|
+
out.topLeftRadius == out.bottomRightRadius &&
|
|
154
|
+
out.topLeftRadius == out.bottomLeftRadius;
|
|
155
|
+
out.radius = uniform ? out.topLeftRadius : 0.0;
|
|
156
|
+
out.curve = curve;
|
|
47
157
|
return true;
|
|
48
158
|
}
|
|
49
159
|
|
|
160
|
+
// Shared clip normalization emitting edges (Android's Outline consumes
|
|
161
|
+
// edges). Mirrors ios/SmoothClipGeometry.h (SmoothClipNormalizeGeometry) and
|
|
162
|
+
// ClipGeometryNormalizer.kt (normalizeClipGeometryPx); a change here must
|
|
163
|
+
// land in all three.
|
|
164
|
+
inline bool SmoothClipNormalize(
|
|
165
|
+
double x,
|
|
166
|
+
double y,
|
|
167
|
+
double width,
|
|
168
|
+
double height,
|
|
169
|
+
double radius,
|
|
170
|
+
double hostWidth,
|
|
171
|
+
double hostHeight,
|
|
172
|
+
NormalizedClip &out) {
|
|
173
|
+
return SmoothClipNormalizeV2(
|
|
174
|
+
x,
|
|
175
|
+
y,
|
|
176
|
+
width,
|
|
177
|
+
height,
|
|
178
|
+
radius,
|
|
179
|
+
radius,
|
|
180
|
+
radius,
|
|
181
|
+
radius,
|
|
182
|
+
radius,
|
|
183
|
+
ClipCurve::Circular,
|
|
184
|
+
hostWidth,
|
|
185
|
+
hostHeight,
|
|
186
|
+
out);
|
|
187
|
+
}
|
|
188
|
+
|
|
50
189
|
} // namespace smoothclip
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
namespace smoothclip {
|
|
7
7
|
|
|
8
8
|
// The two most recent interactive samples used by `initialVelocity: 'inherit'`.
|
|
9
|
-
// Channels are the
|
|
10
|
-
//
|
|
9
|
+
// Channels are the eleven continuous V2 presentation scalars. Scale is stored
|
|
10
|
+
// so histories remain coherent but excluded from velocity projection; each of
|
|
11
|
+
// the four radius channels contributes one quarter of the legacy radius weight.
|
|
11
12
|
// iOS records normalized (host-clamped) geometry, Android raw DIP — the
|
|
12
13
|
// projection only needs both samples and the target in the same space.
|
|
13
14
|
// Shared by ios/SmoothClipView.mm and android/.../SmoothClipRegistry.cpp; a
|
|
@@ -15,8 +16,8 @@ namespace smoothclip {
|
|
|
15
16
|
struct VelocitySampleHistory {
|
|
16
17
|
bool hasPrevious = false;
|
|
17
18
|
bool hasLatest = false;
|
|
18
|
-
std::array<double,
|
|
19
|
-
std::array<double,
|
|
19
|
+
std::array<double, 11> previous{};
|
|
20
|
+
std::array<double, 11> latest{};
|
|
20
21
|
double previousTimeS = 0;
|
|
21
22
|
double latestTimeS = 0;
|
|
22
23
|
};
|
|
@@ -40,7 +41,7 @@ inline constexpr double kVelocityFullCreditS = 1.0 / 60.0;
|
|
|
40
41
|
|
|
41
42
|
inline void recordVelocitySample(
|
|
42
43
|
VelocitySampleHistory &history,
|
|
43
|
-
const std::array<double,
|
|
44
|
+
const std::array<double, 11> &channels,
|
|
44
45
|
double nowS) {
|
|
45
46
|
if (history.hasLatest && channels == history.latest) {
|
|
46
47
|
// Identical re-record (a `from` seed equal to the last interactive
|
|
@@ -97,7 +98,7 @@ inline double velocityStalenessCredit(double holdS) {
|
|
|
97
98
|
// Android seeds each integrator channel with scalar·displacement.
|
|
98
99
|
inline double inheritedVelocity(
|
|
99
100
|
const VelocitySampleHistory &history,
|
|
100
|
-
const std::array<double,
|
|
101
|
+
const std::array<double, 11> &target,
|
|
101
102
|
double nowS) {
|
|
102
103
|
if (!history.hasPrevious || !history.hasLatest) return 0;
|
|
103
104
|
const double elapsed = history.latestTimeS - history.previousTimeS;
|
|
@@ -107,11 +108,14 @@ inline double inheritedVelocity(
|
|
|
107
108
|
}
|
|
108
109
|
double numerator = 0;
|
|
109
110
|
double denominator = 0;
|
|
110
|
-
for (int index = 0; index <
|
|
111
|
+
for (int index = 0; index < 11; index += 1) {
|
|
112
|
+
const double weight = index >= 4 && index <= 7
|
|
113
|
+
? 0.25
|
|
114
|
+
: (index == 10 ? 0.0 : 1.0);
|
|
111
115
|
const double sampleDelta = history.latest[index] - history.previous[index];
|
|
112
116
|
const double destinationDelta = target[index] - history.latest[index];
|
|
113
|
-
numerator += sampleDelta * destinationDelta;
|
|
114
|
-
denominator += destinationDelta * destinationDelta;
|
|
117
|
+
numerator += weight * sampleDelta * destinationDelta;
|
|
118
|
+
denominator += weight * destinationDelta * destinationDelta;
|
|
115
119
|
}
|
|
116
120
|
if (denominator <= 1e-12) return 0;
|
|
117
121
|
const double result = numerator / elapsed / denominator * credit;
|
package/ios/SmoothClipGeometry.h
CHANGED
|
@@ -2,23 +2,77 @@
|
|
|
2
2
|
|
|
3
3
|
#include <math.h>
|
|
4
4
|
|
|
5
|
+
typedef struct {
|
|
6
|
+
CGFloat topLeft;
|
|
7
|
+
CGFloat topRight;
|
|
8
|
+
CGFloat bottomRight;
|
|
9
|
+
CGFloat bottomLeft;
|
|
10
|
+
} SmoothClipCornerRadii;
|
|
11
|
+
|
|
12
|
+
typedef NS_ENUM(NSInteger, SmoothClipCornerCurve) {
|
|
13
|
+
SmoothClipCornerCurveCircular = 0,
|
|
14
|
+
SmoothClipCornerCurveContinuous = 1,
|
|
15
|
+
};
|
|
16
|
+
|
|
5
17
|
typedef struct {
|
|
6
18
|
CGRect rect;
|
|
19
|
+
// Retained for the V1 uniform-radius contract. V2 callers should consume
|
|
20
|
+
// `radii`; this field is zero when the normalized radii are unequal.
|
|
7
21
|
CGFloat radius;
|
|
22
|
+
SmoothClipCornerRadii radii;
|
|
23
|
+
SmoothClipCornerCurve curve;
|
|
8
24
|
} SmoothNormalizedClipGeometry;
|
|
9
25
|
|
|
10
|
-
|
|
11
|
-
|
|
26
|
+
NS_INLINE BOOL SmoothClipCornerRadiiEqual(
|
|
27
|
+
SmoothClipCornerRadii first,
|
|
28
|
+
SmoothClipCornerRadii second) {
|
|
29
|
+
return first.topLeft == second.topLeft &&
|
|
30
|
+
first.topRight == second.topRight &&
|
|
31
|
+
first.bottomRight == second.bottomRight &&
|
|
32
|
+
first.bottomLeft == second.bottomLeft;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
NS_INLINE BOOL SmoothClipCornerRadiiAreUniform(
|
|
36
|
+
SmoothClipCornerRadii radii) {
|
|
37
|
+
return radii.topLeft == radii.topRight &&
|
|
38
|
+
radii.topLeft == radii.bottomRight &&
|
|
39
|
+
radii.topLeft == radii.bottomLeft;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
NS_INLINE CGFloat SmoothClipSafeOverlapFactor(
|
|
43
|
+
CGFloat extent,
|
|
44
|
+
CGFloat firstRadius,
|
|
45
|
+
CGFloat secondRadius) {
|
|
46
|
+
const CGFloat sum = firstRadius + secondRadius;
|
|
47
|
+
return sum > 0 ? extent / sum : 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalizes the V2 rectangle and four radii without allocating.
|
|
52
|
+
*
|
|
53
|
+
* The proportional overlap rule matches CSS border radii and the shared
|
|
54
|
+
* Android/C++ contract: all corners use one scale factor, preserving their
|
|
55
|
+
* relative shape while no opposing pair exceeds its edge.
|
|
56
|
+
*/
|
|
57
|
+
NS_INLINE BOOL SmoothClipNormalizeGeometryV2(
|
|
12
58
|
CGFloat x,
|
|
13
59
|
CGFloat y,
|
|
14
60
|
CGFloat width,
|
|
15
61
|
CGFloat height,
|
|
16
|
-
CGFloat
|
|
62
|
+
CGFloat topLeftRadius,
|
|
63
|
+
CGFloat topRightRadius,
|
|
64
|
+
CGFloat bottomRightRadius,
|
|
65
|
+
CGFloat bottomLeftRadius,
|
|
66
|
+
NSInteger curveCode,
|
|
17
67
|
CGSize hostSize,
|
|
18
68
|
SmoothNormalizedClipGeometry *result) {
|
|
19
69
|
if (!isfinite(x) || !isfinite(y) || !isfinite(width) ||
|
|
20
|
-
!isfinite(height) || !isfinite(
|
|
21
|
-
!isfinite(
|
|
70
|
+
!isfinite(height) || !isfinite(topLeftRadius) ||
|
|
71
|
+
!isfinite(topRightRadius) || !isfinite(bottomRightRadius) ||
|
|
72
|
+
!isfinite(bottomLeftRadius) || !isfinite(hostSize.width) ||
|
|
73
|
+
!isfinite(hostSize.height) ||
|
|
74
|
+
(curveCode != SmoothClipCornerCurveCircular &&
|
|
75
|
+
curveCode != SmoothClipCornerCurveContinuous)) {
|
|
22
76
|
return NO;
|
|
23
77
|
}
|
|
24
78
|
|
|
@@ -35,7 +89,131 @@ NS_INLINE BOOL SmoothClipNormalizeGeometry(
|
|
|
35
89
|
const CGFloat visibleWidth = MAX(0, right - left);
|
|
36
90
|
const CGFloat visibleHeight = MAX(0, bottom - top);
|
|
37
91
|
|
|
92
|
+
SmoothClipCornerRadii radii = {
|
|
93
|
+
MAX(0, topLeftRadius),
|
|
94
|
+
MAX(0, topRightRadius),
|
|
95
|
+
MAX(0, bottomRightRadius),
|
|
96
|
+
MAX(0, bottomLeftRadius),
|
|
97
|
+
};
|
|
98
|
+
const CGFloat overlapScale = MIN(
|
|
99
|
+
1,
|
|
100
|
+
MIN(
|
|
101
|
+
MIN(
|
|
102
|
+
SmoothClipSafeOverlapFactor(
|
|
103
|
+
visibleWidth, radii.topLeft, radii.topRight),
|
|
104
|
+
SmoothClipSafeOverlapFactor(
|
|
105
|
+
visibleWidth, radii.bottomLeft, radii.bottomRight)),
|
|
106
|
+
MIN(
|
|
107
|
+
SmoothClipSafeOverlapFactor(
|
|
108
|
+
visibleHeight, radii.topLeft, radii.bottomLeft),
|
|
109
|
+
SmoothClipSafeOverlapFactor(
|
|
110
|
+
visibleHeight, radii.topRight, radii.bottomRight))));
|
|
111
|
+
radii.topLeft *= overlapScale;
|
|
112
|
+
radii.topRight *= overlapScale;
|
|
113
|
+
radii.bottomRight *= overlapScale;
|
|
114
|
+
radii.bottomLeft *= overlapScale;
|
|
115
|
+
|
|
38
116
|
result->rect = CGRectMake(left, top, visibleWidth, visibleHeight);
|
|
39
|
-
result->
|
|
117
|
+
result->radii = radii;
|
|
118
|
+
result->radius = SmoothClipCornerRadiiAreUniform(radii)
|
|
119
|
+
? radii.topLeft
|
|
120
|
+
: 0;
|
|
121
|
+
result->curve = (SmoothClipCornerCurve)curveCode;
|
|
40
122
|
return YES;
|
|
41
123
|
}
|
|
124
|
+
|
|
125
|
+
/** Mirrors the Android and JavaScript geometry contract without allocating. */
|
|
126
|
+
NS_INLINE BOOL SmoothClipNormalizeGeometry(
|
|
127
|
+
CGFloat x,
|
|
128
|
+
CGFloat y,
|
|
129
|
+
CGFloat width,
|
|
130
|
+
CGFloat height,
|
|
131
|
+
CGFloat radius,
|
|
132
|
+
CGSize hostSize,
|
|
133
|
+
SmoothNormalizedClipGeometry *result) {
|
|
134
|
+
return SmoothClipNormalizeGeometryV2(
|
|
135
|
+
x,
|
|
136
|
+
y,
|
|
137
|
+
width,
|
|
138
|
+
height,
|
|
139
|
+
radius,
|
|
140
|
+
radius,
|
|
141
|
+
radius,
|
|
142
|
+
radius,
|
|
143
|
+
SmoothClipCornerCurveCircular,
|
|
144
|
+
hostSize,
|
|
145
|
+
result);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Creates the stable nine-segment rounded-rectangle topology used by the
|
|
150
|
+
* unequal-corner CAShapeLayer mask. Every corner is one cubic, including a
|
|
151
|
+
* zero-radius corner, so Core Animation can interpolate paths without a
|
|
152
|
+
* topology change.
|
|
153
|
+
*/
|
|
154
|
+
NS_INLINE CGPathRef SmoothClipCreateRoundedRectPath(
|
|
155
|
+
CGRect rect,
|
|
156
|
+
SmoothClipCornerRadii radii,
|
|
157
|
+
SmoothClipCornerCurve curve) CF_RETURNS_RETAINED {
|
|
158
|
+
const CGFloat minX = CGRectGetMinX(rect);
|
|
159
|
+
const CGFloat minY = CGRectGetMinY(rect);
|
|
160
|
+
const CGFloat maxX = CGRectGetMaxX(rect);
|
|
161
|
+
const CGFloat maxY = CGRectGetMaxY(rect);
|
|
162
|
+
// Circular is the exact cubic-circle coefficient. The portable continuous
|
|
163
|
+
// fallback uses kappa=1 so each cubic joins its straight edges with zero
|
|
164
|
+
// curvature, matching the portable Android path. Hit testing derives
|
|
165
|
+
// from this rendered path rather than maintaining a second curve model.
|
|
166
|
+
// Uniform
|
|
167
|
+
// continuous shapes use CALayer.cornerCurve for Apple's platform-native
|
|
168
|
+
// compositor shape and are intentionally not pixel-identical cross-platform.
|
|
169
|
+
const CGFloat controlFactor =
|
|
170
|
+
curve == SmoothClipCornerCurveContinuous
|
|
171
|
+
? 1.0
|
|
172
|
+
: 0.5522847498307936;
|
|
173
|
+
|
|
174
|
+
CGMutablePathRef path = CGPathCreateMutable();
|
|
175
|
+
CGPathMoveToPoint(path, NULL, minX + radii.topLeft, minY);
|
|
176
|
+
CGPathAddLineToPoint(path, NULL, maxX - radii.topRight, minY);
|
|
177
|
+
CGPathAddCurveToPoint(
|
|
178
|
+
path,
|
|
179
|
+
NULL,
|
|
180
|
+
maxX - radii.topRight + radii.topRight * controlFactor,
|
|
181
|
+
minY,
|
|
182
|
+
maxX,
|
|
183
|
+
minY + radii.topRight - radii.topRight * controlFactor,
|
|
184
|
+
maxX,
|
|
185
|
+
minY + radii.topRight);
|
|
186
|
+
CGPathAddLineToPoint(path, NULL, maxX, maxY - radii.bottomRight);
|
|
187
|
+
CGPathAddCurveToPoint(
|
|
188
|
+
path,
|
|
189
|
+
NULL,
|
|
190
|
+
maxX,
|
|
191
|
+
maxY - radii.bottomRight + radii.bottomRight * controlFactor,
|
|
192
|
+
maxX - radii.bottomRight +
|
|
193
|
+
radii.bottomRight * controlFactor,
|
|
194
|
+
maxY,
|
|
195
|
+
maxX - radii.bottomRight,
|
|
196
|
+
maxY);
|
|
197
|
+
CGPathAddLineToPoint(path, NULL, minX + radii.bottomLeft, maxY);
|
|
198
|
+
CGPathAddCurveToPoint(
|
|
199
|
+
path,
|
|
200
|
+
NULL,
|
|
201
|
+
minX + radii.bottomLeft - radii.bottomLeft * controlFactor,
|
|
202
|
+
maxY,
|
|
203
|
+
minX,
|
|
204
|
+
maxY - radii.bottomLeft + radii.bottomLeft * controlFactor,
|
|
205
|
+
minX,
|
|
206
|
+
maxY - radii.bottomLeft);
|
|
207
|
+
CGPathAddLineToPoint(path, NULL, minX, minY + radii.topLeft);
|
|
208
|
+
CGPathAddCurveToPoint(
|
|
209
|
+
path,
|
|
210
|
+
NULL,
|
|
211
|
+
minX,
|
|
212
|
+
minY + radii.topLeft - radii.topLeft * controlFactor,
|
|
213
|
+
minX + radii.topLeft - radii.topLeft * controlFactor,
|
|
214
|
+
minY,
|
|
215
|
+
minX + radii.topLeft,
|
|
216
|
+
minY);
|
|
217
|
+
CGPathCloseSubpath(path);
|
|
218
|
+
return path;
|
|
219
|
+
}
|