diff --git a/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj b/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj index a92232a0f26..e84397943fc 100644 --- a/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj +++ b/packages/e2e-test-app/windows/RNTesterApp/RNTesterApp.csproj @@ -24,6 +24,7 @@ true false 7.3 + false $(AssetTargetFallback);native diff --git a/packages/e2e-test-app/windows/RNTesterApp/packages.lock.json b/packages/e2e-test-app/windows/RNTesterApp/packages.lock.json index 3ac0f02233b..0f87de10120 100644 --- a/packages/e2e-test-app/windows/RNTesterApp/packages.lock.json +++ b/packages/e2e-test-app/windows/RNTesterApp/packages.lock.json @@ -24,7 +24,7 @@ "type": "Direct", "requested": "[0.11.0-ms.6, )", "resolved": "0.11.0-ms.6", - "contentHash": "7KGeDHh4QR4ua5+aSNAfuhj1sF2PBJbTHJ9m520xo1GZZRW4cxJlyNDNjW5t/sFGeHWw/Uhs7ZrWE2maL9BOEw==" + "contentHash": "WAVLsSZBV4p/3hNC3W67su7xu3f/ZMSKxu0ON7g2GaKRbkJmH0Qyif1IlzcJwtvR48kuOdfgPu7Bgtz3AY+gqg==" }, "XamlTreeDump": { "type": "Direct", @@ -32,6 +32,11 @@ "resolved": "1.0.9", "contentHash": "rvh/RZghhSG28PDL1dw56nTZRN0/ViV2TIja/ykU9FHn0gtM8pwtgD8Ebo1nobu0QnSjn8Cg6Ncu39VV19rkrw==" }, + "boost": { + "type": "Transitive", + "resolved": "1.76.0", + "contentHash": "p+w3YvNdXL8Cu9Fzrmexssu0tZbWxuf6ywsQqHjDlKFE5ojXHof1HIyMC3zDLfLnh80dIeFcEUAuR2Asg/XHRA==" + }, "Microsoft.Net.Native.Compiler": { "type": "Transitive", "resolved": "2.2.7-rel-27913-00", @@ -58,6 +63,16 @@ "resolved": "2.1.0", "contentHash": "ok+RPAtESz/9MUXeIEz6Lv5XAGQsaNmEYXMsgVALj4D7kqC8gveKWXWXbufLySR2fWrwZf8smyN5RmHu0e4BHA==" }, + "Microsoft.Windows.CppWinRT": { + "type": "Transitive", + "resolved": "2.0.211028.7", + "contentHash": "JBGI0c3WLoU6aYJRy9Qo0MLDQfObEp+d4nrhR95iyzf7+HOgjRunHDp/6eGFREd7xq3OI1mll9ecJrMfzBvlyg==" + }, + "Microsoft.Windows.SDK.BuildTools": { + "type": "Transitive", + "resolved": "10.0.22000.194", + "contentHash": "4L0P3zqut466SIqT3VBeLTNUQTxCBDOrTRymRuROCRJKazcK7ibLz9yAO1nKWRt50ttCj39oAa2Iuz9ZTDmLlg==" + }, "NETStandard.Library": { "type": "Transitive", "resolved": "2.0.3", @@ -134,11 +149,39 @@ "contentHash": "qF6RRZKaflI+LR1YODNyWYjq5YoX8IJ2wx5y8O+AW2xO+1t/Q6Mm+jQ38zJbWnmXbrcOqUYofn7Y3/KC6lTLBQ==" }, "automationchannel": { + "type": "Project", + "dependencies": { + "Microsoft.ReactNative": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0", + "Microsoft.Windows.CppWinRT": "2.0.211028.7" + } + }, + "common": { "type": "Project" }, - "microsoft.reactnative": { + "fmt": { "type": "Project" }, + "folly": { + "type": "Project", + "dependencies": { + "boost": "1.76.0", + "fmt": "1.0.0" + } + }, + "microsoft.reactnative": { + "type": "Project", + "dependencies": { + "Common": "1.0.0", + "Folly": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0", + "Microsoft.Windows.CppWinRT": "2.0.211028.7", + "Microsoft.Windows.SDK.BuildTools": "10.0.22000.194", + "ReactCommon": "1.0.0", + "ReactNative.Hermes.Windows": "0.11.0-ms.6", + "boost": "1.76.0" + } + }, "microsoft.reactnative.managed": { "type": "Project", "dependencies": { @@ -146,16 +189,25 @@ "Microsoft.ReactNative": "1.0.0" } }, + "reactcommon": { + "type": "Project", + "dependencies": { + "Folly": "1.0.0", + "boost": "1.76.0" + } + }, "reactnativepicker": { "type": "Project", "dependencies": { - "Microsoft.ReactNative": "1.0.0" + "Microsoft.ReactNative": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0" } }, "reactnativexaml": { "type": "Project", "dependencies": { - "Microsoft.ReactNative": "1.0.0" + "Microsoft.ReactNative": "1.0.0", + "Microsoft.UI.Xaml": "2.7.0" } } }, diff --git a/vnext/Microsoft.ReactNative.Managed/packages.lock.json b/vnext/Microsoft.ReactNative.Managed/packages.lock.json index decd73cbdce..85237bbbcfe 100644 --- a/vnext/Microsoft.ReactNative.Managed/packages.lock.json +++ b/vnext/Microsoft.ReactNative.Managed/packages.lock.json @@ -58,7 +58,7 @@ "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "2.1.0", - "contentHash": "GmkKfoyerqmsHMn7OZj0AKpcBabD+GaafqphvX2Mw406IwiJRy1pKcKqdCfKJfYmkRyJ6+e+RaUylgdJoDa1jQ==" + "contentHash": "ok+RPAtESz/9MUXeIEz6Lv5XAGQsaNmEYXMsgVALj4D7kqC8gveKWXWXbufLySR2fWrwZf8smyN5RmHu0e4BHA==" }, "Microsoft.SourceLink.Common": { "type": "Transitive", @@ -83,7 +83,7 @@ "NETStandard.Library": { "type": "Transitive", "resolved": "2.0.3", - "contentHash": "548M6mnBSJWxsIlkQHfbzoYxpiYFXZZSL00p4GHYv8PkiqFBnnT68mW5mGEsA/ch9fDO9GkPgkFQpWiXZN7mAQ==", + "contentHash": "st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.0" } diff --git a/vnext/Microsoft.ReactNative/ABIViewManager.cpp b/vnext/Microsoft.ReactNative/ABIViewManager.cpp index ec29aafa053..6b57d6e24f1 100644 --- a/vnext/Microsoft.ReactNative/ABIViewManager.cpp +++ b/vnext/Microsoft.ReactNative/ABIViewManager.cpp @@ -13,8 +13,124 @@ #include #include "ReactHost/MsoUtils.h" +#include "NativeMeasuringPanel.g.h" +#include +#include +#include + +namespace winrt::Microsoft::ReactNative::implementation { +struct NativeMeasuringPanel : NativeMeasuringPanelT { + using super = xaml::Controls::Grid; + NativeMeasuringPanel() = default; + + static YGSize + SelfMeasureFunc(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode) { + auto context = reinterpret_cast<::Microsoft::ReactNative::YogaContext *>(YGNodeGetContext(node)); + + // TODO: VEC context != nullptr, DefaultYogaSelfMeasureFunc expects a context. + + auto view = context->view; + auto element = view.as(); + + float constrainToWidth = + widthMode == YGMeasureMode::YGMeasureModeUndefined ? std::numeric_limits::max() : width; + float constrainToHeight = + heightMode == YGMeasureMode::YGMeasureModeUndefined ? std::numeric_limits::max() : height; + winrt::Windows::Foundation::Size availableSpace(constrainToWidth, constrainToHeight); + element.Measure(availableSpace); + + YGSize desiredSize{element.DesiredSize().Width, element.DesiredSize().Height}; + + return desiredSize; + } + + static implementation::NativeMeasuringPanel* GetFromWrapped(::Microsoft::ReactNative::XamlView view) + { + if (auto grid = view.try_as()) { + if (grid.Children().Size() == 1) { + if (auto nmp = grid.Children().GetAt(0).try_as()) { + return winrt::get_self(nmp); + } + } + } + return nullptr; + } + + Size MeasureOverride(Size const &available) { + if (Children().Size() == 0) { + return {}; + } + auto child = Children().GetAt(0).as(); + + const auto ret = NativeMeasuringPanelT::MeasureOverride(available); + const bool wasMeasuring = m_isMeasuring; + if (ret != m_last && (ret.Width * ret.Height) != 0 && !m_isMeasuring) { + m_isMeasuring = true; + m_last = ret; + if (auto nativeParent = this->Parent()) { + nativeParent.ClearValue(xaml::FrameworkElement::WidthProperty()); + nativeParent.ClearValue(xaml::FrameworkElement::HeightProperty()); + } + + auto x = 0; + auto &ctx = m_shadowNode->GetViewManager()->GetReactContext(); + if (auto nativeUIManager = ::Microsoft::ReactNative::GetNativeUIManager(ctx).lock()) { + nativeUIManager->DirtyYogaNode(m_shadowNode->m_tag); + nativeUIManager->ensureInBatch(); + nativeUIManager->onBatchComplete(); + } + m_isMeasuring = false; + } + cdebug << L"MeasureOverride " << winrt::get_class_name(child).c_str() << L" available (" << available.Width << L", " + << available.Height << L")" << L" (" << m_last.Width << L", " << m_last.Height << L")" << L" measuring = " + << wasMeasuring << L"\n\n"; + return ret; + } + + Size m_last{}; + ::Microsoft::ReactNative::ShadowNodeBase *m_shadowNode{nullptr}; + bool m_isMeasuring{false}; +}; +} // namespace winrt::Microsoft::ReactNative::implementation + +namespace winrt::Microsoft::ReactNative::factory_implementation { +struct NativeMeasuringPanel : NativeMeasuringPanelT {}; +} // namespace winrt::Microsoft::ReactNative::factory_implementation + +#if __has_include("NativeMeasuringPanel.g.cpp") +#include "NativeMeasuringPanel.g.cpp" +#endif + + namespace winrt::Microsoft::ReactNative { + +xaml::DependencyObject WrapNativeView(xaml::DependencyObject created) { + auto panel = NativeMeasuringPanel(); + panel.Children().Append(created.as()); + + auto grid = xaml::Controls::Grid(); + grid.Children().Append(panel); +#ifdef DEBUG + panel.Name(L"Measuring panel for native view"); + grid.Name(L"Wrapped native view"); +#endif + return grid; +} + +xaml::DependencyObject UnwrapNativeView(xaml::DependencyObject o) { + if (auto grid = o.try_as()) { + if (grid.Children().Size() == 1) { + if (auto panel = grid.Children().GetAt(0).try_as()) { + if (panel.Children().Size() == 1) { + return panel.Children().GetAt(0); + } + } + } + } + return nullptr; +} + class ABIShadowNode : public ::Microsoft::ReactNative::ShadowNodeBase { using Super = ShadowNodeBase; @@ -24,6 +140,14 @@ class ABIShadowNode : public ::Microsoft::ReactNative::ShadowNodeBase { return m_needsForceLayout; } + void createView(const winrt::Microsoft::ReactNative::JSValueObject &props) override { + Super::createView(props); + + auto nmp = implementation::NativeMeasuringPanel::GetFromWrapped(m_view); + + nmp->m_shadowNode = this; + } + private: bool m_needsForceLayout; }; @@ -55,15 +179,21 @@ const wchar_t *ABIViewManager::GetName() const { return m_name.c_str(); } + + xaml::DependencyObject ABIViewManager::CreateViewCore( int64_t, const winrt::Microsoft::ReactNative::JSValueObject &props) { + xaml::DependencyObject created{nullptr}; if (auto viewCreateProps = m_viewManager.try_as()) { auto view = viewCreateProps.CreateViewWithProperties( MakeJSValueTreeReader(winrt::Microsoft::ReactNative::JSValue(props.Copy()))); - return view.as(); + created = view.as(); + } else { + created = m_viewManager.CreateView(); } - return m_viewManager.CreateView(); + + return WrapNativeView(created); } void ABIViewManager::GetExportedViewConstants(const winrt::Microsoft::ReactNative::IJSValueWriter &writer) const { @@ -118,7 +248,7 @@ void ABIViewManager::UpdateProperties( } if (m_viewManagerWithNativeProperties) { - auto view = nodeToUpdate->GetView().as(); + auto view = UnwrapNativeView(nodeToUpdate->GetView()).as(); if (props.size() > 0) { m_viewManagerWithNativeProperties.UpdateProperties( @@ -178,10 +308,19 @@ void ABIViewManager::GetExportedCustomDirectEventTypeConstants( } void ABIViewManager::AddView(const xaml::DependencyObject &parent, const xaml::DependencyObject &child, int64_t index) { + auto parentView = UnwrapNativeView(parent); + auto childView = UnwrapNativeView(child); + if (!parentView || !childView) { + assert(false && "failed to unwrap a native view"); + return; + } + auto childNMP = child.as().Children().GetAt(0).as(); + childNMP.Children().Clear(); // need to unparent the real element before we parent it + if (m_viewManagerWithChildren) { - m_viewManagerWithChildren.AddView(parent.as(), child.as(), index); + m_viewManagerWithChildren.AddView(parentView.as(), childView.as(), index); } else { - Super::AddView(parent, child, index); + Super::AddView(parentView, childView, index); } } @@ -215,7 +354,7 @@ void ABIViewManager::ReplaceChild( YGMeasureFunc ABIViewManager::GetYogaCustomMeasureFunc() const { if (m_viewManagerRequiresNativeLayout && m_viewManagerRequiresNativeLayout.RequiresNativeLayout()) { - return ::Microsoft::ReactNative::DefaultYogaSelfMeasureFunc; + return implementation::NativeMeasuringPanel::SelfMeasureFunc; } else { return nullptr; } diff --git a/vnext/Microsoft.ReactNative/Views/ViewManagerBase.cpp b/vnext/Microsoft.ReactNative/Views/ViewManagerBase.cpp index fbb4d17cd09..362e95e47d6 100644 --- a/vnext/Microsoft.ReactNative/Views/ViewManagerBase.cpp +++ b/vnext/Microsoft.ReactNative/Views/ViewManagerBase.cpp @@ -325,6 +325,19 @@ void ViewManagerBase::NotifyUnimplementedProperty( #endif // DEBUG } +static xaml::DependencyObject UnwrapNativeView(xaml::DependencyObject o) { + if (auto grid = o.try_as()) { + if (grid.Children().Size() == 1) { + if (auto panel = grid.Children().GetAt(0).try_as()) { + if (panel.Children().Size() == 1) { + return panel.Children().GetAt(0); + } + } + } + } + return nullptr; +} + void ViewManagerBase::SetLayoutProps( ShadowNodeBase &nodeToUpdate, const XamlView &viewToUpdate, @@ -343,6 +356,10 @@ void ViewManagerBase::SetLayoutProps( } } + if (auto unwrapped = UnwrapNativeView(viewToUpdate)) { + auto cn = winrt::get_class_name(unwrapped); + } + auto element = viewToUpdate.as(); if (element == nullptr) { // TODO: Assert diff --git a/vnext/Microsoft.ReactNative/Views/cppwinrt/ViewPanel.idl b/vnext/Microsoft.ReactNative/Views/cppwinrt/ViewPanel.idl index caee217953f..29a03dc8e37 100644 --- a/vnext/Microsoft.ReactNative/Views/cppwinrt/ViewPanel.idl +++ b/vnext/Microsoft.ReactNative/Views/cppwinrt/ViewPanel.idl @@ -53,4 +53,12 @@ namespace Microsoft.ReactNative ViewPanel GetPanel(); } + + + [default_interface] + [webhosthidden] + runtimeclass NativeMeasuringPanel : XAML_NAMESPACE.Controls.Grid { + NativeMeasuringPanel(); + } + }