using System; using System.Collections.Generic; using Avalonia.Threading; using Avalonia.VisualTree; namespace Avalonia.Input.TextInput { class TransformTrackingHelper : IDisposable { private IVisual? _visual; private bool _queuedForUpdate; private readonly EventHandler _propertyChangedHandler; private readonly List _propertyChangedSubscriptions = new List(); public TransformTrackingHelper() { _propertyChangedHandler = PropertyChangedHandler; } public void SetVisual(IVisual? visual) { Dispose(); _visual = visual; if (visual != null) { visual.AttachedToVisualTree += OnAttachedToVisualTree; visual.DetachedFromVisualTree -= OnDetachedFromVisualTree; if (visual.IsAttachedToVisualTree) SubscribeToParents(); UpdateMatrix(); } } public Matrix? Matrix { get; private set; } public event Action? MatrixChanged; public void Dispose() { if(_visual == null) return; UnsubscribeFromParents(); _visual.AttachedToVisualTree -= OnAttachedToVisualTree; _visual.DetachedFromVisualTree -= OnDetachedFromVisualTree; _visual = null; } private void SubscribeToParents() { var visual = _visual; // ReSharper disable once ConditionIsAlwaysTrueOrFalse // false positive while (visual != null) { if (visual is Visual v) { v.PropertyChanged += _propertyChangedHandler; _propertyChangedSubscriptions.Add(v); } visual = visual.VisualParent; } } private void UnsubscribeFromParents() { foreach (var v in _propertyChangedSubscriptions) v.PropertyChanged -= _propertyChangedHandler; _propertyChangedSubscriptions.Clear(); } void UpdateMatrix() { Matrix? matrix = null; if (_visual != null && _visual.VisualRoot != null) matrix = _visual.TransformToVisual(_visual.VisualRoot); if (Matrix != matrix) { Matrix = matrix; MatrixChanged?.Invoke(); } } private void OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs) { SubscribeToParents(); UpdateMatrix(); } private void EnqueueForUpdate() { if(_queuedForUpdate) return; _queuedForUpdate = true; Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.Render); } private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) { if (e.IsEffectiveValueChange && e.Property == Visual.BoundsProperty) EnqueueForUpdate(); } private void OnDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs) { UnsubscribeFromParents(); UpdateMatrix(); } } }