Skip to content

Commit

Permalink
[Fabric] Rework custom components to not rely on open compose patterns (
Browse files Browse the repository at this point in the history
#13603)

* Rework custom components to not rely on open compose patterns

* Change files

* fix

* fix

* handle non-visual children

* fix

* fix

* Move delegates to builder instead of exposing them directly from the ComponentViews

* fix
  • Loading branch information
acoates-ms authored Aug 29, 2024
1 parent fac82f7 commit fff6d5a
Show file tree
Hide file tree
Showing 49 changed files with 2,596 additions and 625 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Rework custom components to not rely on open compose patterns",
"packageName": "react-native-windows",
"email": "30809111+acoates-ms@users.noreply.github.com",
"dependentChangeType": "patch"
}
229 changes: 89 additions & 140 deletions packages/playground/windows/playground-composition/CustomComponent.cpp
Original file line number Diff line number Diff line change
@@ -1,22 +1,19 @@
#include "pch.h"

#include <winrt/Microsoft.ReactNative.Composition.Experimental.h>
#include <winrt/Microsoft.ReactNative.Composition.Input.h>
#include <winrt/Microsoft.ReactNative.Composition.h>
#include <winrt/Microsoft.ReactNative.h>
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.h>

#ifdef USE_WINUI3
#include <winrt/Microsoft.UI.Composition.h>
#include <winrt/Microsoft.UI.Content.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Media.h>
#include <winrt/Microsoft.UI.Xaml.h>
#include <winrt/Microsoft.UI.interop.h>
#endif
#include <winrt/Windows.UI.Composition.h>
#include <winrt/Windows.UI.h>

#include "App.CustomComponent.g.h"
#include <NativeModules.h>
#include "YogaXamlPanel.h"

Expand Down Expand Up @@ -47,100 +44,30 @@ struct CustomXamlComponentStateData : winrt::implements<CustomXamlComponentState
winrt::Windows::Foundation::Size desiredSize;
};

struct CustomComponent : CustomComponentT<CustomComponent> {
CustomComponent(
bool nativeLayout,
const winrt::Microsoft::ReactNative::Composition::CreateCompositionComponentViewArgs &args)
: base_type(args),
m_nativeLayout(nativeLayout),
m_compContext(
args.as<winrt::Microsoft::ReactNative::Composition::Experimental::IInternalCreateComponentViewArgs>()
.CompositionContext()) {}

~CustomComponent() {
struct CustomComponentUserData : winrt::implements<CustomComponentUserData, winrt::IInspectable> {
void Initialize(
const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView,
bool nativeLayout) {
nativeLayout;
islandView;
#ifdef USE_EXPERIMENTAL_WINUI3
m_xamlIsland.Close();
m_siteBridge.Close();
// Hit a crash when calling m_contentIsland.Close?
// m_contentIsland.Close();
m_xamlIsland = winrt::Microsoft::UI::Xaml::XamlIsland{};
m_xamlIsland.Content(CreateXamlButtonContent(nativeLayout));
islandView.Connect(m_xamlIsland.ContentIsland());
#endif
}

void HandleCommand(winrt::hstring commandName, const winrt::Microsoft::ReactNative::IJSValueReader &args) {
// Custom commands would be implemented here
base_type::HandleCommand(commandName, args);
}

void UpdateProps(
const winrt::Microsoft::ReactNative::IComponentProps &props,
void PropsChanged(
const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView & /*islandView*/,
const winrt::Microsoft::ReactNative::IComponentProps &newProps,
const winrt::Microsoft::ReactNative::IComponentProps & /*oldProps*/) {
auto myProps = props.as<CustomXamlComponentProps>();
auto myProps = newProps.as<CustomXamlComponentProps>();
#ifdef USE_EXPERIMENTAL_WINUI3
m_buttonLabelTextBlock.Text(myProps->label);
#endif
}
void UpdateState(const winrt::Microsoft::ReactNative::IComponentState &state) {
m_state = state;
}

void UpdateLayoutMetrics(
const winrt::Microsoft::ReactNative::LayoutMetrics &layoutMetrics,
const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) {
base_type::UpdateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
#ifdef USE_EXPERIMENTAL_WINUI3
auto site = m_siteBridge.Site();
auto siteWindow = site.Environment();
auto displayScale = siteWindow.DisplayScale();

site.ParentScale(displayScale);
site.ActualSize({layoutMetrics.Frame.Width, layoutMetrics.Frame.Height});
site.ClientSize(winrt::Windows::Graphics::SizeInt32{
static_cast<int32_t>(layoutMetrics.Frame.Width * layoutMetrics.PointScaleFactor),
static_cast<int32_t>(layoutMetrics.Frame.Height * layoutMetrics.PointScaleFactor)});
#endif
}

void FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentViewUpdateMask updateMask) {
base_type::FinalizeUpdates(updateMask);
}

winrt::Microsoft::ReactNative::Composition::Experimental::IVisual CreateInternalVisual() noexcept {
#ifdef USE_EXPERIMENTAL_WINUI3
auto systemCompContext =
m_compContext
.try_as<winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper>();
if (systemCompContext) {
m_xamlIsland = winrt::Microsoft::UI::Xaml::XamlIsland{};
m_xamlIsland.Content(CreateXamlButtonContent());

m_contentIsland = m_xamlIsland.ContentIsland();
}
#endif

m_visual = m_compContext.CreateSpriteVisual();
// m_visual.Brush(m_compContext.CreateColorBrush({255, 255, 0, 255}));
#ifdef USE_EXPERIMENTAL_WINUI3

if (systemCompContext) {
auto hwnd = reinterpret_cast<HWND>(
winrt::Microsoft::ReactNative::ReactCoreInjection::GetTopLevelWindowId(ReactContext().Properties()));

auto containerVisual =
winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerVisual(
m_visual)
.as<winrt::Windows::UI::Composition::ContainerVisual>();
m_siteBridge = winrt::Microsoft::UI::Content::SystemVisualSiteBridge::Create(
m_contentIsland.Compositor(), containerVisual, winrt::Microsoft::UI::GetWindowIdFromWindow(hwnd));
m_siteBridge.Connect(m_contentIsland);

auto rootXamlVisualSize = m_contentIsland.Root().Size();
}
#endif

return m_visual;
}

winrt::Microsoft::UI::Xaml::UIElement CreateXamlButtonContent() {
winrt::Microsoft::UI::Xaml::UIElement CreateXamlButtonContent(bool nativeLayout) {
m_buttonLabelTextBlock = winrt::Microsoft::UI::Xaml::Controls::TextBlock();
m_buttonLabelTextBlock.Text(L"This is a Xaml Button set to ellipisify on truncation");
m_buttonLabelTextBlock.TextTrimming(winrt::Microsoft::UI::Xaml::TextTrimming::CharacterEllipsis);
Expand All @@ -151,7 +78,7 @@ struct CustomComponent : CustomComponentT<CustomComponent> {

// If we are using native layout then wrap the element in a YogaXamlPanel which reports any changes to desired size
// of the XAML element.
if (!m_nativeLayout) {
if (!nativeLayout) {
return button;
}

Expand All @@ -177,71 +104,93 @@ struct CustomComponent : CustomComponentT<CustomComponent> {
}

private:
const bool m_nativeLayout;
winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext m_compContext;
winrt::Microsoft::UI::Xaml::Controls::TextBlock m_buttonLabelTextBlock{nullptr};
winrt::Microsoft::ReactNative::IComponentState m_state;
winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_visual;
#ifdef USE_EXPERIMENTAL_WINUI3
winrt::Microsoft::UI::Xaml::XamlIsland m_xamlIsland{nullptr};
winrt::Microsoft::UI::Content::ContentIsland m_contentIsland{nullptr};
winrt::Microsoft::UI::Content::SystemVisualSiteBridge m_siteBridge{nullptr};
#endif
};

void ConfigureBuilderForCustomComponent(
winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder,
bool nativeLayout) {
builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props) noexcept {
return winrt::make<CustomXamlComponentProps>(props);
});
auto compBuilder = builder.as<winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder>();

compBuilder.SetContentIslandComponentViewInitializer(
[nativeLayout](
const winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView &islandView) noexcept {
auto userData = winrt::make_self<CustomComponentUserData>();
userData->Initialize(islandView, nativeLayout);

#ifdef USE_EXPERIMENTAL_WINUI3
islandView.Destroying([](const winrt::IInspectable &sender, const winrt::IInspectable &args) {
auto senderIslandView = sender.as<winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView>();
auto userData = senderIslandView.UserData().as<CustomComponentUserData>();
userData->m_xamlIsland.Close();
});
#endif
});

if (nativeLayout) {
builder.SetMeasureContentHandler([](winrt::Microsoft::ReactNative::ShadowNode shadowNode,
winrt::Microsoft::ReactNative::LayoutContext layoutContext,
winrt::Microsoft::ReactNative::LayoutConstraints layoutContraints) noexcept {
shadowNode.Tag(winrt::box_value(layoutContraints));

auto currentState = winrt::get_self<CustomXamlComponentStateData>(shadowNode.StateData());

if (currentState) {
// Snap up to the nearest whole pixel to avoid pixel snapping truncations
auto size = winrt::Windows::Foundation::Size{
std::ceil(currentState->desiredSize.Width * layoutContext.PointScaleFactor()) /
layoutContext.PointScaleFactor() +
1.0f,
std::ceil(currentState->desiredSize.Height * layoutContext.PointScaleFactor()) /
layoutContext.PointScaleFactor() +
1.0f,
};
return size;
}

return winrt::Windows::Foundation::Size{0, 0};
});
builder.SetInitialStateDataFactory([](const winrt::Microsoft::ReactNative::IComponentProps & /*props*/) noexcept {
return winrt::make<CustomXamlComponentStateData>(winrt::Windows::Foundation::Size{0, 0});
});
}

#ifdef USE_EXPERIMENTAL_WINUI3
compBuilder.SetUpdatePropsHandler([](const winrt::Microsoft::ReactNative::ComponentView &source,
const winrt::Microsoft::ReactNative::IComponentProps &newProps,
const winrt::Microsoft::ReactNative::IComponentProps &oldProps) {
auto senderIslandView = source.as<winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView>();
auto userData = senderIslandView.UserData().as<CustomComponentUserData>();
userData->PropsChanged(senderIslandView, newProps, oldProps);
});

compBuilder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &source,
const winrt::Microsoft::ReactNative::IComponentState &newState) {
auto senderIslandView = source.as<winrt::Microsoft::ReactNative::Composition::ContentIslandComponentView>();
auto userData = senderIslandView.UserData().as<CustomComponentUserData>();
userData->m_state = newState;
});
#endif
}

static void RegisterViewComponent(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) {
packageBuilder.as<winrt::Microsoft::ReactNative::IReactPackageBuilderFabric>().AddViewComponent(
L"CustomXamlComponentWithNativeLayout",
[](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept {
builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props) noexcept {
return winrt::make<CustomXamlComponentProps>(props);
});
auto compBuilder =
builder.as<winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder>();
compBuilder.SetCreateViewComponentView(
[](const winrt::Microsoft::ReactNative::Composition::CreateCompositionComponentViewArgs &args) noexcept {
return winrt::make<CustomComponent>(true, args);
});
builder.SetMeasureContentHandler(
[](winrt::Microsoft::ReactNative::ShadowNode shadowNode,
winrt::Microsoft::ReactNative::LayoutContext layoutContext,
winrt::Microsoft::ReactNative::LayoutConstraints layoutContraints) noexcept {
shadowNode.Tag(winrt::box_value(layoutContraints));

auto currentState = winrt::get_self<CustomXamlComponentStateData>(shadowNode.StateData());

if (currentState) {
// Snap up to the nearest whole pixel to avoid pixel snapping truncations
auto size = winrt::Windows::Foundation::Size{
std::ceil(currentState->desiredSize.Width * layoutContext.PointScaleFactor()) /
layoutContext.PointScaleFactor() +
1.0f,
std::ceil(currentState->desiredSize.Height * layoutContext.PointScaleFactor()) /
layoutContext.PointScaleFactor() +
1.0f,
};
return size;
}

return winrt::Windows::Foundation::Size{0, 0};
});
builder.SetInitialStateDataFactory([](const winrt::Microsoft::ReactNative::IComponentProps & /*props*/) {
return winrt::make<CustomXamlComponentStateData>(winrt::Windows::Foundation::Size{0, 0});
});
ConfigureBuilderForCustomComponent(builder, true /*nativeLayout*/);
});

packageBuilder.as<winrt::Microsoft::ReactNative::IReactPackageBuilderFabric>().AddViewComponent(
L"CustomXamlComponentWithYogaLayout",
[](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept {
builder.SetCreateProps([](winrt::Microsoft::ReactNative::ViewProps props) noexcept {
return winrt::make<CustomXamlComponentProps>(props);
});
auto compBuilder =
builder.as<winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder>();
compBuilder.SetCreateViewComponentView(
[](const winrt::Microsoft::ReactNative::Composition::CreateCompositionComponentViewArgs &args) noexcept {
return winrt::make<CustomComponent>(false, args);
});
ConfigureBuilderForCustomComponent(builder, false /*nativeLayout*/);
});
}
} // namespace winrt::PlaygroundApp::implementation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ namespace PlaygroundApp
[default_interface]
[webhosthidden]
[experimental]
runtimeclass CustomComponent : Microsoft.ReactNative.Composition.ViewComponentView, Microsoft.ReactNative.Composition.Experimental.IInternalCreateVisual
runtimeclass DrawingIsland : Windows.Foundation.IClosable
{
DrawingIsland(Microsoft.UI.Composition.Compositor compositor);
}
}
Loading

0 comments on commit fff6d5a

Please sign in to comment.