using System; using System.Collections.Generic; using System.ComponentModel; using Xamarin.Forms.Internals; using ElmSharp; using ESize = ElmSharp.Size; using ERect = ElmSharp.Rect; using EFocusDirection = ElmSharp.FocusDirection; using Specific = Xamarin.Forms.PlatformConfiguration.TizenSpecific.VisualElement; using XFocusDirection = Xamarin.Forms.PlatformConfiguration.TizenSpecific.FocusDirection; namespace Xamarin.Forms.Platform.Tizen { /// /// Base class for rendering of a Xamarin element. /// public abstract class VisualElementRenderer : IVisualElementRenderer, IEffectControlProvider where TElement : VisualElement { /// /// Holds registered element changed handlers. /// readonly List> _elementChangedHandlers = new List>(); /// /// Handler for property changed events. /// PropertyChangedEventHandler _propertyChangedHandler; EventHandler> _batchCommittedHandler; /// /// Flags which control status of renderer. /// VisualElementRendererFlags _flags = VisualElementRendererFlags.None; /// /// Holds the native view. /// EvasObject _view; Dictionary> _propertyHandlersWithInit = new Dictionary>(); Dictionary _propertyHandlers = new Dictionary(); HashSet _batchedProperties = new HashSet(); int _layoutCallback = 0; bool _movedCallbackEnabled = false; /// /// Default constructor. /// protected VisualElementRenderer() { RegisterPropertyHandler(VisualElement.IsVisibleProperty, UpdateIsVisible); RegisterPropertyHandler(VisualElement.OpacityProperty, UpdateOpacity); RegisterPropertyHandler(VisualElement.IsEnabledProperty, UpdateIsEnabled); RegisterPropertyHandler(VisualElement.InputTransparentProperty, UpdateInputTransparent); RegisterPropertyHandler(VisualElement.BackgroundColorProperty, UpdateBackgroundColor); RegisterPropertyHandler(Specific.StyleProperty, UpdateThemeStyle); RegisterPropertyHandler(Specific.IsFocusAllowedProperty, UpdateFocusAllowed); RegisterPropertyHandler(Specific.NextFocusDirectionProperty, UpdateFocusDirection); RegisterPropertyHandler(Specific.NextFocusUpViewProperty, UpdateFocusUpView); RegisterPropertyHandler(Specific.NextFocusDownViewProperty, UpdateFocusDownView); RegisterPropertyHandler(Specific.NextFocusLeftViewProperty, UpdateFocusLeftView); RegisterPropertyHandler(Specific.NextFocusRightViewProperty, UpdateFocusRightView); RegisterPropertyHandler(Specific.NextFocusBackViewProperty, UpdateFocusBackView); RegisterPropertyHandler(Specific.NextFocusForwardViewProperty, UpdateFocusForwardView); RegisterPropertyHandler(Specific.ToolTipProperty, UpdateToolTip); RegisterPropertyHandler(VisualElement.AnchorXProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.AnchorYProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.ScaleProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.RotationProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.RotationXProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.RotationYProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.TranslationXProperty, ApplyTransformation); RegisterPropertyHandler(VisualElement.TranslationYProperty, ApplyTransformation); } ~VisualElementRenderer() { Dispose(false); } event EventHandler ElementChanged { add { _elementChangedHandlers.Add(value); } remove { _elementChangedHandlers.Remove(value); } } /// /// Gets the Xamarin element associated with this renderer. /// public TElement Element { get; private set; } VisualElement IVisualElementRenderer.Element { get { return this.Element; } } public EvasObject NativeView { get { return _view; } } protected bool IsDisposed => _flags.HasFlag(VisualElementRendererFlags.Disposed); /// /// Releases all resource used by the object. /// /// Call when you are finished using the /// . The method /// leaves the in an unusable state. /// After calling , you must release all references to the /// so the garbage collector can reclaim /// the memory that the was occupying. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } public SizeRequest GetDesiredSize(double widthConstraint, double heightConstraint) { if (null == NativeView) { return new SizeRequest(new Size(0, 0)); } else { int availableWidth = Forms.ConvertToScaledPixel(widthConstraint); int availableHeight = Forms.ConvertToScaledPixel(heightConstraint); if (availableWidth < 0) availableWidth = int.MaxValue; if (availableHeight < 0) availableHeight = int.MaxValue; Size measured; var nativeViewMeasurable = NativeView as Native.IMeasurable; if (nativeViewMeasurable != null) { measured = nativeViewMeasurable.Measure(availableWidth, availableHeight).ToDP(); } else { measured = Measure(availableWidth, availableHeight).ToDP(); } return new SizeRequest(measured, MinimumSize()); } } /// /// Sets the element associated with this renderer. /// public void SetElement(TElement newElement) { if (newElement == null) { throw new ArgumentNullException("newElement"); } TElement oldElement = Element; if (oldElement != null) { throw new InvalidOperationException("oldElement"); } Element = newElement; if (_propertyChangedHandler == null) { _propertyChangedHandler = new PropertyChangedEventHandler(OnElementPropertyChanged); } if (_batchCommittedHandler == null) { _batchCommittedHandler = OnBatchCommitted; } // send notification OnElementChanged(new ElementChangedEventArgs(oldElement, newElement)); // store renderer for the new element Platform.SetRenderer(newElement, this); // add children var logicalChildren = (newElement as IElementController).LogicalChildren; foreach (Element child in logicalChildren) { AddChild(child); } OnElementReady(); } public void UpdateNativeGeometry() { var updatedGeometry = new Rectangle(ComputeAbsolutePoint(Element), new Size(Element.Width, Element.Height)).ToPixel(); if (NativeView.Geometry != updatedGeometry) { NativeView.Geometry = updatedGeometry; ApplyTransformation(); } } void IVisualElementRenderer.SetElement(VisualElement element) { TElement tElement = element as TElement; if (tElement == null) { throw new ArgumentException("Element is not of type " + typeof(TElement), "Element"); } SetElement(tElement); } /// /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform. /// /// The effect to register. void IEffectControlProvider.RegisterEffect(Effect effect) { RegisterEffect(effect); } /// /// Registers the effect with the element by establishing the parent-child relations needed for rendering on the specific platform. /// /// The effect to register. protected void RegisterEffect(Effect effect) { var platformEffect = effect as PlatformEffect; if (platformEffect != null) { OnRegisterEffect(platformEffect); } } protected virtual void UpdateLayout() { // we're updating the coordinates of native control only if they were modified // via Xamarin (IsNativeLayouting() returns false); // otherwise native control is already in the right place if (!IsNativeLayouting() && null != NativeView) { UpdateNativeGeometry(); } // we're updating just immediate children // To update the relative postion of children var logicalChildren = (Element as IElementController).LogicalChildren; foreach (var child in logicalChildren) { Platform.GetRenderer(child)?.UpdateNativeGeometry(); } } /// /// Disposes of underlying resources. /// /// True if the memory release was requested on demand. protected virtual void Dispose(bool disposing) { if (IsDisposed) { return; } _flags |= VisualElementRendererFlags.Disposed; if (disposing) { if (Element != null) { Element.PropertyChanged -= _propertyChangedHandler; Element.BatchCommitted -= _batchCommittedHandler; Element.ChildAdded -= OnChildAdded; Element.ChildRemoved -= OnChildRemoved; Element.ChildrenReordered -= OnChildrenReordered; Element.FocusChangeRequested -= OnFocusChangeRequested; Element.Layout(new Rectangle(0, 0, -1, -1)); var logicalChildren = (Element as IElementController).LogicalChildren; foreach (var child in logicalChildren) { Platform.GetRenderer(child)?.Dispose(); } if (Platform.GetRenderer(Element) == this) { Platform.SetRenderer(Element, (IVisualElementRenderer)null); } Element = default(TElement); } if (NativeView != null) { NativeView.Deleted -= NativeViewDeleted; NativeView.Unrealize(); SetNativeView(null); } } } /// /// Notification that the associated element has changed. /// /// Event parameters. protected virtual void OnElementChanged(ElementChangedEventArgs e) { if (null != e.OldElement) { e.OldElement.PropertyChanged -= _propertyChangedHandler; e.OldElement.BatchCommitted -= _batchCommittedHandler; e.OldElement.ChildAdded -= OnChildAdded; e.OldElement.ChildRemoved -= OnChildRemoved; e.OldElement.ChildrenReordered -= OnChildrenReordered; e.OldElement.FocusChangeRequested -= OnFocusChangeRequested; Element.Layout(new Rectangle(0, 0, -1, -1)); var controller = e.OldElement as IElementController; if (controller != null && controller.EffectControlProvider == this) { controller.EffectControlProvider = null; } } if (null != e.NewElement) { e.NewElement.PropertyChanged += _propertyChangedHandler; e.NewElement.BatchCommitted += _batchCommittedHandler; e.NewElement.ChildAdded += OnChildAdded; e.NewElement.ChildRemoved += OnChildRemoved; e.NewElement.ChildrenReordered += OnChildrenReordered; e.NewElement.FocusChangeRequested += OnFocusChangeRequested; UpdateAllProperties(true); var controller = e.NewElement as IElementController; if (controller != null) { controller.EffectControlProvider = this; } } // TODO: handle the event } /// /// Notification that the property of the associated element has changed. /// /// Object which sent the notification. /// Event parameters. protected virtual void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) { if (Element.Batched) { if (e.PropertyName == VisualElement.XProperty.PropertyName || e.PropertyName == VisualElement.YProperty.PropertyName || e.PropertyName == VisualElement.WidthProperty.PropertyName || e.PropertyName == VisualElement.HeightProperty.PropertyName) { _flags |= VisualElementRendererFlags.NeedsLayout; } else if (e.PropertyName == VisualElement.TranslationXProperty.PropertyName || e.PropertyName == VisualElement.TranslationYProperty.PropertyName || e.PropertyName == VisualElement.RotationProperty.PropertyName || e.PropertyName == VisualElement.RotationXProperty.PropertyName || e.PropertyName == VisualElement.RotationYProperty.PropertyName || e.PropertyName == VisualElement.ScaleProperty.PropertyName || e.PropertyName == VisualElement.AnchorXProperty.PropertyName || e.PropertyName == VisualElement.AnchorYProperty.PropertyName) { _flags |= VisualElementRendererFlags.NeedsTransformation; } else { _batchedProperties.Add(e.PropertyName); } return; } Action init; if (_propertyHandlersWithInit.TryGetValue(e.PropertyName, out init)) { init(false); } else { Action handler; if (_propertyHandlers.TryGetValue(e.PropertyName, out handler)) { handler(); } } } /// /// Updates the attached event handlers, sets the native control. /// protected void SetNativeControl(EvasObject control) { if (NativeView != null) { if (_movedCallbackEnabled) { NativeView.Moved -= OnMoved; } NativeView.Deleted -= NativeViewDeleted; } Widget widget = NativeView as Widget; if (widget != null) { widget.Focused -= OnFocused; widget.Unfocused -= OnUnfocused; } SetNativeView(control); if (NativeView != null) { NativeView.Deleted += NativeViewDeleted; if (_movedCallbackEnabled) { NativeView.Moved += OnMoved; } } widget = NativeView as Widget; if (widget != null) { widget.Focused += OnFocused; widget.Unfocused += OnUnfocused; } } void NativeViewDeleted(object sender, EventArgs e) { Dispose(); } void OnBatchCommitted(object sender, EventArg e) { if (_flags.HasFlag(VisualElementRendererFlags.NeedsLayout)) { if (!IsNativeLayouting()) { UpdateNativeGeometry(); // UpdateLayout already updates transformation, clear NeedsTranformation flag then _flags &= ~VisualElementRendererFlags.NeedsTransformation; } _flags ^= VisualElementRendererFlags.NeedsLayout; } if (_flags.HasFlag(VisualElementRendererFlags.NeedsTransformation)) { ApplyTransformation(); _flags ^= VisualElementRendererFlags.NeedsTransformation; } foreach (string property in _batchedProperties) { OnElementPropertyChanged(this, new PropertyChangedEventArgs(property)); } _batchedProperties.Clear(); } /// /// Registers a handler which is executed when specified property changes. /// /// Handled property. /// Action to be executed when property changes. protected void RegisterPropertyHandler(BindableProperty property, Action handler) { RegisterPropertyHandler(property.PropertyName, handler); } /// /// Registers a handler which is executed when specified property changes. /// /// Name of the handled property. /// Action to be executed when property changes. protected void RegisterPropertyHandler(string name, Action handler) { _propertyHandlersWithInit.Add(name, handler); } /// /// Registers a handler which is executed when specified property changes. /// /// Handled property. /// Action to be executed when property changes. protected void RegisterPropertyHandler(BindableProperty property, Action handler) { RegisterPropertyHandler(property.PropertyName, handler); } /// /// Registers a handler which is executed when specified property changes. /// /// Name of the handled property. /// Action to be executed when property changes. protected void RegisterPropertyHandler(string name, Action handler) { _propertyHandlers.Add(name, handler); } /// /// Updates all registered properties. /// /// If set to true the method is called for an uninitialized object. protected void UpdateAllProperties(bool initialization) { foreach (KeyValuePair> kvp in _propertyHandlersWithInit) { kvp.Value(initialization); } foreach (KeyValuePair kvp in _propertyHandlers) { kvp.Value(); } } /// /// Called when Element has been set and its native counterpart /// is properly initialized. /// protected virtual void OnElementReady() { } protected void DoLayout(Native.LayoutEventArgs e) { EnterNativeLayoutCallback(); if (e.HasChanged) { var bound = e.Geometry.ToDP(); bound.X = Element.X; bound.Y = Element.Y; Element.Layout(bound); UpdateLayout(); } LeaveNativeLayoutCallback(); } protected virtual Size MinimumSize() { return new ESize(NativeView.MinimumWidth, NativeView.MinimumHeight).ToDP(); } /// /// Calculates how much space this element should take, given how much room there is. /// /// a desired dimensions of the element protected virtual ESize Measure(int availableWidth, int availableHeight) { return new ESize(NativeView.MinimumWidth, NativeView.MinimumHeight); } protected virtual void UpdateBackgroundColor() { if (NativeView is Widget) { (NativeView as Widget).BackgroundColor = Element.BackgroundColor.ToNative(); } else { Log.Warn("{0} uses {1} which does not support background color", this, NativeView); } } protected virtual void UpdateOpacity() { if (NativeView is Widget) { (NativeView as Widget).Opacity = (int)(Element.Opacity * 255.0); } else { Log.Warn("{0} uses {1} which does not support opacity", this, NativeView); } } static double ComputeAbsoluteX(VisualElement e) { return e.X + (e.RealParent is VisualElement ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).NativeView.Geometry.X) : 0.0); } static double ComputeAbsoluteY(VisualElement e) { return e.Y + (e.RealParent is VisualElement ? Forms.ConvertToScaledDP(Platform.GetRenderer(e.RealParent).NativeView.Geometry.Y) : 0.0); } static Point ComputeAbsolutePoint(VisualElement e) { return new Point(ComputeAbsoluteX(e), ComputeAbsoluteY(e)); } /// /// Handles focus events. /// void OnFocused(object sender, EventArgs e) { if (null != Element) { Element.SetValue(VisualElement.IsFocusedPropertyKey, true); } } /// /// Handles unfocus events. /// void OnUnfocused(object sender, EventArgs e) { if (null != Element) { Element.SetValue(VisualElement.IsFocusedPropertyKey, false); } } /// /// Sets the native control, updates the control's properties. /// void SetNativeView(EvasObject control) { _view = control; } /// /// Adds a new child if it's derived from the VisualElement class. Otherwise this method does nothing. /// /// Child to be added. void AddChild(Element child) { VisualElement vElement = child as VisualElement; if (vElement != null) { var childRenderer = Platform.GetOrCreateRenderer(vElement); // if the native view can have children, attach the new child if (NativeView is Native.IContainable) { (NativeView as Native.IContainable).Children.Add(childRenderer.NativeView); } } } void RemoveChild(VisualElement view) { var renderer = Platform.GetRenderer(view); var containerObject = NativeView as Native.IContainable; if (containerObject != null) { containerObject.Children.Remove(renderer.NativeView); } renderer.Dispose(); } void OnChildAdded(object sender, ElementEventArgs e) { var view = e.Element as VisualElement; if (view != null) { AddChild(view); } // changing the order makes sense only in case of Layouts if (Element is Layout) { IElementController controller = Element as IElementController; if (controller.LogicalChildren[controller.LogicalChildren.Count - 1] != view) { EnsureChildOrder(); } } } void OnChildRemoved(object sender, ElementEventArgs e) { var view = e.Element as VisualElement; if (view != null) { RemoveChild(view); } } void OnChildrenReordered(object sender, EventArgs e) { EnsureChildOrder(); Layout layout = Element as Layout; if (layout != null) { layout.InvalidateMeasureNonVirtual(InvalidationTrigger.MeasureChanged); layout.ForceLayout(); } } void OnFocusChangeRequested(object sender, VisualElement.FocusRequestArgs e) { Widget widget = NativeView as Widget; if (widget == null) { Log.Warn("{0} is not a widget, it cannot receive focus", NativeView); return; } widget.SetFocus(e.Focus); e.Result = true; } /// /// On register the effect /// /// The effect to register. void OnRegisterEffect(PlatformEffect effect) { effect.SetContainer(Element.Parent == null ? null : Platform.GetRenderer(Element.Parent).NativeView); effect.SetControl(NativeView); } void OnMoved(object sender, EventArgs e) { ApplyTransformation(); } void EnsureChildOrder() { var logicalChildren = (Element as IElementController).LogicalChildren; for (var i = logicalChildren.Count - 1; i >= 0; --i) { var element = logicalChildren[i] as VisualElement; if (element != null) { Platform.GetRenderer(element).NativeView?.Lower(); } } } void UpdateIsVisible() { if (null != NativeView) { if (Element.IsVisible) { NativeView.Show(); } else { NativeView.Hide(); } } } /// /// Updates the IsEnabled property. /// void UpdateIsEnabled() { var widget = NativeView as Widget; if (widget != null) { widget.IsEnabled = Element.IsEnabled; } } /// /// Updates the InputTransparent property. /// void UpdateInputTransparent() { NativeView.PassEvents = Element.InputTransparent; } protected virtual void UpdateThemeStyle() { } void UpdateFocusAllowed(bool initialize) { if (!initialize) { var widget = NativeView as Widget; if (widget != null) { widget.AllowFocus(Specific.IsFocusAllowed(Element)); } else { Log.Warn("{0} uses {1} which does not support Focus management", this, NativeView); } } } void UpdateFocusDirection(bool initialize) { var direction = Specific.GetNextFocusDirection(Element); if (!initialize && direction != XFocusDirection.None) { var widget = NativeView as Widget; if (widget != null) { widget.FocusNext(ConvertToNativeFocusDirection(direction)); } else { Log.Warn("{0} uses {1} which does not support Focus management", this, NativeView); } } } void UpdateToolTip() { var tooltip = Specific.GetToolTip(Element); if (tooltip != null) { NativeView.SetTooltipText(tooltip); } else { NativeView.UnsetTooltip(); } } void SetNextFocusViewInternal(string direction) { var widget = NativeView as Widget; if (widget != null) { EvasObject nativeControl; switch (direction) { case XFocusDirection.Back: nativeControl = Platform.GetRenderer(Specific.GetNextFocusBackView(Element))?.NativeView; break; case XFocusDirection.Forward: nativeControl = Platform.GetRenderer(Specific.GetNextFocusForwardView(Element))?.NativeView; break; case XFocusDirection.Up: nativeControl = Platform.GetRenderer(Specific.GetNextFocusUpView(Element))?.NativeView; break; case XFocusDirection.Down: nativeControl = Platform.GetRenderer(Specific.GetNextFocusDownView(Element))?.NativeView; break; case XFocusDirection.Right: nativeControl = Platform.GetRenderer(Specific.GetNextFocusRightView(Element))?.NativeView; break; case XFocusDirection.Left: nativeControl = Platform.GetRenderer(Specific.GetNextFocusLeftView(Element))?.NativeView; break; default: nativeControl = null; break; } if (nativeControl != null) { widget.SetNextFocusObject(nativeControl, ConvertToNativeFocusDirection(direction)); } } else { Log.Warn("{0} uses {1} which does not support Focus management", this, NativeView); } } void UpdateFocusUpView(bool initialize) { if (!initialize && Specific.GetNextFocusUpView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Up); } } void UpdateFocusDownView(bool initialize) { if (!initialize && Specific.GetNextFocusDownView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Down); } } void UpdateFocusLeftView(bool initialize) { if (!initialize && Specific.GetNextFocusLeftView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Left); } } void UpdateFocusRightView(bool initialize) { if (!initialize && Specific.GetNextFocusRightView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Right); } } void UpdateFocusBackView(bool initialize) { if (!initialize && Specific.GetNextFocusBackView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Back); } } void UpdateFocusForwardView(bool initialize) { if (!initialize && Specific.GetNextFocusForwardView(Element) != null) { SetNextFocusViewInternal(XFocusDirection.Forward); } } void ApplyRotation(EvasMap map, ERect geometry, ref bool changed) { var rotationX = Element.RotationX; var rotationY = Element.RotationY; var rotationZ = Element.Rotation; var anchorX = Element.AnchorX; var anchorY = Element.AnchorY; // apply rotations if (rotationX != 0 || rotationY != 0 || rotationZ != 0) { map.Rotate3D(rotationX, rotationY, rotationZ, (int)(geometry.X + geometry.Width * anchorX), (int)(geometry.Y + geometry.Height * anchorY), 0); changed = true; } } void ApplyScale(EvasMap map, ERect geometry, ref bool changed) { var scale = Element.Scale; // apply scale factor if (scale != 1.0) { map.Zoom(scale, scale, geometry.X + (int)(geometry.Width * Element.AnchorX), geometry.Y + (int)(geometry.Height * Element.AnchorY)); changed = true; } } void ApplyTranslation(EvasMap map, ERect geometry, ref bool changed) { var shiftX = Forms.ConvertToScaledPixel(Element.TranslationX); var shiftY = Forms.ConvertToScaledPixel(Element.TranslationY); // apply translation, i.e. move/shift the object a little if (shiftX != 0 || shiftY != 0) { if (changed) { // special care is taken to apply the translation last Point3D p; for (int i = 0; i < 4; i++) { p = map.GetPointCoordinate(i); p.X += shiftX; p.Y += shiftY; map.SetPointCoordinate(i, p); } } else { // in case when we only need translation, then construct the map in a simpler way geometry.X += shiftX; geometry.Y += shiftY; map.PopulatePoints(geometry, 0); changed = true; } } } protected virtual void ApplyTransformation() { if (null == NativeView) { Log.Error("Trying to apply transformation to the non-existent native control"); return; } // prepare the EFL effect structure ERect geometry = NativeView.Geometry; EvasMap map = new EvasMap(4); map.PopulatePoints(geometry, 0); bool changed = false; ApplyRotation(map, geometry, ref changed); ApplyScale(map, geometry, ref changed); ApplyTranslation(map, geometry, ref changed); NativeView.IsMapEnabled = changed; if (changed) { NativeView.EvasMap = map; if (!_movedCallbackEnabled) { _movedCallbackEnabled = true; NativeView.Moved += OnMoved; } } else { if (_movedCallbackEnabled) { _movedCallbackEnabled = false; NativeView.Moved -= OnMoved; } } } EFocusDirection ConvertToNativeFocusDirection(string direction) { if (direction == XFocusDirection.Back) return EFocusDirection.Previous; if (direction == XFocusDirection.Forward) return EFocusDirection.Next; if (direction == XFocusDirection.Up) return EFocusDirection.Up; if (direction == XFocusDirection.Down) return EFocusDirection.Down; if (direction == XFocusDirection.Right) return EFocusDirection.Right; if (direction == XFocusDirection.Left) return EFocusDirection.Left; return EFocusDirection.Next; } void EnterNativeLayoutCallback() { _layoutCallback++; } void LeaveNativeLayoutCallback() { _layoutCallback--; } bool IsNativeLayouting() { return _layoutCallback > 0; } } }