using Avalonia.Automation.Peers;
using Avalonia.Input;
using Avalonia.Reactive;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using System;
using System.Text;
namespace Avalonia.Controls.Primitives
{
///
/// A text block that displays a character prefixed with an underscore as an access key.
///
public class AccessText : TextBlock
{
///
/// Defines the attached property.
///
public static readonly AttachedProperty ShowAccessKeyProperty =
AccessKeyHandler.ShowAccessKeyProperty.AddOwner();
///
/// The access key handler for the current window.
///
private IAccessKeyHandler? _accessKeys;
///
/// Initializes static members of the class.
///
static AccessText()
{
AffectsRender(ShowAccessKeyProperty);
}
///
/// Initializes a new instance of the class.
///
public AccessText()
{
this.GetObservable(TextProperty).Subscribe(TextChanged);
}
///
/// Gets the access key.
///
public string? AccessKey
{
get;
private set;
}
///
/// Gets or sets a value indicating whether the access key should be underlined.
///
public bool ShowAccessKey
{
get => GetValue(ShowAccessKeyProperty);
set => SetValue(ShowAccessKeyProperty, value);
}
///
/// Renders the to a drawing context.
///
/// The drawing context.
private protected override void RenderCore(DrawingContext context)
{
base.RenderCore(context);
int underscore = Text?.IndexOf('_') ?? -1;
if (underscore != -1 && ShowAccessKey)
{
var rect = TextLayout!.HitTestTextPosition(underscore);
var x1 = Math.Round(rect.Left, MidpointRounding.AwayFromZero);
var x2 = Math.Round(rect.Right, MidpointRounding.AwayFromZero);
var y = Math.Round(rect.Bottom, MidpointRounding.AwayFromZero) - 1.5;
context.DrawLine(
new Pen(Foreground, 1),
new Point(x1, y),
new Point(x2, y));
}
}
///
protected override TextLayout CreateTextLayout(string? text)
{
return base.CreateTextLayout(RemoveAccessKeyMarker(text));
}
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_accessKeys = TopLevel.GetTopLevel(this)?.AccessKeyHandler;
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Register(AccessKey, this);
}
}
///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Unregister(this);
_accessKeys = null;
}
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new NoneAutomationPeer(this);
}
internal static string? RemoveAccessKeyMarker(string? text)
{
if (!string.IsNullOrEmpty(text))
{
var accessKeyMarker = "_";
var doubleAccessKeyMarker = accessKeyMarker + accessKeyMarker;
int index = FindAccessKeyMarker(text);
if (index >= 0 && index < text.Length - 1)
text = text.Remove(index, 1);
text = text.Replace(doubleAccessKeyMarker, accessKeyMarker);
}
return text;
}
private static int FindAccessKeyMarker(string text)
{
var length = text.Length;
var startIndex = 0;
while (startIndex < length)
{
int index = text.IndexOf('_', startIndex);
if (index == -1)
return -1;
if (index + 1 < length && text[index + 1] != '_')
return index;
startIndex = index + 2;
}
return -1;
}
///
/// Called when the property changes.
///
/// The new text.
private void TextChanged(string? text)
{
string? key = null;
if (text != null)
{
int underscore = text.IndexOf('_');
if (underscore != -1 && underscore < text.Length - 1)
{
var rune = Rune.GetRuneAt(text, underscore + 1);
key = rune.ToString();
}
}
AccessKey = key;
if (_accessKeys != null && !string.IsNullOrEmpty(AccessKey))
{
_accessKeys.Register(AccessKey, this);
}
}
}
}