diff --git a/apps/example/__tests__/nitro.views.children.harness.tsx b/apps/example/__tests__/nitro.views.children.harness.tsx new file mode 100644 index 0000000000..82f31c3044 --- /dev/null +++ b/apps/example/__tests__/nitro.views.children.harness.tsx @@ -0,0 +1,576 @@ +import * as React from 'react' +import { PixelRatio, Platform, View, type LayoutRectangle } from 'react-native' +import { describe, expect, it, render, waitUntil } from 'react-native-harness' +import { screen } from '@react-native-harness/ui' +import { callback } from 'react-native-nitro-modules' +import { + ChildrenContainerTestView, + type ChildrenContainerTestViewRef, + ChildrenTestView, + type ChildrenTestViewRef, + TestView, +} from 'react-native-nitro-test' +import * as UPNG from 'upng-js' + +// Regression tests for https://github.com/margelo/nitro/issues/873 - rendering +// React children inside a Nitro View. + +const RENDER_TIMEOUT = 4_000 +const CONTAINER_SIZE = { width: 120, height: 120 } +/** Height of a single child, so exactly two of them fill the container. */ +const CHILD_HEIGHT = CONTAINER_SIZE.height / 2 + +interface Deferred { + promise: Promise + resolve: (value: T) => void +} + +function deferred(): Deferred { + let resolve!: (value: T) => void + const promise = new Promise((promiseResolve) => { + resolve = promiseResolve + }) + return { promise, resolve } +} + +interface DecodedImage { + width: number + height: number + rgba: Uint8Array +} + +async function captureView(testID: string): Promise { + const element = await screen.findByTestId(testID) + const screenshot = await screen.screenshot(element) + if (screenshot == null) { + throw new Error(`Failed to capture the mounted View "${testID}".`) + } + const copiedData = Uint8Array.from(screenshot.data) + const decodedImage = UPNG.decode(copiedData.buffer) + const rgbaBuffer = UPNG.toRGBA8(decodedImage)[0] + if (rgbaBuffer == null) { + throw new Error('Failed to decode the Harness UI PNG screenshot.') + } + return { + width: decodedImage.width, + height: decodedImage.height, + rgba: new Uint8Array(rgbaBuffer), + } +} + +type Color = 'red' | 'green' | 'blue' | 'other' + +function classify(red: number, green: number, blue: number): Color { + const margin = 50 + if (red - green > margin && red - blue > margin) return 'red' + if (green - red > margin && green - blue > margin) return 'green' + if (blue - red > margin && blue - green > margin) return 'blue' + return 'other' +} + +/** The colors at the vertical center of the top and the bottom half of a View. */ +async function captureHalves(testID: string): Promise<[Color, Color]> { + const { width, height, rgba } = await captureView(testID) + const sample = (yFraction: number): Color => { + const x = Math.floor(width / 2) + const y = Math.floor(height * yFraction) + const offset = (y * width + x) * 4 + return classify(rgba[offset]!, rgba[offset + 1]!, rgba[offset + 2]!) + } + return [sample(0.25), sample(0.75)] +} + +/** The fraction of pixels of a View that are dominantly red. */ +async function getRedCoverage(testID: string): Promise { + const { rgba } = await captureView(testID) + let redPixels = 0 + for (let i = 0; i < rgba.length; i += 4) { + if ( + rgba[i + 3]! > 250 && + classify(rgba[i]!, rgba[i + 1]!, rgba[i + 2]!) === 'red' + ) { + redPixels += 1 + } + } + return redPixels / (rgba.length / 4) +} + +async function expectNativeChildCount( + view: { getNativeChildCount(): number }, + expectedCount: number, + context = '' +): Promise { + try { + await waitUntil(() => view.getNativeChildCount() === expectedCount, { + timeout: RENDER_TIMEOUT, + }) + } catch { + throw new Error( + `Expected ${expectedCount} native children${context}, but the native View has ${view.getNativeChildCount()}.` + ) + } +} + +/** A full-width, half-height box - `name` picks its color. */ +function ColorBox({ name }: { name: 'a' | 'b' | 'c' }): React.ReactElement { + const backgroundColor = { a: 'red', b: 'lime', c: 'blue' }[name] + return ( + + ) +} + +describe('Nitro View children', () => { + it('mounts a React child into the native View', async () => { + const viewRef = deferred() + await render( + viewRef.resolve(view))} + > + + , + { timeout: RENDER_TIMEOUT } + ) + + const view = await viewRef.promise + await expectNativeChildCount(view, 1) + expect(screen.queryByTestId('children-initial-child')).not.toBeNull() + }) + + it('renders children on top of the native View', async () => { + const viewRef = deferred() + await render( + viewRef.resolve(view))} + > + + , + { timeout: RENDER_TIMEOUT } + ) + + const view = await viewRef.promise + await expectNativeChildCount(view, 1) + // Before this feature, the child was mounted *behind* the Nitro View on + // iOS, so the blue native View covered it completely. + expect(await getRedCoverage('children-zorder')).toBeGreaterThan(0.95) + }) + + it('positions children with Yoga, honouring padding and border', async () => { + const viewRef = deferred() + const childLayout = deferred() + await render( + viewRef.resolve(view))} + > + + childLayout.resolve(nativeEvent.layout) + } + /> + , + { timeout: RENDER_TIMEOUT } + ) + + const view = await viewRef.promise + await expectNativeChildCount(view, 1) + + // Yoga insets the child by border + padding. + const layout = await childLayout.promise + expect(layout.x).toBeCloseTo(22, 0) + expect(layout.y).toBeCloseTo(22, 0) + expect(layout.width).toBeCloseTo(156, 0) + expect(layout.height).toBeCloseTo(156, 0) + + // ...and the child is rendered at that size on screen, i.e. the native + // container did not inset it a second time. Device pixels are rounded, so + // allow the screenshot to be off by one physical pixel per edge. + const expectedPixels = PixelRatio.getPixelSizeForLayoutSize(156) + const rendered = await captureView('children-layout-child') + expect(Math.abs(rendered.width - expectedPixels)).toBeLessThanOrEqual(2) + expect(Math.abs(rendered.height - expectedPixels)).toBeLessThanOrEqual(2) + }) + + it('adds and removes children', async () => { + const viewRef = deferred() + const renderChildren = (children: React.ReactNode) => ( + viewRef.resolve(view))} + > + {children} + + ) + + const renderResult = await render( + renderChildren(), + { timeout: RENDER_TIMEOUT } + ) + const view = await viewRef.promise + await expectNativeChildCount(view, 1) + expect(await captureHalves('children-add-remove')).toEqual(['red', 'blue']) + + // Add a second child below the first one. + await renderResult.rerender( + renderChildren([ + , + , + ]) + ) + await expectNativeChildCount(view, 2) + expect(await captureHalves('children-add-remove')).toEqual(['red', 'green']) + + // Remove the first one - the second one moves up. + await renderResult.rerender(renderChildren()) + await expectNativeChildCount(view, 1) + expect(screen.queryByTestId('children-box-a')).toBeNull() + expect(await captureHalves('children-add-remove')).toEqual([ + 'green', + 'blue', + ]) + }) + + it('reorders and replaces children', async () => { + const viewRef = deferred() + const renderChildren = (children: React.ReactNode) => ( + viewRef.resolve(view))} + > + {children} + + ) + + const renderResult = await render( + renderChildren([ + , + , + ]), + { timeout: RENDER_TIMEOUT } + ) + const view = await viewRef.promise + await expectNativeChildCount(view, 2) + expect(await captureHalves('children-reorder')).toEqual(['red', 'green']) + + // Reorder the very same (keyed) children. + await renderResult.rerender( + renderChildren([ + , + , + ]) + ) + await expectNativeChildCount(view, 2) + expect(await captureHalves('children-reorder')).toEqual(['green', 'red']) + + // Replace both of them with a different child. + await renderResult.rerender(renderChildren()) + await expectNativeChildCount(view, 1) + expect(screen.queryByTestId('children-box-a')).toBeNull() + expect(screen.queryByTestId('children-box-b')).toBeNull() + expect(screen.queryByTestId('children-box-c')).not.toBeNull() + }) + + it('goes from no children to children and back', async () => { + const viewRef = deferred() + const renderChildren = (isVisible: boolean) => ( + viewRef.resolve(view))} + > + {isVisible && } + + ) + + const renderResult = await render(renderChildren(false), { + timeout: RENDER_TIMEOUT, + }) + const view = await viewRef.promise + await expectNativeChildCount(view, 0) + expect(await captureHalves('children-conditional')).toEqual([ + 'blue', + 'blue', + ]) + + await renderResult.rerender(renderChildren(true)) + await expectNativeChildCount(view, 1) + expect(await captureHalves('children-conditional')).toEqual(['red', 'blue']) + + await renderResult.rerender(renderChildren(false)) + await expectNativeChildCount(view, 0) + expect(await captureHalves('children-conditional')).toEqual([ + 'blue', + 'blue', + ]) + + // Unmounting an emptied container must not crash either. + renderResult.unmount() + await waitUntil( + () => screen.queryByTestId('children-conditional') === null, + { timeout: RENDER_TIMEOUT } + ) + }) + + it('honours margin, absolute positioning and clipping', async () => { + const viewRef = deferred() + const marginLayout = deferred() + const absoluteLayout = deferred() + + await render( + + viewRef.resolve(view))} + > + + marginLayout.resolve(nativeEvent.layout) + } + /> + + absoluteLayout.resolve(nativeEvent.layout) + } + /> + + , + { timeout: RENDER_TIMEOUT } + ) + + const view = await viewRef.promise + await expectNativeChildCount(view, 2) + + // A child's margin stacks on top of the container's padding. + const margin = await marginLayout.promise + expect(margin.x).toBeCloseTo(15, 0) + expect(margin.y).toBeCloseTo(15, 0) + + const absolute = await absoluteLayout.promise + expect(absolute.x).toBeCloseTo(20, 0) + expect(absolute.y).toBeCloseTo(30, 0) + + // `overflow` only reaches the native View on iOS - on Android it belongs to + // React Native's own `ReactViewGroup`, so children are never clipped there. + // Clipped, the 400x400 child covers (100-20)x(100-30) of the 200x200 + // wrapper - 14%. Unclipped it covers 76%. + const redCoverage = await getRedCoverage('children-style-wrapper') + if (Platform.OS === 'ios') { + expect(redCoverage).toBeGreaterThan(0.1) + expect(redCoverage).toBeLessThan(0.2) + } else { + expect(redCoverage).toBeGreaterThan(0.7) + } + }) + + it('nests Nitro Views inside each other', async () => { + const outerRef = deferred() + const innerRef = deferred() + const leafLayout = deferred() + + await render( + outerRef.resolve(view))} + > + innerRef.resolve(view))} + > + + leafLayout.resolve(nativeEvent.layout) + } + /> + + {})} + /> + , + { timeout: RENDER_TIMEOUT } + ) + + const outer = await outerRef.promise + const inner = await innerRef.promise + // A nested Nitro View and a leaf Nitro View are both just children. + await expectNativeChildCount(outer, 2) + await expectNativeChildCount(inner, 1) + + // The innermost child is positioned relative to the inner Nitro View. + const layout = await leafLayout.promise + expect(layout.x).toBeCloseTo(10, 0) + expect(layout.y).toBeCloseTo(10, 0) + expect(layout.width).toBeCloseTo(80, 0) + expect(screen.queryByTestId('children-nested-leaf-nitro')).not.toBeNull() + }) + + it('survives repeated child updates', async () => { + const viewRef = deferred() + const renderChildren = (keys: number[]) => ( + viewRef.resolve(view))} + > + {keys.map((key) => ( + // The background color keeps React Native from flattening the View + // away, so every React child is also a native child. + + ))} + + ) + + const renderResult = await render(renderChildren([1, 2, 3]), { + timeout: RENDER_TIMEOUT, + }) + const view = await viewRef.promise + await expectNativeChildCount(view, 3) + + const steps = [ + [], + [1], + [1, 2], + [3, 1], + [], + [1, 2, 3, 4, 5], + [5, 4, 3, 2, 1], + [3], + ] + for (let round = 0; round < 2; round++) { + for (const step of steps) { + await renderResult.rerender(renderChildren(step)) + await expectNativeChildCount( + view, + step.length, + ` after round ${round}, step [${step.join(',')}]` + ) + } + } + + // No stale, duplicated or leaked native children after all of that. + await renderResult.rerender(renderChildren([1, 2, 3])) + await expectNativeChildCount(view, 3) + }) + + it('mounts children into an overridden childrenContainer', async () => { + const viewRef = deferred() + const renderChildren = (children: React.ReactNode) => ( + viewRef.resolve(view))} + > + {children} + + ) + + const renderResult = await render( + renderChildren(), + { timeout: RENDER_TIMEOUT } + ) + const view = await viewRef.promise + await expectNativeChildCount(view, 1) + // The child landed in the container, not in `view` - which still holds + // exactly one child of its own, the container. + expect(view.getViewChildCount()).toBe(1) + + // The container spans the View, so Yoga's frames still land correctly and + // the children are drawn on top of the native View. + expect(await captureHalves('children-container')).toEqual(['red', 'blue']) + + await renderResult.rerender( + renderChildren([ + , + , + ]) + ) + await expectNativeChildCount(view, 2) + expect(view.getViewChildCount()).toBe(1) + expect(await captureHalves('children-container')).toEqual(['red', 'green']) + + await renderResult.rerender(renderChildren(null)) + await expectNativeChildCount(view, 0) + expect(view.getViewChildCount()).toBe(1) + }) + + it('keeps a leaf Nitro View working exactly as before', async () => { + await render( + {})} + />, + { timeout: RENDER_TIMEOUT } + ) + await waitUntil( + () => screen.queryByTestId('children-leaf-regression') !== null, + { timeout: RENDER_TIMEOUT } + ) + expect(screen.queryByTestId('children-leaf-regression')).not.toBeNull() + }) +}) diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 91990671d9..eedd2a1ee8 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -7,6 +7,7 @@ import { useColors } from './useColors' import { Image } from 'react-native' import { ViewScreen } from './screens/ViewScreen' import { EvalScreen } from './screens/EvalScreen' +import { ChildrenScreen } from './screens/ChildrenScreen' const dna = require('./img/dna.png') const map = require('./img/map.png') @@ -53,6 +54,20 @@ export default function App() { ), }} /> + ( + + ), + }} + /> + + {title} + + {children} + + ) +} + +export function ChildrenScreen(): React.ReactElement { + const safeArea = useSafeAreaInsets() + const colors = useColors() + const [items, setItems] = React.useState(['A', 'B']) + + return ( + + + Nitro View children + + +
+ + Hello from React + +
+ +
+ + + flex: 1 + + +
+ +
+ + + inner + + {})} + /> + +
+ +
+ + mounted into the container + +
+ +
+ + {items.map((item) => ( + + {item} + + ))} + + +
+
+ ) +} + +const styles = StyleSheet.create({ + container: { flex: 1 }, + content: { paddingHorizontal: 15 }, + header: { fontSize: 26, fontWeight: 'bold', paddingBottom: 10 }, + section: { paddingVertical: 10 }, + sectionTitle: { fontSize: 13, fontWeight: '600', paddingBottom: 8 }, + box: { + height: 60, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 10, + overflow: 'hidden', + }, + paddedBox: { + height: 110, + padding: 12, + borderWidth: 2, + borderColor: 'black', + borderRadius: 16, + overflow: 'hidden', + flexDirection: 'row', + alignItems: 'stretch', + }, + innerBox: { + flex: 1, + padding: 10, + borderRadius: 8, + overflow: 'hidden', + justifyContent: 'center', + alignItems: 'center', + }, + leaf: { width: 40, marginLeft: 10, borderRadius: 8, overflow: 'hidden' }, + filler: { + flex: 1, + backgroundColor: 'rgba(255,255,255,0.35)', + borderRadius: 8, + justifyContent: 'center', + alignItems: 'center', + }, + listBox: { padding: 8, borderRadius: 10, overflow: 'hidden' }, + row: { + backgroundColor: 'rgba(255,255,255,0.35)', + borderRadius: 6, + paddingVertical: 6, + paddingHorizontal: 10, + marginBottom: 4, + }, + label: { color: 'white', fontWeight: '600' }, + buttons: { flexDirection: 'row', gap: 12, paddingTop: 8 }, +}) diff --git a/docs/docs/concepts/hybrid-views.md b/docs/docs/concepts/hybrid-views.md index 63e7a2eba9..3d8f356e45 100644 --- a/docs/docs/concepts/hybrid-views.md +++ b/docs/docs/concepts/hybrid-views.md @@ -62,6 +62,22 @@ function App() { Internally, the `` view will create the `HybridCamera` hybrid object - one hybrid object per view. +## Rendering children + +A Nitro View is a leaf by default. To let it render React children, declare a `children` prop of +type `HybridViewChildren` in its spec - React Native then mounts the child views into your native +view, and lays them out with Yoga: + +```ts title="Card.nitro.ts" +export interface CardProps extends HybridViewProps { + // highlight-next-line + children?: HybridViewChildren +} +export type Card = HybridView +``` + +See [View Components → Children](../guides/view-components#children) for the native side. + ## Accessing the underlying Hybrid Object To access the actual underlying object, you can use the `hybridRef`: diff --git a/docs/docs/guides/view-components.md b/docs/docs/guides/view-components.md index b44f195d8e..40fdc9e7d5 100644 --- a/docs/docs/guides/view-components.md +++ b/docs/docs/guides/view-components.md @@ -305,6 +305,127 @@ class HybridImageView: HybridImageViewSpec, RecyclableView { } ``` +## Children + +By default a Nitro View is a leaf - passing React children to it is a compile error. + +To render children inside your View, declare a `children` prop of type `HybridViewChildren` in its spec: + +```ts title="Card.nitro.ts" +import type { HybridView, HybridViewProps, HybridViewChildren } from 'react-native-nitro-modules' + +export interface CardProps extends HybridViewProps { + // highlight-next-line + children?: HybridViewChildren + isElevated: boolean +} +export type Card = HybridView +``` + +`children` is only a marker - it is not a Nitro prop, and never crosses the JS ↔ native prop bridge. +React's renderer mounts and unmounts the child views directly, and React Native's layout engine +(Yoga) positions them - exactly like it does for a regular ``. + +Now the View can render children: + +```jsx +function App() { + return ( + + Hello + + ) +} +``` + +### Implementing a container View + +Children are mounted **into** your native View, so it has to be able to hold them. + + + + ```swift title="HybridCard.swift" + class HybridCard : HybridCardSpec { + // Children are added as subviews of this UIView + var view: UIView = UIView() + var isElevated: Bool = false + } + ``` + + + ```kotlin title="HybridCard.kt" + import com.margelo.nitro.views.NitroViewGroup + + class HybridCard(context: ThemedReactContext): HybridCardSpec() { + // Children are added to this ViewGroup + override val view: ViewGroup = NitroViewGroup(context) + override var isElevated: Boolean = false + } + ``` + + + +On **Android**, the generated `HybridCardSpec` narrows `view` to a `ViewGroup` - React Native cannot +mount children into a plain `View`, so a leaf `View` fails to compile instead of crashing at runtime. +Use Nitro's `NitroViewGroup`: React Native positions every child itself, and a `ViewGroup` that lays +out its own children (such as a `LinearLayout`) would fight Fabric and move them to the wrong place. + +On **iOS** any `UIView` works - children become its subviews. + +:::note +Your native View may keep its own subviews, but add them before any React child is mounted - +React Native addresses children by index. If that's awkward, give the children their own container - +see below. +::: + +:::warning +A container View's native view fills the whole component, so React Native's layout for the children +lands in the right place. An opaque native view therefore paints over the component's own +`borderWidth` and `borderRadius` - add `overflow: 'hidden'` to clip it back to the rounded shape, or +draw the border in your native view. + +`overflow` itself only reaches the native View on iOS, where React Native turns it into +`clipsToBounds`. On Android it is implemented by React Native's own `ReactViewGroup`, which a Nitro +View is not, so children are never clipped there - clip them in your own `ViewGroup` if you need it. +::: + +### Mounting children into a sub-view + +Sometimes the children can't live in `view` itself: a `UIVisualEffectView` requires its `contentView`, +a native map wants its markers in an overlay, and a third-party `ViewGroup` may lay out its own +children. Override `childrenContainer` to point React at a different view - it defaults to `view`: + + + + ```swift title="HybridBlurCard.swift" + class HybridBlurCard : HybridBlurCardSpec { + private let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .systemMaterial)) + + var view: UIView { blurView } + // highlight-next-line + var childrenContainer: UIView { blurView.contentView } + } + ``` + + + ```kotlin title="HybridBlurCard.kt" + class HybridBlurCard(context: ThemedReactContext): HybridBlurCardSpec() { + private val overlay = NitroViewGroup(context) + + override val view: ViewGroup = SomeThirdPartyView(context).apply { addView(overlay) } + // diff-add + override val childrenContainer: ViewGroup = overlay + } + ``` + + + +The container has to cover the same area as `view`. React Native positions each child relative to +`view`'s top-left corner, so a container that is offset or smaller moves every child with it - and on +Android a `NitroViewGroup` parent won't lay the container out for you, so size it yourself (in +`onSizeChanged`, for example). Like `view`, `childrenContainer` should not change over the lifetime of +the Hybrid View. + ## Methods Since every `HybridView` is also a `HybridObject`, methods can be directly called on the object. diff --git a/packages/nitrogen/src/createPlatformSpec.ts b/packages/nitrogen/src/createPlatformSpec.ts index 949dbbbce9..ce01b86f41 100644 --- a/packages/nitrogen/src/createPlatformSpec.ts +++ b/packages/nitrogen/src/createPlatformSpec.ts @@ -21,6 +21,32 @@ import { getBaseTypes, getHybridObjectNitroModuleConfig } from './utils.js' import { NitroConfig } from './config/NitroConfig.js' import { isMemberOverridingFromBase } from './syntax/isMemberOverridingFromBase.js' +/** + * Whether the given Hybrid View props type declares a `children` marker prop. + * + * `children` has no native representation - it only marks the View as rendering + * React children - so anything but `HybridViewChildren` is rejected here, + * instead of failing later with a confusing "unsupported type" error. + */ +function supportsChildren(viewName: string, props: Type): boolean { + const children = props.getProperty('children') + if (children == null) { + return false + } + // `children?: HybridViewChildren` is a union with `undefined` - unwrap it. + const type = children + .getTypeAtLocation(children.getValueDeclarationOrThrow()) + .getNonNullableType() + if (type.getSymbol()?.getName() !== 'HybridViewChildren') { + throw new Error( + `${viewName}: The "children" prop is reserved - it marks a Nitro View as rendering ` + + `React children, so it has to be declared as \`children?: HybridViewChildren\` ` + + `(got \`${type.getText()}\`).` + ) + } + return true +} + export function generatePlatformFiles( interfaceType: Type, language: Language @@ -44,7 +70,11 @@ export function generatePlatformFiles( } } -function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec { +function getHybridObjectSpec( + type: Type, + language: Language, + stripChildrenProp = false +): HybridObjectSpec { const config = getHybridObjectNitroModuleConfig(type) ?? NitroConfig.current if (isHybridView(type)) { @@ -59,13 +89,15 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec { throw new Error( `Props cannot be null! ${name}<...> (HybridView) requires type arguments.` ) - const propsSpec = getHybridObjectSpec(props, language) + const hasChildren = supportsChildren(name, props) + const propsSpec = getHybridObjectSpec(props, language, hasChildren) const methodsSpec = methods != null ? getHybridObjectSpec(methods, language) : undefined return { baseTypes: [], isHybridView: true, + supportsChildren: hasChildren, language: language, methods: methodsSpec?.methods ?? [], properties: propsSpec.properties, @@ -80,6 +112,12 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec { const properties: Property[] = [] const methods: Method[] = [] for (const prop of type.getProperties()) { + if (stripChildrenProp && prop.getName() === 'children') { + // The `children` marker has no native representation - skip it before + // `createType(..)` ever sees it. + continue + } + const declarations = prop.getDeclarations() if (declarations.length > 1) { throw new Error( @@ -155,6 +193,7 @@ function getHybridObjectSpec(type: Type, language: Language): HybridObjectSpec { methods: methods, baseTypes: bases, isHybridView: isHybridView(type), + supportsChildren: false, config: config, } diff --git a/packages/nitrogen/src/syntax/HybridObjectSpec.ts b/packages/nitrogen/src/syntax/HybridObjectSpec.ts index 41b6df707e..2049d450dd 100644 --- a/packages/nitrogen/src/syntax/HybridObjectSpec.ts +++ b/packages/nitrogen/src/syntax/HybridObjectSpec.ts @@ -10,5 +10,11 @@ export interface HybridObjectSpec { methods: Method[] baseTypes: HybridObjectSpec[] isHybridView: boolean + /** + * Whether this Hybrid View opted into rendering React children by declaring a + * `children` prop of type `HybridViewChildren` in its Nitro spec. + * Always `false` for Hybrid Objects that aren't Views. + */ + supportsChildren: boolean config: NitroConfig } diff --git a/packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts b/packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts index 2ae5bf4d76..24962ac7b2 100644 --- a/packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts +++ b/packages/nitrogen/src/syntax/kotlin/KotlinHybridObject.ts @@ -13,8 +13,38 @@ import { KotlinCxxBridgedType } from './KotlinCxxBridgedType.js' export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] { const name = getHybridObjectName(spec.name) - const properties = spec.properties - .map((p) => getPropertyForwardImplementation(p)) + // React Native can only mount children into a `ViewGroup`, so narrowing `view` + // here turns a leaf `View` into a compile error instead of a runtime crash. + const childrenMembers = spec.supportsChildren + ? ` +/** + * The [ViewGroup] this HybridView is holding. + * + * React Native positions each child itself, so this should be a + * [com.margelo.nitro.views.NitroViewGroup] (or another [ViewGroup] that + * doesn't lay out its own children). + * + * This value should not change during the lifetime of this \`HybridView\`. + */ +abstract override val view: ViewGroup + +/** + * The [ViewGroup] React children are mounted into. + * + * Defaults to [view]. Override this when the children have to live inside a + * sub-view - e.g. an overlay on top of a third-party [ViewGroup]. The sub-view + * has to cover the same area as [view], otherwise React Native's layout lands + * in the wrong place. + */ +open val childrenContainer: ViewGroup + get() = view +`.trim() + : undefined + const properties = [ + childrenMembers, + ...spec.properties.map((p) => getPropertyForwardImplementation(p)), + ] + .filter((p) => p != null) .join('\n\n') const methods = spec.methods .map((m) => getMethodForwardImplementation(m)) @@ -39,6 +69,13 @@ export function createKotlinHybridObject(spec: HybridObjectSpec): SourceFile[] { language: 'kotlin', }) } + if (spec.supportsChildren) { + extraImports.push({ + name: 'android.view.ViewGroup', + space: 'system', + language: 'kotlin', + }) + } let kotlinBase = spec.isHybridView ? 'HybridView' : 'HybridObject' let cxxPartBase = 'HybridObject.CxxPart' diff --git a/packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts b/packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts index 1a05a77146..e1ec09d55b 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftHybridObject.ts @@ -20,6 +20,31 @@ export function createSwiftHybridObject(spec: HybridObjectSpec): SourceFile[] { ), ] + const childrenMembers = spec.supportsChildren + ? ` +/** + * The \`\`UIView\`\` React children are mounted into. + * + * Defaults to \`\`view\`\`. Override this when the children have to live inside + * a sub-view - e.g. \`\`UIVisualEffectView/contentView\`\`, which is where a + * blur view expects its content. The sub-view has to cover the same area as + * \`\`view\`\`, otherwise React Native's layout lands in the wrong place. + * + * Like \`\`view\`\`, this value should not change during the lifetime of this + * \`\`HybridView\`\`. + */ +var childrenContainer: UIView { get } +`.trim() + : undefined + const childrenDefaults = spec.supportsChildren + ? ` +/// Default implementation of \`\`childrenContainer\`\` +var childrenContainer: UIView { + return self.view +} +`.trim() + : undefined + const protocolBaseClasses = ['HybridObject'] const classBaseClasses: string[] = [] if (spec.baseTypes.length > 0) { @@ -63,8 +88,23 @@ public ${hasBaseClass ? 'override func' : 'func'} getCxxWrapper() -> ${name.Hybr }`.trim() ) + const extensionMembers = [ + childrenDefaults, + ` +/// Default implementation of \`\`HybridObject.toString\`\` +func toString() -> String { + return "[HybridObject ${name.T}]" +} +`.trim(), + ] + .filter((m) => m != null) + .join('\n\n') + const requiredImports = extraImports.map((i) => `import ${i.name}`) requiredImports.push('import NitroModules') + if (spec.supportsChildren) { + requiredImports.push('import UIKit') + } const imports = requiredImports.filter(isNotDuplicate) const protocolCode = ` @@ -75,17 +115,14 @@ ${imports.join('\n')} /// See \`\`${protocolName}\`\` public protocol ${protocolName}_protocol: ${protocolBaseClasses.join(', ')} { // Properties - ${indent(properties, ' ')} + ${indent([childrenMembers, properties].filter((m) => m != null).join('\n'), ' ')} // Methods ${indent(methods, ' ')} } public extension ${protocolName}_protocol { - /// Default implementation of \`\`HybridObject.toString\`\` - func toString() -> String { - return "[HybridObject ${name.T}]" - } + ${indent(extensionMembers, ' ')} } /// See \`\`${protocolName}\`\` diff --git a/packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts b/packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts index 87f2fcdc78..5465b48312 100644 --- a/packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts +++ b/packages/nitrogen/src/syntax/swift/SwiftHybridObjectBridge.ts @@ -76,6 +76,15 @@ public final func onDropView() { } `.trim() ) + if (spec.supportsChildren) { + methodsBridge.push( + ` +public final func getChildrenContainer() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(__implementation.childrenContainer).toOpaque() +} +`.trim() + ) + } } const hybridObject = new HybridObjectType(spec) diff --git a/packages/nitrogen/src/views/CppHybridViewComponent.ts b/packages/nitrogen/src/views/CppHybridViewComponent.ts index 6a697d3eb6..12fda0522d 100644 --- a/packages/nitrogen/src/views/CppHybridViewComponent.ts +++ b/packages/nitrogen/src/views/CppHybridViewComponent.ts @@ -94,6 +94,7 @@ export function createViewComponentShadowNodeFiles( // .hpp code const shadowIndent = createIndentation(shadowNodeClassName.length) + const descriptorIndent = createIndentation(descriptorClassName.length) const componentHeaderCode = ` ${createFileMetadataString(`${component}.hpp`)} @@ -163,7 +164,8 @@ namespace ${namespace} { /** * The Component Descriptor for the "${spec.name}" View. */ - using ${descriptorClassName} = nitro::ViewComponentDescriptor<${shadowNodeClassName}>; + using ${descriptorClassName} = nitro::ViewComponentDescriptor<${shadowNodeClassName}, + ${descriptorIndent} ${spec.supportsChildren} /* supportsChildren */>; /* The actual view for "${spec.name}" needs to be implemented in platform-specific code. */ diff --git a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts index d3f2623865..09b269f8e7 100644 --- a/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts +++ b/packages/nitrogen/src/views/kotlin/KotlinHybridViewManager.ts @@ -38,14 +38,54 @@ export function createKotlinHybridViewManager( } const viewImplementation = implementation.implementationClassName + // React Native can only add children to a `ViewGroup`, and only through a + // `ViewGroupManager`. Views without children stay on `SimpleViewManager`. + const viewType = spec.supportsChildren ? 'ViewGroup' : 'View' + const managerBase = spec.supportsChildren + ? 'ViewGroupManager' + : 'SimpleViewManager' + const viewGroupImport = spec.supportsChildren + ? 'import android.view.ViewGroup\n' + : '' + const managerImport = spec.supportsChildren + ? 'com.facebook.react.uimanager.ViewGroupManager' + : 'com.facebook.react.uimanager.SimpleViewManager' + + // `ViewGroupManager`'s implementations would always use the View itself, so + // route every child operation through the HybridView's `childrenContainer`. + const childrenOverrides = spec.supportsChildren + ? ` override fun addView(parent: ViewGroup, child: View, index: Int) { + getChildrenContainer(parent).addView(child, index) + } + + override fun getChildAt(parent: ViewGroup, index: Int): View? { + return getChildrenContainer(parent).getChildAt(index) + } + + override fun getChildCount(parent: ViewGroup): Int { + return getChildrenContainer(parent).childCount + } + + override fun removeViewAt(parent: ViewGroup, index: Int) { + getChildrenContainer(parent).removeViewAt(index) + } + + private fun getChildrenContainer(parent: ViewGroup): ViewGroup { + val holder = getHybridViewHolder(parent) ?: return parent + return holder.hybridView.childrenContainer + } + +` + : '' + const viewManagerCode = ` ${createFileMetadataString(`${manager}.kt`)} package ${javaSubNamespace} import android.view.View -import com.facebook.react.uimanager.ReactStylesDiffMap -import com.facebook.react.uimanager.SimpleViewManager +${viewGroupImport}import com.facebook.react.uimanager.ReactStylesDiffMap +import ${managerImport} import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.ThemedReactContext import com.margelo.nitro.R.id.associated_hybrid_view_tag @@ -55,7 +95,7 @@ import ${javaNamespace}.* /** * Represents the React Native \`ViewManager\` for the "${spec.name}" Nitro HybridView. */ -public class ${manager}: SimpleViewManager() { +public class ${manager}: ${managerBase}() { /** * Represents the View and its last state snapshot (mutable) */ @@ -75,14 +115,14 @@ public class ${manager}: SimpleViewManager() { return "${spec.name}" } - override fun createViewInstance(reactContext: ThemedReactContext): View { + override fun createViewInstance(reactContext: ThemedReactContext): ${viewType} { val hybridView = ${viewImplementation}(reactContext) val view = hybridView.view view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView)) return view } - override fun updateState(view: View, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + override fun updateState(view: ${viewType}, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { val holder = getHybridViewHolder(view) ?: throw Error("Couldn't find view $view in local views table!") val hybridView = holder.hybridView @@ -99,14 +139,14 @@ public class ${manager}: SimpleViewManager() { return super.updateState(view, props, newState) } - override fun onDropViewInstance(view: View) { + override fun onDropViewInstance(view: ${viewType}) { val holder = getHybridViewHolder(view) holder?.lastState = null holder?.hybridView?.onDropView() return super.onDropViewInstance(view) } - protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: View): View? { + protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: ${viewType}): ${viewType}? { val preparedView = super.prepareToRecycleView(reactContext, view) ?: return null val holder = getHybridViewHolder(preparedView) @@ -126,7 +166,7 @@ public class ${manager}: SimpleViewManager() { } } - private fun getHybridViewHolder(view: View): HybridViewHolder? { +${childrenOverrides} private fun getHybridViewHolder(view: ${viewType}): HybridViewHolder? { return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder } } diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index 39d7b885ef..e74c1716ce 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -56,6 +56,41 @@ if (oldViewProps == nullptr } `.trim() }) + // React children are mounted into the Nitro View, not as siblings of it: + // `RCTViewComponentView` adds `contentView` as its last subview, so any child + // inserted by Fabric would end up *behind* the native View. + const childrenMethods = spec.supportsChildren + ? `- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer mountChildComponentView:childComponentView index:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer unmountChildComponentView:childComponentView index:index]; +} + +- (void) updateLayoutMetrics:(const react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Yoga positions each child relative to this component's border box, so the + // Nitro View has to span the full bounds. \`RCTViewComponentView\` would + // otherwise inset it by border + padding and double-apply that to every child. + self.contentView.frame = self.bounds; +} + +` + : '' + + const childrenIvar = spec.supportsChildren + ? '\n UIView* _childrenContainer;' + : '' + const childrenContainerUpdate = spec.supportsChildren + ? ` + + // 4. Get the UIView* React children are mounted into + void* containerUnsafe = swiftPart.getChildrenContainer(); + _childrenContainer = (__bridge_transfer UIView*) containerUnsafe;` + : '' + const mmFile = ` ${createFileMetadataString(`${component}.mm`)} @@ -90,7 +125,7 @@ using namespace ${namespace}::views; @end @implementation ${component} { - std::shared_ptr<${HybridTSpecSwift}> _hybridView; + std::shared_ptr<${HybridTSpecSwift}> _hybridView;${childrenIvar} BOOL _didDropView; } @@ -122,10 +157,10 @@ using namespace ${namespace}::views; UIView* view = (__bridge_transfer UIView*) viewUnsafe; // 3. Update RCTViewComponentView's [contentView] - [self setContentView:view]; + [self setContentView:view];${childrenContainerUpdate} } -- (void) notifyOnDropView { +${childrenMethods}- (void) notifyOnDropView { // A recycled component can later be invalidated. Notify only once per mount. if (_didDropView) { return; 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..d68115587f --- /dev/null +++ b/packages/react-native-nitro-modules/android/src/main/java/com/margelo/nitro/views/NitroViewGroup.kt @@ -0,0 +1,45 @@ +package com.margelo.nitro.views + +import android.annotation.SuppressLint +import android.content.Context +import android.view.ViewGroup + +/** + * A [ViewGroup] for [HybridView]s that render React children. + * + * React Native measures and positions every child itself, so this [ViewGroup] + * deliberately does not lay out its children - just like React Native's own + * `ReactViewGroup` does. Any [ViewGroup] works as a Hybrid View's `view`, but + * one that lays out its own children (such as a `LinearLayout`) would fight + * Fabric and move the children to the wrong place. + */ +open class NitroViewGroup( + context: Context, +) : ViewGroup(context) { + override fun onMeasure( + widthMeasureSpec: Int, + heightMeasureSpec: Int, + ) { + // React Native always measures with exact dimensions. + setMeasuredDimension( + MeasureSpec.getSize(widthMeasureSpec), + MeasureSpec.getSize(heightMeasureSpec), + ) + } + + override fun onLayout( + changed: Boolean, + left: Int, + top: Int, + right: Int, + bottom: Int, + ) { + // No-op - React Native lays out each child itself. + } + + @SuppressLint("MissingSuperCall") + override fun requestLayout() { + // No-op - React Native drives layout, so a layout request must not travel + // up the Android view hierarchy. + } +} diff --git a/packages/react-native-nitro-modules/cpp/views/ViewComponentDescriptor.hpp b/packages/react-native-nitro-modules/cpp/views/ViewComponentDescriptor.hpp index a2d9f29a87..180c00244b 100644 --- a/packages/react-native-nitro-modules/cpp/views/ViewComponentDescriptor.hpp +++ b/packages/react-native-nitro-modules/cpp/views/ViewComponentDescriptor.hpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include namespace margelo::nitro { @@ -21,8 +23,10 @@ using namespace facebook; * Requires the `TShadowNode` to be a `react::ShadowNode` composited of `Props` * which support Raw Props Parsing, and `State` which supports holding `Props` * for direct transfer to JNI on Android. + * + * `SupportsChildren` mirrors whether the Nitro View declared a `children` prop. */ -template +template class ViewComponentDescriptor final : public react::ConcreteComponentDescriptor { using Base = react::ConcreteComponentDescriptor; using Props = typename TShadowNode::ConcreteProps; @@ -39,6 +43,25 @@ class ViewComponentDescriptor final : public react::ConcreteComponentDescriptor< : Base(parameters, RawPropsCompat::makePropsParser()) {} public: + /** + * Rejects React children for a Nitro View that didn't declare a `children` prop. + * + * This is the render phase, so it is the one place both platforms pass through: + * Android would otherwise crash inside React Native's mounting layer with + * "Unable to add a view into a view that is not a ViewGroup", and iOS would + * silently render the children behind the native View. + */ + void appendChild(const std::shared_ptr& parentShadowNode, + const std::shared_ptr& childShadowNode) const override { + if constexpr (SupportsChildren) { + Base::appendChild(parentShadowNode, childShadowNode); + } else { + throw std::runtime_error(std::string(this->getComponentName()) + + " cannot render React children! To render children inside this Nitro View, " + "declare a `children?: HybridViewChildren` prop in its Nitro spec."); + } + } + /** * A faster path for cloning props - reuses the caching logic from the `Props`. */ diff --git a/packages/react-native-nitro-modules/src/views/HybridView.ts b/packages/react-native-nitro-modules/src/views/HybridView.ts index 9410d56c9d..e844dd6a3a 100644 --- a/packages/react-native-nitro-modules/src/views/HybridView.ts +++ b/packages/react-native-nitro-modules/src/views/HybridView.ts @@ -31,6 +31,48 @@ export interface HybridViewProps { /* no default props */ } +declare const childrenBrand: unique symbol +/** + * Marks a Hybrid View as being able to render React children. + * + * Declare a prop named `children` with this type to opt the view into hosting + * React children. Nitrogen then requires the view's native implementation to be + * a container (a `ViewGroup` on Android), and Fabric mounts the child views + * into it. + * + * `children` is a marker, not a Nitro prop - it never crosses the JS <-> native + * prop bridge. React's renderer mounts and unmounts the child views directly. + * + * Views that don't declare it stay leaf views, and passing children to them is + * a compile error. + * @example + * ```ts + * // Definition: + * interface CardProps extends HybridViewProps { + * children?: HybridViewChildren + * isElevated: boolean + * } + * export type Card = HybridView + * + * // in React: + * function App() { + * return ( + * + * Hello + * + * ) + * } + * ``` + */ +export interface HybridViewChildren { + /** + * Nitrogen identifies the `children` marker by its declared type, so it needs + * a member that no other type structurally matches. + * @internal + */ + readonly [childrenBrand]?: never +} + /** * Represents methods for a Hybrid View. * Such methods are implemented on the native side, and can be @@ -82,7 +124,7 @@ export type HybridRef< Props extends HybridViewProps, Methods extends HybridViewMethods = {}, Platforms extends ViewPlatformSpec = { ios: 'swift'; android: 'kotlin' }, -> = HybridObject & Props & Methods +> = HybridObject & Omit & Methods /** * This interface acts as a tag for Hybrid Views so nitrogen detects them. diff --git a/packages/react-native-nitro-modules/src/views/getHostComponent.ts b/packages/react-native-nitro-modules/src/views/getHostComponent.ts index 5d5710800e..7facd0b2a9 100644 --- a/packages/react-native-nitro-modules/src/views/getHostComponent.ts +++ b/packages/react-native-nitro-modules/src/views/getHostComponent.ts @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react' import { Platform, type HostComponent, type ViewProps } from 'react-native' // TODO: Migrate to the official export of `NativeComponentRegistry` from `react-native` once react-native 0.83.0 becomes more established as this is deprecated // eslint-disable-next-line @react-native/no-deep-imports @@ -84,6 +85,22 @@ type WrapFunctionsInObjects = { : Props[K] } +/** + * Resolves the `children` prop of a Nitro View - only a View that declared a + * `children` prop of type `HybridViewChildren` in its spec accepts children. + */ +type ChildrenPropOf = 'children' extends keyof Props + ? { children?: ReactNode } + : { + /** + * This Nitro View cannot render React children. + * + * To render children inside it, declare a `children` prop of type + * `HybridViewChildren` in its Nitro spec. + */ + children?: never + } + /** * Represents a React Native view, implemented as a Nitro View, with the given props and methods. * @@ -91,15 +108,20 @@ type WrapFunctionsInObjects = { * to the underlying Nitro {@linkcode HybridView}. * @note Every function/callback is wrapped as a `{ f: … }` object. Use {@linkcode callback | callback(...)} for this. * @note Every method can be called on the Ref. Including setting properties directly. + * @note `children` is only accepted if the Nitro View declared a `children` prop of type `HybridViewChildren`. */ export type ReactNativeView< Props extends HybridViewProps, Methods extends HybridViewMethods, > = HostComponent< + // `children` is a React concept, never a Nitro prop - it is neither wrapped as + // a callback object nor a member of the underlying Hybrid Object. WrapFunctionsInObjects< - DefaultHybridViewProps> & Props + DefaultHybridViewProps, 'children'>> & + Omit > & - ViewProps + Omit & + ChildrenPropOf > type ValidAttributes = ViewConfig['validAttributes'] diff --git a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenContainerTestView.kt b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenContainerTestView.kt new file mode 100644 index 0000000000..c4fc8ab14f --- /dev/null +++ b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenContainerTestView.kt @@ -0,0 +1,62 @@ +package com.margelo.nitro.test + +import android.content.Context +import android.graphics.Color +import android.view.View +import android.view.ViewGroup +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.views.NitroViewGroup + +/** A [NitroViewGroup] that keeps a single container child at its own size. */ +private class ContainerHostView( + context: Context, +) : NitroViewGroup(context) { + val childrenContainer = NitroViewGroup(context) + + init { + addView(childrenContainer) + } + + override fun onSizeChanged( + width: Int, + height: Int, + oldWidth: Int, + oldHeight: Int, + ) { + super.onSizeChanged(width, height, oldWidth, oldHeight) + // [NitroViewGroup] doesn't lay out its children, so place the container here. + childrenContainer.measure( + View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), + View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY), + ) + childrenContainer.layout(0, 0, width, height) + } +} + +@Keep +@DoNotStrip +class HybridChildrenContainerTestView( + val context: ThemedReactContext, +) : HybridChildrenContainerTestViewSpec() { + private val hostView = ContainerHostView(context) + + // View + override val view: ViewGroup = hostView + + // React children are mounted into this sub-view instead of `view` + override val childrenContainer: ViewGroup = hostView.childrenContainer + + // Props + override var isBlue: Boolean = false + set(value) { + field = value + hostView.setBackgroundColor(if (value) Color.BLUE else Color.RED) + } + + // Methods + override fun getNativeChildCount(): Double = hostView.childrenContainer.childCount.toDouble() + + override fun getViewChildCount(): Double = hostView.childCount.toDouble() +} diff --git a/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenTestView.kt b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenTestView.kt new file mode 100644 index 0000000000..de3429446a --- /dev/null +++ b/packages/react-native-nitro-test/android/src/main/java/com/margelo/nitro/test/HybridChildrenTestView.kt @@ -0,0 +1,27 @@ +package com.margelo.nitro.test + +import android.graphics.Color +import android.view.ViewGroup +import androidx.annotation.Keep +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.uimanager.ThemedReactContext +import com.margelo.nitro.views.NitroViewGroup + +@Keep +@DoNotStrip +class HybridChildrenTestView( + val context: ThemedReactContext, +) : HybridChildrenTestViewSpec() { + // View - React children are mounted into it + override val view: ViewGroup = NitroViewGroup(context) + + // Props + override var isBlue: Boolean = false + set(value) { + field = value + view.setBackgroundColor(if (value) Color.BLUE else Color.RED) + } + + // Methods + override fun getNativeChildCount(): Double = view.childCount.toDouble() +} 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..f11ea5b6ce 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,8 @@ 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.HybridChildrenContainerTestViewManager +import com.margelo.nitro.test.views.HybridChildrenTestViewManager import com.margelo.nitro.test.views.HybridRecyclableTestViewManager import com.margelo.nitro.test.views.HybridTestViewManager @@ -20,6 +22,8 @@ class NitroTestPackage : BaseReactPackage() { val viewManagers = ArrayList>() viewManagers.add(HybridTestViewManager()) viewManagers.add(HybridRecyclableTestViewManager()) + viewManagers.add(HybridChildrenTestViewManager()) + viewManagers.add(HybridChildrenContainerTestViewManager()) return viewManagers } diff --git a/packages/react-native-nitro-test/ios/HybridChildrenContainerTestView.swift b/packages/react-native-nitro-test/ios/HybridChildrenContainerTestView.swift new file mode 100644 index 0000000000..f55344a616 --- /dev/null +++ b/packages/react-native-nitro-test/ios/HybridChildrenContainerTestView.swift @@ -0,0 +1,51 @@ +// +// HybridChildrenContainerTestView.swift +// react-native-nitro-test +// + +import NitroModules +import UIKit + +/// A `UIView` that keeps a single container sub-view at its own size. +private final class ContainerHostView: UIView { + let childrenContainer = UIView() + + override init(frame: CGRect) { + super.init(frame: frame) + addSubview(childrenContainer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) is not supported") + } + + override func layoutSubviews() { + super.layoutSubviews() + childrenContainer.frame = bounds + } +} + +class HybridChildrenContainerTestView: HybridChildrenContainerTestViewSpec { + private let hostView = ContainerHostView() + + // UIView + var view: UIView { hostView } + // React children are mounted into this sub-view instead of `view` + var childrenContainer: UIView { hostView.childrenContainer } + + // Props + var isBlue: Bool = false { + didSet { + hostView.backgroundColor = isBlue ? .systemBlue : .systemRed + } + } + + // Methods + func getNativeChildCount() throws -> Double { + return Double(hostView.childrenContainer.subviews.count) + } + + func getViewChildCount() throws -> Double { + return Double(hostView.subviews.count) + } +} diff --git a/packages/react-native-nitro-test/ios/HybridChildrenTestView.swift b/packages/react-native-nitro-test/ios/HybridChildrenTestView.swift new file mode 100644 index 0000000000..463b39bcfa --- /dev/null +++ b/packages/react-native-nitro-test/ios/HybridChildrenTestView.swift @@ -0,0 +1,24 @@ +// +// HybridChildrenTestView.swift +// react-native-nitro-test +// + +import NitroModules +import UIKit + +class HybridChildrenTestView: HybridChildrenTestViewSpec { + // UIView - React children are mounted into it + var view: UIView = UIView() + + // Props + var isBlue: Bool = false { + didSet { + view.backgroundColor = isBlue ? .systemBlue : .systemRed + } + } + + // Methods + func getNativeChildCount() throws -> Double { + return Double(view.subviews.count) + } +} diff --git a/packages/react-native-nitro-test/nitro.json b/packages/react-native-nitro-test/nitro.json index b3a180b75e..2818add895 100644 --- a/packages/react-native-nitro-test/nitro.json +++ b/packages/react-native-nitro-test/nitro.json @@ -65,6 +65,26 @@ "implementationClassName": "HybridTestView" } }, + "ChildrenTestView": { + "ios": { + "language": "swift", + "implementationClassName": "HybridChildrenTestView" + }, + "android": { + "language": "kotlin", + "implementationClassName": "HybridChildrenTestView" + } + }, + "ChildrenContainerTestView": { + "ios": { + "language": "swift", + "implementationClassName": "HybridChildrenContainerTestView" + }, + "android": { + "language": "kotlin", + "implementationClassName": "HybridChildrenContainerTestView" + } + }, "RecyclableTestView": { "ios": { "language": "swift", 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 3299863912..7ff801ab7e 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,10 @@ target_sources( # Shared Nitrogen C++ sources ../nitrogen/generated/shared/c++/HybridBaseSpec.cpp ../nitrogen/generated/shared/c++/HybridChildSpec.cpp + ../nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.cpp + ../nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.cpp + ../nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.cpp ../nitrogen/generated/shared/c++/HybridPlatformObjectSpec.cpp ../nitrogen/generated/shared/c++/HybridRecyclableTestViewSpec.cpp ../nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.cpp @@ -47,6 +51,10 @@ target_sources( ../nitrogen/generated/android/c++/JHybridChildSpec.cpp ../nitrogen/generated/android/c++/JNamedVariant.cpp ../nitrogen/generated/android/c++/JVariant_Double_String.cpp + ../nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.cpp + ../nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.cpp + ../nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.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 b2cfa0e5bd..a429ad4efa 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,10 @@ #include "JHybridBaseSpec.hpp" #include "JHybridChildSpec.hpp" +#include "JHybridChildrenContainerTestViewSpec.hpp" +#include "views/JHybridChildrenContainerTestViewStateUpdater.hpp" +#include "JHybridChildrenTestViewSpec.hpp" +#include "views/JHybridChildrenTestViewStateUpdater.hpp" #include "JHybridPlatformObjectSpec.hpp" #include "JHybridRecyclableTestViewSpec.hpp" #include "views/JHybridRecyclableTestViewStateUpdater.hpp" @@ -88,6 +92,22 @@ struct JHybridTestViewSpecImpl: public jni::JavaClassgetJHybridTestViewSpec(); } }; +struct JHybridChildrenTestViewSpecImpl: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenTestView;"; + static std::shared_ptr create() { + static const auto constructorFn = javaClassStatic()->getConstructor(); + jni::local_ref javaPart = javaClassStatic()->newObject(constructorFn); + return javaPart->getJHybridChildrenTestViewSpec(); + } +}; +struct JHybridChildrenContainerTestViewSpecImpl: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenContainerTestView;"; + static std::shared_ptr create() { + static const auto constructorFn = javaClassStatic()->getConstructor(); + jni::local_ref javaPart = javaClassStatic()->newObject(constructorFn); + return javaPart->getJHybridChildrenContainerTestViewSpec(); + } +}; struct JHybridRecyclableTestViewSpecImpl: public jni::JavaClass { static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridRecyclableTestView;"; static std::shared_ptr create() { @@ -104,6 +124,10 @@ void registerAllNatives() { // Register native JNI methods margelo::nitro::test::JHybridBaseSpec::CxxPart::registerNatives(); margelo::nitro::test::JHybridChildSpec::CxxPart::registerNatives(); + margelo::nitro::test::JHybridChildrenContainerTestViewSpec::CxxPart::registerNatives(); + margelo::nitro::test::views::JHybridChildrenContainerTestViewStateUpdater::registerNatives(); + margelo::nitro::test::JHybridChildrenTestViewSpec::CxxPart::registerNatives(); + margelo::nitro::test::views::JHybridChildrenTestViewStateUpdater::registerNatives(); margelo::nitro::test::JHybridPlatformObjectSpec::CxxPart::registerNatives(); margelo::nitro::test::JHybridRecyclableTestViewSpec::CxxPart::registerNatives(); margelo::nitro::test::views::JHybridRecyclableTestViewStateUpdater::registerNatives(); @@ -165,6 +189,18 @@ void registerAllNatives() { return JHybridTestViewSpecImpl::create(); } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ChildrenTestView", + []() -> std::shared_ptr { + return JHybridChildrenTestViewSpecImpl::create(); + } + ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ChildrenContainerTestView", + []() -> std::shared_ptr { + return JHybridChildrenContainerTestViewSpecImpl::create(); + } + ); HybridObjectRegistry::registerHybridObjectConstructor( "RecyclableTestView", []() -> std::shared_ptr { diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.cpp new file mode 100644 index 0000000000..e233a4eb3e --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.cpp @@ -0,0 +1,66 @@ +/// +/// JHybridChildrenContainerTestViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridChildrenContainerTestViewSpec.hpp" + + + + + +namespace margelo::nitro::test { + + std::shared_ptr JHybridChildrenContainerTestViewSpec::JavaPart::getJHybridChildrenContainerTestViewSpec() { + 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 JHybridChildrenContainerTestViewSpec!"); + } + return castHybridObject; + } + + jni::local_ref JHybridChildrenContainerTestViewSpec::CxxPart::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + std::shared_ptr JHybridChildrenContainerTestViewSpec::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 JHybridChildrenContainerTestViewSpec::JavaPart!"); + } + return std::make_shared(castJavaPart); + } + + void JHybridChildrenContainerTestViewSpec::CxxPart::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridChildrenContainerTestViewSpec::CxxPart::initHybrid), + }); + } + + // Properties + bool JHybridChildrenContainerTestViewSpec::getIsBlue() { + static const auto method = _javaPart->javaClassStatic()->getMethod("isBlue"); + auto __result = method(_javaPart); + return static_cast(__result); + } + void JHybridChildrenContainerTestViewSpec::setIsBlue(bool isBlue) { + static const auto method = _javaPart->javaClassStatic()->getMethod("setBlue"); + method(_javaPart, isBlue); + } + + // Methods + double JHybridChildrenContainerTestViewSpec::getNativeChildCount() { + static const auto method = _javaPart->javaClassStatic()->getMethod("getNativeChildCount"); + auto __result = method(_javaPart); + return __result; + } + double JHybridChildrenContainerTestViewSpec::getViewChildCount() { + static const auto method = _javaPart->javaClassStatic()->getMethod("getViewChildCount"); + auto __result = method(_javaPart); + return __result; + } + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.hpp new file mode 100644 index 0000000000..248a64b230 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenContainerTestViewSpec.hpp @@ -0,0 +1,65 @@ +/// +/// HybridChildrenContainerTestViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridChildrenContainerTestViewSpec.hpp" + + + + +namespace margelo::nitro::test { + + using namespace facebook; + + class JHybridChildrenContainerTestViewSpec: public virtual HybridChildrenContainerTestViewSpec, public virtual JHybridObject { + public: + struct JavaPart: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenContainerTestViewSpec;"; + std::shared_ptr getJHybridChildrenContainerTestViewSpec(); + }; + struct CxxPart: public jni::HybridClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenContainerTestViewSpec$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 JHybridChildrenContainerTestViewSpec(const jni::local_ref& javaPart): + HybridObject(HybridChildrenContainerTestViewSpec::TAG), + JHybridObject(javaPart), + _javaPart(jni::make_global(javaPart)) {} + ~JHybridChildrenContainerTestViewSpec() 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 + bool getIsBlue() override; + void setIsBlue(bool isBlue) override; + + public: + // Methods + double getNativeChildCount() override; + double getViewChildCount() override; + + private: + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.cpp new file mode 100644 index 0000000000..a9b7b84787 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.cpp @@ -0,0 +1,61 @@ +/// +/// JHybridChildrenTestViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridChildrenTestViewSpec.hpp" + + + + + +namespace margelo::nitro::test { + + std::shared_ptr JHybridChildrenTestViewSpec::JavaPart::getJHybridChildrenTestViewSpec() { + 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 JHybridChildrenTestViewSpec!"); + } + return castHybridObject; + } + + jni::local_ref JHybridChildrenTestViewSpec::CxxPart::initHybrid(jni::alias_ref jThis) { + return makeCxxInstance(jThis); + } + + std::shared_ptr JHybridChildrenTestViewSpec::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 JHybridChildrenTestViewSpec::JavaPart!"); + } + return std::make_shared(castJavaPart); + } + + void JHybridChildrenTestViewSpec::CxxPart::registerNatives() { + registerHybrid({ + makeNativeMethod("initHybrid", JHybridChildrenTestViewSpec::CxxPart::initHybrid), + }); + } + + // Properties + bool JHybridChildrenTestViewSpec::getIsBlue() { + static const auto method = _javaPart->javaClassStatic()->getMethod("isBlue"); + auto __result = method(_javaPart); + return static_cast(__result); + } + void JHybridChildrenTestViewSpec::setIsBlue(bool isBlue) { + static const auto method = _javaPart->javaClassStatic()->getMethod("setBlue"); + method(_javaPart, isBlue); + } + + // Methods + double JHybridChildrenTestViewSpec::getNativeChildCount() { + static const auto method = _javaPart->javaClassStatic()->getMethod("getNativeChildCount"); + auto __result = method(_javaPart); + return __result; + } + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.hpp new file mode 100644 index 0000000000..17c242a7a6 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/JHybridChildrenTestViewSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridChildrenTestViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include "HybridChildrenTestViewSpec.hpp" + + + + +namespace margelo::nitro::test { + + using namespace facebook; + + class JHybridChildrenTestViewSpec: public virtual HybridChildrenTestViewSpec, public virtual JHybridObject { + public: + struct JavaPart: public jni::JavaClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenTestViewSpec;"; + std::shared_ptr getJHybridChildrenTestViewSpec(); + }; + struct CxxPart: public jni::HybridClass { + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/HybridChildrenTestViewSpec$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 JHybridChildrenTestViewSpec(const jni::local_ref& javaPart): + HybridObject(HybridChildrenTestViewSpec::TAG), + JHybridObject(javaPart), + _javaPart(jni::make_global(javaPart)) {} + ~JHybridChildrenTestViewSpec() 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 + bool getIsBlue() override; + void setIsBlue(bool isBlue) override; + + public: + // Methods + double getNativeChildCount() override; + + private: + jni::global_ref _javaPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.cpp new file mode 100644 index 0000000000..1d5b65a85a --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.cpp @@ -0,0 +1,74 @@ +/// +/// JHybridChildrenContainerTestViewStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridChildrenContainerTestViewStateUpdater.hpp" +#include "views/HybridChildrenContainerTestViewComponent.hpp" +#include +#include + +namespace margelo::nitro::test::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +std::shared_ptr JHybridChildrenContainerTestViewStateUpdater::getPropsFromStateWrapper( + jni::alias_ref stateWrapper) { + if (stateWrapper.get() == nullptr) { + return nullptr; + } + // Get concrete StateWrapperImpl from passed StateWrapper interface object + jobject rawStateWrapper = stateWrapper.get(); + if (!stateWrapper->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { + throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); + } + auto stateWrapperImpl = jni::alias_ref{ + static_cast(rawStateWrapper) + }; + std::shared_ptr state = stateWrapperImpl->cthis()->getState(); + if (state == nullptr) { + return nullptr; + } + auto concreteState = std::static_pointer_cast(state); + const HybridChildrenContainerTestViewState& data = concreteState->getData(); + const std::shared_ptr& props = data.getProps(); + if (props == nullptr) [[unlikely]] { + throw std::runtime_error("HybridChildrenContainerTestViewState's data doesn't contain any props!"); + } + return props; +} + +void JHybridChildrenContainerTestViewStateUpdater::updateViewProps(jni::alias_ref /* class */, + jni::alias_ref javaView, + jni::alias_ref newState, + jni::alias_ref oldState) { + std::shared_ptr hybridView = javaView->getJHybridChildrenContainerTestViewSpec(); + std::shared_ptr newProps = getPropsFromStateWrapper(newState); + std::shared_ptr oldProps = getPropsFromStateWrapper(oldState); + if (newProps == nullptr) [[unlikely]] { + throw std::runtime_error("Current StateWrapper doesn't contain any props!"); + } + + // Update only props that differ from the previous State snapshot. + if (oldProps == nullptr + ? newProps->isBlue.isProvided() + : !newProps->isBlue.hasSameValue(oldProps->isBlue)) { + hybridView->setIsBlue(newProps->isBlue.get()); + } + + // Update hybridRef if it changed + if (oldProps == nullptr + ? newProps->hybridRef.isProvided() + : !newProps->hybridRef.hasSameValue(oldProps->hybridRef)) { + // hybridRef changed - call it with new this + const auto& maybeFunc = newProps->hybridRef.get(); + if (maybeFunc.has_value()) { + maybeFunc.value()(hybridView); + } + } +} + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.hpp new file mode 100644 index 0000000000..7bd536f8b6 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenContainerTestViewStateUpdater.hpp @@ -0,0 +1,54 @@ +/// +/// JHybridChildrenContainerTestViewStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 "JHybridChildrenContainerTestViewSpec.hpp" +#include "views/HybridChildrenContainerTestViewComponent.hpp" + +namespace margelo::nitro::test::views { + +using namespace facebook; + +class JHybridChildrenContainerTestViewStateUpdater final: public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/views/HybridChildrenContainerTestViewStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref /* class */, + jni::alias_ref view, + jni::alias_ref newState, + jni::alias_ref oldState); + +private: + static std::shared_ptr getPropsFromStateWrapper( + jni::alias_ref stateWrapper); + +public: + static void registerNatives() { + // Register JNI calls + javaClassStatic()->registerNatives({ + makeNativeMethod("updateViewProps", JHybridChildrenContainerTestViewStateUpdater::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/c++/views/JHybridChildrenTestViewStateUpdater.cpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.cpp new file mode 100644 index 0000000000..bf7841c5ec --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.cpp @@ -0,0 +1,74 @@ +/// +/// JHybridChildrenTestViewStateUpdater.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "JHybridChildrenTestViewStateUpdater.hpp" +#include "views/HybridChildrenTestViewComponent.hpp" +#include +#include + +namespace margelo::nitro::test::views { + +using namespace facebook; +using ConcreteStateData = react::ConcreteState; + +std::shared_ptr JHybridChildrenTestViewStateUpdater::getPropsFromStateWrapper( + jni::alias_ref stateWrapper) { + if (stateWrapper.get() == nullptr) { + return nullptr; + } + // Get concrete StateWrapperImpl from passed StateWrapper interface object + jobject rawStateWrapper = stateWrapper.get(); + if (!stateWrapper->isInstanceOf(react::StateWrapperImpl::javaClassStatic())) [[unlikely]] { + throw std::runtime_error("StateWrapper is not a StateWrapperImpl"); + } + auto stateWrapperImpl = jni::alias_ref{ + static_cast(rawStateWrapper) + }; + std::shared_ptr state = stateWrapperImpl->cthis()->getState(); + if (state == nullptr) { + return nullptr; + } + auto concreteState = std::static_pointer_cast(state); + const HybridChildrenTestViewState& data = concreteState->getData(); + const std::shared_ptr& props = data.getProps(); + if (props == nullptr) [[unlikely]] { + throw std::runtime_error("HybridChildrenTestViewState's data doesn't contain any props!"); + } + return props; +} + +void JHybridChildrenTestViewStateUpdater::updateViewProps(jni::alias_ref /* class */, + jni::alias_ref javaView, + jni::alias_ref newState, + jni::alias_ref oldState) { + std::shared_ptr hybridView = javaView->getJHybridChildrenTestViewSpec(); + std::shared_ptr newProps = getPropsFromStateWrapper(newState); + std::shared_ptr oldProps = getPropsFromStateWrapper(oldState); + if (newProps == nullptr) [[unlikely]] { + throw std::runtime_error("Current StateWrapper doesn't contain any props!"); + } + + // Update only props that differ from the previous State snapshot. + if (oldProps == nullptr + ? newProps->isBlue.isProvided() + : !newProps->isBlue.hasSameValue(oldProps->isBlue)) { + hybridView->setIsBlue(newProps->isBlue.get()); + } + + // Update hybridRef if it changed + if (oldProps == nullptr + ? newProps->hybridRef.isProvided() + : !newProps->hybridRef.hasSameValue(oldProps->hybridRef)) { + // hybridRef changed - call it with new this + const auto& maybeFunc = newProps->hybridRef.get(); + if (maybeFunc.has_value()) { + maybeFunc.value()(hybridView); + } + } +} + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.hpp b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.hpp new file mode 100644 index 0000000000..50337485a6 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/c++/views/JHybridChildrenTestViewStateUpdater.hpp @@ -0,0 +1,54 @@ +/// +/// JHybridChildrenTestViewStateUpdater.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 "JHybridChildrenTestViewSpec.hpp" +#include "views/HybridChildrenTestViewComponent.hpp" + +namespace margelo::nitro::test::views { + +using namespace facebook; + +class JHybridChildrenTestViewStateUpdater final: public jni::JavaClass { +public: + static constexpr auto kJavaDescriptor = "Lcom/margelo/nitro/test/views/HybridChildrenTestViewStateUpdater;"; + +public: + static void updateViewProps(jni::alias_ref /* class */, + jni::alias_ref view, + jni::alias_ref newState, + jni::alias_ref oldState); + +private: + static std::shared_ptr getPropsFromStateWrapper( + jni::alias_ref stateWrapper); + +public: + static void registerNatives() { + // Register JNI calls + javaClassStatic()->registerNatives({ + makeNativeMethod("updateViewProps", JHybridChildrenTestViewStateUpdater::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/HybridChildrenContainerTestViewSpec.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridChildrenContainerTestViewSpec.kt new file mode 100644 index 0000000000..9c2f207401 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridChildrenContainerTestViewSpec.kt @@ -0,0 +1,88 @@ +/// +/// HybridChildrenContainerTestViewSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 dalvik.annotation.optimization.FastNative +import com.margelo.nitro.core.HybridObject +import com.margelo.nitro.views.HybridView +import android.view.ViewGroup + +/** + * A Kotlin class representing the ChildrenContainerTestView HybridObject. + * Implement this abstract class to create Kotlin-based instances of ChildrenContainerTestView. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridChildrenContainerTestViewSpec: HybridView() { + // Properties + /** + * The [ViewGroup] this HybridView is holding. + * + * React Native positions each child itself, so this should be a + * [com.margelo.nitro.views.NitroViewGroup] (or another [ViewGroup] that + * doesn't lay out its own children). + * + * This value should not change during the lifetime of this `HybridView`. + */ + abstract override val view: ViewGroup + + /** + * The [ViewGroup] React children are mounted into. + * + * Defaults to [view]. Override this when the children have to live inside a + * sub-view - e.g. an overlay on top of a third-party [ViewGroup]. The sub-view + * has to cover the same area as [view], otherwise React Native's layout lands + * in the wrong place. + */ + open val childrenContainer: ViewGroup + get() = view + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var isBlue: Boolean + + // Methods + @DoNotStrip + @Keep + abstract fun getNativeChildCount(): Double + + @DoNotStrip + @Keep + abstract fun getViewChildCount(): Double + + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject ChildrenContainerTestView]" + } + + // C++ backing class + @DoNotStrip + @Keep + protected open class CxxPart(javaPart: HybridChildrenContainerTestViewSpec): HybridObject.CxxPart(javaPart) { + // C++ JHybridChildrenContainerTestViewSpec::CxxPart::initHybrid(...) + @FastNative + external override fun initHybrid(): HybridData + } + override fun createCxxPart(): CxxPart { + return CxxPart(this) + } + + companion object { + protected const val TAG = "HybridChildrenContainerTestViewSpec" + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridChildrenTestViewSpec.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridChildrenTestViewSpec.kt new file mode 100644 index 0000000000..ec72f8af11 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/HybridChildrenTestViewSpec.kt @@ -0,0 +1,84 @@ +/// +/// HybridChildrenTestViewSpec.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 dalvik.annotation.optimization.FastNative +import com.margelo.nitro.core.HybridObject +import com.margelo.nitro.views.HybridView +import android.view.ViewGroup + +/** + * A Kotlin class representing the ChildrenTestView HybridObject. + * Implement this abstract class to create Kotlin-based instances of ChildrenTestView. + */ +@DoNotStrip +@Keep +@Suppress( + "KotlinJniMissingFunction", "unused", + "RedundantSuppression", "RedundantUnitReturnType", "SimpleRedundantLet", + "LocalVariableName", "PropertyName", "PrivatePropertyName", "FunctionName" +) +abstract class HybridChildrenTestViewSpec: HybridView() { + // Properties + /** + * The [ViewGroup] this HybridView is holding. + * + * React Native positions each child itself, so this should be a + * [com.margelo.nitro.views.NitroViewGroup] (or another [ViewGroup] that + * doesn't lay out its own children). + * + * This value should not change during the lifetime of this `HybridView`. + */ + abstract override val view: ViewGroup + + /** + * The [ViewGroup] React children are mounted into. + * + * Defaults to [view]. Override this when the children have to live inside a + * sub-view - e.g. an overlay on top of a third-party [ViewGroup]. The sub-view + * has to cover the same area as [view], otherwise React Native's layout lands + * in the wrong place. + */ + open val childrenContainer: ViewGroup + get() = view + + @get:DoNotStrip + @get:Keep + @set:DoNotStrip + @set:Keep + abstract var isBlue: Boolean + + // Methods + @DoNotStrip + @Keep + abstract fun getNativeChildCount(): Double + + // Default implementation of `HybridObject.toString()` + override fun toString(): String { + return "[HybridObject ChildrenTestView]" + } + + // C++ backing class + @DoNotStrip + @Keep + protected open class CxxPart(javaPart: HybridChildrenTestViewSpec): HybridObject.CxxPart(javaPart) { + // C++ JHybridChildrenTestViewSpec::CxxPart::initHybrid(...) + @FastNative + external override fun initHybrid(): HybridData + } + override fun createCxxPart(): CxxPart { + return CxxPart(this) + } + + companion object { + protected const val TAG = "HybridChildrenTestViewSpec" + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewManager.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewManager.kt new file mode 100644 index 0000000000..dd242b3606 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewManager.kt @@ -0,0 +1,118 @@ +/// +/// HybridChildrenContainerTestViewManager.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.ViewGroupManager +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 "ChildrenContainerTestView" Nitro HybridView. + */ +public class HybridChildrenContainerTestViewManager: ViewGroupManager() { + /** + * Represents the View and its last state snapshot (mutable) + */ + private class HybridViewHolder( + val hybridView: HybridChildrenContainerTestView, + var lastState: StateWrapper? = null, + ) + + init { + if (RecyclableView::class.java.isAssignableFrom(HybridChildrenContainerTestView::class.java)) { + // Enable view recycling + super.setupViewRecycling() + } + } + + override fun getName(): String { + return "ChildrenContainerTestView" + } + + override fun createViewInstance(reactContext: ThemedReactContext): ViewGroup { + val hybridView = HybridChildrenContainerTestView(reactContext) + val view = hybridView.view + view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView)) + return view + } + + override fun updateState(view: ViewGroup, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val holder = getHybridViewHolder(view) + ?: throw Error("Couldn't find view $view in local views table!") + val hybridView = holder.hybridView + val oldState = holder.lastState + val newState = stateWrapper + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridChildrenContainerTestViewStateUpdater.updateViewProps(hybridView, newState, oldState) + hybridView.afterUpdate() + holder.lastState = newState + + // 2. Continue in base View props + return super.updateState(view, props, newState) + } + + override fun onDropViewInstance(view: ViewGroup) { + val holder = getHybridViewHolder(view) + holder?.lastState = null + holder?.hybridView?.onDropView() + return super.onDropViewInstance(view) + } + + protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: ViewGroup): ViewGroup? { + val preparedView = super.prepareToRecycleView(reactContext, view) + ?: return null + val holder = getHybridViewHolder(preparedView) + ?: return null + val hybridView = holder.hybridView + holder.lastState = 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 + } + } + + override fun addView(parent: ViewGroup, child: View, index: Int) { + getChildrenContainer(parent).addView(child, index) + } + + override fun getChildAt(parent: ViewGroup, index: Int): View? { + return getChildrenContainer(parent).getChildAt(index) + } + + override fun getChildCount(parent: ViewGroup): Int { + return getChildrenContainer(parent).childCount + } + + override fun removeViewAt(parent: ViewGroup, index: Int) { + getChildrenContainer(parent).removeViewAt(index) + } + + private fun getChildrenContainer(parent: ViewGroup): ViewGroup { + val holder = getHybridViewHolder(parent) ?: return parent + return holder.hybridView.childrenContainer + } + + private fun getHybridViewHolder(view: ViewGroup): HybridViewHolder? { + return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewStateUpdater.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewStateUpdater.kt new file mode 100644 index 0000000000..396ccfb165 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenContainerTestViewStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridChildrenContainerTestViewStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import com.facebook.react.uimanager.StateWrapper +import com.margelo.nitro.test.* + +internal class HybridChildrenContainerTestViewStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [newState] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridChildrenContainerTestViewSpec, newState: StateWrapper, oldState: StateWrapper?) + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewManager.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewManager.kt new file mode 100644 index 0000000000..d322b859c3 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewManager.kt @@ -0,0 +1,118 @@ +/// +/// HybridChildrenTestViewManager.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import android.view.View +import android.view.ViewGroup +import com.facebook.react.uimanager.ReactStylesDiffMap +import com.facebook.react.uimanager.ViewGroupManager +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 "ChildrenTestView" Nitro HybridView. + */ +public class HybridChildrenTestViewManager: ViewGroupManager() { + /** + * Represents the View and its last state snapshot (mutable) + */ + private class HybridViewHolder( + val hybridView: HybridChildrenTestView, + var lastState: StateWrapper? = null, + ) + + init { + if (RecyclableView::class.java.isAssignableFrom(HybridChildrenTestView::class.java)) { + // Enable view recycling + super.setupViewRecycling() + } + } + + override fun getName(): String { + return "ChildrenTestView" + } + + override fun createViewInstance(reactContext: ThemedReactContext): ViewGroup { + val hybridView = HybridChildrenTestView(reactContext) + val view = hybridView.view + view.setTag(associated_hybrid_view_tag, HybridViewHolder(hybridView)) + return view + } + + override fun updateState(view: ViewGroup, props: ReactStylesDiffMap, stateWrapper: StateWrapper): Any? { + val holder = getHybridViewHolder(view) + ?: throw Error("Couldn't find view $view in local views table!") + val hybridView = holder.hybridView + val oldState = holder.lastState + val newState = stateWrapper + + // 1. Update each prop individually + hybridView.beforeUpdate() + HybridChildrenTestViewStateUpdater.updateViewProps(hybridView, newState, oldState) + hybridView.afterUpdate() + holder.lastState = newState + + // 2. Continue in base View props + return super.updateState(view, props, newState) + } + + override fun onDropViewInstance(view: ViewGroup) { + val holder = getHybridViewHolder(view) + holder?.lastState = null + holder?.hybridView?.onDropView() + return super.onDropViewInstance(view) + } + + protected override fun prepareToRecycleView(reactContext: ThemedReactContext, view: ViewGroup): ViewGroup? { + val preparedView = super.prepareToRecycleView(reactContext, view) + ?: return null + val holder = getHybridViewHolder(preparedView) + ?: return null + val hybridView = holder.hybridView + holder.lastState = 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 + } + } + + override fun addView(parent: ViewGroup, child: View, index: Int) { + getChildrenContainer(parent).addView(child, index) + } + + override fun getChildAt(parent: ViewGroup, index: Int): View? { + return getChildrenContainer(parent).getChildAt(index) + } + + override fun getChildCount(parent: ViewGroup): Int { + return getChildrenContainer(parent).childCount + } + + override fun removeViewAt(parent: ViewGroup, index: Int) { + getChildrenContainer(parent).removeViewAt(index) + } + + private fun getChildrenContainer(parent: ViewGroup): ViewGroup { + val holder = getHybridViewHolder(parent) ?: return parent + return holder.hybridView.childrenContainer + } + + private fun getHybridViewHolder(view: ViewGroup): HybridViewHolder? { + return view.getTag(associated_hybrid_view_tag) as? HybridViewHolder + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewStateUpdater.kt b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewStateUpdater.kt new file mode 100644 index 0000000000..ae90af8697 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/android/kotlin/com/margelo/nitro/test/views/HybridChildrenTestViewStateUpdater.kt @@ -0,0 +1,23 @@ +/// +/// HybridChildrenTestViewStateUpdater.kt +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +package com.margelo.nitro.test.views + +import com.facebook.react.uimanager.StateWrapper +import com.margelo.nitro.test.* + +internal class HybridChildrenTestViewStateUpdater { + companion object { + /** + * Updates the props for [view] through C++. + * The [newState] prop is expected to contain [view]'s props as wrapped Fabric state. + */ + @Suppress("KotlinJniMissingFunction") + @JvmStatic + external fun updateViewProps(view: HybridChildrenTestViewSpec, newState: StateWrapper, oldState: StateWrapper?) + } +} 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 88a6bef556..f0ac3bf149 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,8 @@ // Include C++ implementation defined types #include "HybridBaseSpecSwift.hpp" #include "HybridChildSpecSwift.hpp" +#include "HybridChildrenContainerTestViewSpecSwift.hpp" +#include "HybridChildrenTestViewSpecSwift.hpp" #include "HybridPlatformObjectSpecSwift.hpp" #include "HybridRecyclableTestViewSpecSwift.hpp" #include "HybridTestObjectSwiftKotlinSpecSwift.hpp" @@ -52,6 +54,38 @@ namespace margelo::nitro::test::bridge::swift { return swiftPart.toUnsafe(); } + // pragma MARK: std::shared_ptr + std::shared_ptr create_std__shared_ptr_HybridChildrenContainerTestViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept { + NitroTest::HybridChildrenContainerTestViewSpec_cxx swiftPart = NitroTest::HybridChildrenContainerTestViewSpec_cxx::fromUnsafe(swiftUnsafePointer); + return std::make_shared(swiftPart); + } + void* NON_NULL get_std__shared_ptr_HybridChildrenContainerTestViewSpec_(std__shared_ptr_HybridChildrenContainerTestViewSpec_ cppType) { + std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); + #ifdef NITRO_DEBUG + if (swiftWrapper == nullptr) [[unlikely]] { + throw std::runtime_error("Class \"HybridChildrenContainerTestViewSpec\" is not implemented in Swift!"); + } + #endif + NitroTest::HybridChildrenContainerTestViewSpec_cxx& swiftPart = swiftWrapper->getSwiftPart(); + return swiftPart.toUnsafe(); + } + + // pragma MARK: std::shared_ptr + std::shared_ptr create_std__shared_ptr_HybridChildrenTestViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept { + NitroTest::HybridChildrenTestViewSpec_cxx swiftPart = NitroTest::HybridChildrenTestViewSpec_cxx::fromUnsafe(swiftUnsafePointer); + return std::make_shared(swiftPart); + } + void* NON_NULL get_std__shared_ptr_HybridChildrenTestViewSpec_(std__shared_ptr_HybridChildrenTestViewSpec_ cppType) { + std::shared_ptr swiftWrapper = std::dynamic_pointer_cast(cppType); + #ifdef NITRO_DEBUG + if (swiftWrapper == nullptr) [[unlikely]] { + throw std::runtime_error("Class \"HybridChildrenTestViewSpec\" is not implemented in Swift!"); + } + #endif + NitroTest::HybridChildrenTestViewSpec_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 32c14930e8..225bba74ef 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,10 @@ 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 `HybridChildrenContainerTestViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridChildrenContainerTestViewSpec; } +// Forward declaration of `HybridChildrenTestViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridChildrenTestViewSpec; } // Forward declaration of `HybridPlatformObjectSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridPlatformObjectSpec; } // Forward declaration of `HybridRecyclableTestViewSpec` to properly resolve imports. @@ -56,6 +60,10 @@ 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 `HybridChildrenContainerTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenContainerTestViewSpec_cxx; } +// Forward declaration of `HybridChildrenTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenTestViewSpec_cxx; } // Forward declaration of `HybridPlatformObjectSpec_cxx` to properly resolve imports. namespace NitroTest { class HybridPlatformObjectSpec_cxx; } // Forward declaration of `HybridRecyclableTestViewSpec_cxx` to properly resolve imports. @@ -72,6 +80,8 @@ namespace NitroTest { class HybridTestViewSpec_cxx; } #include "ExternalObjectStruct.hpp" #include "HybridBaseSpec.hpp" #include "HybridChildSpec.hpp" +#include "HybridChildrenContainerTestViewSpec.hpp" +#include "HybridChildrenTestViewSpec.hpp" #include "HybridPlatformObjectSpec.hpp" #include "HybridRecyclableTestViewSpec.hpp" #include "HybridTestObjectSwiftKotlinSpec.hpp" @@ -287,6 +297,39 @@ namespace margelo::nitro::test::bridge::swift { return Result>::withError(error); } + // pragma MARK: std::shared_ptr + /** + * Specialized version of `std::shared_ptr`. + */ + using std__shared_ptr_HybridChildrenContainerTestViewSpec_ = std::shared_ptr; + std::shared_ptr create_std__shared_ptr_HybridChildrenContainerTestViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept; + void* NON_NULL get_std__shared_ptr_HybridChildrenContainerTestViewSpec_(std__shared_ptr_HybridChildrenContainerTestViewSpec_ cppType); + + // pragma MARK: std::weak_ptr + using std__weak_ptr_HybridChildrenContainerTestViewSpec_ = std::weak_ptr; + inline std__weak_ptr_HybridChildrenContainerTestViewSpec_ weakify_std__shared_ptr_HybridChildrenContainerTestViewSpec_(const std::shared_ptr& strong) noexcept { return strong; } + + // pragma MARK: Result + using Result_double_ = Result; + inline Result_double_ create_Result_double_(double value) noexcept { + return Result::withValue(std::move(value)); + } + inline Result_double_ create_Result_double_(const std::exception_ptr& error) noexcept { + return Result::withError(error); + } + + // pragma MARK: std::shared_ptr + /** + * Specialized version of `std::shared_ptr`. + */ + using std__shared_ptr_HybridChildrenTestViewSpec_ = std::shared_ptr; + std::shared_ptr create_std__shared_ptr_HybridChildrenTestViewSpec_(void* NON_NULL swiftUnsafePointer) noexcept; + void* NON_NULL get_std__shared_ptr_HybridChildrenTestViewSpec_(std__shared_ptr_HybridChildrenTestViewSpec_ cppType); + + // pragma MARK: std::weak_ptr + using std__weak_ptr_HybridChildrenTestViewSpec_ = std::weak_ptr; + inline std__weak_ptr_HybridChildrenTestViewSpec_ weakify_std__shared_ptr_HybridChildrenTestViewSpec_(const std::shared_ptr& strong) noexcept { return strong; } + // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. @@ -335,15 +378,6 @@ namespace margelo::nitro::test::bridge::swift { using std__weak_ptr_HybridRecyclableTestViewSpec_ = std::weak_ptr; inline std__weak_ptr_HybridRecyclableTestViewSpec_ weakify_std__shared_ptr_HybridRecyclableTestViewSpec_(const std::shared_ptr& strong) noexcept { return strong; } - // pragma MARK: Result - using Result_double_ = Result; - inline Result_double_ create_Result_double_(double value) noexcept { - return Result::withValue(std::move(value)); - } - inline Result_double_ create_Result_double_(const std::exception_ptr& error) noexcept { - return Result::withError(error); - } - // pragma MARK: std::shared_ptr /** * Specialized version of `std::shared_ptr`. 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 7adf54d7f0..0f30514679 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 @@ -20,6 +20,10 @@ namespace margelo::nitro::test { enum class HardwareBufferFormat; } namespace margelo::nitro::test { class HybridBaseSpec; } // Forward declaration of `HybridChildSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridChildSpec; } +// Forward declaration of `HybridChildrenContainerTestViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridChildrenContainerTestViewSpec; } +// Forward declaration of `HybridChildrenTestViewSpec` to properly resolve imports. +namespace margelo::nitro::test { class HybridChildrenTestViewSpec; } // Forward declaration of `HybridPlatformObjectSpec` to properly resolve imports. namespace margelo::nitro::test { class HybridPlatformObjectSpec; } // Forward declaration of `HybridRecyclableTestViewSpec` to properly resolve imports. @@ -64,6 +68,8 @@ namespace margelo::nitro::test { struct WrappedJsStruct; } #include "HardwareBufferFormat.hpp" #include "HybridBaseSpec.hpp" #include "HybridChildSpec.hpp" +#include "HybridChildrenContainerTestViewSpec.hpp" +#include "HybridChildrenTestViewSpec.hpp" #include "HybridPlatformObjectSpec.hpp" #include "HybridRecyclableTestViewSpec.hpp" #include "HybridTestObjectSwiftKotlinSpec.hpp" @@ -111,6 +117,10 @@ 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 `HybridChildrenContainerTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenContainerTestViewSpec_cxx; } +// Forward declaration of `HybridChildrenTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenTestViewSpec_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 66d3e86d70..f64182bb9a 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.mm @@ -16,6 +16,8 @@ #include "HybridChildSpecSwift.hpp" #include "HybridPlatformObjectSpecSwift.hpp" #include "HybridTestViewSpecSwift.hpp" +#include "HybridChildrenTestViewSpecSwift.hpp" +#include "HybridChildrenContainerTestViewSpecSwift.hpp" #include "HybridRecyclableTestViewSpecSwift.hpp" @interface NitroTestAutolinking : NSObject @@ -71,6 +73,20 @@ + (void) load { return hybridObject; } ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ChildrenTestView", + []() -> std::shared_ptr { + std::shared_ptr hybridObject = NitroTest::NitroTestAutolinking::createChildrenTestView(); + return hybridObject; + } + ); + HybridObjectRegistry::registerHybridObjectConstructor( + "ChildrenContainerTestView", + []() -> std::shared_ptr { + std::shared_ptr hybridObject = NitroTest::NitroTestAutolinking::createChildrenContainerTestView(); + return hybridObject; + } + ); HybridObjectRegistry::registerHybridObjectConstructor( "RecyclableTestView", []() -> std::shared_ptr { 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 2dc7e09721..968dbe361a 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/NitroTestAutolinking.swift @@ -72,6 +72,30 @@ public final class NitroTestAutolinking { return HybridTestView.self is any RecyclableView.Type } + public static func createChildrenTestView() -> bridge.std__shared_ptr_HybridChildrenTestViewSpec_ { + let hybridObject = HybridChildrenTestView() + return { () -> bridge.std__shared_ptr_HybridChildrenTestViewSpec_ in + let __cxxWrapped = hybridObject.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }() + } + + public static func isChildrenTestViewRecyclable() -> Bool { + return HybridChildrenTestView.self is any RecyclableView.Type + } + + public static func createChildrenContainerTestView() -> bridge.std__shared_ptr_HybridChildrenContainerTestViewSpec_ { + let hybridObject = HybridChildrenContainerTestView() + return { () -> bridge.std__shared_ptr_HybridChildrenContainerTestViewSpec_ in + let __cxxWrapped = hybridObject.getCxxWrapper() + return __cxxWrapped.getCxxPart() + }() + } + + public static func isChildrenContainerTestViewRecyclable() -> Bool { + return HybridChildrenContainerTestView.self is any RecyclableView.Type + } + public static func createRecyclableTestView() -> bridge.std__shared_ptr_HybridRecyclableTestViewSpec_ { let hybridObject = HybridRecyclableTestView() return { () -> bridge.std__shared_ptr_HybridRecyclableTestViewSpec_ in diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.cpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.cpp new file mode 100644 index 0000000000..c8c1b8bc9e --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.cpp @@ -0,0 +1,11 @@ +/// +/// HybridChildrenContainerTestViewSpecSwift.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenContainerTestViewSpecSwift.hpp" + +namespace margelo::nitro::test { +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.hpp new file mode 100644 index 0000000000..f604564a8f --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenContainerTestViewSpecSwift.hpp @@ -0,0 +1,95 @@ +/// +/// HybridChildrenContainerTestViewSpecSwift.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include "HybridChildrenContainerTestViewSpec.hpp" + +// Forward declaration of `HybridChildrenContainerTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenContainerTestViewSpec_cxx; } + + + + + +#include "NitroTest-Swift-Cxx-Umbrella.hpp" + +namespace margelo::nitro::test { + + /** + * The C++ part of HybridChildrenContainerTestViewSpec_cxx.swift. + * + * HybridChildrenContainerTestViewSpecSwift (C++) accesses HybridChildrenContainerTestViewSpec_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, HybridChildrenContainerTestViewSpec_cxx can directly inherit from the C++ class HybridChildrenContainerTestViewSpec + * to simplify the whole structure and memory management. + */ + class HybridChildrenContainerTestViewSpecSwift: public virtual HybridChildrenContainerTestViewSpec { + public: + // Constructor from a Swift instance + explicit HybridChildrenContainerTestViewSpecSwift(const NitroTest::HybridChildrenContainerTestViewSpec_cxx& swiftPart): + HybridObject(HybridChildrenContainerTestViewSpec::TAG), + _swiftPart(swiftPart) { } + + public: + // Get the Swift part + inline NitroTest::HybridChildrenContainerTestViewSpec_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 bool getIsBlue() noexcept override { + return _swiftPart.isBlue(); + } + inline void setIsBlue(bool isBlue) noexcept override { + _swiftPart.setIsBlue(std::forward(isBlue)); + } + + public: + // Methods + inline double getNativeChildCount() override { + auto __result = _swiftPart.getNativeChildCount(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + inline double getViewChildCount() override { + auto __result = _swiftPart.getViewChildCount(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + + private: + NitroTest::HybridChildrenContainerTestViewSpec_cxx _swiftPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.cpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.cpp new file mode 100644 index 0000000000..14119c36dd --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.cpp @@ -0,0 +1,11 @@ +/// +/// HybridChildrenTestViewSpecSwift.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenTestViewSpecSwift.hpp" + +namespace margelo::nitro::test { +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.hpp b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.hpp new file mode 100644 index 0000000000..cdc0efb730 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/HybridChildrenTestViewSpecSwift.hpp @@ -0,0 +1,87 @@ +/// +/// HybridChildrenTestViewSpecSwift.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include "HybridChildrenTestViewSpec.hpp" + +// Forward declaration of `HybridChildrenTestViewSpec_cxx` to properly resolve imports. +namespace NitroTest { class HybridChildrenTestViewSpec_cxx; } + + + + + +#include "NitroTest-Swift-Cxx-Umbrella.hpp" + +namespace margelo::nitro::test { + + /** + * The C++ part of HybridChildrenTestViewSpec_cxx.swift. + * + * HybridChildrenTestViewSpecSwift (C++) accesses HybridChildrenTestViewSpec_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, HybridChildrenTestViewSpec_cxx can directly inherit from the C++ class HybridChildrenTestViewSpec + * to simplify the whole structure and memory management. + */ + class HybridChildrenTestViewSpecSwift: public virtual HybridChildrenTestViewSpec { + public: + // Constructor from a Swift instance + explicit HybridChildrenTestViewSpecSwift(const NitroTest::HybridChildrenTestViewSpec_cxx& swiftPart): + HybridObject(HybridChildrenTestViewSpec::TAG), + _swiftPart(swiftPart) { } + + public: + // Get the Swift part + inline NitroTest::HybridChildrenTestViewSpec_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 bool getIsBlue() noexcept override { + return _swiftPart.isBlue(); + } + inline void setIsBlue(bool isBlue) noexcept override { + _swiftPart.setIsBlue(std::forward(isBlue)); + } + + public: + // Methods + inline double getNativeChildCount() override { + auto __result = _swiftPart.getNativeChildCount(); + if (__result.hasError()) [[unlikely]] { + std::rethrow_exception(__result.error()); + } + auto __value = std::move(__result.value()); + return __value; + } + + private: + NitroTest::HybridChildrenTestViewSpec_cxx _swiftPart; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenContainerTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenContainerTestViewComponent.mm new file mode 100644 index 0000000000..b3276dfb2f --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenContainerTestViewComponent.mm @@ -0,0 +1,166 @@ +/// +/// HybridChildrenContainerTestViewComponent.mm +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#import "HybridChildrenContainerTestViewComponent.hpp" +#import +#import +#import +#import +#import +#import +#import + +#import "HybridChildrenContainerTestViewSpecSwift.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 "ChildrenContainerTestView" HybridView. + */ +@interface HybridChildrenContainerTestViewComponent: RCTViewComponentView ++ (BOOL)shouldBeRecycled; +@end + +@implementation HybridChildrenContainerTestViewComponent { + std::shared_ptr _hybridView; + UIView* _childrenContainer; + BOOL _didDropView; +} + ++ (void) load { + [super load]; + [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:[HybridChildrenContainerTestViewComponent class]]; +} + ++ (react::ComponentDescriptorProvider) componentDescriptorProvider { + return react::concreteComponentDescriptorProvider(); +} + +- (instancetype) init { + if (self = [super init]) { + _props = HybridChildrenContainerTestViewShadowNode::defaultSharedProps(); + std::shared_ptr hybridView = NitroTest::NitroTestAutolinking::createChildrenContainerTestView(); + _hybridView = std::dynamic_pointer_cast(hybridView); + [self updateView]; + } + return self; +} + +- (void) updateView { + // 1. Get Swift part + NitroTest::HybridChildrenContainerTestViewSpec_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]; + + // 4. Get the UIView* React children are mounted into + void* containerUnsafe = swiftPart.getChildrenContainer(); + _childrenContainer = (__bridge_transfer UIView*) containerUnsafe; +} + +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer mountChildComponentView:childComponentView index:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer unmountChildComponentView:childComponentView index:index]; +} + +- (void) updateLayoutMetrics:(const react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Yoga positions each child relative to this component's border box, so the + // Nitro View has to span the full bounds. `RCTViewComponentView` would + // otherwise inset it by border + padding and double-apply that to every child. + self.contentView.frame = self.bounds; +} + +- (void) notifyOnDropView { + // A recycled component can later be invalidated. Notify only once per mount. + if (_didDropView) { + return; + } + NitroTest::HybridChildrenContainerTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.onDropView(); + _didDropView = YES; +} + +- (void) updateProps:(const std::shared_ptr&)props + oldProps:(const std::shared_ptr&)oldProps { + // A props update marks a newly mounted or still-active component. + _didDropView = NO; + + // 1. Downcast props + const auto& newViewProps = *std::static_pointer_cast(props); + const auto* oldViewProps = static_cast(oldProps.get()); + NitroTest::HybridChildrenContainerTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + + // 2. Update only props that differ from the previous Props snapshot. + const bool hasTransactionPropChanges = oldViewProps == nullptr + ? newViewProps.hasAnyProvidedProps() + : !newViewProps.hasSameProps(*oldViewProps); + if (hasTransactionPropChanges) { + swiftPart.beforeUpdate(); + + // isBlue: boolean + if (oldViewProps == nullptr + ? newViewProps.isBlue.isProvided() + : !newViewProps.isBlue.hasSameValue(oldViewProps->isBlue)) { + swiftPart.setIsBlue(newViewProps.isBlue.get()); + } + + // Update hybridRef if it changed + if (oldViewProps == nullptr + ? newViewProps.hybridRef.isProvided() + : !newViewProps.hybridRef.hasSameValue(oldViewProps->hybridRef)) { + // hybridRef changed - call it with new this + const auto& maybeFunc = newViewProps.hybridRef.get(); + if (maybeFunc.has_value()) { + maybeFunc.value()(_hybridView); + } + } + + swiftPart.afterUpdate(); + } + + // 3. Continue in base class + [super updateProps:props oldProps:oldProps]; +} + ++ (BOOL)shouldBeRecycled { + return NitroTest::NitroTestAutolinking::isChildrenContainerTestViewRecyclable(); +} + +- (void)prepareForRecycle { + [self notifyOnDropView]; + [super prepareForRecycle]; + NitroTest::HybridChildrenContainerTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.maybePrepareForRecycle(); +} + +#ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE +- (void)invalidate { + [self notifyOnDropView]; + [super invalidate]; +} +#endif + +@end diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenTestViewComponent.mm b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenTestViewComponent.mm new file mode 100644 index 0000000000..f01d570ba4 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/c++/views/HybridChildrenTestViewComponent.mm @@ -0,0 +1,166 @@ +/// +/// HybridChildrenTestViewComponent.mm +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#import "HybridChildrenTestViewComponent.hpp" +#import +#import +#import +#import +#import +#import +#import + +#import "HybridChildrenTestViewSpecSwift.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 "ChildrenTestView" HybridView. + */ +@interface HybridChildrenTestViewComponent: RCTViewComponentView ++ (BOOL)shouldBeRecycled; +@end + +@implementation HybridChildrenTestViewComponent { + std::shared_ptr _hybridView; + UIView* _childrenContainer; + BOOL _didDropView; +} + ++ (void) load { + [super load]; + [RCTComponentViewFactory.currentComponentViewFactory registerComponentViewClass:[HybridChildrenTestViewComponent class]]; +} + ++ (react::ComponentDescriptorProvider) componentDescriptorProvider { + return react::concreteComponentDescriptorProvider(); +} + +- (instancetype) init { + if (self = [super init]) { + _props = HybridChildrenTestViewShadowNode::defaultSharedProps(); + std::shared_ptr hybridView = NitroTest::NitroTestAutolinking::createChildrenTestView(); + _hybridView = std::dynamic_pointer_cast(hybridView); + [self updateView]; + } + return self; +} + +- (void) updateView { + // 1. Get Swift part + NitroTest::HybridChildrenTestViewSpec_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]; + + // 4. Get the UIView* React children are mounted into + void* containerUnsafe = swiftPart.getChildrenContainer(); + _childrenContainer = (__bridge_transfer UIView*) containerUnsafe; +} + +- (void) mountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer mountChildComponentView:childComponentView index:index]; +} + +- (void) unmountChildComponentView:(UIView*)childComponentView index:(NSInteger)index { + [_childrenContainer unmountChildComponentView:childComponentView index:index]; +} + +- (void) updateLayoutMetrics:(const react::LayoutMetrics&)layoutMetrics + oldLayoutMetrics:(const react::LayoutMetrics&)oldLayoutMetrics { + [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics]; + // Yoga positions each child relative to this component's border box, so the + // Nitro View has to span the full bounds. `RCTViewComponentView` would + // otherwise inset it by border + padding and double-apply that to every child. + self.contentView.frame = self.bounds; +} + +- (void) notifyOnDropView { + // A recycled component can later be invalidated. Notify only once per mount. + if (_didDropView) { + return; + } + NitroTest::HybridChildrenTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.onDropView(); + _didDropView = YES; +} + +- (void) updateProps:(const std::shared_ptr&)props + oldProps:(const std::shared_ptr&)oldProps { + // A props update marks a newly mounted or still-active component. + _didDropView = NO; + + // 1. Downcast props + const auto& newViewProps = *std::static_pointer_cast(props); + const auto* oldViewProps = static_cast(oldProps.get()); + NitroTest::HybridChildrenTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + + // 2. Update only props that differ from the previous Props snapshot. + const bool hasTransactionPropChanges = oldViewProps == nullptr + ? newViewProps.hasAnyProvidedProps() + : !newViewProps.hasSameProps(*oldViewProps); + if (hasTransactionPropChanges) { + swiftPart.beforeUpdate(); + + // isBlue: boolean + if (oldViewProps == nullptr + ? newViewProps.isBlue.isProvided() + : !newViewProps.isBlue.hasSameValue(oldViewProps->isBlue)) { + swiftPart.setIsBlue(newViewProps.isBlue.get()); + } + + // Update hybridRef if it changed + if (oldViewProps == nullptr + ? newViewProps.hybridRef.isProvided() + : !newViewProps.hybridRef.hasSameValue(oldViewProps->hybridRef)) { + // hybridRef changed - call it with new this + const auto& maybeFunc = newViewProps.hybridRef.get(); + if (maybeFunc.has_value()) { + maybeFunc.value()(_hybridView); + } + } + + swiftPart.afterUpdate(); + } + + // 3. Continue in base class + [super updateProps:props oldProps:oldProps]; +} + ++ (BOOL)shouldBeRecycled { + return NitroTest::NitroTestAutolinking::isChildrenTestViewRecyclable(); +} + +- (void)prepareForRecycle { + [self notifyOnDropView]; + [super prepareForRecycle]; + NitroTest::HybridChildrenTestViewSpec_cxx& swiftPart = _hybridView->getSwiftPart(); + swiftPart.maybePrepareForRecycle(); +} + +#ifdef ENABLE_RCT_COMPONENT_VIEW_INVALIDATE +- (void)invalidate { + [self notifyOnDropView]; + [super invalidate]; +} +#endif + +@end diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec.swift new file mode 100644 index 0000000000..e4d4acd747 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec.swift @@ -0,0 +1,74 @@ +/// +/// HybridChildrenContainerTestViewSpec.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules +import UIKit + +/// See ``HybridChildrenContainerTestViewSpec`` +public protocol HybridChildrenContainerTestViewSpec_protocol: HybridObject, HybridView { + // Properties + /** + * The ``UIView`` React children are mounted into. + * + * Defaults to ``view``. Override this when the children have to live inside + * a sub-view - e.g. ``UIVisualEffectView/contentView``, which is where a + * blur view expects its content. The sub-view has to cover the same area as + * ``view``, otherwise React Native's layout lands in the wrong place. + * + * Like ``view``, this value should not change during the lifetime of this + * ``HybridView``. + */ + var childrenContainer: UIView { get } + var isBlue: Bool { get set } + + // Methods + func getNativeChildCount() throws -> Double + func getViewChildCount() throws -> Double +} + +public extension HybridChildrenContainerTestViewSpec_protocol { + /// Default implementation of ``childrenContainer`` + var childrenContainer: UIView { + return self.view + } + + /// Default implementation of ``HybridObject.toString`` + func toString() -> String { + return "[HybridObject ChildrenContainerTestView]" + } +} + +/// See ``HybridChildrenContainerTestViewSpec`` +open class HybridChildrenContainerTestViewSpec_base { + private weak var cxxWrapper: HybridChildrenContainerTestViewSpec_cxx? = nil + public init() { } + public func getCxxWrapper() -> HybridChildrenContainerTestViewSpec_cxx { + #if DEBUG + guard self is any HybridChildrenContainerTestViewSpec else { + fatalError("`self` is not a `HybridChildrenContainerTestViewSpec`! Did you accidentally inherit from `HybridChildrenContainerTestViewSpec_base` instead of `HybridChildrenContainerTestViewSpec`?") + } + #endif + if let cxxWrapper = self.cxxWrapper { + return cxxWrapper + } else { + let cxxWrapper = HybridChildrenContainerTestViewSpec_cxx(self as! any HybridChildrenContainerTestViewSpec) + self.cxxWrapper = cxxWrapper + return cxxWrapper + } + } +} + +/** + * A Swift base-protocol representing the ChildrenContainerTestView HybridObject. + * Implement this protocol to create Swift-based instances of ChildrenContainerTestView. + * ```swift + * class HybridChildrenContainerTestView : HybridChildrenContainerTestViewSpec { + * // ... + * } + * ``` + */ +public typealias HybridChildrenContainerTestViewSpec = HybridChildrenContainerTestViewSpec_protocol & HybridChildrenContainerTestViewSpec_base diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec_cxx.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec_cxx.swift new file mode 100644 index 0000000000..c33322ef86 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenContainerTestViewSpec_cxx.swift @@ -0,0 +1,184 @@ +/// +/// HybridChildrenContainerTestViewSpec_cxx.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * A class implementation that bridges HybridChildrenContainerTestViewSpec 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 HybridChildrenContainerTestViewSpec_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 `HybridChildrenContainerTestViewSpec` Swift protocol. + */ + private let __implementation: any HybridChildrenContainerTestViewSpec + + /** + * Holds a weak pointer to the C++ class that wraps the Swift class. + */ + private var __cxxPart: bridge.std__weak_ptr_HybridChildrenContainerTestViewSpec_ + + /** + * Create a new `HybridChildrenContainerTestViewSpec_cxx` that wraps the given `HybridChildrenContainerTestViewSpec`. + * All properties and methods bridge to C++ types. + */ + public init(_ implementation: any HybridChildrenContainerTestViewSpec) { + self.__implementation = implementation + self.__cxxPart = .init() + /* no base class */ + } + + /** + * Get the actual `HybridChildrenContainerTestViewSpec` instance this class wraps. + */ + @inline(__always) + public func getHybridChildrenContainerTestViewSpec() -> any HybridChildrenContainerTestViewSpec { + 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 `HybridChildrenContainerTestViewSpec_cxx`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridChildrenContainerTestViewSpec_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_HybridChildrenContainerTestViewSpec_ { + let cachedCxxPart = self.__cxxPart.lock() + if cachedCxxPart.use_count() > 0 { + return cachedCxxPart + } else { + let newCxxPart = bridge.create_std__shared_ptr_HybridChildrenContainerTestViewSpec_(self.toUnsafe()) + __cxxPart = bridge.weakify_std__shared_ptr_HybridChildrenContainerTestViewSpec_(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: HybridChildrenContainerTestViewSpec_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 isBlue: Bool { + @inline(__always) + get { + return self.__implementation.isBlue + } + @inline(__always) + set { + self.__implementation.isBlue = newValue + } + } + + // Methods + @inline(__always) + public final func getNativeChildCount() -> bridge.Result_double_ { + do { + let __result = try self.__implementation.getNativeChildCount() + let __resultCpp = __result + return bridge.create_Result_double_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_double_(__exceptionPtr) + } + } + + @inline(__always) + public final func getViewChildCount() -> bridge.Result_double_ { + do { + let __result = try self.__implementation.getViewChildCount() + let __resultCpp = __result + return bridge.create_Result_double_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_double_(__exceptionPtr) + } + } + + 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() + } + + public final func getChildrenContainer() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(__implementation.childrenContainer).toOpaque() + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec.swift new file mode 100644 index 0000000000..5b27f737d1 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec.swift @@ -0,0 +1,73 @@ +/// +/// HybridChildrenTestViewSpec.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules +import UIKit + +/// See ``HybridChildrenTestViewSpec`` +public protocol HybridChildrenTestViewSpec_protocol: HybridObject, HybridView { + // Properties + /** + * The ``UIView`` React children are mounted into. + * + * Defaults to ``view``. Override this when the children have to live inside + * a sub-view - e.g. ``UIVisualEffectView/contentView``, which is where a + * blur view expects its content. The sub-view has to cover the same area as + * ``view``, otherwise React Native's layout lands in the wrong place. + * + * Like ``view``, this value should not change during the lifetime of this + * ``HybridView``. + */ + var childrenContainer: UIView { get } + var isBlue: Bool { get set } + + // Methods + func getNativeChildCount() throws -> Double +} + +public extension HybridChildrenTestViewSpec_protocol { + /// Default implementation of ``childrenContainer`` + var childrenContainer: UIView { + return self.view + } + + /// Default implementation of ``HybridObject.toString`` + func toString() -> String { + return "[HybridObject ChildrenTestView]" + } +} + +/// See ``HybridChildrenTestViewSpec`` +open class HybridChildrenTestViewSpec_base { + private weak var cxxWrapper: HybridChildrenTestViewSpec_cxx? = nil + public init() { } + public func getCxxWrapper() -> HybridChildrenTestViewSpec_cxx { + #if DEBUG + guard self is any HybridChildrenTestViewSpec else { + fatalError("`self` is not a `HybridChildrenTestViewSpec`! Did you accidentally inherit from `HybridChildrenTestViewSpec_base` instead of `HybridChildrenTestViewSpec`?") + } + #endif + if let cxxWrapper = self.cxxWrapper { + return cxxWrapper + } else { + let cxxWrapper = HybridChildrenTestViewSpec_cxx(self as! any HybridChildrenTestViewSpec) + self.cxxWrapper = cxxWrapper + return cxxWrapper + } + } +} + +/** + * A Swift base-protocol representing the ChildrenTestView HybridObject. + * Implement this protocol to create Swift-based instances of ChildrenTestView. + * ```swift + * class HybridChildrenTestView : HybridChildrenTestViewSpec { + * // ... + * } + * ``` + */ +public typealias HybridChildrenTestViewSpec = HybridChildrenTestViewSpec_protocol & HybridChildrenTestViewSpec_base diff --git a/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec_cxx.swift b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec_cxx.swift new file mode 100644 index 0000000000..eb2b8dd0a9 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/ios/swift/HybridChildrenTestViewSpec_cxx.swift @@ -0,0 +1,172 @@ +/// +/// HybridChildrenTestViewSpec_cxx.swift +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +import NitroModules + +/** + * A class implementation that bridges HybridChildrenTestViewSpec 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 HybridChildrenTestViewSpec_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 `HybridChildrenTestViewSpec` Swift protocol. + */ + private let __implementation: any HybridChildrenTestViewSpec + + /** + * Holds a weak pointer to the C++ class that wraps the Swift class. + */ + private var __cxxPart: bridge.std__weak_ptr_HybridChildrenTestViewSpec_ + + /** + * Create a new `HybridChildrenTestViewSpec_cxx` that wraps the given `HybridChildrenTestViewSpec`. + * All properties and methods bridge to C++ types. + */ + public init(_ implementation: any HybridChildrenTestViewSpec) { + self.__implementation = implementation + self.__cxxPart = .init() + /* no base class */ + } + + /** + * Get the actual `HybridChildrenTestViewSpec` instance this class wraps. + */ + @inline(__always) + public func getHybridChildrenTestViewSpec() -> any HybridChildrenTestViewSpec { + 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 `HybridChildrenTestViewSpec_cxx`. + * The pointer has to be a retained opaque `Unmanaged`. + * This removes one strong reference from the object! + */ + public class func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> HybridChildrenTestViewSpec_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_HybridChildrenTestViewSpec_ { + let cachedCxxPart = self.__cxxPart.lock() + if cachedCxxPart.use_count() > 0 { + return cachedCxxPart + } else { + let newCxxPart = bridge.create_std__shared_ptr_HybridChildrenTestViewSpec_(self.toUnsafe()) + __cxxPart = bridge.weakify_std__shared_ptr_HybridChildrenTestViewSpec_(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: HybridChildrenTestViewSpec_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 isBlue: Bool { + @inline(__always) + get { + return self.__implementation.isBlue + } + @inline(__always) + set { + self.__implementation.isBlue = newValue + } + } + + // Methods + @inline(__always) + public final func getNativeChildCount() -> bridge.Result_double_ { + do { + let __result = try self.__implementation.getNativeChildCount() + let __resultCpp = __result + return bridge.create_Result_double_(__resultCpp) + } catch (let __error) { + let __exceptionPtr = __error.toCpp() + return bridge.create_Result_double_(__exceptionPtr) + } + } + + 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() + } + + public final func getChildrenContainer() -> UnsafeMutableRawPointer { + return Unmanaged.passRetained(__implementation.childrenContainer).toOpaque() + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.cpp new file mode 100644 index 0000000000..dd806acac2 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.cpp @@ -0,0 +1,24 @@ +/// +/// HybridChildrenContainerTestViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenContainerTestViewSpec.hpp" + +namespace margelo::nitro::test { + + void HybridChildrenContainerTestViewSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridGetter("isBlue", &HybridChildrenContainerTestViewSpec::getIsBlue); + prototype.registerHybridSetter("isBlue", &HybridChildrenContainerTestViewSpec::setIsBlue); + prototype.registerHybridMethod("getNativeChildCount", &HybridChildrenContainerTestViewSpec::getNativeChildCount); + prototype.registerHybridMethod("getViewChildCount", &HybridChildrenContainerTestViewSpec::getViewChildCount); + }); + } + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.hpp new file mode 100644 index 0000000000..8cf3942ba4 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenContainerTestViewSpec.hpp @@ -0,0 +1,64 @@ +/// +/// HybridChildrenContainerTestViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 + + + + + +namespace margelo::nitro::test { + + using namespace margelo::nitro; + + /** + * An abstract base class for `ChildrenContainerTestView` + * Inherit this class to create instances of `HybridChildrenContainerTestViewSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridChildrenContainerTestView: public HybridChildrenContainerTestViewSpec { + * public: + * HybridChildrenContainerTestView(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridChildrenContainerTestViewSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridChildrenContainerTestViewSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridChildrenContainerTestViewSpec() override = default; + + public: + // Properties + virtual bool getIsBlue() = 0; + virtual void setIsBlue(bool isBlue) = 0; + + public: + // Methods + virtual double getNativeChildCount() = 0; + virtual double getViewChildCount() = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "ChildrenContainerTestView"; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.cpp new file mode 100644 index 0000000000..959231e66d --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.cpp @@ -0,0 +1,23 @@ +/// +/// HybridChildrenTestViewSpec.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenTestViewSpec.hpp" + +namespace margelo::nitro::test { + + void HybridChildrenTestViewSpec::loadHybridMethods() { + // load base methods/properties + HybridObject::loadHybridMethods(); + // load custom methods/properties + registerHybrids(this, [](Prototype& prototype) { + prototype.registerHybridGetter("isBlue", &HybridChildrenTestViewSpec::getIsBlue); + prototype.registerHybridSetter("isBlue", &HybridChildrenTestViewSpec::setIsBlue); + prototype.registerHybridMethod("getNativeChildCount", &HybridChildrenTestViewSpec::getNativeChildCount); + }); + } + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.hpp new file mode 100644 index 0000000000..1d4ded741a --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/HybridChildrenTestViewSpec.hpp @@ -0,0 +1,63 @@ +/// +/// HybridChildrenTestViewSpec.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/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 + + + + + +namespace margelo::nitro::test { + + using namespace margelo::nitro; + + /** + * An abstract base class for `ChildrenTestView` + * Inherit this class to create instances of `HybridChildrenTestViewSpec` in C++. + * You must explicitly call `HybridObject`'s constructor yourself, because it is virtual. + * @example + * ```cpp + * class HybridChildrenTestView: public HybridChildrenTestViewSpec { + * public: + * HybridChildrenTestView(...): HybridObject(TAG) { ... } + * // ... + * }; + * ``` + */ + class HybridChildrenTestViewSpec: public virtual HybridObject { + public: + // Constructor + explicit HybridChildrenTestViewSpec(): HybridObject(TAG) { } + + // Destructor + ~HybridChildrenTestViewSpec() override = default; + + public: + // Properties + virtual bool getIsBlue() = 0; + virtual void setIsBlue(bool isBlue) = 0; + + public: + // Methods + virtual double getNativeChildCount() = 0; + + protected: + // Hybrid Setup + void loadHybridMethods() override; + + protected: + // Tag for logging + static constexpr auto TAG = "ChildrenTestView"; + }; + +} // namespace margelo::nitro::test diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.cpp new file mode 100644 index 0000000000..c7196f2e49 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.cpp @@ -0,0 +1,34 @@ +/// +/// HybridChildrenContainerTestViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenContainerTestViewComponent.hpp" + +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + extern const char HybridChildrenContainerTestViewComponentName[] = "ChildrenContainerTestView"; + + HybridChildrenContainerTestViewProps::HybridChildrenContainerTestViewProps(const react::PropsParserContext& context, + const HybridChildrenContainerTestViewProps& sourceProps, + const react::RawProps& rawProps): + react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + isBlue(nitro::ReactProp::fromRawValue("ChildrenContainerTestView", "isBlue", rawProps, sourceProps.isBlue)), + hybridRef(nitro::ReactProp& /* ref */)>>>::fromRawValue("ChildrenContainerTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + + bool HybridChildrenContainerTestViewProps::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + case hashString("isBlue"): return true; + case hashString("hybridRef"): return true; + default: return false; + } + } + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.hpp new file mode 100644 index 0000000000..d0be0b5e22 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenContainerTestViewComponent.hpp @@ -0,0 +1,85 @@ +/// +/// HybridChildrenContainerTestViewComponent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "HybridChildrenContainerTestViewSpec.hpp" +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + /** + * The name of the actual native View. + */ + extern const char HybridChildrenContainerTestViewComponentName[]; + + /** + * Props for the "ChildrenContainerTestView" View. + */ + class HybridChildrenContainerTestViewProps final: public react::ViewProps { + public: + HybridChildrenContainerTestViewProps() = default; + HybridChildrenContainerTestViewProps(const react::PropsParserContext& context, + const HybridChildrenContainerTestViewProps& sourceProps, + const react::RawProps& rawProps); + + public: + nitro::ReactProp isBlue; + nitro::ReactProp& /* ref */)>>> hybridRef; + + [[nodiscard]] + bool hasSameProps(const HybridChildrenContainerTestViewProps& other) const noexcept { + return isBlue.hasSameValue(other.isBlue) && + hybridRef.hasSameValue(other.hybridRef); + } + + [[nodiscard]] + bool hasAnyProvidedProps() const noexcept { + return isBlue.isProvided() || + hybridRef.isProvided(); + } + + private: + static bool filterObjectKeys(const std::string& propName); + }; + + /** + * State for the "ChildrenContainerTestView" View. + */ + using HybridChildrenContainerTestViewState = nitro::ViewPropsHolderState; + + /** + * The Shadow Node for the "ChildrenContainerTestView" View. + */ + using HybridChildrenContainerTestViewShadowNode = react::ConcreteViewShadowNode; + + /** + * The Component Descriptor for the "ChildrenContainerTestView" View. + */ + using HybridChildrenContainerTestViewComponentDescriptor = nitro::ViewComponentDescriptor; + + /* The actual view for "ChildrenContainerTestView" needs to be implemented in platform-specific code. */ + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.cpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.cpp new file mode 100644 index 0000000000..7e9b52f94a --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.cpp @@ -0,0 +1,34 @@ +/// +/// HybridChildrenTestViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#include "HybridChildrenTestViewComponent.hpp" + +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + extern const char HybridChildrenTestViewComponentName[] = "ChildrenTestView"; + + HybridChildrenTestViewProps::HybridChildrenTestViewProps(const react::PropsParserContext& context, + const HybridChildrenTestViewProps& sourceProps, + const react::RawProps& rawProps): + react::ViewProps(context, sourceProps, rawProps, filterObjectKeys), + isBlue(nitro::ReactProp::fromRawValue("ChildrenTestView", "isBlue", rawProps, sourceProps.isBlue)), + hybridRef(nitro::ReactProp& /* ref */)>>>::fromRawValue("ChildrenTestView", "hybridRef", rawProps, sourceProps.hybridRef)) { } + + bool HybridChildrenTestViewProps::filterObjectKeys(const std::string& propName) { + switch (hashString(propName)) { + case hashString("isBlue"): return true; + case hashString("hybridRef"): return true; + default: return false; + } + } + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.hpp new file mode 100644 index 0000000000..97854ca63e --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridChildrenTestViewComponent.hpp @@ -0,0 +1,85 @@ +/// +/// HybridChildrenTestViewComponent.hpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/margelo/nitro +/// Copyright © Marc Rousavy @ Margelo +/// + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "HybridChildrenTestViewSpec.hpp" +#include +#include + +namespace margelo::nitro::test::views { + + using namespace facebook; + + /** + * The name of the actual native View. + */ + extern const char HybridChildrenTestViewComponentName[]; + + /** + * Props for the "ChildrenTestView" View. + */ + class HybridChildrenTestViewProps final: public react::ViewProps { + public: + HybridChildrenTestViewProps() = default; + HybridChildrenTestViewProps(const react::PropsParserContext& context, + const HybridChildrenTestViewProps& sourceProps, + const react::RawProps& rawProps); + + public: + nitro::ReactProp isBlue; + nitro::ReactProp& /* ref */)>>> hybridRef; + + [[nodiscard]] + bool hasSameProps(const HybridChildrenTestViewProps& other) const noexcept { + return isBlue.hasSameValue(other.isBlue) && + hybridRef.hasSameValue(other.hybridRef); + } + + [[nodiscard]] + bool hasAnyProvidedProps() const noexcept { + return isBlue.isProvided() || + hybridRef.isProvided(); + } + + private: + static bool filterObjectKeys(const std::string& propName); + }; + + /** + * State for the "ChildrenTestView" View. + */ + using HybridChildrenTestViewState = nitro::ViewPropsHolderState; + + /** + * The Shadow Node for the "ChildrenTestView" View. + */ + using HybridChildrenTestViewShadowNode = react::ConcreteViewShadowNode; + + /** + * The Component Descriptor for the "ChildrenTestView" View. + */ + using HybridChildrenTestViewComponentDescriptor = nitro::ViewComponentDescriptor; + + /* The actual view for "ChildrenTestView" needs to be implemented in platform-specific code. */ + +} // namespace margelo::nitro::test::views diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp index 04b97e118d..43182172c2 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridRecyclableTestViewComponent.hpp @@ -80,7 +80,8 @@ namespace margelo::nitro::test::views { /** * The Component Descriptor for the "RecyclableTestView" View. */ - using HybridRecyclableTestViewComponentDescriptor = nitro::ViewComponentDescriptor; + using HybridRecyclableTestViewComponentDescriptor = nitro::ViewComponentDescriptor; /* The actual view for "RecyclableTestView" needs to be implemented in platform-specific code. */ diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp index 8b8ad1e566..763b8419eb 100644 --- a/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/c++/views/HybridTestViewComponent.hpp @@ -90,7 +90,8 @@ namespace margelo::nitro::test::views { /** * The Component Descriptor for the "TestView" View. */ - using HybridTestViewComponentDescriptor = nitro::ViewComponentDescriptor; + using HybridTestViewComponentDescriptor = nitro::ViewComponentDescriptor; /* The actual view for "TestView" needs to be implemented in platform-specific code. */ diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenContainerTestViewConfig.json b/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenContainerTestViewConfig.json new file mode 100644 index 0000000000..521a525e10 --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenContainerTestViewConfig.json @@ -0,0 +1,10 @@ +{ + "uiViewClassName": "ChildrenContainerTestView", + "supportsRawText": false, + "bubblingEventTypes": {}, + "directEventTypes": {}, + "validAttributes": { + "isBlue": true, + "hybridRef": true + } +} diff --git a/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenTestViewConfig.json b/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenTestViewConfig.json new file mode 100644 index 0000000000..60efcda75f --- /dev/null +++ b/packages/react-native-nitro-test/nitrogen/generated/shared/json/ChildrenTestViewConfig.json @@ -0,0 +1,10 @@ +{ + "uiViewClassName": "ChildrenTestView", + "supportsRawText": false, + "bubblingEventTypes": {}, + "directEventTypes": {}, + "validAttributes": { + "isBlue": true, + "hybridRef": true + } +} diff --git a/packages/react-native-nitro-test/src/__tests__/index.test.tsx b/packages/react-native-nitro-test/src/__tests__/index.test.tsx index d41123fcbe..7219cb8ffc 100644 --- a/packages/react-native-nitro-test/src/__tests__/index.test.tsx +++ b/packages/react-native-nitro-test/src/__tests__/index.test.tsx @@ -1 +1,64 @@ -it.todo('write a test') +import * as React from 'react' +import { Text } from 'react-native' + +// Type-level regression tests for https://github.com/margelo/nitro/issues/873. +// +// They are verified by `tsc --noEmit` (`bun typecheck`) - a `@ts-expect-error` +// that stops erroring fails the type check. Nothing here is rendered: the views +// are declared type-only, because importing the real ones would pull in the +// native `NitroModules` TurboModule, which does not exist under jest. +type NitroTest = typeof import('../index') +declare const ChildrenTestView: NitroTest['ChildrenTestView'] +declare const TestView: NitroTest['TestView'] + +function acceptsChildren(): React.ReactElement { + return ( + + Hello + + ) +} + +function acceptsNoChildren(): React.ReactElement { + return +} + +function rejectsChildrenOnALeafView(): React.ReactElement { + return ( + {} }} + > + {/* @ts-expect-error - `TestView` is a leaf View and cannot render children. */} + Hello + + ) +} + +function doesNotExposeChildrenOnTheHybridObject(): React.ReactElement { + return ( + { + // `children` is a React concept - it is not a member of the Hybrid Object. + // @ts-expect-error - `children` does not exist on the Hybrid Object. + const children = ref.children + expect(children).toBeUndefined() + ref.getNativeChildCount() + }, + }} + /> + ) +} + +it('type-checks Nitro View children', () => { + expect([ + acceptsChildren, + acceptsNoChildren, + rejectsChildrenOnALeafView, + doesNotExposeChildrenOnTheHybridObject, + ]).toHaveLength(4) +}) diff --git a/packages/react-native-nitro-test/src/index.ts b/packages/react-native-nitro-test/src/index.ts index 6d4b6070d4..b60e231ef2 100644 --- a/packages/react-native-nitro-test/src/index.ts +++ b/packages/react-native-nitro-test/src/index.ts @@ -10,6 +10,8 @@ import type { Child } from './specs/Child.nitro' // Export all Hybrid Object types export * from './specs/Base.nitro' export * from './specs/Child.nitro' +export * from './specs/ChildrenContainerTestView.nitro' +export * from './specs/ChildrenTestView.nitro' export * from './specs/PlatformObject.nitro' export * from './specs/TestObject.nitro' export * from './specs/TestView.nitro' @@ -28,6 +30,14 @@ export const HybridPlatformObject = // Export View (+ its ref type) export { TestView, type TestViewRef } from './views/TestView' +export { + ChildrenTestView, + type ChildrenTestViewRef, +} from './views/ChildrenTestView' +export { + ChildrenContainerTestView, + type ChildrenContainerTestViewRef, +} from './views/ChildrenContainerTestView' export { RecyclableTestView, type RecyclableTestViewRef, diff --git a/packages/react-native-nitro-test/src/specs/ChildrenContainerTestView.nitro.ts b/packages/react-native-nitro-test/src/specs/ChildrenContainerTestView.nitro.ts new file mode 100644 index 0000000000..38a1f8fb91 --- /dev/null +++ b/packages/react-native-nitro-test/src/specs/ChildrenContainerTestView.nitro.ts @@ -0,0 +1,27 @@ +import type { + HybridView, + HybridViewChildren, + HybridViewMethods, + HybridViewProps, +} from 'react-native-nitro-modules' + +export interface ChildrenContainerTestViewProps extends HybridViewProps { + children?: HybridViewChildren + isBlue: boolean +} +export interface ChildrenContainerTestViewMethods extends HybridViewMethods { + /** + * The number of child Views mounted into this View's `childrenContainer`, + * which is a sub-view of `view` rather than `view` itself. + */ + getNativeChildCount(): number + /** + * The number of children `view` itself holds - always 1, the container. + */ + getViewChildCount(): number +} + +export type ChildrenContainerTestView = HybridView< + ChildrenContainerTestViewProps, + ChildrenContainerTestViewMethods +> diff --git a/packages/react-native-nitro-test/src/specs/ChildrenTestView.nitro.ts b/packages/react-native-nitro-test/src/specs/ChildrenTestView.nitro.ts new file mode 100644 index 0000000000..ad38ca6725 --- /dev/null +++ b/packages/react-native-nitro-test/src/specs/ChildrenTestView.nitro.ts @@ -0,0 +1,27 @@ +import type { + HybridView, + HybridViewChildren, + HybridViewMethods, + HybridViewProps, +} from 'react-native-nitro-modules' + +export interface ChildrenTestViewProps extends HybridViewProps { + /** + * Opts this View into rendering React children - it is a marker, not a + * native prop. + */ + children?: HybridViewChildren + isBlue: boolean +} +export interface ChildrenTestViewMethods extends HybridViewMethods { + /** + * The number of child Views React Native mounted into this View's native + * container. + */ + getNativeChildCount(): number +} + +export type ChildrenTestView = HybridView< + ChildrenTestViewProps, + ChildrenTestViewMethods +> diff --git a/packages/react-native-nitro-test/src/views/ChildrenContainerTestView.ts b/packages/react-native-nitro-test/src/views/ChildrenContainerTestView.ts new file mode 100644 index 0000000000..ab0896d897 --- /dev/null +++ b/packages/react-native-nitro-test/src/views/ChildrenContainerTestView.ts @@ -0,0 +1,20 @@ +import { getHostComponent, type HybridRef } from 'react-native-nitro-modules' +import ChildrenContainerTestViewConfig from '../../nitrogen/generated/shared/json/ChildrenContainerTestViewConfig.json' +import { + type ChildrenContainerTestViewMethods, + type ChildrenContainerTestViewProps, +} from '../specs/ChildrenContainerTestView.nitro' + +/** + * Represents the HybridView `ChildrenContainerTestView`, which renders React + * children into a sub-view of its `view` via `childrenContainer`. + */ +export const ChildrenContainerTestView = getHostComponent< + ChildrenContainerTestViewProps, + ChildrenContainerTestViewMethods +>('ChildrenContainerTestView', () => ChildrenContainerTestViewConfig) + +export type ChildrenContainerTestViewRef = HybridRef< + ChildrenContainerTestViewProps, + ChildrenContainerTestViewMethods +> diff --git a/packages/react-native-nitro-test/src/views/ChildrenTestView.ts b/packages/react-native-nitro-test/src/views/ChildrenTestView.ts new file mode 100644 index 0000000000..5a2046a6be --- /dev/null +++ b/packages/react-native-nitro-test/src/views/ChildrenTestView.ts @@ -0,0 +1,20 @@ +import { getHostComponent, type HybridRef } from 'react-native-nitro-modules' +import ChildrenTestViewConfig from '../../nitrogen/generated/shared/json/ChildrenTestViewConfig.json' +import { + type ChildrenTestViewMethods, + type ChildrenTestViewProps, +} from '../specs/ChildrenTestView.nitro' + +/** + * Represents the HybridView `ChildrenTestView`, which can be rendered as a + * React Native view, and which renders React children. + */ +export const ChildrenTestView = getHostComponent< + ChildrenTestViewProps, + ChildrenTestViewMethods +>('ChildrenTestView', () => ChildrenTestViewConfig) + +export type ChildrenTestViewRef = HybridRef< + ChildrenTestViewProps, + ChildrenTestViewMethods +>