using System; using System.Collections.Generic; using System.Diagnostics; using Avalonia.Controls.Platform; using Avalonia.Platform; using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Controls { public class NativeControlHost : Control { private TopLevel? _currentRoot; private INativeControlHostImpl? _currentHost; private INativeControlHostControlTopLevelAttachment? _attachment; private IPlatformHandle? _nativeControlHandle; private bool _queuedForDestruction; private bool _queuedForMoveResize; private readonly List _propertyChangedSubscriptions = new List(); protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; var visual = (IVisual)this; while (visual != null) { if (visual is Visual v) { v.PropertyChanged += PropertyChangedHandler; _propertyChangedSubscriptions.Add(v); } visual = visual.GetVisualParent(); } UpdateHost(); } private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.IsEffectiveValueChange && (e.Property == BoundsProperty || e.Property == IsVisibleProperty)) EnqueueForMoveResize(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; if (_propertyChangedSubscriptions != null) { foreach (var v in _propertyChangedSubscriptions) v.PropertyChanged -= PropertyChangedHandler; _propertyChangedSubscriptions.Clear(); } UpdateHost(); } private void UpdateHost() { _queuedForMoveResize = false; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; if (_currentHost != null) { // If there is an existing attachment, ensure that we are attached to the proper host or destroy the attachment if (_attachment != null && _attachment.AttachedTo != _currentHost) { if (_attachment != null) { if (_attachment.IsCompatibleWith(_currentHost)) { _attachment.AttachedTo = _currentHost; } else { _attachment.Dispose(); _attachment = null; } } } // If there is no attachment, but the control exists, // attempt to attach to to the current toplevel or destroy the control if it's incompatible if (_attachment == null && _nativeControlHandle != null) { if (_currentHost.IsCompatibleWith(_nativeControlHandle)) _attachment = _currentHost.CreateNewAttachment(_nativeControlHandle); else DestroyNativeControl(); } // There is no control handle an no attachment, create both if (_nativeControlHandle == null) { _attachment = _currentHost.CreateNewAttachment(parent => _nativeControlHandle = CreateNativeControlCore(parent)); } } else { // Immediately detach the control from the current toplevel if there is an existing attachment if (_attachment != null) _attachment.AttachedTo = null; // Don't destroy the control immediately, it might be just being reparented to another TopLevel if (_nativeControlHandle != null && !_queuedForDestruction) { _queuedForDestruction = true; Dispatcher.UIThread.Post(CheckDestruction, DispatcherPriority.Background); } } if (_attachment?.AttachedTo != _currentHost) return; TryUpdateNativeControlPosition(); } private Rect? GetAbsoluteBounds() { Debug.Assert(_currentRoot is not null); var bounds = Bounds; var position = this.TranslatePoint(default, _currentRoot); if (position == null) return null; return new Rect(position.Value, bounds.Size); } void EnqueueForMoveResize() { if(_queuedForMoveResize) return; _queuedForMoveResize = true; Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render); } public bool TryUpdateNativeControlPosition() { if (_currentHost == null) return false; var bounds = GetAbsoluteBounds(); if (IsEffectivelyVisible && bounds.HasValue) { if (bounds.Value.IsEmpty) return false; _attachment?.ShowInBounds(bounds.Value); } else _attachment?.HideWithSize(Bounds.Size); return true; } private void CheckDestruction() { _queuedForDestruction = false; if (_currentRoot == null) DestroyNativeControl(); } protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) { if (_currentHost == null) throw new InvalidOperationException(); return _currentHost.CreateDefaultChild(parent); } private void DestroyNativeControl() { if (_nativeControlHandle != null) { _attachment?.Dispose(); _attachment = null; DestroyNativeControlCore(_nativeControlHandle); _nativeControlHandle = null; } } protected virtual void DestroyNativeControlCore(IPlatformHandle control) { ((INativeControlHostDestroyableControlHandle)control).Destroy(); } } }