From 5c708a1600f1824f8e598d3fabdc7b392b66429c Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 14 Jun 2026 14:37:12 +0200 Subject: [PATCH 1/3] feat(views): add support for children --- docs/docs/guides/view-components.md | 81 +++++++++ example/src/screens/ViewScreen.tsx | 65 ++++++- .../views/kotlin/KotlinHybridViewManager.ts | 33 +++- .../src/views/swift/SwiftHybridViewManager.ts | 25 +++ .../com/margelo/nitro/views/NitroViewGroup.kt | 27 +++ .../margelo/nitro/test/HybridGradientView.kt | 72 ++++++++ .../margelo/nitro/test/NitroTestPackage.kt | 2 + .../ios/HybridGradientView.swift | 72 ++++++++ packages/react-native-nitro-test/nitro.json | 10 ++ .../android/NitroTest+autolinking.cmake | 4 + .../generated/android/NitroTestOnLoad.cpp | 18 ++ .../android/c++/JHybridGradientViewSpec.cpp | 76 ++++++++ .../android/c++/JHybridGradientViewSpec.hpp | 64 +++++++ .../views/JHybridGradientViewStateUpdater.cpp | 56 ++++++ .../views/JHybridGradientViewStateUpdater.hpp | 49 ++++++ .../nitro/test/HybridGradientViewSpec.kt | 57 ++++++ .../test/views/HybridGradientViewManager.kt | 111 ++++++++++++ .../views/HybridGradientViewStateUpdater.kt | 23 +++ .../views/HybridRecyclableTestViewManager.kt | 33 +++- .../nitro/test/views/HybridTestViewManager.kt | 33 +++- .../ios/NitroTest-Swift-Cxx-Bridge.cpp | 17 ++ .../ios/NitroTest-Swift-Cxx-Bridge.hpp | 39 +++-- .../ios/NitroTest-Swift-Cxx-Umbrella.hpp | 5 + .../generated/ios/NitroTestAutolinking.mm | 8 + .../generated/ios/NitroTestAutolinking.swift | 12 ++ .../ios/c++/HybridGradientViewSpecSwift.cpp | 11 ++ .../ios/c++/HybridGradientViewSpecSwift.hpp | 82 +++++++++ .../c++/views/HybridGradientViewComponent.mm | 147 ++++++++++++++++ .../HybridRecyclableTestViewComponent.mm | 25 +++ .../ios/c++/views/HybridTestViewComponent.mm | 25 +++ .../ios/swift/HybridGradientViewSpec.swift | 55 ++++++ .../swift/HybridGradientViewSpec_cxx.swift | 162 ++++++++++++++++++ .../shared/c++/HybridGradientViewSpec.cpp | 22 +++ .../shared/c++/HybridGradientViewSpec.hpp | 64 +++++++ .../c++/views/HybridGradientViewComponent.cpp | 83 +++++++++ .../c++/views/HybridGradientViewComponent.hpp | 112 ++++++++++++ .../shared/json/GradientViewConfig.json | 10 ++ packages/react-native-nitro-test/src/index.ts | 2 + .../src/specs/GradientView.nitro.ts | 13 ++ .../src/views/GradientView.ts | 13 ++ 40 files changed, 1803 insertions(+), 15 deletions(-) create mode 100644 packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/views/NitroViewGroup.kt create mode 100644 packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridGradientView.kt create mode 100644 packages/react-native-nitro-test/ios/HybridGradientView.swift create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridGradientViewSpec.kt create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewManager.kt create mode 100644 packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewStateUpdater.kt create mode 100644 packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridGradientViewComponent.mm create mode 100644 packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec.swift create mode 100644 packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec_cxx.swift create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.cpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.hpp create mode 100644 packages/react-native-nitro-test/nitrogen/generated/shared/json/GradientViewConfig.json create mode 100644 packages/react-native-nitro-test/src/specs/GradientView.nitro.ts create mode 100644 packages/react-native-nitro-test/src/views/GradientView.ts diff --git a/docs/docs/guides/view-components.md b/docs/docs/guides/view-components.md index b44f195d8e..1923e3a1e6 100644 --- a/docs/docs/guides/view-components.md +++ b/docs/docs/guides/view-components.md @@ -158,6 +158,87 @@ function App() { } ``` +## Children + +A Nitro `HybridView` renders React children like a regular `` - they're laid out by Flexbox/Yoga and mounted as **subviews of your native view**, letting it act as a _container_ that draws behind or around React content. For example, a native `` that renders a gradient behind its children: + +
+
+ +```ts title="GradientView.nitro.ts" +export interface GradientViewProps + extends HybridViewProps { + colors: string[] +} +export type GradientView = + HybridView +``` + +
+
+ +```jsx title="App.tsx" +function App() { + return ( + + Welcome back +
+
+ +No props or setup are required - children work out of the box. The only requirement is that your native view can hold subviews: + +- **iOS**: any `UIView` works. +- **Android**: return a **`NitroViewGroup`** (or a subclass) from `view`. It keeps the Fabric-assigned child frames - a normal `ViewGroup` would re-lay-out children and fight Fabric, and a plain `View` can't hold children (Nitro throws a descriptive error). + +
+
+ +```swift title="HybridGradientView.swift" +class HybridGradientView: HybridGradientViewSpec { + // A UIView backed by a CAGradientLayer. + // React children are added as subviews on top. + let view = GradientUIView() + + func setColors(_ colors: [String]) { + view.gradientLayer.colors = colors.map { + UIColor(hex: $0).cgColor + } + } +} +``` + +
+
+ +```kotlin title="HybridGradientView.kt" +// GradientFrameLayout extends NitroViewGroup so +// children keep their Fabric-assigned layout. +class HybridGradientView(context: ThemedReactContext): + HybridGradientViewSpec() { + override val view = GradientFrameLayout(context) + + override fun setColors(colors: Array) { + view.setGradientColors(colors.map(Color::parseColor)) + } +} +``` + +
+
+ +:::note Styling +A Hybrid View bridges **layout** (Flexbox/Yoga) to its native view but does **not** apply RN **visual** style props (`backgroundColor`, `borderRadius`, `boxShadow`, …) - the native view owns its appearance. To decorate one, wrap it in a regular `` (with `overflow: 'hidden'` to clip), the standard RN pattern for any native view. +::: + ## Props Since every `HybridView` is also a `HybridObject`, you can use any type that Nitro supports as a property - including custom types (`interface`), `ArrayBuffer`, and even other `HybridObject`s! diff --git a/example/src/screens/ViewScreen.tsx b/example/src/screens/ViewScreen.tsx index c17b84864d..c596a15816 100644 --- a/example/src/screens/ViewScreen.tsx +++ b/example/src/screens/ViewScreen.tsx @@ -1,10 +1,11 @@ import * as React from 'react' -import { StyleSheet, View, Text, Button, Platform } from 'react-native' +import { StyleSheet, View, Text, Button, Pressable, Platform } from 'react-native' import { callback, NitroModules } from 'react-native-nitro-modules' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useColors } from '../useColors' import { + GradientView, HybridTestObjectSwiftKotlin, RecyclableTestView, TestView, @@ -76,6 +77,27 @@ export function ViewScreenImpl() { {NitroModules.buildType} + + + Nitro GradientView + + These children are mounted on top of a native gradient + + console.log('Gradient child button tapped!')} + style={({ pressed }) => [ + styles.tapChild, + pressed && styles.tapChildPressed, + ]} + > + Tap a child + + + + @@ -128,6 +150,47 @@ const styles = StyleSheet.create({ }), fontWeight: 'bold', }, + gradientBannerWrapper: { + marginHorizontal: 15, + marginBottom: 15, + borderRadius: 16, + overflow: 'hidden', + }, + gradientBanner: { + padding: 16, + gap: 8, + }, + gradientTitle: { + color: 'white', + fontSize: 18, + fontWeight: 'bold', + }, + gradientSubtitle: { + color: 'white', + fontSize: 13, + }, + tapChild: { + alignSelf: 'flex-start', + marginTop: 4, + backgroundColor: 'rgba(255, 255, 255, 0.95)', + paddingVertical: 10, + paddingHorizontal: 20, + borderRadius: 999, + shadowColor: 'black', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 4, + elevation: 3, + }, + tapChildPressed: { + opacity: 0.85, + transform: [{ scale: 0.96 }], + }, + tapChildText: { + color: '#7928CA', + fontSize: 14, + fontWeight: '700', + }, segmentedControl: { minWidth: 180, }, diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index a27718115b..1a98ca2c44 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -44,6 +44,8 @@ ${createFileMetadataString(`${manager}.kt`)} package ${javaSubNamespace} import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.IViewGroupManager import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.StateWrapper @@ -55,7 +57,7 @@ import ${javaNamespace}.* /** * Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView. */ -public class ${manager}: SimpleViewManager() { +public class ${manager}: SimpleViewManager(), IViewGroupManager { init { if (RecyclableView::class.java.isAssignableFrom(${viewImplementation}::class.java)) { // Enable view recycling @@ -113,6 +115,35 @@ public class ${manager}: SimpleViewManager() { private fun getHybridView(view: View): ${viewImplementation}? { return view.getTag(associated_hybrid_view_tag) as? ${viewImplementation} } + + override fun needsCustomLayoutForChildren(): Boolean { + // false: Fabric positions children directly, so we don't lay them out. + return false + } + + override fun addView(parent: View, child: View, index: Int) { + asViewGroup(parent).addView(child, index) + } + + override fun getChildAt(parent: View, index: Int): View? { + return asViewGroup(parent).getChildAt(index) + } + + override fun getChildCount(parent: View): Int { + return (parent as? ViewGroup)?.childCount ?: 0 + } + + override fun removeViewAt(parent: View, index: Int) { + asViewGroup(parent).removeViewAt(index) + } + + private fun asViewGroup(view: View): ViewGroup { + if (view is ViewGroup) { + return view + } + val className = view.javaClass.simpleName + throw IllegalStateException("Nitro view \\"${spec.name}\\" received React children, but its native view ($className) is not a ViewGroup! To render children, return a \`NitroViewGroup\` (or another \`android.view.ViewGroup\`) from your HybridView's \`view\`.") + } } `.trim() diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index 68c579ffb6..ee8f7a920b 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -119,6 +119,31 @@ using namespace ${namespace}::views; [self setContentView:view]; } +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + // Mount children inside the contentView, not as siblings of it (fall back if there's none yet). + UIView* container = self.contentView; + if (container == nil) { + [super mountChildComponentView:childComponentView index:index]; + return; + } + [container insertSubview:childComponentView atIndex:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + if (childComponentView.superview == nil) { + [super unmountChildComponentView:childComponentView index:index]; + return; + } + [childComponentView removeFromSuperview]; +} + +- (void) updateLayoutMetrics:(const facebook::react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const facebook::react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Make the contentView fill the component, else children would be double-inset by padding/border. + self.contentView.frame = self.bounds; +} + - (void) updateProps:(const std::shared_ptr&)props oldProps:(const std::shared_ptr&)oldProps { // 1. Downcast props diff --git a/packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/views/NitroViewGroup.kt b/packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/views/NitroViewGroup.kt new file mode 100644 index 0000000000..70c9db94fe --- /dev/null +++ b/packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/views/NitroViewGroup.kt @@ -0,0 +1,27 @@ +package com.margelo.nitro.views + +import android.annotation.SuppressLint +import android.content.Context +import android.view.ViewGroup + +/** + * Base [ViewGroup] for [HybridView]s that render React children. Return one of these + * (or a subclass) from your `HybridView`'s `view` when it should host children. + */ +public open class NitroViewGroup(context: Context) : ViewGroup(context) { + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + // No-op: React Native positions each child. + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + setMeasuredDimension( + MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec), + ) + } + + @SuppressLint("MissingSuperCall") + override fun requestLayout() { + // No-op: React Native drives layout, not Android. + } +} diff --git a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridGradientView.kt b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridGradientView.kt new file mode 100644 index 0000000000..76265e6cf5 --- /dev/null +++ b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridGradientView.kt @@ -0,0 +1,72 @@ +package com.margelo.nitro.test + +import android.content.Context +import android.graphics.Canvas +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Shader +import androidx.annotation.Keep +import androidx.core.graphics.toColorInt +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.views.NitroViewGroup + +class GradientView(context: Context) : NitroViewGroup(context) { + private var gradientColors: IntArray = intArrayOf() + private val paint = Paint() + + init { + setWillNotDraw(false) + } + + fun setGradientColors(colors: IntArray) { + gradientColors = colors + updateShader() + invalidate() + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + updateShader() + } + + private fun updateShader() { + paint.shader = if (gradientColors.size >= 2 && width > 0 && height > 0) { + LinearGradient( + 0f, + 0f, + width.toFloat(), + height.toFloat(), + gradientColors, + null, + Shader.TileMode.CLAMP, + ) + } else { + null + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (paint.shader != null) { + canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) + } + } +} + +@Keep +@DoNotStrip +class HybridGradientView( + val context: ThemedReactContext, +) : HybridGradientViewSpec() { + // View + override val view: GradientView = GradientView(context) + + // Props + override var colors: Array = arrayOf() + set(value) { + field = value + val parsed = value.mapNotNull { runCatching { it.toColorInt() }.getOrNull() } + view.setGradientColors(parsed.toIntArray()) + } +} diff --git a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/NitroTestPackage.kt b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/NitroTestPackage.kt index 50bc8911c5..b7e8e6e6e2 100644 --- a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/NitroTestPackage.kt +++ b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/NitroTestPackage.kt @@ -5,6 +5,7 @@ import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.module.model.ReactModuleInfoProvider import com.facebook.react.uimanager.ViewManager +import com.margelo.nitro.test.views.HybridGradientViewManager import com.margelo.nitro.test.views.HybridRecyclableTestViewManager import com.margelo.nitro.test.views.HybridTestViewManager @@ -20,6 +21,7 @@ class NitroTestPackage : BaseReactPackage() { val viewManagers = ArrayList>() viewManagers.add(HybridTestViewManager()) viewManagers.add(HybridRecyclableTestViewManager()) + viewManagers.add(HybridGradientViewManager()) return viewManagers } diff --git a/packages/react-native-nitro-test/ios/HybridGradientView.swift b/packages/react-native-nitro-test/ios/HybridGradientView.swift new file mode 100644 index 0000000000..324a8ea4ce --- /dev/null +++ b/packages/react-native-nitro-test/ios/HybridGradientView.swift @@ -0,0 +1,72 @@ +// +// HybridGradientView.swift +// react-native-nitro-test +// +// Created by Patrick Kabwe on 12.06.26. +// + +import NitroModules +import UIKit + +final class GradientUIView: UIView { + override class var layerClass: AnyClass { + return CAGradientLayer.self + } + + var gradientLayer: CAGradientLayer { + return layer as! CAGradientLayer + } + + override init(frame: CGRect) { + super.init(frame: frame) + gradientLayer.startPoint = CGPoint(x: 0, y: 0) + gradientLayer.endPoint = CGPoint(x: 1, y: 1) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class HybridGradientView: HybridGradientViewSpec { + // View + let view = GradientUIView() + + // Props + var colors: [String] = [] { + didSet { + view.gradientLayer.colors = colors.compactMap { UIColor(hexString: $0)?.cgColor } + } + } +} + +private extension UIColor { + convenience init?(hexString: String) { + var hex = hexString.trimmingCharacters(in: .whitespacesAndNewlines) + if hex.hasPrefix("#") { + hex.removeFirst() + } + + guard let value = UInt64(hex, radix: 16) else { + return nil + } + + let red, green, blue, alpha: CGFloat + switch hex.count { + case 6: + red = CGFloat((value & 0xFF0000) >> 16) / 255 + green = CGFloat((value & 0x00FF00) >> 8) / 255 + blue = CGFloat(value & 0x0000FF) / 255 + alpha = 1 + case 8: + red = CGFloat((value & 0xFF00_0000) >> 24) / 255 + green = CGFloat((value & 0x00FF_0000) >> 16) / 255 + blue = CGFloat((value & 0x0000_FF00) >> 8) / 255 + alpha = CGFloat(value & 0x0000_00FF) / 255 + default: + return nil + } + + self.init(red: red, green: green, blue: blue, alpha: alpha) + } +} diff --git a/packages/react-native-nitro-test/nitro.json b/packages/react-native-nitro-test/nitro.json index b3a180b75e..c275537ab5 100644 --- a/packages/react-native-nitro-test/nitro.json +++ b/packages/react-native-nitro-test/nitro.json @@ -74,6 +74,16 @@ "language": "kotlin", "implementationClassName": "HybridRecyclableTestView" } + }, + "GradientView": { + "ios": { + "language": "swift", + "implementationClassName": "HybridGradientView" + }, + "android": { + "language": "kotlin", + "implementationClassName": "HybridGradientView" + } } }, "ignorePaths": [ diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake index 453148d1b5..8f991de991 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake +++ b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTest+autolinking.cmake @@ -35,6 +35,8 @@ target_sources( # Shared Nitrogen C++ sources ../nitrogen/generated/shared/c++/HybridBaseSpec.cpp ../nitrogen/generated/shared/c++/HybridChildSpec.cpp + ../nitrogen/generated/shared/c++/HybridGradientViewSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridGradientViewComponent.cpp ../nitrogen/generated/shared/c++/HybridPlatformObjectSpec.cpp ../nitrogen/generated/shared/c++/HybridRecyclableTestViewSpec.cpp ../nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -47,6 +49,8 @@ target_sources( ../nitrogen/generated/android/c++/JHybridChildSpec.cpp ../nitrogen/generated/android/c++/JNamedVariant.cpp ../nitrogen/generated/android/c++/JVariant_String_Double.cpp + ../nitrogen/generated/android/c++/JHybridGradientViewSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.cpp ../nitrogen/generated/android/c++/JHybridPlatformObjectSpec.cpp ../nitrogen/generated/android/c++/JHybridRecyclableTestViewSpec.cpp ../nitrogen/generated/android/c++/views/JHybridRecyclableTestViewStateUpdater.cpp diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp index 5ebdab53ba..707b79e363 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/android/NitroTestOnLoad.cpp @@ -17,6 +17,8 @@ #include "JHybridBaseSpec.hpp" #include "JHybridChildSpec.hpp" +#include "JHybridGradientViewSpec.hpp" +#include "views/JHybridGradientViewStateUpdater.hpp" #include "JHybridPlatformObjectSpec.hpp" #include "JHybridRecyclableTestViewSpec.hpp" #include "views/JHybridRecyclableTestViewStateUpdater.hpp" @@ -95,6 +97,14 @@ struct JHybridRecyclableTestViewSpecImpl: public jni::JavaClassgetJHybridRecyclableTestViewSpec(); } }; +struct JHybridGradientViewSpecImpl: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridGradientView;"; + static std::shared_ptr create() { + static const auto constructorFn = javaClassStatic()->getConstructor(); + jni::local_ref javaPart = javaClassStatic()->newObject(constructorFn); + return javaPart->getJHybridGradientViewSpec(); + } +}; void registerAllNatives() { using namespace margelo::nitro; @@ -103,6 +113,8 @@ void registerAllNatives() { // Register native JNI methods margelo::nitro::test::JHybridBaseSpec::CxxPart::registerNatives(); margelo::nitro::test::JHybridChildSpec::CxxPart::registerNatives(); + margelo::nitro::test::JHybridGradientViewSpec::CxxPart::registerNatives(); + margelo::nitro::test::views::JHybridGradientViewStateUpdater::registerNatives(); margelo::nitro::test::JHybridPlatformObjectSpec::CxxPart::registerNatives(); margelo::nitro::test::JHybridRecyclableTestViewSpec::CxxPart::registerNatives(); margelo::nitro::test::views::JHybridRecyclableTestViewStateUpdater::registerNatives(); @@ -169,6 +181,12 @@ void registerAllNatives() { return JHybridRecyclableTestViewSpecImpl::create(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "GradientView", + []() -> std::shared_ptr { + return JHybridGradientViewSpecImpl::create(); + } + ); } } // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.cpp new file mode 100644 index 0000000000..2bf8d0e440 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.cpp @@ -0,0 +1,76 @@ +/// +/// JHybridGradientViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridGradientViewSpec.hpp" + + + +#include +#include + +namespace margelo::nitro::test { + + std::shared_ptr JHybridGradientViewSpec::JavaPart::getJHybridGradientViewSpec() { + auto hybridObject = JHybridObject::JavaPart::getJHybridObject(); + auto castHybridObject = std::dynamic_pointer_cast(hybridObject); + if (castHybridObject == nullptr) [[unlikely]] { + throw std::runtime_error("Failed to downcast JHybridObject to JHybridGradientViewSpec!"); + } + return castHybridObject; + } + + jni::local_ref JHybridGradientViewSpec::CxxPart::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + std::shared_ptr JHybridGradientViewSpec::CxxPart::createHybridObject(const jni::local_ref& javaPart) { + auto castJavaPart = jni::dynamic_ref_cast(javaPart); + if (castJavaPart == nullptr) [[unlikely]] { + throw std::runtime_error("Failed to cast JHybridObject::JavaPart to JHybridGradientViewSpec::JavaPart!"); + } + return std::make_shared(castJavaPart); + } + + void JHybridGradientViewSpec::CxxPart::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridGradientViewSpec::CxxPart::initHybrid), + }); + } + + // Properties + std::vector JHybridGradientViewSpec::getColors() { + static const auto method = _javaPart->javaClassStatic()->getMethod>()>("getColors"); + auto __result = method(_javaPart); + return [&](auto&& __input) { + size_t __size = __input->size(); + std::vector __vector; + __vector.reserve(__size); + for (size_t __i = 0; __i < __size; __i++) { + auto __element = __input->getElement(__i); + __vector.push_back(__element->toStdString()); + } + return __vector; + }(__result); + } + void JHybridGradientViewSpec::setColors(const std::vector& colors) { + static const auto method = _javaPart->javaClassStatic()->getMethod> /* colors */)>("setColors"); + method(_javaPart, [&](auto&& __input) { + size_t __size = __input.size(); + jni::local_ref> __array = jni::JArrayClass::newArray(__size); + for (size_t __i = 0; __i < __size; __i++) { + const auto& __element = __input[__i]; + auto __elementJni = jni::make_jstring(__element); + __array->setElement(__i, *__elementJni); + } + return __array; + }(colors)); + } + + // Methods + + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.hpp new file mode 100644 index 0000000000..0de71c8d08 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridGradientViewSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridGradientViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridGradientViewSpec.hpp" + + + + +namespace margelo::nitro::test { + + using namespace facebook; + + class JHybridGradientViewSpec: public virtual HybridGradientViewSpec, public virtual JHybridObject { + public: + struct JavaPart: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridGradientViewSpec;"; + std::shared_ptr getJHybridGradientViewSpec(); + }; + struct CxxPart: public jni::HybridClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridGradientViewSpec$CxxPart;"; + static jni::local_ref initHybrid(jni::alias_ref jThis); + static void registerNatives(); + using HybridBase::HybridBase; + protected: + std::shared_ptr createHybridObject(const jni::local_ref& javaPart) override; + }; + + public: + explicit JHybridGradientViewSpec(const jni::local_ref& javaPart): + HybridObject(HybridGradientViewSpec::TAG), + JHybridObject(javaPart), + _javaPart(jni::make_global(javaPart)) {} + ~JHybridGradientViewSpec() override { + // Hermes GC can destroy JS objects on a non-JNI Thread. + jni::ThreadScope::WithClassLoader([&] { _javaPart.reset(); }); + } + + public: + inline const jni::global_ref& getJavaPart() const noexcept { + return _javaPart; + } + + public: + // Properties + std::vector getColors() override; + void setColors(const std::vector& colors) override; + + public: + // Methods + + + private: + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.cpp new file mode 100644 index 0000000000..9786d01a45 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.cpp @@ -0,0 +1,56 @@ +/// +/// JHybridGradientViewStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridGradientViewStateUpdater.hpp" +#include "views/HybridGradientViewComponent.hpp" +#include +#include + +namespace margelo::nitro::test::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +void JHybridGradientViewStateUpdater::updateViewProps(jni::alias_ref /* class */, + jni::alias_ref javaView, + jni::alias_ref stateWrapperInterface) { + std::shared_ptr hybridView = javaView->getJHybridGradientViewSpec(); + + // Get concrete StateWrapperImpl from passed StateWrapper interface object + jobject rawStateWrapper = stateWrapperInterface.get(); + if (!stateWrapperInterface->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { + throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); + } + auto stateWrapper = jni::alias_ref{ + static_cast(rawStateWrapper)}; + std::shared_ptr state = stateWrapper->cthis()->getState(); + auto concreteState = std::static_pointer_cast(state); + const HybridGradientViewState& data = concreteState->getData(); + const std::shared_ptr& props = data.getProps(); + if (props == nullptr) [[unlikely]] { + // Props aren't set yet! + throw std::runtime_error("HybridGradientViewState's data doesn't contain any props!"); + } + + // Update all props if they are dirty + if (props->colors.isDirty) { + hybridView->setColors(props->colors.value); + props->colors.isDirty = false; + } + + // Update hybridRef if it changed + if (props->hybridRef.isDirty) { + // hybridRef changed - call it with new this + const auto& maybeFunc = props->hybridRef.value; + if (maybeFunc.has_value()) { + maybeFunc.value()(hybridView); + } + props->hybridRef.isDirty = false; + } +} + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.hpp new file mode 100644 index 0000000000..1caeff0e69 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridGradientViewStateUpdater.hpp @@ -0,0 +1,49 @@ +/// +/// JHybridGradientViewStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#ifndef RN_SERIALIZABLE_STATE +#error NitroTest was compiled without the 'RN_SERIALIZABLE_STATE' flag. This flag is required for Nitro Views - set it in your CMakeLists! +#endif + +#include +#include +#include +#include +#include +#include +#include "JHybridGradientViewSpec.hpp" +#include "views/HybridGradientViewComponent.hpp" + +namespace margelo::nitro::test::views { + +using namespace facebook; + +class JHybridGradientViewStateUpdater final: public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/views/HybridGradientViewStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref /* class */, + jni::alias_ref view, + jni::alias_ref stateWrapperInterface); + +public: + static void registerNatives() { + // Register JNI calls + javaClassStatic()->registerNatives({ + makeNativeMethod("updateViewProps", JHybridGradientViewStateUpdater::updateViewProps), + }); + // Register React Native view component descriptor + auto provider = react::concreteComponentDescriptorProvider(); + auto providerRegistry = react::CoreComponentsRegistry::sharedProviderRegistry(); + providerRegistry->add(provider); + } +}; + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridGradientViewSpec.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridGradientViewSpec.kt new file mode 100644 index 0000000000..12c248ee6a --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridGradientViewSpec.kt @@ -0,0 +1,57 @@ +/// +/// HybridGradientViewSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test + +import androidx.annotation.Keep +import com.facebook.jni.HybridData +import com.facebook.proguard.annotations.DoNotStrip +import com.margelo.nitro.core.HybridObject +import com.margelo.nitro.views.HybridView + +/** + * A Kotlin class representing the GradientView HybridObject. + * Implement this abstract class to create Kotlin-based instances of GradientView. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridGradientViewSpec: HybridView() { + // Properties + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var colors: Array + + // Methods + + + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject GradientView]" + } + + // C++ backing class + @DoNotStrip + @Keep + protected open class CxxPart(javaPart: HybridGradientViewSpec): HybridObject.CxxPart(javaPart) { + // C++ JHybridGradientViewSpec::CxxPart::initHybrid(...) + external override fun initHybrid(): HybridData + } + override fun createCxxPart(): CxxPart { + return CxxPart(this) + } + + companion object { + protected const val TAG = "HybridGradientViewSpec" + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewManager.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewManager.kt new file mode 100644 index 0000000000..625906285e --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewManager.kt @@ -0,0 +1,111 @@ +/// +/// HybridGradientViewManager.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.IViewGroupManager +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.SimpleViewManager +import com.facebook.react.uimanager.StateWrapper +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.R.id.associated_hybrid_view_tag +import com.margelo.nitro.views.RecyclableView +import com.margelo.nitro.test.* + +/** + * Represents the React Native `ViewManager` for the "GradientView" Nitro HybridView. + */ +public class HybridGradientViewManager: SimpleViewManager(), IViewGroupManager { + init { + if (RecyclableView::class.java.isAssignableFrom(HybridGradientView::class.java)) { + // Enable view recycling + super.setupViewRecycling() + } + } + + override fun getName(): String { + return "GradientView" + } + + override fun createViewInstance(reactContext: ThemedReactContext): View { + val hybridView = HybridGradientView(reactContext) + val view = hybridView.view + view.setTag(associated_hybrid_view_tag, hybridView) + return view + } + + override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val hybridView = getHybridView(view) + ?: throw Error("Couldn't find view $view in local views table!") + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridGradientViewStateUpdater.updateViewProps(hybridView, stateWrapper) + hybridView.afterUpdate() + + // 2. Continue in base View props + return super.updateState(view, props, stateWrapper) + } + + override fun onDropViewInstance(view: View) { + val hybridView = getHybridView(view) + hybridView?.onDropView() + return super.onDropViewInstance(view) + } + + protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? { + super.prepareToRecycleView(reactContext, view) + val hybridView = getHybridView(view) + ?: return null + + @Suppress("USELESS_IS_CHECK") + if (hybridView is RecyclableView) { + // Recycle in it's implementation + hybridView.prepareForRecycle() + + // Maybe update the view if it changed + return hybridView.view + } else { + return null + } + } + + private fun getHybridView(view: View): HybridGradientView? { + return view.getTag(associated_hybrid_view_tag) as? HybridGradientView + } + + override fun needsCustomLayoutForChildren(): Boolean { + // false: Fabric positions children directly, so we don't lay them out. + return false + } + + override fun addView(parent: View, child: View, index: Int) { + asViewGroup(parent).addView(child, index) + } + + override fun getChildAt(parent: View, index: Int): View? { + return asViewGroup(parent).getChildAt(index) + } + + override fun getChildCount(parent: View): Int { + return (parent as? ViewGroup)?.childCount ?: 0 + } + + override fun removeViewAt(parent: View, index: Int) { + asViewGroup(parent).removeViewAt(index) + } + + private fun asViewGroup(view: View): ViewGroup { + if (view is ViewGroup) { + return view + } + val className = view.javaClass.simpleName + throw IllegalStateException("Nitro view \"GradientView\" received React children, but its native view ($className) is not a ViewGroup! To render children, return a `NitroViewGroup` (or another `android.view.ViewGroup`) from your HybridView's `view`.") + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewStateUpdater.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewStateUpdater.kt new file mode 100644 index 0000000000..952377563b --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridGradientViewStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridGradientViewStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import com.facebook.react.uimanager.StateWrapper +import com.margelo.nitro.test.* + +internal class HybridGradientViewStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [state] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridGradientViewSpec, state: StateWrapper) + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridRecyclableTestViewManager.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridRecyclableTestViewManager.kt index c325e51a57..eee51f9902 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridRecyclableTestViewManager.kt +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridRecyclableTestViewManager.kt @@ -8,6 +8,8 @@ package com.margelo.nitro.test.views import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.IViewGroupManager import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.StateWrapper @@ -19,7 +21,7 @@ import com.margelo.nitro.test.* /** * Represents the React Native `ViewManager` for the "RecyclableTestView" Nitro HybridView. */ -public class HybridRecyclableTestViewManager: SimpleViewManager() { +public class HybridRecyclableTestViewManager: SimpleViewManager(), IViewGroupManager { init { if (RecyclableView::class.java.isAssignableFrom(HybridRecyclableTestView::class.java)) { // Enable view recycling @@ -77,4 +79,33 @@ public class HybridRecyclableTestViewManager: SimpleViewManager() { private fun getHybridView(view: View): HybridRecyclableTestView? { return view.getTag(associated_hybrid_view_tag) as? HybridRecyclableTestView } + + override fun needsCustomLayoutForChildren(): Boolean { + // false: Fabric positions children directly, so we don't lay them out. + return false + } + + override fun addView(parent: View, child: View, index: Int) { + asViewGroup(parent).addView(child, index) + } + + override fun getChildAt(parent: View, index: Int): View? { + return asViewGroup(parent).getChildAt(index) + } + + override fun getChildCount(parent: View): Int { + return (parent as? ViewGroup)?.childCount ?: 0 + } + + override fun removeViewAt(parent: View, index: Int) { + asViewGroup(parent).removeViewAt(index) + } + + private fun asViewGroup(view: View): ViewGroup { + if (view is ViewGroup) { + return view + } + val className = view.javaClass.simpleName + throw IllegalStateException("Nitro view \"RecyclableTestView\" received React children, but its native view ($className) is not a ViewGroup! To render children, return a `NitroViewGroup` (or another `android.view.ViewGroup`) from your HybridView's `view`.") + } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridTestViewManager.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridTestViewManager.kt index 6ee9d802a2..6695902e07 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridTestViewManager.kt +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridTestViewManager.kt @@ -8,6 +8,8 @@ package com.margelo.nitro.test.views import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.IViewGroupManager import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.StateWrapper @@ -19,7 +21,7 @@ import com.margelo.nitro.test.* /** * Represents the React Native `ViewManager` for the "TestView" Nitro HybridView. */ -public class HybridTestViewManager: SimpleViewManager() { +public class HybridTestViewManager: SimpleViewManager(), IViewGroupManager { init { if (RecyclableView::class.java.isAssignableFrom(HybridTestView::class.java)) { // Enable view recycling @@ -77,4 +79,33 @@ public class HybridTestViewManager: SimpleViewManager() { private fun getHybridView(view: View): HybridTestView? { return view.getTag(associated_hybrid_view_tag) as? HybridTestView } + + override fun needsCustomLayoutForChildren(): Boolean { + // false: Fabric positions children directly, so we don't lay them out. + return false + } + + override fun addView(parent: View, child: View, index: Int) { + asViewGroup(parent).addView(child, index) + } + + override fun getChildAt(parent: View, index: Int): View? { + return asViewGroup(parent).getChildAt(index) + } + + override fun getChildCount(parent: View): Int { + return (parent as? ViewGroup)?.childCount ?: 0 + } + + override fun removeViewAt(parent: View, index: Int) { + asViewGroup(parent).removeViewAt(index) + } + + private fun asViewGroup(view: View): ViewGroup { + if (view is ViewGroup) { + return view + } + val className = view.javaClass.simpleName + throw IllegalStateException("Nitro view \"TestView\" received React children, but its native view ($className) is not a ViewGroup! To render children, return a `NitroViewGroup` (or another `android.view.ViewGroup`) from your HybridView's `view`.") + } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.cpp b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.cpp index 7b54268b15..50520e18c3 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.cpp +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.cpp @@ -10,6 +10,7 @@ // Include C++ implementation defined types #include "HybridBaseSpecSwift.hpp" #include "HybridChildSpecSwift.hpp" +#include "HybridGradientViewSpecSwift.hpp" #include "HybridPlatformObjectSpecSwift.hpp" #include "HybridRecyclableTestViewSpecSwift.hpp" #include "HybridTestObjectSwiftKotlinSpecSwift.hpp" @@ -52,6 +53,22 @@ namespace margelo::nitro::test::bridge::swift { return swiftPart.toUnsafe(); } + // pragma MARK: std::shared_ptr + std::shared_ptr create_std__shared_ptr_HybridGradientViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept { + NitroTest::HybridGradientViewSpec_cxx swiftPart = NitroTest::HybridGradientViewSpec_cxx::fromUnsafe(swiftUnsafePointer); + return std::make_shared(swiftPart); + } + void* NON_NULL get_std__shared_ptr_HybridGradientViewSpec_(std__shared_ptr_HybridGradientViewSpec_ cppType) { + std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); + #ifdef NITRO_DEBUG + if (swiftWrapper == nullptr) [[unlikely]] { + throw std::runtime_error("Class \"HybridGradientViewSpec\" is not implemented in Swift!"); + } + #endif + NitroTest::HybridGradientViewSpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); + return swiftPart.toUnsafe(); + } + // pragma MARK: std::shared_ptr std::shared_ptr create_std__shared_ptr_HybridPlatformObjectSpec_(void* NON_NULL swiftUnsafePointer) noexcept { NitroTest::HybridPlatformObjectSpec_cxx swiftPart = NitroTest::HybridPlatformObjectSpec_cxx::fromUnsafe(swiftUnsafePointer); diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.hpp index c53666adc7..ba05483ae7 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Bridge.hpp @@ -18,6 +18,8 @@ namespace margelo::nitro::test { struct ExternalObjectStruct; } namespace margelo::nitro::test { class HybridBaseSpec; } // Forward declaration of `HybridChildSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridChildSpec; } +// Forward declaration of `HybridGradientViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridGradientViewSpec; } // Forward declaration of `HybridPlatformObjectSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridPlatformObjectSpec; } // Forward declaration of `HybridRecyclableTestViewSpec` to properly resolve imports. @@ -54,6 +56,8 @@ namespace margelo::nitro::test { struct WrappedJsStruct; } namespace NitroTest { class HybridBaseSpec_cxx; } // Forward declaration of `HybridChildSpec_cxx` to properly resolve imports. namespace NitroTest { class HybridChildSpec_cxx; } +// Forward declaration of `HybridGradientViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridGradientViewSpec_cxx; } // Forward declaration of `HybridPlatformObjectSpec_cxx` to properly resolve imports. namespace NitroTest { class HybridPlatformObjectSpec_cxx; } // Forward declaration of `HybridRecyclableTestViewSpec_cxx` to properly resolve imports. @@ -70,6 +74,7 @@ namespace NitroTest { class HybridTestViewSpec_cxx; } #include "ExternalObjectStruct.hpp" #include "HybridBaseSpec.hpp" #include "HybridChildSpec.hpp" +#include "HybridGradientViewSpec.hpp" #include "HybridPlatformObjectSpec.hpp" #include "HybridRecyclableTestViewSpec.hpp" #include "HybridTestObjectSwiftKotlinSpec.hpp" @@ -284,6 +289,29 @@ namespace margelo::nitro::test::bridge::swift { return Result>::withError(error); } + // pragma MARK: std::vector + /** + * Specialized version of `std::vector`. + */ + using std__vector_std__string_ = std::vector; + inline std::vector create_std__vector_std__string_(size_t size) noexcept { + std::vector vector; + vector.reserve(size); + return vector; + } + + // pragma MARK: std::shared_ptr + /** + * Specialized version of `std::shared_ptr`. + */ + using std__shared_ptr_HybridGradientViewSpec_ = std::shared_ptr; + std::shared_ptr create_std__shared_ptr_HybridGradientViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept; + void* NON_NULL get_std__shared_ptr_HybridGradientViewSpec_(std__shared_ptr_HybridGradientViewSpec_ cppType); + + // pragma MARK: std::weak_ptr + using std__weak_ptr_HybridGradientViewSpec_ = std::weak_ptr; + inline std__weak_ptr_HybridGradientViewSpec_ weakify_std__shared_ptr_HybridGradientViewSpec_(const std::shared_ptr& strong) noexcept { return strong; } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. @@ -402,17 +430,6 @@ namespace margelo::nitro::test::bridge::swift { return std__variant_nitro__NullType__std__string_(value); } - // pragma MARK: std::vector - /** - * Specialized version of `std::vector`. - */ - using std__vector_std__string_ = std::vector; - inline std::vector create_std__vector_std__string_(size_t size) noexcept { - std::vector vector; - vector.reserve(size); - return vector; - } - // pragma MARK: std::optional> /** * Specialized version of `std::optional>`. diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp index 8ad7417a51..82c70f652f 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTest-Swift-Cxx-Umbrella.hpp @@ -18,6 +18,8 @@ namespace margelo::nitro::test { struct ExternalObjectStruct; } namespace margelo::nitro::test { class HybridBaseSpec; } // Forward declaration of `HybridChildSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridChildSpec; } +// Forward declaration of `HybridGradientViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridGradientViewSpec; } // Forward declaration of `HybridPlatformObjectSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridPlatformObjectSpec; } // Forward declaration of `HybridRecyclableTestViewSpec` to properly resolve imports. @@ -59,6 +61,7 @@ namespace margelo::nitro::test { struct WrappedJsStruct; } #include "ExternalObjectStruct.hpp" #include "HybridBaseSpec.hpp" #include "HybridChildSpec.hpp" +#include "HybridGradientViewSpec.hpp" #include "HybridPlatformObjectSpec.hpp" #include "HybridRecyclableTestViewSpec.hpp" #include "HybridTestObjectSwiftKotlinSpec.hpp" @@ -105,6 +108,8 @@ namespace margelo::nitro::test { struct WrappedJsStruct; } namespace NitroTest { class HybridBaseSpec_cxx; } // Forward declaration of `HybridChildSpec_cxx` to properly resolve imports. namespace NitroTest { class HybridChildSpec_cxx; } +// Forward declaration of `HybridGradientViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridGradientViewSpec_cxx; } // Forward declaration of `HybridPlatformObjectSpec_cxx` to properly resolve imports. namespace NitroTest { class HybridPlatformObjectSpec_cxx; } // Forward declaration of `HybridRecyclableTestViewSpec_cxx` to properly resolve imports. diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm index 1a2b5b22c2..34aa63a102 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm @@ -17,6 +17,7 @@ #include "HybridPlatformObjectSpecSwift.hpp" #include "HybridTestViewSpecSwift.hpp" #include "HybridRecyclableTestViewSpecSwift.hpp" +#include "HybridGradientViewSpecSwift.hpp" @interface NitroTestAutolinking : NSObject @end @@ -78,6 +79,13 @@ + (void) load { return hybridObject; } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "GradientView", + []() -> std::shared_ptr { + std::shared_ptr hybridObject = NitroTest::NitroTestAutolinking::createGradientView(); + return hybridObject; + } + ); } @end diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift index 4e19db7c51..494f465449 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift @@ -83,4 +83,16 @@ public final class NitroTestAutolinking { public static func isRecyclableTestViewRecyclable() -> Bool { return HybridRecyclableTestView.self is any RecyclableView.Type } + + public static func createGradientView() -> bridge.std__shared_ptr_HybridGradientViewSpec_ { + let hybridObject = HybridGradientView() + return { () -> bridge.std__shared_ptr_HybridGradientViewSpec_ in + let __cxxWrapped = hybridObject.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }() + } + + public static func isGradientViewRecyclable() -> Bool { + return HybridGradientView.self is any RecyclableView.Type + } } diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.cpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.cpp new file mode 100644 index 0000000000..e09ad71897 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.cpp @@ -0,0 +1,11 @@ +/// +/// HybridGradientViewSpecSwift.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridGradientViewSpecSwift.hpp" + +namespace margelo::nitro::test { +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.hpp new file mode 100644 index 0000000000..86beaad8f5 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridGradientViewSpecSwift.hpp @@ -0,0 +1,82 @@ +/// +/// HybridGradientViewSpecSwift.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include "HybridGradientViewSpec.hpp" + +// Forward declaration of `HybridGradientViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridGradientViewSpec_cxx; } + + + +#include +#include + +#include "NitroTest-Swift-Cxx-Umbrella.hpp" + +namespace margelo::nitro::test { + + /** + * The C++ part of HybridGradientViewSpec_cxx.swift. + * + * HybridGradientViewSpecSwift (C++) accesses HybridGradientViewSpec_cxx (Swift), and might + * contain some additional bridging code for C++ <> Swift interop. + * + * Since this obviously introduces an overhead, I hope at some point in + * the future, HybridGradientViewSpec_cxx can directly inherit from the C++ class HybridGradientViewSpec + * to simplify the whole structure and memory management. + */ + class HybridGradientViewSpecSwift: public virtual HybridGradientViewSpec { + public: + // Constructor from a Swift instance + explicit HybridGradientViewSpecSwift(const NitroTest::HybridGradientViewSpec_cxx& swiftPart): + HybridObject(HybridGradientViewSpec::TAG), + _swiftPart(swiftPart) { } + + public: + // Get the Swift part + inline NitroTest::HybridGradientViewSpec_cxx& getSwiftPart() noexcept { + return _swiftPart; + } + + public: + inline size_t getExternalMemorySize() noexcept override { + return _swiftPart.getMemorySize(); + } + bool equals(const std::shared_ptr& other) override { + if (auto otherCast = std::dynamic_pointer_cast(other)) { + return _swiftPart.equals(otherCast->_swiftPart); + } + return false; + } + void dispose() noexcept override { + _swiftPart.dispose(); + } + std::string toString() override { + return _swiftPart.toString(); + } + + public: + // Properties + inline std::vector getColors() noexcept override { + auto __result = _swiftPart.getColors(); + return __result; + } + inline void setColors(const std::vector& colors) noexcept override { + _swiftPart.setColors(colors); + } + + public: + // Methods + + + private: + NitroTest::HybridGradientViewSpec_cxx _swiftPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridGradientViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridGradientViewComponent.mm new file mode 100644 index 0000000000..2361f9b931 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridGradientViewComponent.mm @@ -0,0 +1,147 @@ +/// +/// HybridGradientViewComponent.mm +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#import "HybridGradientViewComponent.hpp" +#import +#import +#import +#import +#import +#import +#import + +#import "HybridGradientViewSpecSwift.hpp" +#import "NitroTest-Swift-Cxx-Umbrella.hpp" + +#if __has_include() +#include +#if REACT_NATIVE_VERSION_MINOR >= 82 +#define ENABLE_RCT_COMPONENT_VIEW_INVALIDATE +#endif +#endif + +using namespace facebook; +using namespace margelo::nitro::test; +using namespace margelo::nitro::test::views; + +/** + * Represents the React Native View holder for the Nitro "GradientView" HybridView. + */ +@interface HybridGradientViewComponent: RCTViewComponentView ++ (BOOL)shouldBeRecycled; +@end + +@implementation HybridGradientViewComponent { + std::shared_ptr _hybridView; +} + ++ (void) load { + [super load]; + [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:[HybridGradientViewComponent class]]; +} + ++ (react::ComponentDescriptorProvider) componentDescriptorProvider { + return react::concreteComponentDescriptorProvider(); +} + +- (instancetype) init { + if (self = [super init]) { + std::shared_ptr hybridView = NitroTest::NitroTestAutolinking::createGradientView(); + _hybridView = std::dynamic_pointer_cast(hybridView); + [self updateView]; + } + return self; +} + +- (void) updateView { + // 1. Get Swift part + NitroTest::HybridGradientViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + + // 2. Get UIView* + void* viewUnsafe = swiftPart.getView(); + UIView* view = (__bridge_transfer UIView*) viewUnsafe; + + // 3. Update RCTViewComponentView's [contentView] + [self setContentView:view]; +} + +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + // Mount children inside the contentView, not as siblings of it (fall back if there's none yet). + UIView* container = self.contentView; + if (container == nil) { + [super mountChildComponentView:childComponentView index:index]; + return; + } + [container insertSubview:childComponentView atIndex:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + if (childComponentView.superview == nil) { + [super unmountChildComponentView:childComponentView index:index]; + return; + } + [childComponentView removeFromSuperview]; +} + +- (void) updateLayoutMetrics:(const facebook::react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const facebook::react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Make the contentView fill the component, else children would be double-inset by padding/border. + self.contentView.frame = self.bounds; +} + +- (void) updateProps:(const std::shared_ptr&)props + oldProps:(const std::shared_ptr&)oldProps { + // 1. Downcast props + const auto& newViewPropsConst = *std::static_pointer_cast(props); + auto& newViewProps = const_cast(newViewPropsConst); + NitroTest::HybridGradientViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + + // 2. Update each prop individually + swiftPart.beforeUpdate(); + + // colors: array + if (newViewProps.colors.isDirty) { + swiftPart.setColors(newViewProps.colors.value); + newViewProps.colors.isDirty = false; + } + + swiftPart.afterUpdate(); + + // 3. Update hybridRef if it changed + if (newViewProps.hybridRef.isDirty) { + // hybridRef changed - call it with new this + const auto& maybeFunc = newViewProps.hybridRef.value; + if (maybeFunc.has_value()) { + maybeFunc.value()(_hybridView); + } + newViewProps.hybridRef.isDirty = false; + } + + // 4. Continue in base class + [super updateProps:props oldProps:oldProps]; +} + ++ (BOOL)shouldBeRecycled { + return NitroTest::NitroTestAutolinking::isGradientViewRecyclable(); +} + +- (void)prepareForRecycle { + [super prepareForRecycle]; + NitroTest::HybridGradientViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.maybePrepareForRecycle(); +} + +#ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE +- (void)invalidate { + NitroTest::HybridGradientViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.onDropView(); + [super invalidate]; +} +#endif + +@end diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm index 06ecc420d1..e1d1dc593c 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridRecyclableTestViewComponent.mm @@ -69,6 +69,31 @@ - (void) updateView { [self setContentView:view]; } +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + // Mount children inside the contentView, not as siblings of it (fall back if there's none yet). + UIView* container = self.contentView; + if (container == nil) { + [super mountChildComponentView:childComponentView index:index]; + return; + } + [container insertSubview:childComponentView atIndex:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + if (childComponentView.superview == nil) { + [super unmountChildComponentView:childComponentView index:index]; + return; + } + [childComponentView removeFromSuperview]; +} + +- (void) updateLayoutMetrics:(const facebook::react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const facebook::react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Make the contentView fill the component, else children would be double-inset by padding/border. + self.contentView.frame = self.bounds; +} + - (void) updateProps:(const std::shared_ptr&)props oldProps:(const std::shared_ptr&)oldProps { // 1. Downcast props diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm index 191b76da7a..18f033bf95 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridTestViewComponent.mm @@ -69,6 +69,31 @@ - (void) updateView { [self setContentView:view]; } +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + // Mount children inside the contentView, not as siblings of it (fall back if there's none yet). + UIView* container = self.contentView; + if (container == nil) { + [super mountChildComponentView:childComponentView index:index]; + return; + } + [container insertSubview:childComponentView atIndex:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + if (childComponentView.superview == nil) { + [super unmountChildComponentView:childComponentView index:index]; + return; + } + [childComponentView removeFromSuperview]; +} + +- (void) updateLayoutMetrics:(const facebook::react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const facebook::react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Make the contentView fill the component, else children would be double-inset by padding/border. + self.contentView.frame = self.bounds; +} + - (void) updateProps:(const std::shared_ptr&)props oldProps:(const std::shared_ptr&)oldProps { // 1. Downcast props diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec.swift new file mode 100644 index 0000000000..d3efdad4c6 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec.swift @@ -0,0 +1,55 @@ +/// +/// HybridGradientViewSpec.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/// See ``HybridGradientViewSpec`` +public protocol HybridGradientViewSpec_protocol: HybridObject, HybridView { + // Properties + var colors: [String] { get set } + + // Methods + +} + +public extension HybridGradientViewSpec_protocol { + /// Default implementation of ``HybridObject.toString`` + func toString() -> String { + return "[HybridObject GradientView]" + } +} + +/// See ``HybridGradientViewSpec`` +open class HybridGradientViewSpec_base { + private weak var cxxWrapper: HybridGradientViewSpec_cxx? = nil + public init() { } + public func getCxxWrapper() -> HybridGradientViewSpec_cxx { + #if DEBUG + guard self is any HybridGradientViewSpec else { + fatalError("`self` is not a `HybridGradientViewSpec`! Did you accidentally inherit from `HybridGradientViewSpec_base` instead of `HybridGradientViewSpec`?") + } + #endif + if let cxxWrapper = self.cxxWrapper { + return cxxWrapper + } else { + let cxxWrapper = HybridGradientViewSpec_cxx(self as! any HybridGradientViewSpec) + self.cxxWrapper = cxxWrapper + return cxxWrapper + } + } +} + +/** + * A Swift base-protocol representing the GradientView HybridObject. + * Implement this protocol to create Swift-based instances of GradientView. + * ```swift + * class HybridGradientView : HybridGradientViewSpec { + * // ... + * } + * ``` + */ +public typealias HybridGradientViewSpec = HybridGradientViewSpec_protocol & HybridGradientViewSpec_base diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec_cxx.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec_cxx.swift new file mode 100644 index 0000000000..1de2926ace --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridGradientViewSpec_cxx.swift @@ -0,0 +1,162 @@ +/// +/// HybridGradientViewSpec_cxx.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * A class implementation that bridges HybridGradientViewSpec over to C++. + * In C++, we cannot use Swift protocols - so we need to wrap it in a class to make it strongly defined. + * + * Also, some Swift types need to be bridged with special handling: + * - Enums need to be wrapped in Structs, otherwise they cannot be accessed bi-directionally (Swift bug: https://github.com/swiftlang/swift/issues/75330) + * - Other HybridObjects need to be wrapped/unwrapped from the Swift TCxx wrapper + * - Throwing methods need to be wrapped with a Result type, as exceptions cannot be propagated to C++ + */ +open class HybridGradientViewSpec_cxx { + /** + * The Swift <> C++ bridge's namespace (`margelo::nitro::test::bridge::swift`) + * from `NitroTest-Swift-Cxx-Bridge.hpp`. + * This contains specialized C++ templates, and C++ helper functions that can be accessed from Swift. + */ + public typealias bridge = margelo.nitro.test.bridge.swift + + /** + * Holds an instance of the `HybridGradientViewSpec` Swift protocol. + */ + private var __implementation: any HybridGradientViewSpec + + /** + * Holds a weak pointer to the C++ class that wraps the Swift class. + */ + private var __cxxPart: bridge.std__weak_ptr_HybridGradientViewSpec_ + + /** + * Create a new `HybridGradientViewSpec_cxx` that wraps the given `HybridGradientViewSpec`. + * All properties and methods bridge to C++ types. + */ + public init(_ implementation: any HybridGradientViewSpec) { + self.__implementation = implementation + self.__cxxPart = .init() + /* no base class */ + } + + /** + * Get the actual `HybridGradientViewSpec` instance this class wraps. + */ + @inline(__always) + public func getHybridGradientViewSpec() -> any HybridGradientViewSpec { + return __implementation + } + + /** + * Casts this instance to a retained unsafe raw pointer. + * This acquires one additional strong reference on the object! + */ + public func toUnsafe() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(self).toOpaque() + } + + /** + * Casts an unsafe pointer to a `HybridGradientViewSpec_cxx`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridGradientViewSpec_cxx { + return Unmanaged.fromOpaque(pointer).takeRetainedValue() + } + + /** + * Gets (or creates) the C++ part of this Hybrid Object. + * The C++ part is a `std::shared_ptr`. + */ + public func getCxxPart() -> bridge.std__shared_ptr_HybridGradientViewSpec_ { + let cachedCxxPart = self.__cxxPart.lock() + if Bool(fromCxx: cachedCxxPart) { + return cachedCxxPart + } else { + let newCxxPart = bridge.create_std__shared_ptr_HybridGradientViewSpec_(self.toUnsafe()) + __cxxPart = bridge.weakify_std__shared_ptr_HybridGradientViewSpec_(newCxxPart) + return newCxxPart + } + } + + + + /** + * Get the memory size of the Swift class (plus size of any other allocations) + * so the JS VM can properly track it and garbage-collect the JS object if needed. + */ + @inline(__always) + public var memorySize: Int { + return MemoryHelper.getSizeOf(self.__implementation) + self.__implementation.memorySize + } + + /** + * Compares this object with the given [other] object for reference equality. + */ + @inline(__always) + public func equals(other: HybridGradientViewSpec_cxx) -> Bool { + return self.__implementation === other.__implementation + } + + /** + * Call dispose() on the Swift class. + * This _may_ be called manually from JS. + */ + @inline(__always) + public func dispose() { + self.__implementation.dispose() + } + + /** + * Call toString() on the Swift class. + */ + @inline(__always) + public func toString() -> String { + return self.__implementation.toString() + } + + // Properties + public final var colors: bridge.std__vector_std__string_ { + @inline(__always) + get { + return { () -> bridge.std__vector_std__string_ in + var __vector = bridge.create_std__vector_std__string_(self.__implementation.colors.count) + for __item in self.__implementation.colors { + __vector.push_back(std.string(__item)) + } + return __vector + }() + } + @inline(__always) + set { + self.__implementation.colors = newValue.map({ __item in String(__item) }) + } + } + + // Methods + public final func getView() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(__implementation.view).toOpaque() + } + + public final func beforeUpdate() { + __implementation.beforeUpdate() + } + + public final func afterUpdate() { + __implementation.afterUpdate() + } + + public final func maybePrepareForRecycle() { + guard let recyclable = __implementation as? any RecyclableView else { return } + recyclable.prepareForRecycle() + } + + public final func onDropView() { + __implementation.onDropView() + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.cpp new file mode 100644 index 0000000000..983fdca4b8 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.cpp @@ -0,0 +1,22 @@ +/// +/// HybridGradientViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridGradientViewSpec.hpp" + +namespace margelo::nitro::test { + + void HybridGradientViewSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridGetter("colors", &HybridGradientViewSpec::getColors); + prototype.registerHybridSetter("colors", &HybridGradientViewSpec::setColors); + }); + } + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.hpp new file mode 100644 index 0000000000..fa04c01675 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridGradientViewSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridGradientViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#if __has_include() +#include +#else +#error NitroModules cannot be found! Are you sure you installed NitroModules properly? +#endif + + + +#include +#include + +namespace margelo::nitro::test { + + using namespace margelo::nitro; + + /** + * An abstract base class for `GradientView` + * Inherit this class to create instances of `HybridGradientViewSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridGradientView: public HybridGradientViewSpec { + * public: + * HybridGradientView(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridGradientViewSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridGradientViewSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridGradientViewSpec() override = default; + + public: + // Properties + virtual std::vector getColors() = 0; + virtual void setColors(const std::vector& colors) = 0; + + public: + // Methods + + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "GradientView"; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.cpp new file mode 100644 index 0000000000..9fcec8c7d7 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.cpp @@ -0,0 +1,83 @@ +/// +/// HybridGradientViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridGradientViewComponent.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace margelo::nitro::test::views { + + extern const char HybridGradientViewComponentName[] = "GradientView"; + + HybridGradientViewProps::HybridGradientViewProps(const react::PropsParserContext& context, + const HybridGradientViewProps& sourceProps, + const react::RawProps& rawProps): + react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + colors([&]() -> CachedProp> { + try { + const react::RawValue* rawValue = rawProps.at("colors", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.colors; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp>::fromRawValue(*runtime, value, sourceProps.colors); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("GradientView.colors: ") + exc.what()); + } + }()), + hybridRef([&]() -> CachedProp& /* ref */)>>> { + try { + const react::RawValue* rawValue = rawProps.at("hybridRef", nullptr, nullptr); + if (rawValue == nullptr) return sourceProps.hybridRef; + const auto& [runtime, value] = (std::pair)*rawValue; + return CachedProp& /* ref */)>>>::fromRawValue(*runtime, value.asObject(*runtime).getProperty(*runtime, PropNameIDCache::get(*runtime, "f")), sourceProps.hybridRef); + } catch (const std::exception& exc) { + throw std::runtime_error(std::string("GradientView.hybridRef: ") + exc.what()); + } + }()) { } + + bool HybridGradientViewProps::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + case hashString("colors"): return true; + case hashString("hybridRef"): return true; + default: return false; + } + } + + HybridGradientViewComponentDescriptor::HybridGradientViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters) + : ConcreteComponentDescriptor(parameters, + react::RawPropsParser()) {} + + std::shared_ptr HybridGradientViewComponentDescriptor::cloneProps(const react::PropsParserContext& context, + const std::shared_ptr& props, + react::RawProps rawProps) const { + // 1. Prepare raw props parser + rawProps.parse(rawPropsParser_); + // 2. Copy props with Nitro's cached copy constructor + return HybridGradientViewShadowNode::Props(context, /* & */ rawProps, props); + } + +#ifdef ANDROID + void HybridGradientViewComponentDescriptor::adopt(react::ShadowNode& shadowNode) const { + // This is called immediately after `ShadowNode` is created, cloned or in progress. + // On Android, we need to wrap props in our state, which gets routed through Java and later unwrapped in JNI/C++. + auto& concreteShadowNode = static_cast(shadowNode); + const std::shared_ptr& constProps = concreteShadowNode.getConcreteSharedProps(); + const std::shared_ptr& props = std::const_pointer_cast(constProps); + HybridGradientViewState state{props}; + concreteShadowNode.setStateData(std::move(state)); + } +#endif + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.hpp new file mode 100644 index 0000000000..c78fbef233 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridGradientViewComponent.hpp @@ -0,0 +1,112 @@ +/// +/// HybridGradientViewComponent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "HybridGradientViewSpec.hpp" +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + /** + * The name of the actual native View. + */ + extern const char HybridGradientViewComponentName[]; + + /** + * Props for the "GradientView" View. + */ + class HybridGradientViewProps final: public react::ViewProps { + public: + HybridGradientViewProps() = default; + HybridGradientViewProps(const react::PropsParserContext& context, + const HybridGradientViewProps& sourceProps, + const react::RawProps& rawProps); + + public: + CachedProp> colors; + CachedProp& /* ref */)>>> hybridRef; + + private: + static bool filterObjectKeys(const std::string& propName); + }; + + /** + * State for the "GradientView" View. + */ + class HybridGradientViewState final { + public: + HybridGradientViewState() = default; + explicit HybridGradientViewState(const std::shared_ptr& props): + _props(props) {} + + public: + [[nodiscard]] + const std::shared_ptr& getProps() const { + return _props; + } + + public: +#ifdef ANDROID + HybridGradientViewState(const HybridGradientViewState& /* previousState */, folly::dynamic /* data */) {} + folly::dynamic getDynamic() const { + throw std::runtime_error("HybridGradientViewState does not support folly!"); + } + react::MapBuffer getMapBuffer() const { + throw std::runtime_error("HybridGradientViewState does not support MapBuffer!"); + }; +#endif + + private: + std::shared_ptr _props; + }; + + /** + * The Shadow Node for the "GradientView" View. + */ + using HybridGradientViewShadowNode = react::ConcreteViewShadowNode; + + /** + * The Component Descriptor for the "GradientView" View. + */ + class HybridGradientViewComponentDescriptor final: public react::ConcreteComponentDescriptor { + public: + explicit HybridGradientViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters); + + public: + /** + * A faster path for cloning props - reuses the caching logic from `HybridGradientViewProps`. + */ + std::shared_ptr cloneProps(const react::PropsParserContext& context, + const std::shared_ptr& props, + react::RawProps rawProps) const override; +#ifdef ANDROID + void adopt(react::ShadowNode& shadowNode) const override; +#endif + }; + + /* The actual view for "GradientView" needs to be implemented in platform-specific code. */ + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/json/GradientViewConfig.json b/packages/react-native-nitro-test/nitrogen/generated/shared/json/GradientViewConfig.json new file mode 100644 index 0000000000..dcca8ae14f --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/json/GradientViewConfig.json @@ -0,0 +1,10 @@ +{ + "uiViewClassName": "GradientView", + "supportsRawText": false, + "bubblingEventTypes": {}, + "directEventTypes": {}, + "validAttributes": { + "colors": true, + "hybridRef": true + } +} diff --git a/packages/react-native-nitro-test/src/index.ts b/packages/react-native-nitro-test/src/index.ts index 6d4b6070d4..7ad26cf356 100644 --- a/packages/react-native-nitro-test/src/index.ts +++ b/packages/react-native-nitro-test/src/index.ts @@ -13,6 +13,7 @@ export * from './specs/Child.nitro' export * from './specs/PlatformObject.nitro' export * from './specs/TestObject.nitro' export * from './specs/TestView.nitro' +export * from './specs/GradientView.nitro' // Export all HybridObject singleton instances export const HybridTestObjectCpp = @@ -32,3 +33,4 @@ export { RecyclableTestView, type RecyclableTestViewRef, } from './views/RecyclableTestView' +export { GradientView, type GradientViewRef } from './views/GradientView' diff --git a/packages/react-native-nitro-test/src/specs/GradientView.nitro.ts b/packages/react-native-nitro-test/src/specs/GradientView.nitro.ts new file mode 100644 index 0000000000..428d2a7d07 --- /dev/null +++ b/packages/react-native-nitro-test/src/specs/GradientView.nitro.ts @@ -0,0 +1,13 @@ +import type { + HybridView, + HybridViewProps, + HybridViewMethods, +} from 'react-native-nitro-modules' + +export interface GradientViewProps extends HybridViewProps { + colors: string[] +} + +export interface GradientViewMethods extends HybridViewMethods {} + +export type GradientView = HybridView diff --git a/packages/react-native-nitro-test/src/views/GradientView.ts b/packages/react-native-nitro-test/src/views/GradientView.ts new file mode 100644 index 0000000000..24965cdebe --- /dev/null +++ b/packages/react-native-nitro-test/src/views/GradientView.ts @@ -0,0 +1,13 @@ +import { getHostComponent, type HybridRef } from 'react-native-nitro-modules' +import GradientViewConfig from '../../nitrogen/generated/shared/json/GradientViewConfig.json' +import { + type GradientViewMethods, + type GradientViewProps, +} from '../specs/GradientView.nitro' + +export const GradientView = getHostComponent< + GradientViewProps, + GradientViewMethods +>('GradientView', () => GradientViewConfig) + +export type GradientViewRef = HybridRef From 4c3100f68d4db5dd5ad482a9a9803b0deffd3c5a Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 14 Jun 2026 16:26:37 +0200 Subject: [PATCH 2/3] docs: simplify content --- docs/docs/guides/view-components.md | 14 +++++--------- example/ios/Podfile.lock | 12 ++++++------ 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/docs/docs/guides/view-components.md b/docs/docs/guides/view-components.md index 1923e3a1e6..8443cfa1d4 100644 --- a/docs/docs/guides/view-components.md +++ b/docs/docs/guides/view-components.md @@ -160,7 +160,7 @@ function App() { ## Children -A Nitro `HybridView` renders React children like a regular `` - they're laid out by Flexbox/Yoga and mounted as **subviews of your native view**, letting it act as a _container_ that draws behind or around React content. For example, a native `` that renders a gradient behind its children: +A Nitro `HybridView` can render React children, just like a normal ``. They get laid out by Yoga and mounted as subviews of your native view, so the view can draw behind or around them.
@@ -194,18 +194,16 @@ function App() {
-No props or setup are required - children work out of the box. The only requirement is that your native view can hold subviews: +This works out of the box, there's nothing extra to set up. The only requirement is that your native view can actually hold subviews: -- **iOS**: any `UIView` works. -- **Android**: return a **`NitroViewGroup`** (or a subclass) from `view`. It keeps the Fabric-assigned child frames - a normal `ViewGroup` would re-lay-out children and fight Fabric, and a plain `View` can't hold children (Nitro throws a descriptive error). +- On iOS, any `UIView` already can. +- On Android, return a `NitroViewGroup` (or a subclass) from `view`. It leaves children where Fabric placed them. A regular `ViewGroup` would run its own layout and move them around, and a plain `View` can't hold children at all, so Nitro throws an error.
```swift title="HybridGradientView.swift" class HybridGradientView: HybridGradientViewSpec { - // A UIView backed by a CAGradientLayer. - // React children are added as subviews on top. let view = GradientUIView() func setColors(_ colors: [String]) { @@ -220,8 +218,6 @@ class HybridGradientView: HybridGradientViewSpec {
```kotlin title="HybridGradientView.kt" -// GradientFrameLayout extends NitroViewGroup so -// children keep their Fabric-assigned layout. class HybridGradientView(context: ThemedReactContext): HybridGradientViewSpec() { override val view = GradientFrameLayout(context) @@ -236,7 +232,7 @@ class HybridGradientView(context: ThemedReactContext):
:::note Styling -A Hybrid View bridges **layout** (Flexbox/Yoga) to its native view but does **not** apply RN **visual** style props (`backgroundColor`, `borderRadius`, `boxShadow`, …) - the native view owns its appearance. To decorate one, wrap it in a regular `` (with `overflow: 'hidden'` to clip), the standard RN pattern for any native view. +A Hybrid View only forwards layout to its native view. It won't apply visual style props like `backgroundColor`, `borderRadius` or `boxShadow`, since the native view decides how it looks. To style one, wrap it in a normal `` and add `overflow: 'hidden'` to clip. ::: ## Props diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index a010eedacb..22496ca21b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -2227,10 +2227,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: 24e62c765683b8d89006a88a2c8f5cf019f0074d - hermes-engine: f0e8ed549bb5a3befb3e292e5c0dafd3e2cee3ab - NitroModules: 16bc17a076b12304d608f7c915b9d321f56dfc19 - NitroTest: 015ec3bddf31c0bd83c0d6d4e10b6d3a42e4e18d - NitroTestExternal: 01bc29bd779142e98432f2178760baebb134de57 + hermes-engine: 0bf3e46f12b9a9102be444d5d30dd35eebd0324e + NitroModules: 1c12300c03d751a09723430c05a420667ffde90f + NitroTest: 767bbefb4d84ac67849f5545f1a96d21caf5deeb + NitroTestExternal: 2808c429cfc1724e2695a15a2eb13c8008cc8b51 RCTDeprecation: a4c521821fab57cbb125b36effe84d897d0dfa12 RCTRequired: 9f3a7e5645d4bc3f551593de7550bb66ab6e42bc RCTSwiftUI: 239ed2eb9e73de5a6f518810630f0c95e01c8702 @@ -2239,7 +2239,7 @@ SPEC CHECKSUMS: React: e2dc35338068bbd299c66f043ae0d7f25de8499e React-callinvoker: 28b25d21b124c26cebaea713ba7d801b9351dc48 React-Core: 02ed7d2ffb70437bdf2aba074a13078a7b0b9ff0 - React-Core-prebuilt: 72454b4caa0309d15c45de529b1c5a0e4607cf9a + React-Core-prebuilt: fb974d1664c98b4ece04886442bbad2fa51c80eb React-CoreModules: b3a5a42dadcde3b5d47b325bd912eb2ced89e146 React-cxxreact: fe8f88dda044e5905e99a00f41b7a874c3908716 React-debug: 92944dc4d89f56d640e75498266cbde557a48189 @@ -2303,7 +2303,7 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: 25c9c516839be2c5e3d3344f95dc7da5f7e63fc2 ReactCodegen: 0f100aa6334186385a43f0dd13d63efc6805ea55 ReactCommon: 7dfc3250793bf36cf221096ff59e1179e13eef7f - ReactNativeDependencies: 86c5427d73b954c0671c6ab9691d486b196595b6 + ReactNativeDependencies: 589c57612aa76b4cf9130967abc61b82999676ef RNScreens: 991cc417cd396602a6cf59a42139e5a9d91462a9 Yoga: 77dfa8673de2874e1855002ae59c68b8be9b007b From 152768646398b9d34899655575d95813c5cfd825 Mon Sep 17 00:00:00 2001 From: Patrick Kabwe Date: Sun, 14 Jun 2026 16:27:52 +0200 Subject: [PATCH 3/3] chore: more clarity --- docs/docs/guides/view-components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/guides/view-components.md b/docs/docs/guides/view-components.md index 8443cfa1d4..e146e4598e 100644 --- a/docs/docs/guides/view-components.md +++ b/docs/docs/guides/view-components.md @@ -220,7 +220,7 @@ class HybridGradientView: HybridGradientViewSpec { ```kotlin title="HybridGradientView.kt" class HybridGradientView(context: ThemedReactContext): HybridGradientViewSpec() { - override val view = GradientFrameLayout(context) + override val view = GradientView(context) override fun setColors(colors: Array) { view.setGradientColors(colors.map(Color::parseColor))