Skip to content

Commit

Permalink
Add CircleMask view example to NativeModuleSample (#996)
Browse files Browse the repository at this point in the history
## Description

This PR adds a `CircleMask` native view to the `NativeModuleSample`.

### Why
So we have an example of implementing the same native view for both
Paper (using Xaml) and Fabric (using Composition).

## Screenshots

| New Arch | Old Arch |
|:-:|:-:|
|
![image](https://github.com/user-attachments/assets/0cd5d355-f970-4fbf-9d25-237efed14e1b)
|
![image](https://github.com/user-attachments/assets/b707c136-0e5f-4336-9940-01e87bbb7c8f)
|

---------

Co-authored-by: Andrew Coates <30809111+acoates-ms@users.noreply.github.com>
  • Loading branch information
jonthysell and acoates-ms authored Dec 9, 2024
1 parent 24a6f6c commit 9046ff5
Show file tree
Hide file tree
Showing 11 changed files with 486 additions and 0 deletions.
15 changes: 15 additions & 0 deletions samples/NativeModuleSample/cpp-lib/example-old/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SimpleHttpModule,
type Point,
DataMarshallingExamples,
CircleMask,
} from 'native-module-sample';
import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes';

Expand Down Expand Up @@ -255,6 +256,20 @@ export default function App() {
)}
</Text>
<Text />
<CircleMask style={{ margin: 10 }}>
<View
style={{
backgroundColor: '#006666',
width: 100,
height: 100,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{ fontSize: 20 }}>CircleMask</Text>
</View>
</CircleMask>
<Text />
</View>
</ScrollView>
);
Expand Down
15 changes: 15 additions & 0 deletions samples/NativeModuleSample/cpp-lib/example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
SimpleHttpModule,
type Point,
DataMarshallingExamples,
CircleMask,
} from 'native-module-sample';
import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes';

Expand Down Expand Up @@ -255,6 +256,20 @@ export default function App() {
)}
</Text>
<Text />
<CircleMask style={{ margin: 10 }}>
<View
style={{
backgroundColor: '#006666',
width: 100,
height: 100,
justifyContent: 'center',
alignItems: 'center',
}}
>
<Text style={{ fontSize: 20 }}>CircleMask</Text>
</View>
</CircleMask>
<Text />
</View>
</ScrollView>
);
Expand Down
4 changes: 4 additions & 0 deletions samples/NativeModuleSample/cpp-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,10 @@
"includesGeneratedCode": true,
"windows": {
"namespace": "NativeModuleSampleCodegen",
"generators": [
"modulesWindows",
"componentsWindows"
],
"outputDirectory": "windows/NativeModuleSample/codegen",
"separateDataTypes": true
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CircleMaskProps>('CircleMask');
3 changes: 3 additions & 0 deletions samples/NativeModuleSample/cpp-lib/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ export { type Response } from './NativeSimpleHttpModule';
export const SimpleHttpModule = {
GetHttpResponse: NativeSimpleHttpModule.GetHttpResponse,
};

export {default as CircleMask} from './CircleMaskNativeComponent';
export * from './CircleMaskNativeComponent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// 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<CircleMaskComponentView>(packageBuilder, [](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder& /*builder*/) {
// Once 0.76 includes SetViewFeatures - enable this code:
/*
// Turn off default border handling, as it overrides the Clip property of the visual
builder.SetViewFeatures(winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::Default & ~winrt::Microsoft::ReactNative::Composition::ComponentViewFeatures::NativeBorder);
*/
});
#else
packageBuilder.AddViewManager(L"CircleMaskViewManager", []() { return winrt::make<CircleMaskViewManager>(); });
#endif
}

#ifdef RNW_NEW_ARCH

winrt::Microsoft::UI::Composition::Visual CircleMaskComponentView::CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept
{
auto compositor = view.as<winrt::Microsoft::ReactNative::Composition::ComponentView>().Compositor();

m_visual = compositor.CreateSpriteVisual();

auto ellipseGeometry = compositor.CreateEllipseGeometry();
auto clip = compositor.CreateGeometricClip();
clip.Geometry(ellipseGeometry);
m_visual.Clip(clip);

return m_visual;
}

void CircleMaskComponentView::Initialize(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept
{
m_layoutMetricChangedRevoker = view.LayoutMetricsChanged(
winrt::auto_revoke,
[wkThis = get_weak()](
const winrt::IInspectable &/*sender*/, const winrt::Microsoft::ReactNative::LayoutMetricsChangedArgs& args) {
if (auto strongThis = wkThis.get()) {
// Once 0.76 includes SetViewFeatures - remove this code, since the core border code will not override our clip:
{
auto compositor = strongThis->m_visual.Compositor();
auto ellipseGeometry = compositor.CreateEllipseGeometry();
auto clip = compositor.CreateGeometricClip();
clip.Geometry(ellipseGeometry);
strongThis->m_visual.Clip(clip);
}
// End of workaround for not being able to disable core clipping code


auto ellipseGeometry = strongThis->m_visual.Clip().as<winrt::Microsoft::UI::Composition::CompositionGeometricClip>().Geometry().as<winrt::Microsoft::UI::Composition::CompositionEllipseGeometry>();
winrt::Windows::Foundation::Numerics::float2 radius = {args.NewLayoutMetrics().Frame.Width * args.NewLayoutMetrics().PointScaleFactor / 2, args.NewLayoutMetrics().Frame.Height * args.NewLayoutMetrics().PointScaleFactor / 2};
ellipseGeometry.Center(radius);
ellipseGeometry.Radius(radius);
}
});
}

#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<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(child);
}
}

void CircleMaskViewManager::RemoveAllChildren(winrt::Windows::UI::Xaml::FrameworkElement const &parent) noexcept
{
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
border.Child(nullptr);
}
}

void CircleMaskViewManager::RemoveChildAt(winrt::Windows::UI::Xaml::FrameworkElement const &parent, int64_t /*index*/) noexcept
{
if (auto const &border = parent.try_as<winrt::Windows::UI::Xaml::Controls::Border>()) {
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<winrt::Windows::UI::Xaml::Controls::Border>()) {
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<double>(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<HeightToCornerRadiusConverter>();
return instance;
};

#endif // #ifndef RNW_NEW_ARCH

} // namespace winrt::NativeModuleSample::implementation
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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 <winrt/Microsoft.ReactNative.Composition.Experimental.h>

#else

#include <winrt/Windows.UI.Xaml.Controls.h>
#include <winrt/Windows.UI.Xaml.Data.h>

#endif

namespace winrt::NativeModuleSample::implementation
{

void RegisterCircleMaskNativeComponent(
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) noexcept;

#ifdef RNW_NEW_ARCH

struct CircleMaskComponentView : winrt::implements<CircleMaskComponentView, winrt::IInspectable>,
NativeModuleSampleCodegen::BaseCircleMask<CircleMaskComponentView>
{
winrt::Microsoft::UI::Composition::Visual CreateVisual(const winrt::Microsoft::ReactNative::ComponentView &view) noexcept override;
void Initialize(const winrt::Microsoft::ReactNative::ComponentView &/*view*/) noexcept override;

private:
winrt::Microsoft::ReactNative::ComponentView::LayoutMetricsChanged_revoker m_layoutMetricChangedRevoker;
winrt::Microsoft::UI::Composition::SpriteVisual m_visual{nullptr};
};

#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<HeightToCornerRadiusConverter, winrt::Windows::UI::Xaml::Data::IValueConverter>
{
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
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@
<ClInclude Include="DataMarshallingExamples.h" />
<ClInclude Include="FancyMath.h" />
<ClInclude Include="SimpleHttpModule.h" />
<ClInclude Include="CircleMask.h" />
<ClInclude Include="ReactPackageProvider.h">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClInclude>
Expand All @@ -114,6 +115,7 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="CircleMask.cpp" />
<ClCompile Include="ReactPackageProvider.cpp">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClCompile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,17 @@
<ClInclude Include="SimpleHttpModule.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="CircleMask.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CircleMask.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#include "FancyMath.h"
#include "SimpleHttpModule.h"

#include "CircleMask.h"

using namespace winrt::Microsoft::ReactNative;

namespace winrt::NativeModuleSample::implementation
Expand All @@ -20,6 +22,7 @@ namespace winrt::NativeModuleSample::implementation
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept
{
AddAttributedModules(packageBuilder, true);
RegisterCircleMaskNativeComponent(packageBuilder);
}

} // namespace winrt::NativeModuleSample::implementation
Loading

0 comments on commit 9046ff5

Please sign in to comment.