squircle-view 0.0.0-stage → 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/LICENSE +20 -0
- package/README.md +82 -2
- package/SquircleView.podspec +22 -0
- package/android/CMakeLists.txt +17 -0
- package/android/build.gradle +78 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/SquircleGeometryJni.cpp +52 -0
- package/android/src/main/java/com/squircleview/SquircleViewManager.kt +72 -0
- package/android/src/main/java/com/squircleview/SquircleViewPackage.kt +16 -0
- package/android/src/main/java/com/squircleview/geometry/SquircleGeometry.kt +58 -0
- package/android/src/main/java/com/squircleview/style/SquircleStyle.kt +142 -0
- package/android/src/main/java/com/squircleview/views/SquircleView.kt +361 -0
- package/common/cpp/SquircleGeometry.cpp +248 -0
- package/common/cpp/SquircleGeometry.h +75 -0
- package/ios/Geometry/SquirclePath.h +21 -0
- package/ios/Geometry/SquirclePath.mm +39 -0
- package/ios/Layers/SquircleBorderLayer.h +24 -0
- package/ios/Layers/SquircleBorderLayer.m +102 -0
- package/ios/Views/SquircleView.h +16 -0
- package/ios/Views/SquircleView.mm +337 -0
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -0
- package/lib/module/components/squircle-view.js +22 -0
- package/lib/module/components/squircle-view.js.map +1 -0
- package/lib/module/components/squircle-view.native.js +23 -0
- package/lib/module/components/squircle-view.native.js.map +1 -0
- package/lib/module/constants/index.js +5 -0
- package/lib/module/constants/index.js.map +1 -0
- package/lib/module/constants/native.constants.js +10 -0
- package/lib/module/constants/native.constants.js.map +1 -0
- package/lib/module/constants/squircle.constants.js +5 -0
- package/lib/module/constants/squircle.constants.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/interfaces/index.js +5 -0
- package/lib/module/interfaces/index.js.map +1 -0
- package/lib/module/interfaces/squircle-style.interface.js +4 -0
- package/lib/module/interfaces/squircle-style.interface.js.map +1 -0
- package/lib/module/interfaces/squircle-view.interface.js +4 -0
- package/lib/module/interfaces/squircle-view.interface.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/root/index.js +4 -0
- package/lib/module/root/index.js.map +1 -0
- package/lib/module/types/index.js +5 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/types/native-component-registry.d.js +2 -0
- package/lib/module/types/native-component-registry.d.js.map +1 -0
- package/lib/module/types/native.types.js +4 -0
- package/lib/module/types/native.types.js.map +1 -0
- package/lib/module/types/squircle.types.js +4 -0
- package/lib/module/types/squircle.types.js.map +1 -0
- package/lib/module/utils/index.js +4 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/style.utils.js +22 -0
- package/lib/module/utils/style.utils.js.map +1 -0
- package/lib/module/views/SquircleViewNativeComponent.ts +12 -0
- package/lib/module/views/index.js +15 -0
- package/lib/module/views/index.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/components/index.d.ts +2 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -0
- package/lib/typescript/src/components/squircle-view.d.ts +5 -0
- package/lib/typescript/src/components/squircle-view.d.ts.map +1 -0
- package/lib/typescript/src/components/squircle-view.native.d.ts +5 -0
- package/lib/typescript/src/components/squircle-view.native.d.ts.map +1 -0
- package/lib/typescript/src/constants/index.d.ts +3 -0
- package/lib/typescript/src/constants/index.d.ts.map +1 -0
- package/lib/typescript/src/constants/native.constants.d.ts +8 -0
- package/lib/typescript/src/constants/native.constants.d.ts.map +1 -0
- package/lib/typescript/src/constants/squircle.constants.d.ts +3 -0
- package/lib/typescript/src/constants/squircle.constants.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/interfaces/index.d.ts +3 -0
- package/lib/typescript/src/interfaces/index.d.ts.map +1 -0
- package/lib/typescript/src/interfaces/squircle-style.interface.d.ts +7 -0
- package/lib/typescript/src/interfaces/squircle-style.interface.d.ts.map +1 -0
- package/lib/typescript/src/interfaces/squircle-view.interface.d.ts +10 -0
- package/lib/typescript/src/interfaces/squircle-view.interface.d.ts.map +1 -0
- package/lib/typescript/src/root/index.d.ts +6 -0
- package/lib/typescript/src/root/index.d.ts.map +1 -0
- package/lib/typescript/src/types/index.d.ts +3 -0
- package/lib/typescript/src/types/index.d.ts.map +1 -0
- package/lib/typescript/src/types/native.types.d.ts +5 -0
- package/lib/typescript/src/types/native.types.d.ts.map +1 -0
- package/lib/typescript/src/types/squircle.types.d.ts +6 -0
- package/lib/typescript/src/types/squircle.types.d.ts.map +1 -0
- package/lib/typescript/src/utils/index.d.ts +2 -0
- package/lib/typescript/src/utils/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/style.utils.d.ts +6 -0
- package/lib/typescript/src/utils/style.utils.d.ts.map +1 -0
- package/lib/typescript/src/views/SquircleViewNativeComponent.d.ts +8 -0
- package/lib/typescript/src/views/SquircleViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/views/index.d.ts +7 -0
- package/lib/typescript/src/views/index.d.ts.map +1 -0
- package/package.json +200 -4
- package/src/components/index.ts +1 -0
- package/src/components/squircle-view.native.tsx +34 -0
- package/src/components/squircle-view.tsx +24 -0
- package/src/constants/index.ts +2 -0
- package/src/constants/native.constants.ts +9 -0
- package/src/constants/squircle.constants.ts +3 -0
- package/src/index.ts +1 -0
- package/src/interfaces/index.ts +2 -0
- package/src/interfaces/squircle-style.interface.ts +8 -0
- package/src/interfaces/squircle-view.interface.ts +11 -0
- package/src/root/index.ts +5 -0
- package/src/types/index.ts +2 -0
- package/src/types/native-component-registry.d.ts +15 -0
- package/src/types/native.types.ts +8 -0
- package/src/types/squircle.types.ts +7 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/style.utils.ts +17 -0
- package/src/views/SquircleViewNativeComponent.ts +12 -0
- package/src/views/index.ts +27 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SquirclePath.mm
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by rit3zh CX on 10/7/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "SquirclePath.h"
|
|
9
|
+
|
|
10
|
+
void SquirclePathAppend(
|
|
11
|
+
CGMutablePathRef path,
|
|
12
|
+
const squircle::Rect &rect,
|
|
13
|
+
const squircle::CornerRadii &radii,
|
|
14
|
+
double smoothing) {
|
|
15
|
+
for (const auto &element : squircle::buildPath(rect, radii, smoothing)) {
|
|
16
|
+
const auto &points = element.points;
|
|
17
|
+
switch (element.verb) {
|
|
18
|
+
case squircle::Verb::Move:
|
|
19
|
+
CGPathMoveToPoint(path, NULL, points[0].x, points[0].y);
|
|
20
|
+
break;
|
|
21
|
+
case squircle::Verb::Line:
|
|
22
|
+
CGPathAddLineToPoint(path, NULL, points[0].x, points[0].y);
|
|
23
|
+
break;
|
|
24
|
+
case squircle::Verb::Cubic:
|
|
25
|
+
CGPathAddCurveToPoint(
|
|
26
|
+
path, NULL, points[0].x, points[0].y, points[1].x, points[1].y, points[2].x, points[2].y);
|
|
27
|
+
break;
|
|
28
|
+
case squircle::Verb::Close:
|
|
29
|
+
CGPathCloseSubpath(path);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
CGPathRef SquirclePathCreate(const squircle::Rect &rect, const squircle::CornerRadii &radii, double smoothing) {
|
|
36
|
+
CGMutablePathRef path = CGPathCreateMutable();
|
|
37
|
+
SquirclePathAppend(path, rect, radii, smoothing);
|
|
38
|
+
return path;
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SquircleBorderLayer.h
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by rit3zh CX on 10/7/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <QuartzCore/QuartzCore.h>
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
12
|
+
|
|
13
|
+
@interface SquircleBorderLayer : CALayer
|
|
14
|
+
|
|
15
|
+
- (void)updateWithBorderPath:(CGPathRef)borderPath
|
|
16
|
+
insets:(UIEdgeInsets)insets
|
|
17
|
+
topColor:(UIColor *)topColor
|
|
18
|
+
rightColor:(UIColor *)rightColor
|
|
19
|
+
bottomColor:(UIColor *)bottomColor
|
|
20
|
+
leftColor:(UIColor *)leftColor;
|
|
21
|
+
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SquircleBorderLayer.m
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by rit3zh CX on 10/7/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "SquircleBorderLayer.h"
|
|
9
|
+
|
|
10
|
+
static CGPoint SquircleSpokePoint(CGPoint corner, CGFloat dx, CGFloat dy, CGSize half) {
|
|
11
|
+
CGFloat scale = CGFLOAT_MAX;
|
|
12
|
+
if (dx != 0) {
|
|
13
|
+
scale = MIN(scale, half.width / fabs(dx));
|
|
14
|
+
}
|
|
15
|
+
if (dy != 0) {
|
|
16
|
+
scale = MIN(scale, half.height / fabs(dy));
|
|
17
|
+
}
|
|
18
|
+
if (scale == CGFLOAT_MAX) {
|
|
19
|
+
return corner;
|
|
20
|
+
}
|
|
21
|
+
return CGPointMake(corner.x + dx * scale, corner.y + dy * scale);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static void SquircleFillWedge(CGContextRef context, UIColor *color, const CGPoint points[5]) {
|
|
25
|
+
CGContextSetFillColorWithColor(context, color.CGColor);
|
|
26
|
+
CGContextBeginPath(context);
|
|
27
|
+
CGContextAddLines(context, points, 5);
|
|
28
|
+
CGContextClosePath(context);
|
|
29
|
+
CGContextFillPath(context);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@implementation SquircleBorderLayer {
|
|
33
|
+
CGPathRef _borderPath;
|
|
34
|
+
UIEdgeInsets _insets;
|
|
35
|
+
UIColor *_topColor;
|
|
36
|
+
UIColor *_rightColor;
|
|
37
|
+
UIColor *_bottomColor;
|
|
38
|
+
UIColor *_leftColor;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
- (instancetype)init {
|
|
42
|
+
if (self = [super init]) {
|
|
43
|
+
self.needsDisplayOnBoundsChange = YES;
|
|
44
|
+
}
|
|
45
|
+
return self;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (void)dealloc {
|
|
49
|
+
CGPathRelease(_borderPath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
- (void)updateWithBorderPath:(CGPathRef)borderPath
|
|
53
|
+
insets:(UIEdgeInsets)insets
|
|
54
|
+
topColor:(UIColor *)topColor
|
|
55
|
+
rightColor:(UIColor *)rightColor
|
|
56
|
+
bottomColor:(UIColor *)bottomColor
|
|
57
|
+
leftColor:(UIColor *)leftColor {
|
|
58
|
+
if (_borderPath != borderPath) {
|
|
59
|
+
CGPathRelease(_borderPath);
|
|
60
|
+
_borderPath = CGPathRetain(borderPath);
|
|
61
|
+
}
|
|
62
|
+
_insets = insets;
|
|
63
|
+
_topColor = topColor;
|
|
64
|
+
_rightColor = rightColor;
|
|
65
|
+
_bottomColor = bottomColor;
|
|
66
|
+
_leftColor = leftColor;
|
|
67
|
+
[self setNeedsDisplay];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
- (void)drawInContext:(CGContextRef)context {
|
|
71
|
+
if (_borderPath == NULL) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const CGSize size = self.bounds.size;
|
|
76
|
+
const CGSize half = CGSizeMake(size.width / 2, size.height / 2);
|
|
77
|
+
const CGPoint center = CGPointMake(half.width, half.height);
|
|
78
|
+
const CGPoint topLeft = CGPointZero;
|
|
79
|
+
const CGPoint topRight = CGPointMake(size.width, 0);
|
|
80
|
+
const CGPoint bottomRight = CGPointMake(size.width, size.height);
|
|
81
|
+
const CGPoint bottomLeft = CGPointMake(0, size.height);
|
|
82
|
+
|
|
83
|
+
const CGPoint topLeftSpoke = SquircleSpokePoint(topLeft, _insets.left, _insets.top, half);
|
|
84
|
+
const CGPoint topRightSpoke = SquircleSpokePoint(topRight, -_insets.right, _insets.top, half);
|
|
85
|
+
const CGPoint bottomRightSpoke = SquircleSpokePoint(bottomRight, -_insets.right, -_insets.bottom, half);
|
|
86
|
+
const CGPoint bottomLeftSpoke = SquircleSpokePoint(bottomLeft, _insets.left, -_insets.bottom, half);
|
|
87
|
+
|
|
88
|
+
CGContextAddPath(context, _borderPath);
|
|
89
|
+
CGContextEOClip(context);
|
|
90
|
+
|
|
91
|
+
const CGPoint top[5] = {topLeft, topRight, topRightSpoke, center, topLeftSpoke};
|
|
92
|
+
const CGPoint right[5] = {topRight, bottomRight, bottomRightSpoke, center, topRightSpoke};
|
|
93
|
+
const CGPoint bottom[5] = {bottomRight, bottomLeft, bottomLeftSpoke, center, bottomRightSpoke};
|
|
94
|
+
const CGPoint left[5] = {bottomLeft, topLeft, topLeftSpoke, center, bottomLeftSpoke};
|
|
95
|
+
|
|
96
|
+
SquircleFillWedge(context, _topColor, top);
|
|
97
|
+
SquircleFillWedge(context, _rightColor, right);
|
|
98
|
+
SquircleFillWedge(context, _bottomColor, bottom);
|
|
99
|
+
SquircleFillWedge(context, _leftColor, left);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SquircleView.h
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by rit3zh CX on 10/7/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <React/RCTViewComponentView.h>
|
|
9
|
+
#import <UIKit/UIKit.h>
|
|
10
|
+
|
|
11
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
12
|
+
|
|
13
|
+
@interface SquircleView : RCTViewComponentView
|
|
14
|
+
@end
|
|
15
|
+
|
|
16
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
//
|
|
2
|
+
// SquircleView.mm
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by rit3zh CX on 10/7/26.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "SquircleView.h"
|
|
9
|
+
|
|
10
|
+
#import <objc/runtime.h>
|
|
11
|
+
|
|
12
|
+
#import <optional>
|
|
13
|
+
|
|
14
|
+
#import <React/RCTConversions.h>
|
|
15
|
+
#import <React/RCTUtils.h>
|
|
16
|
+
#import <react/featureflags/ReactNativeFeatureFlags.h>
|
|
17
|
+
#import <react/renderer/components/SquircleViewSpec/ComponentDescriptors.h>
|
|
18
|
+
#import <react/renderer/components/SquircleViewSpec/Props.h>
|
|
19
|
+
|
|
20
|
+
#import "SquircleBorderLayer.h"
|
|
21
|
+
#import "SquirclePath.h"
|
|
22
|
+
|
|
23
|
+
using namespace facebook::react;
|
|
24
|
+
|
|
25
|
+
static const CGFloat SquircleBackgroundZPosition = -1024;
|
|
26
|
+
static const CGFloat SquircleBorderZPosition = -1023;
|
|
27
|
+
|
|
28
|
+
@interface RCTViewComponentView (SquircleView)
|
|
29
|
+
- (UIView *)currentContainerView;
|
|
30
|
+
- (void)invalidateLayer;
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
namespace {
|
|
34
|
+
|
|
35
|
+
struct SquircleGeometryKey {
|
|
36
|
+
squircle::Rect bounds;
|
|
37
|
+
squircle::CornerRadii radii;
|
|
38
|
+
squircle::Insets insets;
|
|
39
|
+
double smoothing;
|
|
40
|
+
|
|
41
|
+
bool operator==(const SquircleGeometryKey &other) const = default;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
squircle::CornerRadii SquircleCornerRadiiFromBorderRadii(const BorderRadii &radii) {
|
|
45
|
+
return {
|
|
46
|
+
{radii.topLeft.horizontal, radii.topLeft.vertical},
|
|
47
|
+
{radii.topRight.horizontal, radii.topRight.vertical},
|
|
48
|
+
{radii.bottomRight.horizontal, radii.bottomRight.vertical},
|
|
49
|
+
{radii.bottomLeft.horizontal, radii.bottomLeft.vertical}};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
squircle::Insets SquircleInsetsFromBorderWidths(const BorderWidths &widths) {
|
|
53
|
+
return {widths.top, widths.right, widths.bottom, widths.left};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
UIColor *SquircleResolvedColor(const SharedColor &color, UITraitCollection *traitCollection) {
|
|
57
|
+
UIColor *resolved = RCTUIColorFromSharedColor(color) ?: UIColor.blackColor;
|
|
58
|
+
return [resolved resolvedColorWithTraitCollection:traitCollection];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
} // namespace
|
|
62
|
+
|
|
63
|
+
@implementation SquircleView {
|
|
64
|
+
CGFloat _borderSmoothing;
|
|
65
|
+
BOOL _needsSquircleUpdate;
|
|
66
|
+
std::optional<SquircleGeometryKey> _geometryKey;
|
|
67
|
+
CGPathRef _outerPath;
|
|
68
|
+
CGPathRef _innerPath;
|
|
69
|
+
CGPathRef _borderPath;
|
|
70
|
+
CGPathRef _strokePath;
|
|
71
|
+
CAShapeLayer *_backgroundLayer;
|
|
72
|
+
CAShapeLayer *_borderShapeLayer;
|
|
73
|
+
SquircleBorderLayer *_multicolorBorderLayer;
|
|
74
|
+
CAShapeLayer *_clipLayer;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider {
|
|
78
|
+
return concreteComponentDescriptorProvider<SquircleViewComponentDescriptor>();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
82
|
+
if (self = [super initWithFrame:frame]) {
|
|
83
|
+
static const auto defaultProps = std::make_shared<const SquircleViewProps>();
|
|
84
|
+
_props = defaultProps;
|
|
85
|
+
}
|
|
86
|
+
return self;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
- (void)dealloc {
|
|
90
|
+
[self releasePaths];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#pragma mark - RCTComponentViewProtocol
|
|
94
|
+
|
|
95
|
+
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
|
|
96
|
+
const auto &newProps = static_cast<const SquircleViewProps &>(*props);
|
|
97
|
+
const CGFloat smoothing = squircle::clampSmoothing(newProps.borderSmoothing);
|
|
98
|
+
if (smoothing != _borderSmoothing) {
|
|
99
|
+
_borderSmoothing = smoothing;
|
|
100
|
+
_needsSquircleUpdate = YES;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[super updateProps:props oldProps:oldProps];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
- (void)finalizeUpdates:(RNComponentViewUpdateMask)updateMask {
|
|
107
|
+
[super finalizeUpdates:updateMask];
|
|
108
|
+
|
|
109
|
+
if (_needsSquircleUpdate) {
|
|
110
|
+
[self invalidateLayer];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
- (void)prepareForRecycle {
|
|
115
|
+
[super prepareForRecycle];
|
|
116
|
+
|
|
117
|
+
_borderSmoothing = 0;
|
|
118
|
+
_needsSquircleUpdate = NO;
|
|
119
|
+
[self removeSquircleLayers];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#pragma mark - Layer
|
|
123
|
+
|
|
124
|
+
- (void)invalidateLayer {
|
|
125
|
+
_needsSquircleUpdate = NO;
|
|
126
|
+
|
|
127
|
+
const auto borderMetrics = _props->resolveBorderMetrics(_layoutMetrics);
|
|
128
|
+
const auto radii = SquircleCornerRadiiFromBorderRadii(borderMetrics.borderRadii);
|
|
129
|
+
const CGSize size = self.layer.bounds.size;
|
|
130
|
+
|
|
131
|
+
if (_borderSmoothing <= 0 || !squircle::hasRoundedCorner(radii) || size.width <= 0 || size.height <= 0) {
|
|
132
|
+
[self removeSquircleLayers];
|
|
133
|
+
[super invalidateLayer];
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
UIColor *backgroundColor = self.backgroundColor;
|
|
138
|
+
self.backgroundColor = nil;
|
|
139
|
+
[super invalidateLayer];
|
|
140
|
+
self.backgroundColor = backgroundColor;
|
|
141
|
+
|
|
142
|
+
const SquircleGeometryKey key{
|
|
143
|
+
.bounds = {0, 0, size.width, size.height},
|
|
144
|
+
.radii = radii,
|
|
145
|
+
.insets = SquircleInsetsFromBorderWidths(borderMetrics.borderWidths),
|
|
146
|
+
.smoothing = _borderSmoothing,
|
|
147
|
+
};
|
|
148
|
+
[self updateGeometry:key];
|
|
149
|
+
|
|
150
|
+
[CATransaction begin];
|
|
151
|
+
[CATransaction setDisableActions:YES];
|
|
152
|
+
[self hideDefaultBackgroundAndBorder];
|
|
153
|
+
[self updateBackgroundWithColor:backgroundColor];
|
|
154
|
+
[self updateBorderWithMetrics:borderMetrics];
|
|
155
|
+
[self updateClipping];
|
|
156
|
+
[CATransaction commit];
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
- (void)updateGeometry:(const SquircleGeometryKey &)key {
|
|
160
|
+
if (_geometryKey == key) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
[self releasePaths];
|
|
165
|
+
_geometryKey = key;
|
|
166
|
+
|
|
167
|
+
const auto innerRect = squircle::insetRect(key.bounds, key.insets);
|
|
168
|
+
const auto innerRadii = squircle::insetRadii(key.radii, key.insets);
|
|
169
|
+
|
|
170
|
+
_outerPath = SquirclePathCreate(key.bounds, key.radii, key.smoothing);
|
|
171
|
+
_innerPath = SquirclePathCreate(innerRect, innerRadii, key.smoothing);
|
|
172
|
+
|
|
173
|
+
CGMutablePathRef borderPath = CGPathCreateMutableCopy(_outerPath);
|
|
174
|
+
SquirclePathAppend(borderPath, innerRect, innerRadii, key.smoothing);
|
|
175
|
+
_borderPath = borderPath;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
- (CGPathRef)strokePath {
|
|
179
|
+
if (_strokePath == NULL && _geometryKey) {
|
|
180
|
+
const auto &key = *_geometryKey;
|
|
181
|
+
const squircle::Insets half{
|
|
182
|
+
key.insets.top / 2, key.insets.right / 2, key.insets.bottom / 2, key.insets.left / 2};
|
|
183
|
+
_strokePath = SquirclePathCreate(
|
|
184
|
+
squircle::insetRect(key.bounds, half), squircle::insetRadii(key.radii, half), key.smoothing);
|
|
185
|
+
}
|
|
186
|
+
return _strokePath;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
- (void)releasePaths {
|
|
190
|
+
CGPathRelease(_outerPath);
|
|
191
|
+
CGPathRelease(_innerPath);
|
|
192
|
+
CGPathRelease(_borderPath);
|
|
193
|
+
CGPathRelease(_strokePath);
|
|
194
|
+
_outerPath = NULL;
|
|
195
|
+
_innerPath = NULL;
|
|
196
|
+
_borderPath = NULL;
|
|
197
|
+
_strokePath = NULL;
|
|
198
|
+
_geometryKey.reset();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// RCTViewComponentView draws non-uniform borders into a private sublayer; it has to be hidden
|
|
202
|
+
// while the squircle border is rendered in its place.
|
|
203
|
+
- (nullable CALayer *)defaultBorderLayer {
|
|
204
|
+
static Ivar ivar = class_getInstanceVariable([RCTViewComponentView class], "_borderLayer");
|
|
205
|
+
return ivar != nullptr ? object_getIvar(self, ivar) : nil;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
- (void)hideDefaultBackgroundAndBorder {
|
|
209
|
+
CALayer *layer = self.layer;
|
|
210
|
+
layer.backgroundColor = nil;
|
|
211
|
+
layer.borderWidth = 0;
|
|
212
|
+
layer.borderColor = nil;
|
|
213
|
+
layer.cornerRadius = 0;
|
|
214
|
+
[self defaultBorderLayer].hidden = YES;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
- (void)updateBackgroundWithColor:(nullable UIColor *)backgroundColor {
|
|
218
|
+
CALayer *layer = self.layer;
|
|
219
|
+
UIColor *color = [backgroundColor resolvedColorWithTraitCollection:self.traitCollection];
|
|
220
|
+
const CGFloat alpha = color != nil ? CGColorGetAlpha(color.CGColor) : 0;
|
|
221
|
+
|
|
222
|
+
if (alpha > 0) {
|
|
223
|
+
if (_backgroundLayer == nil) {
|
|
224
|
+
_backgroundLayer = [CAShapeLayer layer];
|
|
225
|
+
_backgroundLayer.zPosition = SquircleBackgroundZPosition;
|
|
226
|
+
[layer addSublayer:_backgroundLayer];
|
|
227
|
+
}
|
|
228
|
+
_backgroundLayer.frame = layer.bounds;
|
|
229
|
+
_backgroundLayer.path = _outerPath;
|
|
230
|
+
_backgroundLayer.fillColor = color.CGColor;
|
|
231
|
+
} else {
|
|
232
|
+
[_backgroundLayer removeFromSuperlayer];
|
|
233
|
+
_backgroundLayer = nil;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const BOOL hasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0;
|
|
237
|
+
layer.shadowPath = hasShadow && alpha > 0.999 ? _outerPath : nil;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
- (void)updateBorderWithMetrics:(const BorderMetrics &)metrics {
|
|
241
|
+
const auto &widths = metrics.borderWidths;
|
|
242
|
+
if (widths.left <= 0 && widths.top <= 0 && widths.right <= 0 && widths.bottom <= 0) {
|
|
243
|
+
[self removeBorderLayers];
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
CALayer *layer = self.layer;
|
|
248
|
+
UITraitCollection *traitCollection = self.traitCollection;
|
|
249
|
+
UIColor *top = SquircleResolvedColor(metrics.borderColors.top, traitCollection);
|
|
250
|
+
UIColor *right = SquircleResolvedColor(metrics.borderColors.right, traitCollection);
|
|
251
|
+
UIColor *bottom = SquircleResolvedColor(metrics.borderColors.bottom, traitCollection);
|
|
252
|
+
UIColor *left = SquircleResolvedColor(metrics.borderColors.left, traitCollection);
|
|
253
|
+
|
|
254
|
+
const BOOL uniformColor = CGColorEqualToColor(top.CGColor, right.CGColor) &&
|
|
255
|
+
CGColorEqualToColor(top.CGColor, bottom.CGColor) && CGColorEqualToColor(top.CGColor, left.CGColor);
|
|
256
|
+
|
|
257
|
+
if (!uniformColor) {
|
|
258
|
+
[_borderShapeLayer removeFromSuperlayer];
|
|
259
|
+
_borderShapeLayer = nil;
|
|
260
|
+
|
|
261
|
+
if (_multicolorBorderLayer == nil) {
|
|
262
|
+
_multicolorBorderLayer = [SquircleBorderLayer layer];
|
|
263
|
+
_multicolorBorderLayer.zPosition = SquircleBorderZPosition;
|
|
264
|
+
_multicolorBorderLayer.contentsScale = RCTScreenScale();
|
|
265
|
+
[layer addSublayer:_multicolorBorderLayer];
|
|
266
|
+
}
|
|
267
|
+
_multicolorBorderLayer.frame = layer.bounds;
|
|
268
|
+
[_multicolorBorderLayer updateWithBorderPath:_borderPath
|
|
269
|
+
insets:UIEdgeInsetsMake(widths.top, widths.left, widths.bottom, widths.right)
|
|
270
|
+
topColor:top
|
|
271
|
+
rightColor:right
|
|
272
|
+
bottomColor:bottom
|
|
273
|
+
leftColor:left];
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
[_multicolorBorderLayer removeFromSuperlayer];
|
|
278
|
+
_multicolorBorderLayer = nil;
|
|
279
|
+
|
|
280
|
+
if (_borderShapeLayer == nil) {
|
|
281
|
+
_borderShapeLayer = [CAShapeLayer layer];
|
|
282
|
+
_borderShapeLayer.zPosition = SquircleBorderZPosition;
|
|
283
|
+
[layer addSublayer:_borderShapeLayer];
|
|
284
|
+
}
|
|
285
|
+
_borderShapeLayer.frame = layer.bounds;
|
|
286
|
+
|
|
287
|
+
const BorderStyle style = metrics.borderStyles.left;
|
|
288
|
+
if (style != BorderStyle::Solid && metrics.borderStyles.isUniform() && widths.isUniform()) {
|
|
289
|
+
const CGFloat width = widths.left;
|
|
290
|
+
const BOOL dotted = style == BorderStyle::Dotted;
|
|
291
|
+
_borderShapeLayer.path = [self strokePath];
|
|
292
|
+
_borderShapeLayer.fillColor = nil;
|
|
293
|
+
_borderShapeLayer.strokeColor = top.CGColor;
|
|
294
|
+
_borderShapeLayer.lineWidth = width;
|
|
295
|
+
_borderShapeLayer.lineCap = dotted ? kCALineCapRound : kCALineCapButt;
|
|
296
|
+
_borderShapeLayer.lineDashPattern = dotted ? @[ @0, @(width * 2) ] : @[ @(width * 3), @(width * 3) ];
|
|
297
|
+
} else {
|
|
298
|
+
_borderShapeLayer.path = _borderPath;
|
|
299
|
+
_borderShapeLayer.fillRule = kCAFillRuleEvenOdd;
|
|
300
|
+
_borderShapeLayer.fillColor = top.CGColor;
|
|
301
|
+
_borderShapeLayer.strokeColor = nil;
|
|
302
|
+
_borderShapeLayer.lineDashPattern = nil;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
- (void)updateClipping {
|
|
307
|
+
UIView *containerView = self.currentContainerView;
|
|
308
|
+
if (!containerView.clipsToBounds) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (_clipLayer == nil) {
|
|
313
|
+
_clipLayer = [CAShapeLayer layer];
|
|
314
|
+
}
|
|
315
|
+
_clipLayer.frame = containerView.bounds;
|
|
316
|
+
_clipLayer.path = ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() ? _innerPath : _outerPath;
|
|
317
|
+
containerView.layer.cornerRadius = 0;
|
|
318
|
+
containerView.layer.mask = _clipLayer;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
- (void)removeBorderLayers {
|
|
322
|
+
[_borderShapeLayer removeFromSuperlayer];
|
|
323
|
+
_borderShapeLayer = nil;
|
|
324
|
+
[_multicolorBorderLayer removeFromSuperlayer];
|
|
325
|
+
_multicolorBorderLayer = nil;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
- (void)removeSquircleLayers {
|
|
329
|
+
[_backgroundLayer removeFromSuperlayer];
|
|
330
|
+
_backgroundLayer = nil;
|
|
331
|
+
[self removeBorderLayers];
|
|
332
|
+
_clipLayer = nil;
|
|
333
|
+
[self defaultBorderLayer].hidden = NO;
|
|
334
|
+
[self releasePaths];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
@end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["SquircleView"],"sourceRoot":"../../../src","sources":["components/index.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,iBAAiB","ignoreList":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { View } from 'react-native';
|
|
6
|
+
import { COMPONENT_NAMES } from "../constants/index.js";
|
|
7
|
+
import { splitSquircleStyle } from "../utils/index.js";
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const SquircleView = ({
|
|
10
|
+
style,
|
|
11
|
+
borderSmoothing: _borderSmoothing,
|
|
12
|
+
...rest
|
|
13
|
+
}) => {
|
|
14
|
+
const squircleStyle = useMemo(() => splitSquircleStyle(style), [style]);
|
|
15
|
+
return /*#__PURE__*/_jsx(View, {
|
|
16
|
+
...rest,
|
|
17
|
+
style: squircleStyle.style
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
SquircleView.displayName = COMPONENT_NAMES.SQUIRCLE_VIEW;
|
|
21
|
+
export { SquircleView };
|
|
22
|
+
//# sourceMappingURL=squircle-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useMemo","View","COMPONENT_NAMES","splitSquircleStyle","jsx","_jsx","SquircleView","style","borderSmoothing","_borderSmoothing","rest","squircleStyle","displayName","SQUIRCLE_VIEW"],"sourceRoot":"../../../src","sources":["components/squircle-view.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,OAAO,QAAQ,OAAO;AAC/B,SAASC,IAAI,QAAwB,cAAc;AACnD,SAASC,eAAe,QAAQ,uBAAc;AAE9C,SAASC,kBAAkB,QAAQ,mBAAU;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAG9C,MAAMC,YAA0C,GAAGA,CAAC;EAClDC,KAAK;EACLC,eAAe,EAAEC,gBAAgB;EACjC,GAAGC;AACe,CAAC,KAA0C;EAC7D,MAAMC,aAAa,GAAGX,OAAO,CAC3B,MAAMG,kBAAkB,CAAgCI,KAAK,CAAC,EAC9D,CAACA,KAAK,CACR,CAAC;EAED,oBAAOF,IAAA,CAACJ,IAAI;IAAA,GAAKS,IAAI;IAAEH,KAAK,EAAEI,aAAa,CAACJ;EAAM,CAAE,CAAC;AACvD,CAAC;AAEDD,YAAY,CAACM,WAAW,GAAGV,eAAe,CAACW,aAAa;AAExD,SAASP,YAAY","ignoreList":[]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { useMemo } from 'react';
|
|
5
|
+
import { COMPONENT_NAMES, DEFAULT_BORDER_SMOOTHING } from "../constants/index.js";
|
|
6
|
+
import { splitSquircleStyle } from "../utils/index.js";
|
|
7
|
+
import { NativeSquircleView } from "../views/index.js";
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const SquircleView = ({
|
|
10
|
+
style,
|
|
11
|
+
borderSmoothing,
|
|
12
|
+
...rest
|
|
13
|
+
}) => {
|
|
14
|
+
const squircleStyle = useMemo(() => splitSquircleStyle(style), [style]);
|
|
15
|
+
return /*#__PURE__*/_jsx(NativeSquircleView, {
|
|
16
|
+
...rest,
|
|
17
|
+
style: squircleStyle.style,
|
|
18
|
+
borderSmoothing: squircleStyle.borderSmoothing ?? borderSmoothing ?? DEFAULT_BORDER_SMOOTHING
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
SquircleView.displayName = COMPONENT_NAMES.SQUIRCLE_VIEW;
|
|
22
|
+
export { SquircleView };
|
|
23
|
+
//# sourceMappingURL=squircle-view.native.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["React","useMemo","COMPONENT_NAMES","DEFAULT_BORDER_SMOOTHING","splitSquircleStyle","NativeSquircleView","jsx","_jsx","SquircleView","style","borderSmoothing","rest","squircleStyle","displayName","SQUIRCLE_VIEW"],"sourceRoot":"../../../src","sources":["components/squircle-view.native.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,OAAO,QAAQ,OAAO;AAE/B,SAASC,eAAe,EAAEC,wBAAwB,QAAQ,uBAAc;AAExE,SAASC,kBAAkB,QAAQ,mBAAU;AAC7C,SAASC,kBAAkB,QAAQ,mBAAU;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE9C,MAAMC,YAA0C,GAAGA,CAAC;EAClDC,KAAK;EACLC,eAAe;EACf,GAAGC;AACe,CAAC,KAAwB;EAC3C,MAAMC,aAAa,GAAGX,OAAO,CAC3B,MAAMG,kBAAkB,CAACK,KAAK,CAAC,EAC/B,CAACA,KAAK,CACR,CAAC;EAED,oBACEF,IAAA,CAACF,kBAAkB;IAAA,GACbM,IAAI;IACRF,KAAK,EAAEG,aAAa,CAACH,KAAM;IAC3BC,eAAe,EACbE,aAAa,CAACF,eAAe,IAC7BA,eAAe,IACfP;EACD,CACF,CAAC;AAEN,CAAC;AAEDK,YAAY,CAACK,WAAW,GAAGX,eAAe,CAACY,aAAa;AAExD,SAASN,YAAY","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["constants/index.ts"],"mappings":";;AAAA,cAAc,uBAAoB;AAClC,cAAc,yBAAsB","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NATIVE_VIEW_NAMES","SQUIRCLE_VIEW","COMPONENT_NAMES"],"sourceRoot":"../../../src","sources":["constants/native.constants.ts"],"mappings":";;AAAA,MAAMA,iBAAiB,GAAG;EACxBC,aAAa,EAAE;AACjB,CAA2C;AAE3C,MAAMC,eAAe,GAAG;EACtBD,aAAa,EAAE;AACjB,CAA2C;AAE3C,SAASD,iBAAiB,EAAEE,eAAe","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["DEFAULT_BORDER_SMOOTHING"],"sourceRoot":"../../../src","sources":["constants/squircle.constants.ts"],"mappings":";;AAAA,MAAMA,wBAAgC,GAAG,CAAC;AAE1C,SAASA,wBAAwB","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,cAAc,iBAAQ","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["interfaces/index.ts"],"mappings":";;AAAA,cAAc,+BAA4B;AAC1C,cAAc,8BAA2B","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["interfaces/squircle-style.interface.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../src","sources":["interfaces/squircle-view.interface.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["SquircleView"],"sourceRoot":"../../../src","sources":["root/index.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAQ,wBAAe","ignoreList":[]}
|