Adds VM interface for custom measurement
Description
Type of Change
Erase all that don't apply.
- New feature (non-breaking change which adds functionality)
Why
While the default self measure function implemented in ViewManagerBase works in most scenarios, it may not work for all use cases. This change introduces an ABI VM interface to callback when Yoga measurement is being performed.
For example, if we want to implement re-entrant Yoga layout on a "native layout" view we have two options:
- Something analogous to what was added in #10237, which would require that you invoke Yoga layout from XAML layout via MeasureOverride or ArrangeOverride. The main limitation of this is that recursive layout of children nested in a native layout view will always have their layout delayed by at least one frame because the sequence is:
1. Yoga runs from root view and applies updated layout to XAML view
2. XAML runs native layout, invoking MeasureOverride / ArrangeOverride on the native layout view
3. Yoga is recursively invoked from XAML layout and native layout properties are applied to children
4. XAML renders changes up to native layout view
5. XAML layout runs again with new layout properties from native layout view's children
6. XAML renders changes to native layout view's children
- Alternatively, we can "listen" for native layout changes that are going to occur during the Yoga layout pass via the self-measure callback, update some state on the native layout view's children, and invoke recursive layout on the children before calling
ViewManagerBase::SetLayoutPropson any view. This will allow XAML to render the entire tree in one pass.
This change opens up the opportunity for the second option, though we'll still need one more change to allow children of native layout views to invoke Yoga layout (coming in a future PR).
What
This change adds a new ABI interface for view managers to define a custom measure function.
Since Yoga self measure functions are C-style function pointers, we can't pass instance methods to Yoga. Thus we needed to use a trampoline approach to provide a static method that can subsequently call the appropriate measure instance method on the view manager (that has access to the IViewManagerWithNativeLayout intsance as well). In order to support this, we needed to add a pointer to the view manager to the YogaContext that gets passed back to the measure function.
Adding the VM pointer to YogaContext will add 8 bytes for each Yoga node, which is not great, but YogaContext is only added to Text, TextInput, and any ABI view managers with native layout, so this probably shouldn't be a concern.
Testing
I added a test native view manager to Playground-win32 and ensured that the measure callback was being invoked and the measurement values it set were being applied to the custom view.
Optional: Describe the tests that you ran locally to verify your changes.
Microsoft Reviewers: Open in CodeFlow
Converting to draft, as we may also need to set Yoga props on children in our app, and we may only be able to do this reliably with a core view manager implementation.
Bringing back from draft. Setting a custom measure function is the best way to prevent Yoga from recursing into children. I also updated the name of the interface to further differentiate from IViewManagerRequiresNativeLayout. IMO, IViewManagerRequiresNativeLayout basically implies "let XAML do it's thing for layout", where as IViewManagerWithMeasure implies "custom VM is going to control measurement and layout of children independent of underlying implementation"
Back to draft - we'll have to think about how feasible this would be with background thread layout in Fabric. Immediate thoughts that come to mind is that there might be 2 flavors of this interface - one that is safe to be called on a background thread (because it implies that the consumer should not try to do anything like measure a XAML view) and one that can be called on the UI thread (and does whatever it needs to do to invalidate the tree from the UI thread in Fabric).