Skip to content

Commit

Permalink
Adds IViewManagerWithOnLayout interface for ABI VMs (#10424)
Browse files Browse the repository at this point in the history
* Adds IViewManagerWithOnLayout interface for ABI VMs

Core VMs can override `SetLayoutProps` to get a callback just before
native layout values are applied from Yoga. There is no option to
observe these changes from Yoga in ABI VMs.

This change introduces a new interface to receive a callback when
computed Yoga layout values are being applied to a native view.

We have a use case for this interface that is also dependent on #10422 (ensuring parent SetLayoutProps occur
after children), #10237 (adding an API to invoke nested Yoga layout for
a given React tag), and #10393 (custom measure func). Specifically, the following sequence occurs:
1. Prop updates cause Yoga layout to be applied on a root node
2. SetLayoutProps is invoked on children of custom VM,
Top/Left/Width/Height get set
3. SetLayoutProps/OnLayout is called on the custom VM
4. Custom VM runs logic to re-caclulate dimensions of each child in
OnLayout
5. Custom VM invokes XamlUIService::ApplyYogaLayout with computed
dimensions on each child.
6. Custom VM applies Top/Left values to children.

The custom measure function is used in this example to prevent children from having Yoga
layout computed twice.

* Change files

* Ensure we set the IViewManagerWithOnLayout instance to ABIViewManager

* yarn format
  • Loading branch information
rozele committed Sep 12, 2022
1 parent 7410abb commit 8f584d0
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Adds IViewManagerWithOnLayout interface for ABI VMs",
"packageName": "react-native-windows",
"email": "erozell@outlook.com",
"dependentChangeType": "patch"
}
17 changes: 16 additions & 1 deletion vnext/Microsoft.ReactNative/ABIViewManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ ABIViewManager::ABIViewManager(
m_viewManagerRequiresNativeLayout{viewManager.try_as<IViewManagerRequiresNativeLayout>()},
m_viewManagerWithChildren{viewManager.try_as<IViewManagerWithChildren>()},
m_viewManagerWithPointerEvents{viewManager.try_as<IViewManagerWithPointerEvents>()},
m_viewManagerWithDropViewInstance{viewManager.try_as<IViewManagerWithDropViewInstance>()} {
m_viewManagerWithDropViewInstance{viewManager.try_as<IViewManagerWithDropViewInstance>()},
m_viewManagerWithOnLayout{viewManager.try_as<IViewManagerWithOnLayout>()} {
if (m_viewManagerWithReactContext) {
m_viewManagerWithReactContext.ReactContext(winrt::make<implementation::ReactContext>(Mso::Copy(reactContext)));
}
Expand Down Expand Up @@ -237,6 +238,20 @@ void ABIViewManager::OnDropViewInstance(const ::Microsoft::ReactNative::XamlView
}
}

void ABIViewManager::SetLayoutProps(
::Microsoft::ReactNative::ShadowNodeBase &nodeToUpdate,
const ::Microsoft::ReactNative::XamlView &viewToUpdate,
float left,
float top,
float width,
float height) {
if (m_viewManagerWithOnLayout) {
m_viewManagerWithOnLayout.OnLayout(viewToUpdate.as<xaml::FrameworkElement>(), left, top, width, height);
}
// Call the base method to handle `SetLayoutProps` behavior
Super::SetLayoutProps(nodeToUpdate, viewToUpdate, left, top, width, height);
}

::Microsoft::ReactNative::ShadowNode *ABIViewManager::createShadow() const {
return new ABIShadowNode(
m_viewManagerRequiresNativeLayout && m_viewManagerRequiresNativeLayout.RequiresNativeLayout());
Expand Down
9 changes: 9 additions & 0 deletions vnext/Microsoft.ReactNative/ABIViewManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ class ABIViewManager : public ::Microsoft::ReactNative::FrameworkElementViewMana

void OnDropViewInstance(const ::Microsoft::ReactNative::XamlView &view);

void SetLayoutProps(
::Microsoft::ReactNative::ShadowNodeBase &nodeToUpdate,
const ::Microsoft::ReactNative::XamlView &viewToUpdate,
float left,
float top,
float width,
float height) override;

protected:
xaml::DependencyObject CreateViewCore(int64_t, const winrt::Microsoft::ReactNative::JSValueObject &props) override;

Expand All @@ -84,6 +92,7 @@ class ABIViewManager : public ::Microsoft::ReactNative::FrameworkElementViewMana
IViewManagerRequiresNativeLayout m_viewManagerRequiresNativeLayout;
IViewManagerWithPointerEvents m_viewManagerWithPointerEvents;
IViewManagerWithDropViewInstance m_viewManagerWithDropViewInstance;
IViewManagerWithOnLayout m_viewManagerWithOnLayout;

winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, ViewManagerPropertyType> m_nativeProps;
};
Expand Down
14 changes: 14 additions & 0 deletions vnext/Microsoft.ReactNative/IViewManager.idl
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,18 @@ namespace Microsoft.ReactNative
)
void OnDropViewInstance(XAML_NAMESPACE.FrameworkElement view);
};

[webhosthidden]
DOC_STRING(
"Enables view managers to receive callback when Yoga layout props are applied. "
)
interface IViewManagerWithOnLayout {
DOC_STRING(
"Invoked just before `onLayout` event is emitted for a view. "
"Note: this callback is invoked any time the Yoga node returns true from "
"YGNodeHasNewLayout. React Native Windows suppresses `onLayout` events "
"when the final layout has not changed, so this method may be called "
"without a corresponding `onLayout` event in JavaScript.")
void OnLayout(XAML_NAMESPACE.FrameworkElement view, Single left, Single top, Single width, Single height);
};
} // namespace Microsoft.ReactNative

0 comments on commit 8f584d0

Please sign in to comment.