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); } } } }