XFEstudio/MyFirstRespo / XFEToolBox/Utilities/DecoratedTextConverter.cs

using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Threading;

namespace XFEToolBox.Utilities;

public partial class DecoratedTextConverter
{
    /// <summary>
    /// 装饰文本
    /// </summary>
    /// <returns></returns>
    [GeneratedRegex(@"\[(?:(?:(?<type>color)\s+(?<color>\#[0-9a-fA-F]{6}|\w+)(?:\s+(?<background>\#[0-9a-fA-F]{6}|\w+))?)|(?:(?<type>hyperlink)\s+(?:color:\s*(?<color>\#[0-9a-fA-F]{6}|\w+)(?<background>\s+\#[0-9a-fA-F]{6}|\w+)?(?:\s+))?link:\s*(?<link>.+)\s+text:\s*(?<text>.+))|(?:(?<type>foldblock)\s+(?:color:\s*(?<color>\#[0-9a-fA-F]{6}|\w+)\s+(?<background>\#[0-9a-fA-F]{6}|\w+))\s+title:\s*(?<title>.+)\s+text:\s*(?<text>(?s).+)))\]")]
    public static partial Regex DecorationRegex();
    /// <summary>
    /// 转为文本块
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <returns></returns>
    public static TextBlock ConvertToTextBlock(string decoratedText, Color defaultColor)
    {
        var results = ConvertToInlineList(decoratedText, defaultColor);
        var textBlock = new TextBlock();
        foreach (var result in results)
        {
            if (result is Inline inline)
                textBlock.Inlines.Add(inline);
            else
                textBlock.Inlines.Add((UIElement)result);
        }
        return textBlock;
    }
    /// <summary>
    /// 转为文本块
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <param name="dispatcher"></param>
    /// <returns></returns>
    public static async Task<TextBlock> ConvertToTextBlockAsync(string decoratedText, Color defaultColor, Dispatcher dispatcher)
    {
        var results = await ConvertToInlineListAsync(decoratedText, defaultColor, dispatcher);
        var textBlock = new TextBlock();
        foreach (var result in results)
        {
            if (result is Inline inline)
                textBlock.Inlines.Add(inline);
            else
                textBlock.Inlines.Add((UIElement)result);
        }
        return textBlock;
    }
    /// <summary>
    /// 转为行内组件列表
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <returns></returns>
    public static List<object> ConvertToInlineList(string decoratedText, Color defaultColor)
    {
        var results = ConvertText(decoratedText, defaultColor);
        return ConvertToInlineList(results, decoratedText);
    }
    /// <summary>
    /// 转为行内组件列表
    /// </summary>
    /// <param name="decTextSpans"></param>
    /// <param name="decoratedText"></param>
    /// <returns></returns>
    public static List<object> ConvertToInlineList(List<DecTextSpan> decTextSpans, string decoratedText = "")
    {
        var inLineList = new List<object>();
        if (decTextSpans.Count > 0)
        {
            foreach (var result in decTextSpans)
            {
                if (result is HyperLinkDecSpan hyperLinkDecSpan)
                {
                    var hyperLink = new Hyperlink(new Run(hyperLinkDecSpan.Text))
                    {
                        Foreground = new SolidColorBrush(hyperLinkDecSpan.Color),
                        NavigateUri = new Uri(hyperLinkDecSpan.Link),
                        Background = new SolidColorBrush(hyperLinkDecSpan.BackgroundColor)
                    };
                    hyperLink.RequestNavigate += (sender, e) =>
                    {
                        try { Process.Start(new ProcessStartInfo(e.Uri.AbsoluteUri) { UseShellExecute = true }); } catch { }
                    };
                    inLineList.Add(hyperLink);
                }
                else if (result is FoldBlockDecSpan foldBlockDecSpan)
                {
                    var foldGrid = new Grid
                    {
                        Margin = new Thickness(3)
                    };
                    foldGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new(220) });
                    foldGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new(20) });
                    foldGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new(1, GridUnitType.Star) });
                    foldGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
                    foldGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                    var titleTextBlock = new TextBlock
                    {
                        Margin = new Thickness(5, 2, 5, 2),
                        Text = foldBlockDecSpan.Title,
                        TextTrimming = TextTrimming.CharacterEllipsis,
                        Foreground = new SolidColorBrush(foldBlockDecSpan.Color),
                        VerticalAlignment = VerticalAlignment.Center,
                        HorizontalAlignment = HorizontalAlignment.Center
                    };
                    var titleBorder = new Border
                    {
                        Background = new SolidColorBrush(foldBlockDecSpan.BackgroundColor),
                        CornerRadius = new CornerRadius(5, 0, 0, 5),
                        Child = titleTextBlock
                    };
                    foldGrid.Children.Add(titleBorder);
                    var buttonText = new TextBlock
                    {
                        Foreground = new SolidColorBrush(foldBlockDecSpan.BackgroundColor),
                        Background = new SolidColorBrush(foldBlockDecSpan.Color),
                        Text = "▼",
                        FontSize = 18
                    };
                    var button = new Button
                    {
                        Style = (Style)Application.Current.FindResource("FoldButton"),
                        Background = new SolidColorBrush(foldBlockDecSpan.Color),
                        Content = buttonText
                    };
                    var foldButtonBorder = new Border
                    {
                        Background = new SolidColorBrush(foldBlockDecSpan.Color),
                        CornerRadius = new CornerRadius(0, 5, 5, 0),
                        Child = button
                    };
                    Grid.SetColumn(foldButtonBorder, 1);
                    foldGrid.Children.Add(foldButtonBorder);
                    var contentTextBlock = new TextBlock
                    {
                        TextWrapping = TextWrapping.Wrap,
                        Foreground = new SolidColorBrush(foldBlockDecSpan.Color),
                    };
                    if (foldBlockDecSpan.DecTextList.Count > 0)
                        contentTextBlock.Inlines.AddRange(foldBlockDecSpan.DecTextList.Select(x => new Span(new Run(x.Text))
                        {
                            Foreground = new SolidColorBrush(x.Color),
                            Background = new SolidColorBrush(x.BackgroundColor)
                        }));
                    else
                        contentTextBlock.Text = foldBlockDecSpan.Text;
                    var contentBorder = new Border
                    {
                        Visibility = Visibility.Collapsed,
                        CornerRadius = new CornerRadius(0, 5, 5, 5),
                        Background = Brushes.Transparent,
                        BorderBrush = new SolidColorBrush(foldBlockDecSpan.BackgroundColor),
                        BorderThickness = new Thickness(3),
                        Padding = new Thickness(3),
                        Child = contentTextBlock
                    };
                    button.Click += (sender, e) =>
                    {
                        if (contentBorder.Visibility == Visibility.Visible)
                        {
                            contentBorder.Visibility = Visibility.Collapsed;
                            titleBorder.CornerRadius = new(5, 0, 0, 5);
                            buttonText.Text = "▼";
                        }
                        else
                        {
                            contentBorder.Visibility = Visibility.Visible;
                            titleBorder.CornerRadius = new(5, 0, 0, 0);
                            buttonText.Text = "▲";
                        }
                    };
                    Grid.SetColumnSpan(contentBorder, 3);
                    Grid.SetRow(contentBorder, 1);
                    Grid.SetRow(contentBorder, 1);
                    Grid.SetColumnSpan(contentBorder, 3);
                    foldGrid.Children.Add(contentBorder);
                    inLineList.Add(new Span(new Run("\n")));
                    inLineList.Add(foldGrid);
                }
                else
                {
                    inLineList.Add(new Span(new Run(result.Text))
                    {
                        Foreground = new SolidColorBrush(result.Color),
                        Background = new SolidColorBrush(result.BackgroundColor)
                    });
                }
            }
        }
        else if (decoratedText != string.Empty)
        {
            inLineList.Add(new Span(new Run(decoratedText))
            {
                Foreground = Brushes.White
            });
        }
        return inLineList;
    }
    /// <summary>
    /// 转为行内组件列表
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <param name="dispatcher"></param>
    /// <returns></returns>
    public static async Task<List<object>> ConvertToInlineListAsync(string decoratedText, Color defaultColor, Dispatcher dispatcher)
    {
        var result = await ConvertTextAsync(decoratedText, defaultColor);
        return dispatcher.Invoke(() => ConvertToInlineList(result, decoratedText));
    }
    /// <summary>
    /// 转为装饰文本
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <returns></returns>
    public static List<DecTextSpan> ConvertText(string decoratedText, Color defaultColor)
    {
        var textSpanList = new List<DecTextSpan>();
        var matches = DecorationRegex().Matches(decoratedText);
        for (int i = 0; i < matches.Count; i++)
        {
            var match = matches[i];
            var nextMatch = i + 1 < matches.Count ? matches[i + 1] : null;
            var type = match.Groups["type"].Value;
            var colorText = match.Groups["color"].Value;
            var backgroundColorText = match.Groups["background"].Value ?? "Transparent";
            if (backgroundColorText == string.Empty)
                backgroundColorText = "Transparent";
            Color backgroundColor = (Color)ColorConverter.ConvertFromString(backgroundColorText);
            var link = match.Groups["link"].Value;
            var text = match.Groups["text"].Value;
            var title = match.Groups["title"].Value;
            var unMatchString = "";
            if (i == 0 && match.Index != 0)
            {
                textSpanList.Add(new(decoratedText[..match.Index], defaultColor, Colors.Transparent));
            }
            if (nextMatch is null)
            {
                unMatchString = decoratedText.Substring(match.Index + match.Length, decoratedText.Length - match.Index - match.Length);
            }
            else
            {
                var unMatchStringLength = nextMatch.Index - match.Index - match.Length;
                if (unMatchStringLength > 0)
                    unMatchString = decoratedText.Substring(match.Index + match.Length, unMatchStringLength);
            }
            switch (type)
            {
                case "color":
                    textSpanList.Add(new(unMatchString, (Color)ColorConverter.ConvertFromString(colorText)!, backgroundColor));
                    continue;
                case "hyperlink":
                    var color = ColorConverter.ConvertFromString(colorText);
                    textSpanList.Add(new HyperLinkDecSpan(text, link, color is null ? defaultColor : (Color)color, backgroundColor));
                    textSpanList.Add(new(unMatchString, defaultColor, Colors.Transparent));
                    continue;
                case "foldblock":
                    textSpanList.Add(new FoldBlockDecSpan(text, title, ConvertText(text, defaultColor), (Color)ColorConverter.ConvertFromString(colorText), backgroundColor));
                    textSpanList.Add(new(unMatchString, defaultColor, Colors.Transparent));
                    continue;
                default:
                    continue;
            }
        }
        return textSpanList;
    }
    /// <summary>
    /// 转为装饰文本
    /// </summary>
    /// <param name="decoratedText"></param>
    /// <param name="defaultColor"></param>
    /// <returns></returns>
    public static async Task<List<DecTextSpan>> ConvertTextAsync(string decoratedText, Color defaultColor) => await TaskManager.Run(() => ConvertText(decoratedText, defaultColor));
}