AvaloniaUI/Avalonia

AccessText.cs pen hardcoded to thickness of 1, should be using FontMetrics.UnderlinePosition and FontMetrics.UnderlineThickness.

Opened this issue · 7 comments

tomlm commented

Describe the bug

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

This code is not using FontMetric.UnderlinPosition and FontMetric.UnderlingThickness. It is hard coded to thickness of 1.

To Reproduce

All access text rendering.

Expected behavior

It should honor the fontmetric data, something like this:

         double thickness = 1;
         double offset = 1.5;
         
         if (FontManager.Current.TryGetGlyphTypeface(new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), out var glyphTypeface))
         {
             thickness = Math.Max(1, (double)glyphTypeface.Metrics.UnderlineThickness / (double)glyphTypeface.Metrics.DesignEmHeight);
             offset = (double)glyphTypeface.Metrics.UnderlinePosition / (double)glyphTypeface.Metrics.UnderlinePosition;
         }
         
         var x1 = Math.Round(rect.Left, MidpointRounding.AwayFromZero);
         var x2 = Math.Round(rect.Right, MidpointRounding.AwayFromZero);
         var y = Math.Round(rect.Bottom, MidpointRounding.AwayFromZero) - offset;

         context.DrawLine(
             new Pen(Foreground, thickness),
             new Point(x1, y),
             new Point(x2, y));
 }

Avalonia version

11.2.1

OS

Windows

Additional context

No response

I think AccessText should just add a textStyleOverride to the TextLayout that shows a TextDecoration for the access key

tomlm commented

Yeah, I was kind of surprised that approach was used given it derives from textblock. Seems like a text run that you toggle the decoration on would be better

AccessText was never adjusted to the new capabilities. In the past you could not format text in such a way.

tomlm commented

FWIW I implemented my own like this and it works perfectly:

    public sealed class MyAccessText : AccessText
    {
        private Run _accessRun;

        public MyAccessText()
        {
            this.PropertyChanged += OnPropertyChanged;
        }

        private void OnPropertyChanged(object sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
        {
            switch (e.Property.Name)
            {
                case nameof(Text):
                    {
                        if (!String.IsNullOrEmpty(Text))
                        {
                            InlineCollection inlines = new InlineCollection();
                            var iPos = Text.IndexOf('_', StringComparison.Ordinal);
                            if (iPos >= 0 && iPos < Text.Length - 1)  
                            {
                                inlines.Add(new Run(Text[..iPos]));
                                _accessRun = new Run(Text[++iPos..++iPos]);
                                inlines.Add(_accessRun);
                                inlines.Add(new Run(Text[iPos..]));
                            }
                            else
                            {
                                _accessRun = null;
                                inlines.Add(new Run(Text));
                            }
                            this.Inlines = inlines;
                        }
                    }
                    break;

                case nameof(ShowAccessKey):
                    if (_accessRun != null)
                    {
                        if (this.ShowAccessKey)
                            _accessRun.TextDecorations = Avalonia.Media.TextDecorations.Underline;
                        else
                            _accessRun.TextDecorations = null;
                    }
                    break;
            }
        }
    }
tomlm commented

Ah shoot. I can override AccessText for Menu/MenuItem, but it appears that overriding it for Label/Button/etc uses hardcoded readonly static definition in FuncDataTemplate.AccessText.

And ContentPresenter.CreateChild() is hard coded to use FuncDataTemplate.AccessText.

I sure wish Avalonia had a real dependency injection system exposed to clients.

Just use the textStyleOverrides parameter on the TextLayout ctor

tomlm commented

I figured out I can override the dependency in my application with an application level datatemplate. Thanks for your help @Gillibald