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.
Files changed (115) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +82 -2
  3. package/SquircleView.podspec +22 -0
  4. package/android/CMakeLists.txt +17 -0
  5. package/android/build.gradle +78 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/SquircleGeometryJni.cpp +52 -0
  8. package/android/src/main/java/com/squircleview/SquircleViewManager.kt +72 -0
  9. package/android/src/main/java/com/squircleview/SquircleViewPackage.kt +16 -0
  10. package/android/src/main/java/com/squircleview/geometry/SquircleGeometry.kt +58 -0
  11. package/android/src/main/java/com/squircleview/style/SquircleStyle.kt +142 -0
  12. package/android/src/main/java/com/squircleview/views/SquircleView.kt +361 -0
  13. package/common/cpp/SquircleGeometry.cpp +248 -0
  14. package/common/cpp/SquircleGeometry.h +75 -0
  15. package/ios/Geometry/SquirclePath.h +21 -0
  16. package/ios/Geometry/SquirclePath.mm +39 -0
  17. package/ios/Layers/SquircleBorderLayer.h +24 -0
  18. package/ios/Layers/SquircleBorderLayer.m +102 -0
  19. package/ios/Views/SquircleView.h +16 -0
  20. package/ios/Views/SquircleView.mm +337 -0
  21. package/lib/module/components/index.js +4 -0
  22. package/lib/module/components/index.js.map +1 -0
  23. package/lib/module/components/squircle-view.js +22 -0
  24. package/lib/module/components/squircle-view.js.map +1 -0
  25. package/lib/module/components/squircle-view.native.js +23 -0
  26. package/lib/module/components/squircle-view.native.js.map +1 -0
  27. package/lib/module/constants/index.js +5 -0
  28. package/lib/module/constants/index.js.map +1 -0
  29. package/lib/module/constants/native.constants.js +10 -0
  30. package/lib/module/constants/native.constants.js.map +1 -0
  31. package/lib/module/constants/squircle.constants.js +5 -0
  32. package/lib/module/constants/squircle.constants.js.map +1 -0
  33. package/lib/module/index.js +4 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/module/interfaces/index.js +5 -0
  36. package/lib/module/interfaces/index.js.map +1 -0
  37. package/lib/module/interfaces/squircle-style.interface.js +4 -0
  38. package/lib/module/interfaces/squircle-style.interface.js.map +1 -0
  39. package/lib/module/interfaces/squircle-view.interface.js +4 -0
  40. package/lib/module/interfaces/squircle-view.interface.js.map +1 -0
  41. package/lib/module/package.json +1 -0
  42. package/lib/module/root/index.js +4 -0
  43. package/lib/module/root/index.js.map +1 -0
  44. package/lib/module/types/index.js +5 -0
  45. package/lib/module/types/index.js.map +1 -0
  46. package/lib/module/types/native-component-registry.d.js +2 -0
  47. package/lib/module/types/native-component-registry.d.js.map +1 -0
  48. package/lib/module/types/native.types.js +4 -0
  49. package/lib/module/types/native.types.js.map +1 -0
  50. package/lib/module/types/squircle.types.js +4 -0
  51. package/lib/module/types/squircle.types.js.map +1 -0
  52. package/lib/module/utils/index.js +4 -0
  53. package/lib/module/utils/index.js.map +1 -0
  54. package/lib/module/utils/style.utils.js +22 -0
  55. package/lib/module/utils/style.utils.js.map +1 -0
  56. package/lib/module/views/SquircleViewNativeComponent.ts +12 -0
  57. package/lib/module/views/index.js +15 -0
  58. package/lib/module/views/index.js.map +1 -0
  59. package/lib/typescript/package.json +1 -0
  60. package/lib/typescript/src/components/index.d.ts +2 -0
  61. package/lib/typescript/src/components/index.d.ts.map +1 -0
  62. package/lib/typescript/src/components/squircle-view.d.ts +5 -0
  63. package/lib/typescript/src/components/squircle-view.d.ts.map +1 -0
  64. package/lib/typescript/src/components/squircle-view.native.d.ts +5 -0
  65. package/lib/typescript/src/components/squircle-view.native.d.ts.map +1 -0
  66. package/lib/typescript/src/constants/index.d.ts +3 -0
  67. package/lib/typescript/src/constants/index.d.ts.map +1 -0
  68. package/lib/typescript/src/constants/native.constants.d.ts +8 -0
  69. package/lib/typescript/src/constants/native.constants.d.ts.map +1 -0
  70. package/lib/typescript/src/constants/squircle.constants.d.ts +3 -0
  71. package/lib/typescript/src/constants/squircle.constants.d.ts.map +1 -0
  72. package/lib/typescript/src/index.d.ts +2 -0
  73. package/lib/typescript/src/index.d.ts.map +1 -0
  74. package/lib/typescript/src/interfaces/index.d.ts +3 -0
  75. package/lib/typescript/src/interfaces/index.d.ts.map +1 -0
  76. package/lib/typescript/src/interfaces/squircle-style.interface.d.ts +7 -0
  77. package/lib/typescript/src/interfaces/squircle-style.interface.d.ts.map +1 -0
  78. package/lib/typescript/src/interfaces/squircle-view.interface.d.ts +10 -0
  79. package/lib/typescript/src/interfaces/squircle-view.interface.d.ts.map +1 -0
  80. package/lib/typescript/src/root/index.d.ts +6 -0
  81. package/lib/typescript/src/root/index.d.ts.map +1 -0
  82. package/lib/typescript/src/types/index.d.ts +3 -0
  83. package/lib/typescript/src/types/index.d.ts.map +1 -0
  84. package/lib/typescript/src/types/native.types.d.ts +5 -0
  85. package/lib/typescript/src/types/native.types.d.ts.map +1 -0
  86. package/lib/typescript/src/types/squircle.types.d.ts +6 -0
  87. package/lib/typescript/src/types/squircle.types.d.ts.map +1 -0
  88. package/lib/typescript/src/utils/index.d.ts +2 -0
  89. package/lib/typescript/src/utils/index.d.ts.map +1 -0
  90. package/lib/typescript/src/utils/style.utils.d.ts +6 -0
  91. package/lib/typescript/src/utils/style.utils.d.ts.map +1 -0
  92. package/lib/typescript/src/views/SquircleViewNativeComponent.d.ts +8 -0
  93. package/lib/typescript/src/views/SquircleViewNativeComponent.d.ts.map +1 -0
  94. package/lib/typescript/src/views/index.d.ts +7 -0
  95. package/lib/typescript/src/views/index.d.ts.map +1 -0
  96. package/package.json +200 -4
  97. package/src/components/index.ts +1 -0
  98. package/src/components/squircle-view.native.tsx +34 -0
  99. package/src/components/squircle-view.tsx +24 -0
  100. package/src/constants/index.ts +2 -0
  101. package/src/constants/native.constants.ts +9 -0
  102. package/src/constants/squircle.constants.ts +3 -0
  103. package/src/index.ts +1 -0
  104. package/src/interfaces/index.ts +2 -0
  105. package/src/interfaces/squircle-style.interface.ts +8 -0
  106. package/src/interfaces/squircle-view.interface.ts +11 -0
  107. package/src/root/index.ts +5 -0
  108. package/src/types/index.ts +2 -0
  109. package/src/types/native-component-registry.d.ts +15 -0
  110. package/src/types/native.types.ts +8 -0
  111. package/src/types/squircle.types.ts +7 -0
  112. package/src/utils/index.ts +1 -0
  113. package/src/utils/style.utils.ts +17 -0
  114. package/src/views/SquircleViewNativeComponent.ts +12 -0
  115. package/src/views/index.ts +27 -0
package/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rit3zh
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,83 @@
1
- # Temporary Holding Version
1
+ # SquircleView
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ ![squircle-view](.github/banner.png)
4
+
5
+ A drop-in React Native `View` with smooth, continuous corners.
6
+
7
+ ```tsx
8
+ <SquircleView style={{ borderRadius: 24, borderSmoothing: 1 }}>…</SquircleView>
9
+ ```
10
+
11
+ ## Installation
12
+
13
+ ```sh
14
+ npm install squircle-view
15
+ cd ios && pod install
16
+ ```
17
+
18
+ Requires the New Architecture. On Expo, use a development build.
19
+
20
+ ## Usage
21
+
22
+ ```tsx
23
+ import { SquircleView } from 'squircle-view';
24
+
25
+ export function Card({ children }) {
26
+ return (
27
+ <SquircleView
28
+ style={{
29
+ padding: 20,
30
+ borderRadius: 24,
31
+ borderSmoothing: 1,
32
+ backgroundColor: '#fff',
33
+ }}
34
+ >
35
+ {children}
36
+ </SquircleView>
37
+ );
38
+ }
39
+ ```
40
+
41
+ It's a real `View`, so you can swap the import and keep your JSX:
42
+
43
+ ```tsx
44
+ import { SquircleView as View } from 'squircle-view';
45
+ ```
46
+
47
+ ### Typed styles
48
+
49
+ `StyleSheet.create` rejects unknown keys. Use the prop, or type the style as `SquircleViewStyle`:
50
+
51
+ ```tsx
52
+ import type { SquircleViewStyle } from 'squircle-view';
53
+
54
+ const smooth: SquircleViewStyle = { borderSmoothing: 1 };
55
+
56
+ <SquircleView borderSmoothing={0.6} style={styles.card} />
57
+ <SquircleView style={[styles.card, smooth]} />
58
+ ```
59
+
60
+ ## API
61
+
62
+ | `borderSmoothing` | Result |
63
+ | ----------------- | ----------------------------------- |
64
+ | `0` (default) | Standard React Native corners |
65
+ | `0.6` | Close to Apple's continuous corners |
66
+ | `1` | Fully smoothed |
67
+
68
+ Set it in `style` or as a prop (`style` wins). Values are clamped to `0…1`.
69
+
70
+ Everything else a `View` supports works as usual: per-corner radii, per-edge borders, `borderStyle`, `overflow: 'hidden'` clipping, shadows, `elevation`, accessibility, and `Animated` (including animated `borderSmoothing`).
71
+
72
+ **Platforms:** iOS (Fabric) · Android · Web falls back to a plain `View`.
73
+
74
+ ## Example
75
+
76
+ ```sh
77
+ bun install
78
+ bun run example ios # or android
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,22 @@
1
+ require "json"
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = "SquircleView"
7
+ s.version = package["version"]
8
+ s.summary = package["description"]
9
+ s.homepage = package["homepage"]
10
+ s.license = package["license"]
11
+ s.authors = package["author"]
12
+
13
+ s.platforms = { :ios => min_ios_version_supported }
14
+ s.source = { :git => "https://github.com/rit3zh/squircle-view.git", :tag => "#{s.version}" }
15
+
16
+ s.source_files = "ios/**/*.{h,m,mm}", "common/cpp/**/*.{h,cpp}"
17
+ s.exclude_files = "common/cpp/__tests__/**/*"
18
+ s.private_header_files = "ios/**/*.h", "common/cpp/**/*.h"
19
+ s.pod_target_xcconfig = { "HEADER_SEARCH_PATHS" => "\"$(PODS_TARGET_SRCROOT)/common/cpp\"" }
20
+
21
+ install_modules_dependencies(s)
22
+ end
@@ -0,0 +1,17 @@
1
+ cmake_minimum_required(VERSION 3.13)
2
+ project(squircleview LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 20)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ set(COMMON_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../common/cpp)
8
+
9
+ add_library(
10
+ squircleview
11
+ SHARED
12
+ ${COMMON_CPP_DIR}/SquircleGeometry.cpp
13
+ src/main/cpp/SquircleGeometryJni.cpp
14
+ )
15
+
16
+ target_include_directories(squircleview PRIVATE ${COMMON_CPP_DIR})
17
+ target_compile_options(squircleview PRIVATE -O2 -Wall -Wextra -Werror)
@@ -0,0 +1,78 @@
1
+ buildscript {
2
+ ext.SquircleView = [
3
+ kotlinVersion: "2.1.20",
4
+ minSdkVersion: 24,
5
+ compileSdkVersion: 36,
6
+ targetSdkVersion: 36,
7
+ ndkVersion: "27.1.12297006"
8
+ ]
9
+
10
+ ext.getExtOrDefault = { prop ->
11
+ if (rootProject.ext.has(prop)) {
12
+ return rootProject.ext.get(prop)
13
+ }
14
+
15
+ return SquircleView[prop]
16
+ }
17
+
18
+ repositories {
19
+ google()
20
+ mavenCentral()
21
+ }
22
+
23
+ dependencies {
24
+ classpath "com.android.tools.build:gradle:8.7.2"
25
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
26
+ }
27
+ }
28
+
29
+ def reactNativeArchitectures() {
30
+ def value = rootProject.findProperty("reactNativeArchitectures")
31
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
32
+ }
33
+
34
+ apply plugin: "com.android.library"
35
+
36
+ if (project.extensions.findByName("kotlin") == null) {
37
+ apply plugin: "kotlin-android"
38
+ }
39
+
40
+ apply plugin: "com.facebook.react"
41
+
42
+ android {
43
+ namespace "com.squircleview"
44
+
45
+ compileSdkVersion getExtOrDefault("compileSdkVersion")
46
+ ndkVersion getExtOrDefault("ndkVersion")
47
+
48
+ defaultConfig {
49
+ minSdkVersion getExtOrDefault("minSdkVersion")
50
+ targetSdkVersion getExtOrDefault("targetSdkVersion")
51
+
52
+ externalNativeBuild {
53
+ cmake {
54
+ arguments "-DANDROID_STL=c++_static"
55
+ abiFilters(*reactNativeArchitectures())
56
+ }
57
+ }
58
+ }
59
+
60
+ externalNativeBuild {
61
+ cmake {
62
+ path "CMakeLists.txt"
63
+ }
64
+ }
65
+
66
+ buildFeatures {
67
+ buildConfig false
68
+ }
69
+
70
+ compileOptions {
71
+ sourceCompatibility JavaVersion.VERSION_17
72
+ targetCompatibility JavaVersion.VERSION_17
73
+ }
74
+ }
75
+
76
+ dependencies {
77
+ implementation "com.facebook.react:react-android"
78
+ }
@@ -0,0 +1,2 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ </manifest>
@@ -0,0 +1,52 @@
1
+ #include <jni.h>
2
+
3
+ #include <vector>
4
+
5
+ #include "SquircleGeometry.h"
6
+
7
+ namespace {
8
+
9
+ squircle::CornerRadii cornerRadiiFromArray(const jfloat *values) {
10
+ return {
11
+ {values[0], values[1]},
12
+ {values[2], values[3]},
13
+ {values[4], values[5]},
14
+ {values[6], values[7]}};
15
+ }
16
+
17
+ }
18
+
19
+ extern "C" JNIEXPORT jfloatArray JNICALL Java_com_squircleview_geometry_SquircleGeometry_nativeBuildPath(
20
+ JNIEnv *env,
21
+ jclass,
22
+ jfloat width,
23
+ jfloat height,
24
+ jfloatArray radii,
25
+ jfloatArray insets,
26
+ jfloat smoothing) {
27
+ jfloat radiiValues[8];
28
+ jfloat insetValues[4];
29
+ env->GetFloatArrayRegion(radii, 0, 8, radiiValues);
30
+ env->GetFloatArrayRegion(insets, 0, 4, insetValues);
31
+
32
+ const squircle::Insets inset{insetValues[0], insetValues[1], insetValues[2], insetValues[3]};
33
+ const auto path = squircle::buildPath(
34
+ squircle::insetRect({0, 0, width, height}, inset),
35
+ squircle::insetRadii(cornerRadiiFromArray(radiiValues), inset),
36
+ smoothing);
37
+
38
+ std::vector<jfloat> data;
39
+ data.reserve(path.size() * 7);
40
+ for (const auto &element : path) {
41
+ data.push_back(static_cast<jfloat>(element.verb));
42
+ const int count = element.verb == squircle::Verb::Cubic ? 3 : element.verb == squircle::Verb::Close ? 0 : 1;
43
+ for (int index = 0; index < count; ++index) {
44
+ data.push_back(static_cast<jfloat>(element.points[index].x));
45
+ data.push_back(static_cast<jfloat>(element.points[index].y));
46
+ }
47
+ }
48
+
49
+ jfloatArray result = env->NewFloatArray(static_cast<jsize>(data.size()));
50
+ env->SetFloatArrayRegion(result, 0, static_cast<jsize>(data.size()), data.data());
51
+ return result;
52
+ }
@@ -0,0 +1,72 @@
1
+ package com.squircleview
2
+
3
+ import com.facebook.react.bridge.Dynamic
4
+ import com.facebook.react.module.annotations.ReactModule
5
+ import com.facebook.react.uimanager.LengthPercentage
6
+ import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.annotations.ReactProp
8
+ import com.facebook.react.uimanager.style.BorderRadiusProp
9
+ import com.facebook.react.uimanager.style.BorderStyle
10
+ import com.facebook.react.uimanager.style.LogicalEdge
11
+ import com.facebook.react.views.view.ReactViewGroup
12
+ import com.facebook.react.views.view.ReactViewManager
13
+ import com.squircleview.views.SquircleView
14
+
15
+ @ReactModule(name = SquircleViewManager.NAME)
16
+ public class SquircleViewManager : ReactViewManager() {
17
+ override fun getName(): String = NAME
18
+
19
+ override fun createViewInstance(context: ThemedReactContext): ReactViewGroup = SquircleView(context)
20
+
21
+ override fun prepareToRecycleView(reactContext: ThemedReactContext, view: ReactViewGroup): ReactViewGroup? {
22
+ val prepared = super.prepareToRecycleView(reactContext, view)
23
+ (prepared as? SquircleView)?.resetSquircle()
24
+ return prepared
25
+ }
26
+
27
+ @ReactProp(name = "borderSmoothing", defaultFloat = 0f)
28
+ public fun setBorderSmoothing(view: ReactViewGroup, borderSmoothing: Float) {
29
+ (view as SquircleView).setBorderSmoothing(borderSmoothing)
30
+ }
31
+
32
+ override fun setBorderRadius(view: ReactViewGroup, index: Int, rawBorderRadius: Dynamic) {
33
+ (view as SquircleView).setSquircleBorderRadius(
34
+ BorderRadiusProp.entries[index],
35
+ LengthPercentage.setFromDynamic(rawBorderRadius),
36
+ )
37
+ }
38
+
39
+ override fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float) {
40
+ (view as SquircleView).setSquircleBorderWidth(LogicalEdge.entries[index], width.takeUnless { it.isNaN() })
41
+ }
42
+
43
+ override fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?) {
44
+ (view as SquircleView).setSquircleBorderColor(BORDER_COLOR_EDGES[index], color)
45
+ }
46
+
47
+ override fun setBorderStyle(view: ReactViewGroup, borderStyle: String?) {
48
+ (view as SquircleView).setSquircleBorderStyle(borderStyle?.let(BorderStyle::fromString))
49
+ }
50
+
51
+ override fun setBackgroundColor(view: ReactViewGroup, backgroundColor: Int) {
52
+ (view as SquircleView).setSquircleBackgroundColor(backgroundColor)
53
+ }
54
+
55
+ public companion object {
56
+ public const val NAME: String = "SquircleView"
57
+
58
+ private val BORDER_COLOR_EDGES =
59
+ arrayOf(
60
+ LogicalEdge.ALL,
61
+ LogicalEdge.LEFT,
62
+ LogicalEdge.RIGHT,
63
+ LogicalEdge.TOP,
64
+ LogicalEdge.BOTTOM,
65
+ LogicalEdge.START,
66
+ LogicalEdge.END,
67
+ LogicalEdge.BLOCK,
68
+ LogicalEdge.BLOCK_END,
69
+ LogicalEdge.BLOCK_START,
70
+ )
71
+ }
72
+ }
@@ -0,0 +1,16 @@
1
+ package com.squircleview
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+ import com.facebook.react.uimanager.ViewManager
8
+
9
+ public class SquircleViewPackage : BaseReactPackage() {
10
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
11
+ listOf(SquircleViewManager())
12
+
13
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
14
+
15
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { emptyMap() }
16
+ }
@@ -0,0 +1,58 @@
1
+ package com.squircleview.geometry
2
+
3
+ import android.graphics.Path
4
+ import com.facebook.soloader.SoLoader
5
+
6
+ internal object SquircleGeometry {
7
+ private const val VERB_MOVE = 0
8
+ private const val VERB_LINE = 1
9
+ private const val VERB_CUBIC = 2
10
+ private const val VERB_CLOSE = 3
11
+
12
+ init {
13
+ SoLoader.loadLibrary("squircleview")
14
+ }
15
+
16
+ fun clampSmoothing(smoothing: Float): Float =
17
+ if (smoothing > 0f) smoothing.coerceAtMost(1f) else 0f
18
+
19
+ fun appendPath(
20
+ path: Path,
21
+ width: Float,
22
+ height: Float,
23
+ radii: FloatArray,
24
+ insets: FloatArray,
25
+ smoothing: Float,
26
+ ) {
27
+ val data = nativeBuildPath(width, height, radii, insets, smoothing)
28
+ var index = 0
29
+ while (index < data.size) {
30
+ when (data[index].toInt()) {
31
+ VERB_MOVE -> path.moveTo(data[index + 1], data[index + 2]).also { index += 3 }
32
+ VERB_LINE -> path.lineTo(data[index + 1], data[index + 2]).also { index += 3 }
33
+ VERB_CUBIC -> {
34
+ path.cubicTo(
35
+ data[index + 1],
36
+ data[index + 2],
37
+ data[index + 3],
38
+ data[index + 4],
39
+ data[index + 5],
40
+ data[index + 6],
41
+ )
42
+ index += 7
43
+ }
44
+ VERB_CLOSE -> path.close().also { index += 1 }
45
+ else -> return
46
+ }
47
+ }
48
+ }
49
+
50
+ @JvmStatic
51
+ private external fun nativeBuildPath(
52
+ width: Float,
53
+ height: Float,
54
+ radii: FloatArray,
55
+ insets: FloatArray,
56
+ smoothing: Float,
57
+ ): FloatArray
58
+ }
@@ -0,0 +1,142 @@
1
+ package com.squircleview.style
2
+
3
+ import android.graphics.Color
4
+ import androidx.annotation.ColorInt
5
+ import com.facebook.react.uimanager.LengthPercentage
6
+ import com.facebook.react.uimanager.LengthPercentageType
7
+ import com.facebook.react.uimanager.PixelUtil
8
+ import com.facebook.react.uimanager.style.BorderRadiusProp
9
+ import com.facebook.react.uimanager.style.BorderStyle
10
+ import com.facebook.react.uimanager.style.LogicalEdge
11
+
12
+ internal class SquircleStyle {
13
+ val radii: Array<LengthPercentage?> = arrayOfNulls(BorderRadiusProp.entries.size)
14
+ val borderWidths: Array<Float?> = arrayOfNulls(LogicalEdge.entries.size)
15
+ val borderColors: Array<Int?> = arrayOfNulls(LogicalEdge.entries.size)
16
+ @ColorInt var backgroundColor: Int = Color.TRANSPARENT
17
+ var borderStyle: BorderStyle? = null
18
+
19
+ fun hasRadius(): Boolean = radii.any { it != null && it.resolve(1f) > 0f }
20
+
21
+ fun reset() {
22
+ radii.fill(null)
23
+ borderWidths.fill(null)
24
+ borderColors.fill(null)
25
+ backgroundColor = Color.TRANSPARENT
26
+ borderStyle = null
27
+ }
28
+
29
+ fun resolveRadii(isRtl: Boolean, swapLeftAndRight: Boolean, width: Float, height: Float, out: FloatArray) {
30
+ val topLeft: LengthPercentage?
31
+ val topRight: LengthPercentage?
32
+ val bottomLeft: LengthPercentage?
33
+ val bottomRight: LengthPercentage?
34
+ val uniform = radius(BorderRadiusProp.BORDER_RADIUS)
35
+
36
+ when {
37
+ !isRtl -> {
38
+ topLeft = radius(BorderRadiusProp.BORDER_START_START_RADIUS, BorderRadiusProp.BORDER_TOP_START_RADIUS, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS)
39
+ topRight = radius(BorderRadiusProp.BORDER_END_START_RADIUS, BorderRadiusProp.BORDER_TOP_END_RADIUS, BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS)
40
+ bottomLeft = radius(BorderRadiusProp.BORDER_START_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_START_RADIUS, BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS)
41
+ bottomRight = radius(BorderRadiusProp.BORDER_END_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS)
42
+ }
43
+ swapLeftAndRight -> {
44
+ topLeft = radius(BorderRadiusProp.BORDER_END_START_RADIUS, BorderRadiusProp.BORDER_TOP_END_RADIUS, BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS)
45
+ topRight = radius(BorderRadiusProp.BORDER_START_START_RADIUS, BorderRadiusProp.BORDER_TOP_START_RADIUS, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS)
46
+ bottomLeft = radius(BorderRadiusProp.BORDER_END_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS)
47
+ bottomRight = radius(BorderRadiusProp.BORDER_START_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_START_RADIUS, BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS)
48
+ }
49
+ else -> {
50
+ topLeft = radius(BorderRadiusProp.BORDER_END_START_RADIUS, BorderRadiusProp.BORDER_TOP_END_RADIUS, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS)
51
+ topRight = radius(BorderRadiusProp.BORDER_START_START_RADIUS, BorderRadiusProp.BORDER_TOP_START_RADIUS, BorderRadiusProp.BORDER_TOP_RIGHT_RADIUS)
52
+ bottomLeft = radius(BorderRadiusProp.BORDER_END_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_START_RADIUS, BorderRadiusProp.BORDER_BOTTOM_LEFT_RADIUS)
53
+ bottomRight = radius(BorderRadiusProp.BORDER_START_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_END_RADIUS, BorderRadiusProp.BORDER_BOTTOM_RIGHT_RADIUS)
54
+ }
55
+ }
56
+
57
+ writeCorner(out, 0, topLeft ?: uniform, width, height)
58
+ writeCorner(out, 2, topRight ?: uniform, width, height)
59
+ writeCorner(out, 4, bottomRight ?: uniform, width, height)
60
+ writeCorner(out, 6, bottomLeft ?: uniform, width, height)
61
+ ensureNoOverlap(out, width, height)
62
+ }
63
+
64
+ fun resolveBorderWidths(isRtl: Boolean, swapLeftAndRight: Boolean, out: FloatArray) {
65
+ resolveEdges(borderWidths, isRtl, swapLeftAndRight) { top, right, bottom, left ->
66
+ out[0] = PixelUtil.toPixelFromDIP(top ?: 0f)
67
+ out[1] = PixelUtil.toPixelFromDIP(right ?: 0f)
68
+ out[2] = PixelUtil.toPixelFromDIP(bottom ?: 0f)
69
+ out[3] = PixelUtil.toPixelFromDIP(left ?: 0f)
70
+ }
71
+ }
72
+
73
+ fun resolveBorderColors(isRtl: Boolean, swapLeftAndRight: Boolean, out: IntArray) {
74
+ resolveEdges(borderColors, isRtl, swapLeftAndRight) { top, right, bottom, left ->
75
+ out[0] = top ?: Color.BLACK
76
+ out[1] = right ?: Color.BLACK
77
+ out[2] = bottom ?: Color.BLACK
78
+ out[3] = left ?: Color.BLACK
79
+ }
80
+ }
81
+
82
+ private fun radius(prop: BorderRadiusProp): LengthPercentage? = radii[prop.ordinal]
83
+
84
+ private fun radius(
85
+ logical: BorderRadiusProp,
86
+ side: BorderRadiusProp,
87
+ physical: BorderRadiusProp,
88
+ ): LengthPercentage? = radii[logical.ordinal] ?: radii[side.ordinal] ?: radii[physical.ordinal]
89
+
90
+ private inline fun <T> resolveEdges(
91
+ values: Array<T?>,
92
+ isRtl: Boolean,
93
+ swapLeftAndRight: Boolean,
94
+ block: (top: T?, right: T?, bottom: T?, left: T?) -> Unit,
95
+ ) {
96
+ val leftLogical = if (isRtl) LogicalEdge.END else LogicalEdge.START
97
+ val rightLogical = if (isRtl) LogicalEdge.START else LogicalEdge.END
98
+ val swap = isRtl && swapLeftAndRight
99
+ val leftPhysical = if (swap) LogicalEdge.RIGHT else LogicalEdge.LEFT
100
+ val rightPhysical = if (swap) LogicalEdge.LEFT else LogicalEdge.RIGHT
101
+ val all = values[LogicalEdge.ALL.ordinal]
102
+ val horizontal = values[LogicalEdge.HORIZONTAL.ordinal] ?: all
103
+ val vertical = values[LogicalEdge.BLOCK.ordinal] ?: values[LogicalEdge.VERTICAL.ordinal] ?: all
104
+
105
+ block(
106
+ values[LogicalEdge.BLOCK_START.ordinal] ?: values[LogicalEdge.TOP.ordinal] ?: vertical,
107
+ values[rightLogical.ordinal] ?: values[rightPhysical.ordinal] ?: horizontal,
108
+ values[LogicalEdge.BLOCK_END.ordinal] ?: values[LogicalEdge.BOTTOM.ordinal] ?: vertical,
109
+ values[leftLogical.ordinal] ?: values[leftPhysical.ordinal] ?: horizontal,
110
+ )
111
+ }
112
+
113
+ private fun writeCorner(out: FloatArray, offset: Int, radius: LengthPercentage?, width: Float, height: Float) {
114
+ out[offset] = toPixels(radius, width)
115
+ out[offset + 1] = toPixels(radius, height)
116
+ }
117
+
118
+ private fun toPixels(radius: LengthPercentage?, reference: Float): Float =
119
+ when (radius?.type) {
120
+ null -> 0f
121
+ LengthPercentageType.POINT -> PixelUtil.toPixelFromDIP(radius.resolve(0f))
122
+ LengthPercentageType.PERCENT -> radius.resolve(reference)
123
+ }
124
+
125
+ private fun ensureNoOverlap(radii: FloatArray, width: Float, height: Float) {
126
+ fun scale(sum: Float, side: Float): Float = if (sum > 0f) minOf(side / sum, 1f) else 0f
127
+
128
+ val top = scale(radii[0] + radii[2], width)
129
+ val right = scale(radii[3] + radii[5], height)
130
+ val bottom = scale(radii[6] + radii[4], width)
131
+ val left = scale(radii[1] + radii[7], height)
132
+ scaleCorner(radii, 0, minOf(top, left))
133
+ scaleCorner(radii, 2, minOf(right, top))
134
+ scaleCorner(radii, 4, minOf(bottom, right))
135
+ scaleCorner(radii, 6, minOf(left, bottom))
136
+ }
137
+
138
+ private fun scaleCorner(radii: FloatArray, offset: Int, factor: Float) {
139
+ radii[offset] *= factor
140
+ radii[offset + 1] *= factor
141
+ }
142
+ }