Skip to content

Do not merge: Xaml in RNW prototyping #14649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions packages/sample-app-fabric/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ import {
ReloadInstructions,
} from 'react-native/Libraries/NewAppScreen';

import {XamlHost} from 'react-native-windows';

import {
StackPanel,
Button,
CalendarView,
} from 'react-native-windows/Libraries/Components/Xaml/FabricXamlControl';

type SectionProps = PropsWithChildren<{
title: string;
}>;
Expand Down Expand Up @@ -77,9 +85,17 @@ function App(): React.JSX.Element {
backgroundColor: isDarkMode ? Colors.black : Colors.white,
}}>
<Section title="Step One">
Edit <Text style={styles.highlight}>App.tsx</Text> to change this
screen and then come back to see your edits.
Edit <Text style={styles.highlight}>App.tsx</Text> to change this.
I'll try showing some Xaml here:
</Section>
<XamlHost style={{width: 400, height: 400}}>
<StackPanel>
<Button content="Hello, World!" />
{/* width and height are for the Xaml layout here, not RN. Confusing, right? */}
<Button content="1 Wide button" width="400" />
<CalendarView width="400" height="300" />
</StackPanel>
</XamlHost>
<Section title="See Your Changes">
<ReloadInstructions />
</Section>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@
"boost": "[1.83.0, )"
}
},
"microsoft.reactnative.xaml": {
"type": "Project",
"dependencies": {
"Common": "[1.0.0, )",
"Folly": "[1.0.0, )",
"Microsoft.ReactNative": "[1.0.0, )",
"Microsoft.WindowsAppSDK": "[1.7.250401001, )",
"ReactCommon": "[1.0.0, )",
"boost": "[1.83.0, )"
}
},
"reactcommon": {
"type": "Project",
"dependencies": {
Expand All @@ -95,6 +106,7 @@
"dependencies": {
"Microsoft.JavaScript.Hermes": "[0.0.0-2505.2001-0e4bc3b9, )",
"Microsoft.ReactNative": "[1.0.0, )",
"Microsoft.ReactNative.Xaml": "[1.0.0, )",
"Microsoft.VCRTForwarders.140": "[1.0.2-rc, )",
"Microsoft.WindowsAppSDK": "[1.7.250401001, )",
"boost": "[1.83.0, )"
Expand Down
15 changes: 15 additions & 0 deletions packages/sample-app-fabric/windows/SampleAppFabric.sln
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\..\..\vnext\Mso\M
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\..\..\vnext\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Xaml", "..\..\..\vnext\Microsoft.ReactNative.Xaml\Microsoft.ReactNative.Xaml.vcxproj", "{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\..\..\vnext\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
Expand Down Expand Up @@ -154,6 +156,19 @@ Global
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.ActiveCfg = Release|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Build.0 = Release|Win32
{14B93DC8-FD93-4A6D-81CB-8BC96644501C}.Release|x86.Deploy.0 = Release|Win32

{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x64.ActiveCfg = Debug|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x64.Build.0 = Debug|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x86.ActiveCfg = Debug|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x86.Build.0 = Debug|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|ARM64.Build.0 = Debug|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x64.ActiveCfg = Release|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x64.Build.0 = Release|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x86.ActiveCfg = Release|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x86.Build.0 = Release|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|ARM64.ActiveCfg = Release|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|ARM64.Build.0 = Release|ARM64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

#include "NativeModules.h"

#include <winrt/Microsoft.ReactNative.Xaml.h>

// A PackageProvider containing any turbo modules you define within this app project
struct CompReactPackageProvider
: winrt::implements<CompReactPackageProvider, winrt::Microsoft::ReactNative::IReactPackageProvider> {
Expand Down Expand Up @@ -39,6 +41,10 @@ _Use_decl_annotations_ int CALLBACK WinMain(HINSTANCE instance, HINSTANCE, PSTR
RegisterAutolinkedNativeModulePackages(settings.PackageProviders());
// Register any native modules defined within this app project
settings.PackageProviders().Append(winrt::make<CompReactPackageProvider>());
// TODO: Can we make apps register this automatically? (e.g. with autolinking)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To get autolinking to do its thing, we'd need to move the rn-xaml JS code into a new npm package, and then autolinking would bring in this dependency if the app adds that new package to their dependencies.

// TODO: But ideally we don't load the M.RN.Xaml.dll at all if we're not using Xaml.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that would actually be an interesting thing to try to get autolinking to do.
Right now auto linking would end up loading the dll. -- Having said that, I wouldn't expect M.RN.Xaml.dll to be particularly big. We will want to make sure that MUX doesnt' get loaded until you use a control though.

settings.PackageProviders().Append(winrt::Microsoft::ReactNative::Xaml::ReactPackageProvider());


#if BUNDLE
// Load the JS bundle from a file (not Metro):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Composition.CppApp.targets" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Composition.CppApp.targets')" />
</ImportGroup>
<ItemGroup>
<!-- TODO: Can we make this happen automatically, like with autolinking? -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, autolinking can do this. But again needs a separate npm package.

<ProjectReference Include="$(ReactNativeWindowsDir)Microsoft.ReactNative.Xaml\Microsoft.ReactNative.Xaml.vcxproj">
<Project>{e5d1b5d2-0e6a-4011-8bcd-08968256dcbd}</Project>
<Name>Microsoft.ReactNative.Xaml</Name>
<Private Condition="'$(ConfigurationType)' != 'Application'">false</Private>
</ProjectReference>
</ItemGroup>
<Target Name="EnsureReactNativeWindowsTargets" BeforeTargets="PrepareForBuild">
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@
"boost": "[1.83.0, )"
}
},
"microsoft.reactnative.xaml": {
"type": "Project",
"dependencies": {
"Common": "[1.0.0, )",
"Folly": "[1.0.0, )",
"Microsoft.ReactNative": "[1.0.0, )",
"Microsoft.WindowsAppSDK": "[1.7.250401001, )",
"ReactCommon": "[1.0.0, )",
"boost": "[1.83.0, )"
}
},
"reactcommon": {
"type": "Project",
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions packages/sample-app-fabric/windows/SampleAppFabric/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <CppWinRTIncludes.h>
#include <winrt/Microsoft.ReactNative.Composition.h>
#include <winrt/Microsoft.ReactNative.h>
#include <winrt/Microsoft.ReactNative.Xaml.h>
#include <winrt/Microsoft.UI.Composition.h>
#include <winrt/Microsoft.UI.Content.h>
#include <winrt/Microsoft.UI.Dispatching.h>
Expand Down
37 changes: 37 additions & 0 deletions vnext/Microsoft.ReactNative.Xaml.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35913.81 d17.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Xaml", "Microsoft.ReactNative.Xaml\Microsoft.ReactNative.Xaml.vcxproj", "{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|ARM64.Build.0 = Debug|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x64.ActiveCfg = Debug|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x64.Build.0 = Debug|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x86.ActiveCfg = Debug|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Debug|x86.Build.0 = Debug|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|ARM64.ActiveCfg = Release|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|ARM64.Build.0 = Release|ARM64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x64.ActiveCfg = Release|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x64.Build.0 = Release|x64
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x86.ActiveCfg = Release|Win32
{E5D1B5D2-0E6A-4011-8BCD-08968256DCBD}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3B36CAF0-BCD9-42C7-B311-05FDDCCEC9D4}
EndGlobalSection
EndGlobal
19 changes: 19 additions & 0 deletions vnext/Microsoft.ReactNative.Xaml/App.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.ReactNative.Xaml
{
[webhosthidden][default_interface] runtimeclass XamlApplication : Microsoft.UI.Xaml.Application,
Microsoft.UI.Xaml.Markup.IXamlMetadataProvider
{
XamlApplication();

static void EnsureCreated();

static XamlApplication Current {
get;
};

void AddMetadataProvider(Microsoft.UI.Xaml.Markup.IXamlMetadataProvider otherProvider);
}
}
164 changes: 164 additions & 0 deletions vnext/Microsoft.ReactNative.Xaml/FabricXamlControl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"

#if !defined(RNW_NEW_ARCH)
static_assert(false, "This module requires RNW_NEW_ARCH to be defined.");
#endif

#include "FabricXamlControl.h"

#include <NativeModules.h>

#include <JSValueComposition.h>

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

#include <winrt/Microsoft.UI.Xaml.Controls.h>

namespace winrt::Microsoft::ReactNative::Xaml {

template <typename TUserData>
void CustomRegisterXamlControlNativeComponent(
winrt::hstring name,
winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder,
std::function<void(const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &)>
builderCallback) noexcept {
packageBuilder.as<winrt::Microsoft::ReactNative::IReactPackageBuilderFabric>().AddViewComponent(
name, [name, builderCallback](winrt::Microsoft::ReactNative::IReactViewComponentBuilder const &builder) noexcept {
auto compBuilder =
builder.as<winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder>();

builder.SetCreateProps([name](
winrt::Microsoft::ReactNative::ViewProps props,
const winrt::Microsoft::ReactNative::IComponentProps &cloneFrom) noexcept {
return winrt::make<FabricXamlControlProps>(name, props, cloneFrom);
});

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<TUserData>();
userData->UpdateProps(
view,
newProps ? newProps.as<FabricXamlControlProps>() : nullptr,
oldProps ? oldProps.as<FabricXamlControlProps>() : nullptr);
});

compBuilder.SetUpdateLayoutMetricsHandler(
[](const winrt::Microsoft::ReactNative::ComponentView &view,
const winrt::Microsoft::ReactNative::LayoutMetrics &newLayoutMetrics,
const winrt::Microsoft::ReactNative::LayoutMetrics &oldLayoutMetrics) noexcept {
auto userData = view.UserData().as<TUserData>();
userData->UpdateLayoutMetrics(view, newLayoutMetrics, oldLayoutMetrics);
});

builder.SetUpdateEventEmitterHandler(
[](const winrt::Microsoft::ReactNative::ComponentView &view,
const winrt::Microsoft::ReactNative::EventEmitter &eventEmitter) noexcept {
auto userData = view.UserData().as<TUserData>();
userData->UpdateEventEmitter(std::make_shared<FabricXamlControlEventEmitter>(eventEmitter));
});

if constexpr (
&TUserData::FinalizeUpdate !=
&winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::FinalizeUpdate) {
builder.SetFinalizeUpdateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view,
winrt::Microsoft::ReactNative::ComponentViewUpdateMask mask) noexcept {
auto userData = view.UserData().as<TUserData>();
userData->FinalizeUpdate(view, mask);
});
}

if constexpr (
&TUserData::UpdateState != &winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::UpdateState) {
builder.SetUpdateStateHandler([](const winrt::Microsoft::ReactNative::ComponentView &view,
const winrt::Microsoft::ReactNative::IComponentState &newState) noexcept {
auto userData = view.UserData().as<TUserData>();
userData->UpdateState(view, newState);
});
}

if constexpr (
&TUserData::MountChildComponentView !=
&winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::MountChildComponentView) {
builder.SetMountChildComponentViewHandler(
[](const winrt::Microsoft::ReactNative::ComponentView &view,
const winrt::Microsoft::ReactNative::MountChildComponentViewArgs &args) noexcept {
auto userData = view.UserData().as<TUserData>();
return userData->MountChildComponentView(view, args);
});
}

if constexpr (
&TUserData::UnmountChildComponentView !=
&winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::UnmountChildComponentView) {
builder.SetUnmountChildComponentViewHandler(
[](const winrt::Microsoft::ReactNative::ComponentView &view,
const winrt::Microsoft::ReactNative::UnmountChildComponentViewArgs &args) noexcept {
auto userData = view.UserData().as<TUserData>();
return userData->UnmountChildComponentView(view, args);
});
}

compBuilder.SetViewComponentViewInitializer(
[name](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
auto userData = winrt::make_self<TUserData>(name);
if constexpr (
&TUserData::Initialize !=
&winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::Initialize) {
userData->Initialize(view);
}
view.UserData(*userData);
});

if constexpr (
&TUserData::CreateVisual != &winrt::Microsoft::ReactNative::Xaml::BaseFabricXamlControl<TUserData>::CreateVisual) {
compBuilder.SetCreateVisualHandler([](const winrt::Microsoft::ReactNative::ComponentView &view) noexcept {
// I suppose we should return null here since we're not backed by a visual?
// No, it looks like in CompositionViewComponentView.cpp ViewComponentView::ensureVisual that this will
// fail.
// TODO: Don't create a dummy visual here.
auto userData = view.UserData().as<TUserData>();
return userData->CreateVisual(view);
});
}

// Allow app to further customize the builder
if (builderCallback) {
builderCallback(compBuilder);
}
});
}

} // namespace winrt::Microsoft::ReactNative::Xaml

void RegisterXamlControl(winrt::Microsoft::ReactNative::IReactPackageBuilder const &packageBuilder) {
winrt::Microsoft::ReactNative::Xaml::CustomRegisterXamlControlNativeComponent<
winrt::Microsoft::ReactNative::Xaml::XamlControlComponentView>(
winrt::hstring{L"FX_StackPanel"},
packageBuilder,
[](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) {
UNREFERENCED_PARAMETER(builder);
// TODO: Do we need to do anything here?
});

winrt::Microsoft::ReactNative::Xaml::CustomRegisterXamlControlNativeComponent<
winrt::Microsoft::ReactNative::Xaml::XamlControlComponentView>(
winrt::hstring{L"FX_Button"},
packageBuilder,
[](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) {
UNREFERENCED_PARAMETER(builder);
// TODO: Do we need to do anything here?
});

winrt::Microsoft::ReactNative::Xaml::CustomRegisterXamlControlNativeComponent<
winrt::Microsoft::ReactNative::Xaml::XamlControlComponentView>(
winrt::hstring{L"FX_CalendarView"},
packageBuilder,
[](const winrt::Microsoft::ReactNative::Composition::IReactCompositionViewComponentBuilder &builder) {
UNREFERENCED_PARAMETER(builder);
// TODO: Do we need to do anything here?
});
}
Loading
Loading