OF-DL/OF DL.Gui/Controls/FileNameFormatOverlayTextBlock.cs

212 lines
6.2 KiB
C#

using System.Collections.Specialized;
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<IEnumerable<FileNameFormatSegmentViewModel>?> SegmentsProperty =
AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, IEnumerable<FileNameFormatSegmentViewModel>?>(
nameof(Segments));
public static readonly StyledProperty<TextBox?> SourceTextBoxProperty =
AvaloniaProperty.Register<FileNameFormatOverlayTextBlock, TextBox?>(nameof(SourceTextBox));
private INotifyCollectionChanged? _segmentsCollection;
private TextBox? _attachedTextBox;
private ScrollViewer? _attachedScrollViewer;
static FileNameFormatOverlayTextBlock()
{
SegmentsProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSegmentsChanged);
SourceTextBoxProperty.Changed.AddClassHandler<FileNameFormatOverlayTextBlock>(OnSourceTextBoxChanged);
}
public IEnumerable<FileNameFormatSegmentViewModel>? 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<FileNameFormatSegmentViewModel>);
sender.RebuildInlines();
}
private static void OnSourceTextBoxChanged(
FileNameFormatOverlayTextBlock sender,
AvaloniaPropertyChangedEventArgs e) =>
sender.AttachSourceTextBox(e.NewValue as TextBox);
private void AttachSegmentsCollection(IEnumerable<FileNameFormatSegmentViewModel>? 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<ScrollViewer>("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<ScrollViewer>().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, 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;
}
}