207 lines
6.3 KiB
C#
207 lines
6.3 KiB
C#
using System;
|
|
using System.Buffers;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using WPFDark.Internals;
|
|
using Jewelry.Text;
|
|
|
|
namespace WPFDark.Controls
|
|
{
|
|
public class BiaHighlightTextBlock : BiaTextBlock
|
|
{
|
|
#region Highlight
|
|
|
|
public Brush? Highlight
|
|
{
|
|
get => _Highlight;
|
|
set
|
|
{
|
|
if (value != _Highlight)
|
|
SetValue(HighlightProperty, value);
|
|
}
|
|
}
|
|
|
|
private Brush? _Highlight;
|
|
|
|
public static readonly DependencyProperty HighlightProperty =
|
|
DependencyProperty.Register(
|
|
nameof(Highlight),
|
|
typeof(Brush),
|
|
typeof(BiaHighlightTextBlock),
|
|
new FrameworkPropertyMetadata(
|
|
default,
|
|
FrameworkPropertyMetadataOptions.AffectsRender |
|
|
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
|
|
(s, e) =>
|
|
{
|
|
var self = (BiaHighlightTextBlock) s;
|
|
self._Highlight = (Brush) e.NewValue;
|
|
}));
|
|
|
|
#endregion
|
|
|
|
#region Words
|
|
|
|
public string? Words
|
|
{
|
|
get => _Words;
|
|
set
|
|
{
|
|
if (value != _Words)
|
|
SetValue(WordsProperty, value);
|
|
}
|
|
}
|
|
|
|
private string? _Words;
|
|
|
|
public static readonly DependencyProperty WordsProperty =
|
|
DependencyProperty.Register(
|
|
nameof(Words),
|
|
typeof(string),
|
|
typeof(BiaHighlightTextBlock),
|
|
new FrameworkPropertyMetadata(
|
|
default,
|
|
FrameworkPropertyMetadataOptions.AffectsRender |
|
|
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
|
|
(s, e) =>
|
|
{
|
|
var self = (BiaHighlightTextBlock) s;
|
|
self._Words = e.NewValue?.ToString() ?? "";
|
|
}));
|
|
|
|
#endregion
|
|
|
|
static BiaHighlightTextBlock()
|
|
{
|
|
DefaultStyleKeyProperty.OverrideMetadata(typeof(BiaHighlightTextBlock),
|
|
new FrameworkPropertyMetadata(typeof(BiaHighlightTextBlock)));
|
|
}
|
|
|
|
[SuppressMessage("ReSharper", "PossiblyImpureMethodCallOnReadonlyVariable")]
|
|
protected override void OnRender(DrawingContext dc)
|
|
{
|
|
using var ss = new StringSplitter(stackalloc StringSplitter.StringSpan[32]);
|
|
|
|
var wordsArray = ss.Split(Words, ' ', StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
if (wordsArray.Length == 0)
|
|
base.OnRender(dc);
|
|
else
|
|
{
|
|
if (Words != null)
|
|
RenderHighlight(dc, wordsArray, Words);
|
|
}
|
|
}
|
|
|
|
private void RenderHighlight(DrawingContext dc, ReadOnlySpan<StringSplitter.StringSpan> wordsSpans, string words)
|
|
{
|
|
if (ActualWidth <= 1 ||
|
|
ActualHeight <= 1)
|
|
return;
|
|
|
|
if (string.IsNullOrEmpty(Text))
|
|
return;
|
|
|
|
var textStatesArray =
|
|
Text.Length >= 512
|
|
? ArrayPool<byte>.Shared.Rent(Text.Length)
|
|
: null;
|
|
|
|
// ReSharper disable once MergeConditionalExpression
|
|
var textStates =
|
|
textStatesArray != null
|
|
? textStatesArray.AsSpan(0, Text.Length)
|
|
: stackalloc byte[Text.Length];
|
|
|
|
try
|
|
{
|
|
textStates.Fill(0);
|
|
|
|
var textSpan = Text.AsSpan();
|
|
|
|
foreach (var wordsSpan in wordsSpans)
|
|
{
|
|
var stateOffset = 0;
|
|
|
|
var wordSpan = wordsSpan.ToSpan(words.AsSpan());
|
|
|
|
while (true)
|
|
{
|
|
var wordIndex = textSpan.Slice(stateOffset).IndexOf(wordSpan, StringComparison.OrdinalIgnoreCase);
|
|
if (wordIndex == -1)
|
|
break;
|
|
|
|
for (var i = 0; i != wordSpan.Length; ++i)
|
|
textStates[stateOffset + wordIndex + i] = 1;
|
|
|
|
stateOffset += wordIndex + wordSpan.Length;
|
|
}
|
|
}
|
|
|
|
var state = textStates[0];
|
|
var index = 0;
|
|
var startIndex = 0;
|
|
|
|
var x = 0.0;
|
|
|
|
while (true)
|
|
{
|
|
if (textStates[index] != state)
|
|
{
|
|
var brush =
|
|
textStates[startIndex] == 0
|
|
? Foreground
|
|
: Highlight;
|
|
|
|
if (brush != null)
|
|
x = RenderText(dc, Text, startIndex, index - 1, x, brush);
|
|
|
|
state = textStates[index];
|
|
startIndex = index;
|
|
}
|
|
|
|
++index;
|
|
|
|
if (index == Text.Length)
|
|
{
|
|
var brush =
|
|
textStates[startIndex] == 0
|
|
? Foreground
|
|
: Highlight;
|
|
|
|
if (brush != null)
|
|
RenderText(dc, Text, startIndex, index - 1, x, brush);
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
if (textStatesArray != null)
|
|
ArrayPool<byte>.Shared.Return(textStatesArray);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private double RenderText(DrawingContext dc, string text, int startIndex, int endIndex, double x, Brush brush)
|
|
{
|
|
var length = endIndex - startIndex + 1;
|
|
|
|
var w = DefaultTextRenderer.Instance.Draw(
|
|
this,
|
|
text.AsSpan(startIndex, length),
|
|
x, 0,
|
|
brush,
|
|
dc,
|
|
ActualWidth - x,
|
|
TextAlignment.Left,
|
|
TextTrimming,
|
|
true);
|
|
|
|
return x + w;
|
|
}
|
|
}
|
|
} |