From 8c624affc8ae43fe2383d66c001684da175ef49a Mon Sep 17 00:00:00 2001 From: Jon Thysell Date: Tue, 26 Nov 2024 12:34:56 -0800 Subject: [PATCH] Add CircleMask view example to NativeModuleSample --- .../cpp-lib/example-old/src/App.tsx | 15 ++ .../cpp-lib/example/src/App.tsx | 15 ++ .../NativeModuleSample/cpp-lib/package.json | 4 + .../cpp-lib/src/CircleMaskNativeComponent.ts | 9 + .../NativeModuleSample/cpp-lib/src/index.tsx | 3 + .../windows/NativeModuleSample/CircleMask.cpp | 142 ++++++++++++++ .../windows/NativeModuleSample/CircleMask.h | 92 ++++++++++ .../NativeModuleSample.vcxproj | 2 + .../NativeModuleSample.vcxproj.filters | 6 + .../ReactPackageProvider.cpp | 3 + .../NativeModuleSampleSpec/CircleMask.g.h | 173 ++++++++++++++++++ 11 files changed, 464 insertions(+) create mode 100644 samples/NativeModuleSample/cpp-lib/src/CircleMaskNativeComponent.ts create mode 100644 samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.cpp create mode 100644 samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.h create mode 100644 samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/codegen/react/components/NativeModuleSampleSpec/CircleMask.g.h diff --git a/samples/NativeModuleSample/cpp-lib/example-old/src/App.tsx b/samples/NativeModuleSample/cpp-lib/example-old/src/App.tsx index 658c3c0f8..807d6c5f3 100644 --- a/samples/NativeModuleSample/cpp-lib/example-old/src/App.tsx +++ b/samples/NativeModuleSample/cpp-lib/example-old/src/App.tsx @@ -9,6 +9,7 @@ import { SimpleHttpModule, type Point, DataMarshallingExamples, + CircleMask, } from 'native-module-sample'; import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes'; @@ -255,6 +256,20 @@ export default function App() { )} + + + CircleMask + + + ); diff --git a/samples/NativeModuleSample/cpp-lib/example/src/App.tsx b/samples/NativeModuleSample/cpp-lib/example/src/App.tsx index 658c3c0f8..807d6c5f3 100644 --- a/samples/NativeModuleSample/cpp-lib/example/src/App.tsx +++ b/samples/NativeModuleSample/cpp-lib/example/src/App.tsx @@ -9,6 +9,7 @@ import { SimpleHttpModule, type Point, DataMarshallingExamples, + CircleMask, } from 'native-module-sample'; import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes'; @@ -255,6 +256,20 @@ export default function App() { )} + + + CircleMask + + + ); diff --git a/samples/NativeModuleSample/cpp-lib/package.json b/samples/NativeModuleSample/cpp-lib/package.json index 03fb53163..78770567d 100644 --- a/samples/NativeModuleSample/cpp-lib/package.json +++ b/samples/NativeModuleSample/cpp-lib/package.json @@ -153,6 +153,10 @@ "includesGeneratedCode": true, "windows": { "namespace": "NativeModuleSampleCodegen", + "generators": [ + "modulesWindows", + "componentsWindows" + ], "outputDirectory": "windows/NativeModuleSample/codegen", "separateDataTypes": true } diff --git a/samples/NativeModuleSample/cpp-lib/src/CircleMaskNativeComponent.ts b/samples/NativeModuleSample/cpp-lib/src/CircleMaskNativeComponent.ts new file mode 100644 index 000000000..9c4f36f5c --- /dev/null +++ b/samples/NativeModuleSample/cpp-lib/src/CircleMaskNativeComponent.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent'; +import type { ViewProps } from 'react-native'; + +interface CircleMaskProps extends ViewProps {} + +export default codegenNativeComponent('CircleMask'); diff --git a/samples/NativeModuleSample/cpp-lib/src/index.tsx b/samples/NativeModuleSample/cpp-lib/src/index.tsx index 2f263ba05..29c5283bf 100644 --- a/samples/NativeModuleSample/cpp-lib/src/index.tsx +++ b/samples/NativeModuleSample/cpp-lib/src/index.tsx @@ -17,3 +17,6 @@ export { type Response } from './NativeSimpleHttpModule'; export const SimpleHttpModule = { GetHttpResponse: NativeSimpleHttpModule.GetHttpResponse, }; + +export {default as CircleMask} from './CircleMaskNativeComponent'; +export * from './CircleMaskNativeComponent'; diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.cpp b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.cpp new file mode 100644 index 000000000..f7454810f --- /dev/null +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.cpp @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" + +#include "CircleMask.h" + +namespace winrt::NativeModuleSample::implementation +{ + +void RegisterCircleMaskNativeComponent( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept +{ +#ifdef RNW_NEW_ARCH + NativeModuleSampleCodegen::RegisterCircleMaskNativeComponent(packageBuilder, {}); +#else + packageBuilder.AddViewManager(L"CircleMaskViewManager", []() { return winrt::make(); }); +#endif +} + +#ifdef RNW_NEW_ARCH + +winrt::Microsoft::UI::Composition::Visual CircleMaskComponentView::CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept +{ + // Adapting https://github.com/microsoft/WindowsAppSDK-Samples/blob/main/Samples/SceneGraph/SampleGalleryApp/Samples/CompCapabilities/CompCapabilities.xaml.cs#L139 + + auto compositor = view.as().Compositor(); + // auto compositionContext = view.as().CompositionContext(); + + winrt::Windows::Foundation::Size size{100, 100}; + + // // Create circle mask + // auto circleMaskSurface = compositionContext.CreateDrawingSurfaceBrush(size) + + // Get child visual + auto circleVisual = compositor.CreateSpriteVisual(); + + // // Apply mask to Visual + // auto maskBrush = compositor.CreateMaskBrush(); + // maskBrush.Source(???); + // maskBrush.Mask(circleMaskSurface); + + + // circleVisual.Brush(maskBrush); + + // // Insert visual + // InnerVisual().InsertAt(circleVisual, 0); + + return circleVisual; +} + +#else + +// IViewManager +winrt::hstring CircleMaskViewManager::Name() noexcept +{ + return L"CircleMask"; +} + +winrt::Windows::UI::Xaml::FrameworkElement CircleMaskViewManager::CreateView() noexcept +{ + auto const &view = winrt::Windows::UI::Xaml::Controls::Border(); + + auto const &binding = winrt::Windows::UI::Xaml::Data::Binding(); + binding.Source(view); + binding.Path(winrt::Windows::UI::Xaml::PropertyPath(L"Height")); + binding.Converter(HeightToCornerRadiusConverter::Instance()); + + view.SetBinding(winrt::Windows::UI::Xaml::Controls::Border::CornerRadiusProperty(), binding); + + return view; +} + +// IViewManagerWithChildren + +void CircleMaskViewManager::AddView( + winrt::Windows::UI::Xaml::FrameworkElement const &parent, + winrt::Windows::UI::Xaml::UIElement const &child, + int64_t /*index*/) noexcept +{ + if (auto const &border = parent.try_as()) { + border.Child(child); + } +} + +void CircleMaskViewManager::RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const &parent) noexcept +{ + if (auto const &border = parent.try_as()) { + border.Child(nullptr); + } +} + +void CircleMaskViewManager::RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const &parent, int64_t /*index*/) noexcept +{ + if (auto const &border = parent.try_as()) { + border.Child(nullptr); + } +} + +void CircleMaskViewManager::ReplaceChild( + winrt::Windows::UI::Xaml::FrameworkElement const &parent, + winrt::Windows::UI::Xaml::UIElement const & /*oldChild*/, + winrt::Windows::UI::Xaml::UIElement const &newChild) noexcept +{ + if (auto const &border = parent.try_as()) { + border.Child(newChild); + } +} + +winrt::Windows::Foundation::IInspectable HeightToCornerRadiusConverter::Convert( + winrt::Windows::Foundation::IInspectable const &value, + winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/, + winrt::Windows::Foundation::IInspectable const & /*parameter*/, + winrt::hstring const & /*language*/) noexcept +{ + double d = winrt::unbox_value(value); + + if (isnan(d)) { + d = 0.0; + } + + return winrt::box_value(winrt::Windows::UI::Xaml::CornerRadiusHelper::FromUniformRadius(d)); +} + +winrt::Windows::Foundation::IInspectable HeightToCornerRadiusConverter::ConvertBack( + winrt::Windows::Foundation::IInspectable const &value, + winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/, + winrt::Windows::Foundation::IInspectable const & /*parameter*/, + winrt::hstring const & /*language*/) noexcept +{ + return value; +} + +winrt::Windows::UI::Xaml::Data::IValueConverter HeightToCornerRadiusConverter::Instance() noexcept +{ + static auto const &instance = winrt::make(); + return instance; +}; + +#endif // #ifndef RNW_NEW_ARCH + +} // namespace winrt::NativeModuleSample::implementation diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.h b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.h new file mode 100644 index 000000000..28f4d6ba8 --- /dev/null +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/CircleMask.h @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "pch.h" + +#ifdef RNW_NEW_ARCH + +#include "codegen/react/components/NativeModuleSampleSpec/CircleMask.g.h" + +#include + +#else + +#include +#include + +#endif + +namespace winrt::NativeModuleSample::implementation +{ + +void RegisterCircleMaskNativeComponent( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept; + +#ifdef RNW_NEW_ARCH + +struct CircleMaskComponentView : winrt::implements, + NativeModuleSampleCodegen::BaseCircleMask +{ + winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept; +}; + +#else + +struct CircleMaskViewManager : winrt::implements< + CircleMaskViewManager, + winrt::Microsoft::ReactNative::IViewManager, + winrt::Microsoft::ReactNative::IViewManagerWithChildren> +{ + public: + CircleMaskViewManager(){} + + // IViewManager + winrt::hstring Name() noexcept; + + winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept; + + // IViewManagerWithChildren + + void AddView( + winrt::Windows::UI::Xaml::FrameworkElement const &parent, + winrt::Windows::UI::Xaml::UIElement const &child, + int64_t /*index*/) noexcept; + + void RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const &parent) noexcept; + + void RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const &parent, int64_t /*index*/) noexcept; + + void ReplaceChild( + winrt::Windows::UI::Xaml::FrameworkElement const &parent, + winrt::Windows::UI::Xaml::UIElement const & /*oldChild*/, + winrt::Windows::UI::Xaml::UIElement const &newChild) noexcept; +}; + +struct HeightToCornerRadiusConverter + : winrt::implements +{ + public: + HeightToCornerRadiusConverter(){} + + winrt::Windows::Foundation::IInspectable Convert( + winrt::Windows::Foundation::IInspectable const &value, + winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/, + winrt::Windows::Foundation::IInspectable const & /*parameter*/, + winrt::hstring const & /*language*/) noexcept; + + winrt::Windows::Foundation::IInspectable ConvertBack( + winrt::Windows::Foundation::IInspectable const &value, + winrt::Windows::UI::Xaml::Interop::TypeName const & /*targetType*/, + winrt::Windows::Foundation::IInspectable const & /*parameter*/, + winrt::hstring const & /*language*/) noexcept; + + static winrt::Windows::UI::Xaml::Data::IValueConverter Instance() noexcept; + + // IValueConverter +}; + +#endif // #ifdef RNW_NEW_ARCH + +} // namespace winrt::NativeModuleSample::implementation diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj index 97767916e..59c01048d 100644 --- a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj @@ -103,6 +103,7 @@ + ReactPackageProvider.idl @@ -114,6 +115,7 @@ Create + ReactPackageProvider.idl diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj.filters b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj.filters index 2f967e645..2f11854b3 100644 --- a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj.filters +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/NativeModuleSample.vcxproj.filters @@ -30,11 +30,17 @@ Header Files + + Header Files + Header Files + + Source Files + Source Files diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/ReactPackageProvider.cpp b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/ReactPackageProvider.cpp index 6ca4c42bc..9add392c8 100644 --- a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/ReactPackageProvider.cpp +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/ReactPackageProvider.cpp @@ -12,6 +12,8 @@ #include "FancyMath.h" #include "SimpleHttpModule.h" +#include "CircleMask.h" + using namespace winrt::Microsoft::ReactNative; namespace winrt::NativeModuleSample::implementation @@ -20,6 +22,7 @@ namespace winrt::NativeModuleSample::implementation void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept { AddAttributedModules(packageBuilder, true); + RegisterCircleMaskNativeComponent(packageBuilder); } } // namespace winrt::NativeModuleSample::implementation diff --git a/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/codegen/react/components/NativeModuleSampleSpec/CircleMask.g.h b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/codegen/react/components/NativeModuleSampleSpec/CircleMask.g.h new file mode 100644 index 000000000..97df5165f --- /dev/null +++ b/samples/NativeModuleSample/cpp-lib/windows/NativeModuleSample/codegen/react/components/NativeModuleSampleSpec/CircleMask.g.h @@ -0,0 +1,173 @@ + +/* + * This file is auto-generated from CircleMaskNativeComponent spec file in flow / TypeScript. + */ +#pragma once + +#include + +#ifdef RNW_NEW_ARCH +#include + +#include +#include +#endif // #ifdef RNW_NEW_ARCH + +#ifdef RNW_NEW_ARCH + +namespace NativeModuleSampleCodegen { + +REACT_STRUCT(CircleMaskProps) +struct CircleMaskProps : winrt::implements { + CircleMaskProps(winrt::Microsoft::ReactNative::ViewProps props) : ViewProps(props) {} + + void SetProp(uint32_t hash, winrt::hstring propName, winrt::Microsoft::ReactNative::IJSValueReader value) noexcept { + winrt::Microsoft::ReactNative::ReadProp(hash, propName, value, *this); + } + + const winrt::Microsoft::ReactNative::ViewProps ViewProps; +}; + +struct CircleMaskEventEmitter { + CircleMaskEventEmitter(const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) + : m_eventEmitter(eventEmitter) {} + + private: + winrt::Microsoft::ReactNative::EventEmitter m_eventEmitter{nullptr}; +}; + +template +struct BaseCircleMask { + + virtual void UpdateProps( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::com_ptr &newProps, + const winrt::com_ptr &/*oldProps*/) noexcept { + m_props = newProps; + } + + // UpdateState will only be called if this method is overridden + virtual void UpdateState( + const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::IComponentState &/*newState*/) noexcept { + } + + virtual void UpdateEventEmitter(const std::shared_ptr &eventEmitter) noexcept { + m_eventEmitter = eventEmitter; + } + + // MountChildComponentView will only be called if this method is overridden + virtual void MountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &/*args*/) noexcept { + } + + // UnmountChildComponentView will only be called if this method is overridden + virtual void UnmountChildComponentView(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &/*args*/) noexcept { + } + + // Initialize will only be called if this method is overridden + virtual void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept { + } + + // CreateVisual will only be called if this method is overridden + virtual winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + return view.as().Compositor().CreateSpriteVisual(); + } + + // FinalizeUpdate will only be called if this method is overridden + virtual void FinalizeUpdate(const winrt::Microsoft::ReactNative::ComponentView &/*view*/, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask /*mask*/) noexcept { + } + + + + const std::shared_ptr& EventEmitter() const { return m_eventEmitter; } + const winrt::com_ptr& Props() const { return m_props; } + +private: + winrt::com_ptr m_props; + std::shared_ptr m_eventEmitter; +}; + +template +void RegisterCircleMaskNativeComponent( + winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder, + std::function builderCallback) noexcept { + packageBuilder.as().AddViewComponent( + L"CircleMask", [builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept { + auto compBuilder = builder.as(); + + builder.SetCreateProps( + [](winrt::Microsoft::ReactNative::ViewProps props) noexcept { return winrt::make(props); }); + + builder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentProps &newProps, + const winrt::Microsoft::ReactNative::IComponentProps &oldProps) noexcept { + auto userData = view.UserData().as(); + userData->UpdateProps(view, newProps ? newProps.as() : nullptr, oldProps ? oldProps.as() : nullptr); + }); + + builder.SetUpdateEventEmitterHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept { + auto userData = view.UserData().as(); + userData->UpdateEventEmitter(std::make_shared(eventEmitter)); + }); + + if constexpr (&TUserData::FinalizeUpdate != &BaseCircleMask::FinalizeUpdate) { + builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept { + auto userData = view.UserData().as(); + userData->FinalizeUpdate(view, mask); + }); + } + + if constexpr (&TUserData::UpdateState != &BaseCircleMask::UpdateState) { + builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept { + auto userData = view.UserData().as(); + userData->member(view, newState); + }); + } + + if constexpr (&TUserData::MountChildComponentView != &BaseCircleMask::MountChildComponentView) { + builder.SetMountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->MountChildComponentView(view, args); + }); + } + + if constexpr (&TUserData::UnmountChildComponentView != &BaseCircleMask::UnmountChildComponentView) { + builder.SetUnmountChildComponentViewHandler([](const winrt::Microsoft::ReactNative::ComponentView &view, + const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept { + auto userData = view.UserData().as(); + return userData->UnmountChildComponentView(view, args); + }); + } + + compBuilder.SetViewComponentViewInitializer([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = winrt::make_self(); + if constexpr (&TUserData::Initialize != &BaseCircleMask::Initialize) { + userData->Initialize(view); + } + view.UserData(*userData); + }); + + if constexpr (&TUserData::CreateVisual != &BaseCircleMask::CreateVisual) { + compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept { + auto userData = view.UserData().as(); + return userData->CreateVisual(view); + }); + } + + // Allow app to further customize the builder + if (builderCallback) { + builderCallback(compBuilder); + } + }); +} + +} // namespace NativeModuleSampleCodegen + +#endif // #ifdef RNW_NEW_ARCH