using System.Collections; using System.Collections.Specialized; using System.Linq; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Documents; using Avalonia.Controls.Primitives; using Avalonia.Media; using Avalonia.VisualTree; using OF_DL.Gui.ViewModels; namespace OF_DL.Gui.Controls; public class FileNameFormatOverlayTextBlock : TextBlock { public static readonly StyledProperty?> SegmentsProperty = AvaloniaProperty.Register?>(nameof(Segments)); public static readonly StyledProperty SourceTextBoxProperty = AvaloniaProperty.Register(nameof(SourceTextBox)); private INotifyCollectionChanged? _segmentsCollection; private TextBox? _attachedTextBox; private ScrollViewer? _attachedScrollViewer; static FileNameFormatOverlayTextBlock() { SegmentsProperty.Changed.AddClassHandler(OnSegmentsChanged); SourceTextBoxProperty.Changed.AddClassHandler(OnSourceTextBoxChanged); } public IEnumerable? Segments { get => GetValue(SegmentsProperty); set => SetValue(SegmentsProperty, value); } public TextBox? SourceTextBox { get => GetValue(SourceTextBoxProperty); set => SetValue(SourceTextBoxProperty, value); } protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); DetachSegmentsCollection(); AttachSegmentsCollection(Segments); AttachSourceTextBox(SourceTextBox); RebuildInlines(); } protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { DetachSourceTextBox(); DetachSegmentsCollection(); base.OnDetachedFromVisualTree(e); } private static void OnSegmentsChanged( FileNameFormatOverlayTextBlock sender, AvaloniaPropertyChangedEventArgs e) { sender.DetachSegmentsCollection(); sender.AttachSegmentsCollection(e.NewValue as IEnumerable); sender.RebuildInlines(); } private static void OnSourceTextBoxChanged( FileNameFormatOverlayTextBlock sender, AvaloniaPropertyChangedEventArgs e) { sender.AttachSourceTextBox(e.NewValue as TextBox); } private void AttachSegmentsCollection(IEnumerable? segments) { _segmentsCollection = segments as INotifyCollectionChanged; if (_segmentsCollection is not null) { _segmentsCollection.CollectionChanged += OnSegmentsCollectionChanged; } } private void DetachSegmentsCollection() { if (_segmentsCollection is null) { return; } _segmentsCollection.CollectionChanged -= OnSegmentsCollectionChanged; _segmentsCollection = null; } private void OnSegmentsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { RebuildInlines(); } private void AttachSourceTextBox(TextBox? textBox) { if (ReferenceEquals(_attachedTextBox, textBox)) { UpdateOverlayOffset(); return; } DetachSourceTextBox(); _attachedTextBox = textBox; if (_attachedTextBox is null) { UpdateOverlayOffset(); return; } _attachedTextBox.TemplateApplied += OnSourceTextBoxTemplateApplied; AttachScrollViewer(FindSourceScrollViewer(_attachedTextBox)); UpdateOverlayOffset(); } private void DetachSourceTextBox() { if (_attachedTextBox is not null) { _attachedTextBox.TemplateApplied -= OnSourceTextBoxTemplateApplied; } if (_attachedScrollViewer is not null) { _attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged; _attachedScrollViewer = null; } _attachedTextBox = null; UpdateOverlayOffset(); } private void OnSourceTextBoxTemplateApplied(object? sender, TemplateAppliedEventArgs e) { ScrollViewer? scrollViewer = e.NameScope.Find("PART_ScrollViewer"); AttachScrollViewer(scrollViewer ?? FindSourceScrollViewer(_attachedTextBox)); UpdateOverlayOffset(); } private void OnSourceScrollChanged(object? sender, ScrollChangedEventArgs e) { UpdateOverlayOffset(); } private void AttachScrollViewer(ScrollViewer? scrollViewer) { if (ReferenceEquals(_attachedScrollViewer, scrollViewer)) { return; } if (_attachedScrollViewer is not null) { _attachedScrollViewer.ScrollChanged -= OnSourceScrollChanged; } _attachedScrollViewer = scrollViewer; if (_attachedScrollViewer is not null) { _attachedScrollViewer.ScrollChanged += OnSourceScrollChanged; } } private void UpdateOverlayOffset() { if (_attachedScrollViewer is null) { RenderTransform = null; return; } RenderTransform = new TranslateTransform(-_attachedScrollViewer.Offset.X, -_attachedScrollViewer.Offset.Y); } private static ScrollViewer? FindSourceScrollViewer(TextBox? textBox) { if (textBox is null) { return null; } return textBox.GetVisualDescendants().OfType().FirstOrDefault(); } private void RebuildInlines() { if (Inlines is null) { return; } Inlines.Clear(); if (Segments is null) { return; } foreach (FileNameFormatSegmentViewModel segment in Segments) { Run run = new() { Text = segment.Text ?? string.Empty, Foreground = ParseForegroundBrush(segment.Foreground) }; Inlines.Add(run); } } private IBrush ParseForegroundBrush(string? value) { if (!string.IsNullOrWhiteSpace(value) && Color.TryParse(value, out Color color)) { return new SolidColorBrush(color); } return Foreground ?? Brushes.Transparent; } }