Skip to content

Commit

Permalink
Create .mm file as well
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Jan 15, 2025
1 parent 09b00af commit cb2f358
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 57 deletions.
151 changes: 151 additions & 0 deletions packages/nitrogen/src/views/ViewComponentShadowNode.ts
Original file line number Diff line number Diff line change
@@ -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 <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
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<jsi::Runtime*, const jsi::Value&>)*rawValue;
// TODO: Parse runtime and value
}
${nameVariable} = "${name.HybridT}";
${descriptorClassName}::${descriptorClassName}(const react::ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters,
std::make_unique<react::RawPropsParser>(/* 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'],
},
]
}
80 changes: 32 additions & 48 deletions packages/nitrogen/src/views/swift/SwiftHybridViewManager.ts
Original file line number Diff line number Diff line change
@@ -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 <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>
#import "${component}.hpp"
#import <React/RCTComponentViewFactory.h>
#import <React/UIView+ComponentViewProtocol.h>
#import <UIKit/UIKit.h>
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<react::RawPropsParser>(/* 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<CustomViewProps const>(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'],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <React/RCTComponentViewFactory.h>
#import <React/UIView+ComponentViewProtocol.h>
#import <UIKit/UIKit.h>

namespace margelo::nitro::image::views {

@interface TestViewComponent: RCTViewComponentView
@end

@implementation TestViewComponent
+ (void) load {
// TODO: Register it!
}

+ (ComponentDescriptorProvider)componentDescriptorProvider {
return concreteComponentDescriptorProvider<HybridTestViewComponentDescriptor>();
}

- (void)updateProps:(const facebook::react::Props::Shared&)props
oldProps:(const facebook::react::Props::Shared&)oldProps {
// TODO: const auto& newViewProps = *std::static_pointer_cast<CustomViewProps const>(props);

[super updateProps:props oldProps:oldProps];
}
@end

} // namespace margelo::nitro::image::views

#endif
Original file line number Diff line number Diff line change
@@ -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<jsi::Runtime*, const jsi::Value&>)*rawValue;
// TODO: Parse runtime and value
}

HybridTestViewComponentName = "HybridTestView";

HybridTestViewComponentDescriptor::HybridTestViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters,
std::make_unique<react::RawPropsParser>(/* enableJsiParser */ true)) {}

} // namespace margelo::nitro::image::views

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewProps.h>

namespace margelo::nitro::image {
namespace margelo::nitro::image::views {

using namespace facebook;

Expand All @@ -25,29 +25,26 @@ 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 {
public:
explicit HybridTestViewState() = default;
};

extern const char HybridTestViewComponentName[] = "HybridTestView";
extern const char HybridTestViewComponentName[];
using HybridTestViewShadowNode = react::ConcreteViewShadowNode<HybridTestViewComponentName, HybridTestViewProps, react::ViewEventEmitter, HybridTestViewState>;

class HybridTestViewComponentDescriptor: public react::ConcreteComponentDescriptor<HybridTestViewShadowNode> {
public:
HybridTestViewComponentDescriptor(const react::ComponentDescriptorParameters& parameters)
: ConcreteComponentDescriptor(parameters, std::make_unique<react::RawPropsParser>(/* 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

0 comments on commit cb2f358

Please sign in to comment.