From cb2f358e47d6e62f4d873e2b75a669dda15e2586 Mon Sep 17 00:00:00 2001 From: Marc Rousavy Date: Wed, 15 Jan 2025 16:37:54 +0100 Subject: [PATCH] Create `.mm` file as well --- .../src/views/ViewComponentShadowNode.ts | 151 ++++++++++++++++++ .../src/views/swift/SwiftHybridViewManager.ts | 80 ++++------ .../android/NitroImage+autolinking.cmake | 1 + .../ios/c++/views/TestViewComponent.mm | 39 +++++ .../shared/c++/views/TestViewComponent.cpp | 34 ++++ .../c++/views/TestViewComponent.hpp | 15 +- 6 files changed, 263 insertions(+), 57 deletions(-) create mode 100644 packages/nitrogen/src/views/ViewComponentShadowNode.ts create mode 100644 packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.mm create mode 100644 packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.cpp rename packages/react-native-nitro-image/nitrogen/generated/{ios => shared}/c++/views/TestViewComponent.hpp (68%) diff --git a/packages/nitrogen/src/views/ViewComponentShadowNode.ts b/packages/nitrogen/src/views/ViewComponentShadowNode.ts new file mode 100644 index 000000000..0e424d815 --- /dev/null +++ b/packages/nitrogen/src/views/ViewComponentShadowNode.ts @@ -0,0 +1,151 @@ +import type { SourceFile } from '../syntax/SourceFile.js' +import type { HybridObjectSpec } from '../syntax/HybridObjectSpec.js' +import { createIndentation } from '../utils.js' +import { createFileMetadataString } from '../syntax/helpers.js' +import { NitroConfig } from '../config/NitroConfig.js' +import { getHybridObjectName } from '../syntax/getHybridObjectName.js' + +interface ViewComponentNames { + propsClassName: `${string}Props` + stateClassName: `${string}State` + nameVariable: `${string}ComponentName` + shadowNodeClassName: `${string}ShadowNode` + descriptorClassName: `${string}ComponentDescriptor` + component: `${string}Component` +} + +export function getViewComponentNames( + spec: HybridObjectSpec +): ViewComponentNames { + const name = getHybridObjectName(spec.name) + return { + propsClassName: `${name.HybridT}Props`, + stateClassName: `${name.HybridT}State`, + nameVariable: `${name.HybridT}ComponentName`, + shadowNodeClassName: `${name.HybridT}ShadowNode`, + descriptorClassName: `${name.HybridT}ComponentDescriptor`, + component: `${spec.name}Component`, + } +} + +export function createViewComponentShadowNodeFiles( + spec: HybridObjectSpec +): SourceFile[] { + if (!spec.isHybridView) { + throw new Error( + `Cannot create View Component ShadowNode code for ${spec.name} - it's not a HybridView!` + ) + } + + const name = getHybridObjectName(spec.name) + const { + propsClassName, + stateClassName, + nameVariable, + shadowNodeClassName, + descriptorClassName, + component, + } = getViewComponentNames(spec) + + const namespace = NitroConfig.getCxxNamespace('c++', 'views') + + // .hpp code + const componentHeaderCode = ` +${createFileMetadataString(`${component}.hpp`)} + +#pragma once + +#include "NitroDefines.hpp" + +#if REACT_NATIVE_VERSION >= 78 + +#include +#include +#include +#include + +namespace ${namespace} { + + using namespace facebook; + + class ${propsClassName}: public react::ViewProps { + public: + explicit ${propsClassName}() = default; + ${propsClassName}(const react::PropsParserContext& context, + ${createIndentation(propsClassName.length)} const ${propsClassName}& sourceProps, + ${createIndentation(propsClassName.length)} const react::RawProps& rawProps); + }; + + class ${stateClassName} { + public: + explicit ${stateClassName}() = default; + }; + + extern const char ${nameVariable}[]; + using ${shadowNodeClassName} = react::ConcreteViewShadowNode<${nameVariable}, ${propsClassName}, react::ViewEventEmitter, ${stateClassName}>; + + class ${descriptorClassName}: public react::ConcreteComponentDescriptor<${shadowNodeClassName}> { + public: + ${descriptorClassName}(const react::ComponentDescriptorParameters& parameters); + }; + + // TODO: Actual RCTViewComponentView goes here... or in Swift? + +} // namespace ${namespace} + +#else + #warning "View Component '${name.HybridT}' will be unavailable in React Native, because it requires React Native 78 or higher." +#endif +`.trim() + + // .cpp code + const ctorIndent = createIndentation(propsClassName.length * 2) + const componentCode = ` +${createFileMetadataString(`${component}.cpp`)} + +#include "${component}.hpp" + +#if REACT_NATIVE_VERSION >= 78 + +namespace ${namespace} { + + ${propsClassName}::${propsClassName}(const react::PropsParserContext& context, + ${ctorIndent} const ${propsClassName}& sourceProps, + ${ctorIndent} const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps) { + if (rawProps.isEmpty()) { + // TODO: idk? Hanno? + return; + } + const RawValue* rawValue = rawProps.at("nativeProp", nullptr, nullptr); + const auto& [runtime, value] = (std::pair)*rawValue; + // TODO: Parse runtime and value + } + + ${nameVariable} = "${name.HybridT}"; + + ${descriptorClassName}::${descriptorClassName}(const react::ComponentDescriptorParameters& parameters) + : ConcreteComponentDescriptor(parameters, + std::make_unique(/* enableJsiParser */ true)) {} + +} // namespace ${namespace} + +#endif +`.trim() + + return [ + { + name: `${component}.hpp`, + content: componentHeaderCode, + language: 'c++', + platform: 'shared', + subdirectory: ['views'], + }, + { + name: `${component}.cpp`, + content: componentCode, + language: 'c++', + platform: 'shared', + subdirectory: ['views'], + }, + ] +} diff --git a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts index 56948c8b0..21a3a9159 100644 --- a/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts +++ b/packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts @@ -1,78 +1,62 @@ import type { SourceFile } from '../../syntax/SourceFile.js' import type { HybridObjectSpec } from '../../syntax/HybridObjectSpec.js' -import { getHybridObjectName } from '../../syntax/getHybridObjectName.js' +import { + createViewComponentShadowNodeFiles, + getViewComponentNames, +} from '../ViewComponentShadowNode.js' import { createFileMetadataString } from '../../syntax/helpers.js' import { NitroConfig } from '../../config/NitroConfig.js' -import { createIndentation } from '../../utils.js' export function createSwiftHybridViewManager( spec: HybridObjectSpec ): SourceFile[] { - const name = getHybridObjectName(spec.name) - const propsClassName = `${name.HybridT}Props` - const stateClassName = `${name.HybridT}State` - const nameVariable = `${name.HybridT}ComponentName` - const shadowNodeClassName = `${name.HybridT}ShadowNode` - const descriptorClassName = `${name.HybridT}ComponentDescriptor` - const component = `${spec.name}Component` - const namespace = NitroConfig.getCxxNamespace('c++') + const cppFiles = createViewComponentShadowNodeFiles(spec) + const namespace = NitroConfig.getCxxNamespace('c++', 'views') + const { component, descriptorClassName } = getViewComponentNames(spec) - const propsCode = ` -${createFileMetadataString(`${component}.hpp`)} - -#pragma once - -#include "NitroDefines.hpp" + const mmFile = ` +${createFileMetadataString(`${component}.mm`)} #if REACT_NATIVE_VERSION >= 78 -#include -#include -#include -#include +#import "${component}.hpp" +#import +#import +#import namespace ${namespace} { - using namespace facebook; - - class ${propsClassName}: public react::ViewProps { - public: - explicit ${propsClassName}() = default; - ${propsClassName}(const react::PropsParserContext& context, -${createIndentation(propsClassName.length)} const ${propsClassName}& sourceProps, -${createIndentation(propsClassName.length)} const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps) { - throw std::runtime_error("not yet implemented!"); - } - }; + @interface ${component}: RCTViewComponentView + @end - class ${stateClassName} { - public: - explicit ${stateClassName}() = default; - }; + @implementation ${component} + + (void) load { + // TODO: Register it! + } - extern const char ${nameVariable}[] = "${name.HybridT}"; - using ${shadowNodeClassName} = react::ConcreteViewShadowNode<${nameVariable}, ${propsClassName}, react::ViewEventEmitter, ${stateClassName}>; + + (ComponentDescriptorProvider)componentDescriptorProvider { + return concreteComponentDescriptorProvider<${descriptorClassName}>(); + } - class ${descriptorClassName}: public react::ConcreteComponentDescriptor<${shadowNodeClassName}> { - public: - ${descriptorClassName}(const react::ComponentDescriptorParameters& parameters) - : ConcreteComponentDescriptor(parameters, std::make_unique(/* enable raw JSI props parsing */ true)) {} - }; + - (void)updateProps:(const facebook::react::Props::Shared&)props + oldProps:(const facebook::react::Props::Shared&)oldProps { + // TODO: const auto& newViewProps = *std::static_pointer_cast(props); - // TODO: Actual RCTViewComponentView goes here... or in Swift? + [super updateProps:props oldProps:oldProps]; + } + @end } // namespace ${namespace} -#else -#warning "View Component '${name.HybridT}' will be unavailable in React Native, because it requires React Native 78 or higher." #endif - `.trim() + ` return [ + ...cppFiles, { - name: `${component}.hpp`, - content: propsCode, + content: mmFile, language: 'c++', + name: `${component}.mm`, platform: 'ios', subdirectory: ['views'], }, diff --git a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake index eb8faacf7..2a234d7ed 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake +++ b/packages/react-native-nitro-image/nitrogen/generated/android/NitroImage+autolinking.cmake @@ -34,6 +34,7 @@ target_sources( ../nitrogen/generated/shared/c++/HybridBaseSpec.cpp ../nitrogen/generated/shared/c++/HybridChildSpec.cpp ../nitrogen/generated/shared/c++/HybridTestViewSpec.cpp + ../nitrogen/generated/shared/c++/views/TestViewComponent.cpp # Android-specific Nitrogen C++ sources ../nitrogen/generated/android/c++/JHybridImageSpec.cpp ../nitrogen/generated/android/c++/JHybridImageFactorySpec.cpp diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.mm b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.mm new file mode 100644 index 000000000..202c0cea3 --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.mm @@ -0,0 +1,39 @@ +/// +/// TestViewComponent.mm +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#if REACT_NATIVE_VERSION >= 78 + +#import "TestViewComponent.hpp" +#import +#import +#import + +namespace margelo::nitro::image::views { + + @interface TestViewComponent: RCTViewComponentView + @end + + @implementation TestViewComponent + + (void) load { + // TODO: Register it! + } + + + (ComponentDescriptorProvider)componentDescriptorProvider { + return concreteComponentDescriptorProvider(); + } + + - (void)updateProps:(const facebook::react::Props::Shared&)props + oldProps:(const facebook::react::Props::Shared&)oldProps { + // TODO: const auto& newViewProps = *std::static_pointer_cast(props); + + [super updateProps:props oldProps:oldProps]; + } + @end + +} // namespace margelo::nitro::image::views + +#endif diff --git a/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.cpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.cpp new file mode 100644 index 000000000..5f533e2fe --- /dev/null +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.cpp @@ -0,0 +1,34 @@ +/// +/// TestViewComponent.cpp +/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE. +/// https://github.com/mrousavy/nitro +/// Copyright © 2025 Marc Rousavy @ Margelo +/// + +#include "TestViewComponent.hpp" + +#if REACT_NATIVE_VERSION >= 78 + +namespace margelo::nitro::image::views { + + HybridTestViewProps::HybridTestViewProps(const react::PropsParserContext& context, + const HybridTestViewProps& sourceProps, + const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps) { + if (rawProps.isEmpty()) { + // TODO: idk? Hanno? + return; + } + const RawValue* rawValue = rawProps.at("nativeProp", nullptr, nullptr); + const auto& [runtime, value] = (std::pair)*rawValue; + // TODO: Parse runtime and value + } + + HybridTestViewComponentName = "HybridTestView"; + + HybridTestViewComponentDescriptor::HybridTestViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters) + : ConcreteComponentDescriptor(parameters, + std::make_unique(/* enableJsiParser */ true)) {} + +} // namespace margelo::nitro::image::views + +#endif diff --git a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.hpp b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.hpp similarity index 68% rename from packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.hpp rename to packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.hpp index 7ed6099e9..8b060539b 100644 --- a/packages/react-native-nitro-image/nitrogen/generated/ios/c++/views/TestViewComponent.hpp +++ b/packages/react-native-nitro-image/nitrogen/generated/shared/c++/views/TestViewComponent.hpp @@ -16,7 +16,7 @@ #include #include -namespace margelo::nitro::image { +namespace margelo::nitro::image::views { using namespace facebook; @@ -25,9 +25,7 @@ namespace margelo::nitro::image { explicit HybridTestViewProps() = default; HybridTestViewProps(const react::PropsParserContext& context, const HybridTestViewProps& sourceProps, - const react::RawProps& rawProps): react::ViewProps(context, sourceProps, rawProps) { - throw std::runtime_error("not yet implemented!"); - } + const react::RawProps& rawProps); }; class HybridTestViewState { @@ -35,19 +33,18 @@ namespace margelo::nitro::image { explicit HybridTestViewState() = default; }; - extern const char HybridTestViewComponentName[] = "HybridTestView"; + extern const char HybridTestViewComponentName[]; using HybridTestViewShadowNode = react::ConcreteViewShadowNode; class HybridTestViewComponentDescriptor: public react::ConcreteComponentDescriptor { public: - HybridTestViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters) - : ConcreteComponentDescriptor(parameters, std::make_unique(/* enable raw JSI props parsing */ true)) {} + HybridTestViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters); }; // TODO: Actual RCTViewComponentView goes here... or in Swift? -} // namespace margelo::nitro::image +} // namespace margelo::nitro::image::views #else -#warning "View Component 'HybridTestView' will be unavailable in React Native, because it requires React Native 78 or higher." + #warning "View Component 'HybridTestView' will be unavailable in React Native, because it requires React Native 78 or higher." #endif