OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux / AvaloniaShell.Tests / DiamondClipProbe.cs

102 lines · csharp ref:
using System.Linq;

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Headless.XUnit;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.VisualTree;

using IFSRenderer.AvaloniaShell.Controls;

using Xunit;
using Xunit.Abstractions;

namespace IFSRenderer.AvaloniaShell.Tests;

/// <summary>
/// The keyframe diamond had its right vertex sliced off by a clean vertical cut.
/// </summary>
/// <remarks>
/// The cut is a clean vertical line inside the content area, several px short of the value row
/// below it — so it is not the scrollbar and not the pane edge. This dumps the full chain from
/// the slider down to the glyph so the clipping element identifies itself.
/// </remarks>
public class DiamondClipProbe
{
    private readonly ITestOutputHelper _out;

    public DiamondClipProbe(ITestOutputHelper output) => _out = output;

    [AvaloniaFact]
    public void The_keyframe_diamond_glyph_is_never_clipped()
    {
        (MainWindow window, _) = MainWindowHarness.Start();
        window.Measure(new Size(1500, 900));
        window.Arrange(new Rect(0, 0, 1500, 900));
        Dispatcher.UIThread.RunJobs();

        var slider = window.FindControl<ValueSlider>("BrightnessSlider");
        Assert.NotNull(slider);

        var labelRow = slider.GetVisualDescendants().OfType<Grid>()
            .FirstOrDefault(g => g.Name == "LabelRow");
        var diamond = slider.GetVisualDescendants().OfType<Button>()
            .FirstOrDefault(b => b.Name == "Diamond1");
        var valueField = slider.GetVisualDescendants().OfType<Border>()
            .FirstOrDefault(b => b.Name == "ValueField");
        var up = slider.GetVisualDescendants().OfType<Button>()
            .FirstOrDefault(b => b.Name == "UpButton");

        _out.WriteLine($"ValueSlider  bounds={slider.Bounds}  clip={slider.ClipToBounds}");
        _out.WriteLine($"LabelRow     bounds={labelRow?.Bounds}  clip={labelRow?.ClipToBounds}");
        _out.WriteLine($"Diamond1     bounds={diamond?.Bounds}  clip={diamond?.ClipToBounds}" +
                       $"  padding={diamond?.Padding}  fontSize={diamond?.FontSize}" +
                       $"  content='{diamond?.Content}'");
        _out.WriteLine($"ValueField   bounds={valueField?.Bounds}");
        _out.WriteLine($"UpButton     bounds={up?.Bounds}");

        if (diamond is not null)
        {
            foreach (Visual v in diamond.GetVisualDescendants())
            {
                string extra = v is TextBlock tb
                    ? $" text='{tb.Text}' desired={tb.DesiredSize}"
                    : v is ContentPresenter cp ? $" cpPadding={cp.Padding}" : "";
                _out.WriteLine($"    {v.GetType().Name,-22} bounds={v.Bounds} " +
                               $"clip={(v as Control)?.ClipToBounds}{extra}");
            }

            // Where the glyph's right edge lands relative to everything around it.
            Point diamondRightInSlider =
                diamond.TranslatePoint(new Point(diamond.Bounds.Width, 0), slider) ?? default;
            Point fieldRightInSlider =
                valueField?.TranslatePoint(new Point(valueField.Bounds.Width, 0), slider) ?? default;
            _out.WriteLine($"-> diamond right edge in slider : {diamondRightInSlider.X}");
            _out.WriteLine($"-> value field right in slider  : {fieldRightInSlider.X}");
            _out.WriteLine($"-> slider width                 : {slider.Bounds.Width}");

            // How wide the glyph actually wants to be at this font size.
            var probe = new TextBlock { Text = "◇", FontSize = diamond.FontSize };
            probe.Measure(Size.Infinity);
            _out.WriteLine($"-> bare glyph desired size      : {probe.DesiredSize}");

            // THE REGRESSION. Nothing sized to the glyph's advance box may clip it: ◇ ◆ ◈ are
            // absent from Inter and the fallback face inks them wider than the advance it
            // reports, so any clip at that size shears the right vertex off. A bare string
            // content reintroduces exactly that, via ContentPresenter -> AccessText.
            var glyphHost = diamond.GetVisualDescendants()
                .OfType<Control>()
                .FirstOrDefault(c => c is TextBlock || c.GetType().Name == "AccessText");
            Assert.NotNull(glyphHost);
            Assert.False(glyphHost.GetType().Name == "AccessText",
                "the diamond's content is a bare string again; ContentPresenter wraps that in a "
                + "clipping AccessText and shears the glyph. Give the Button a TextBlock.");
            Assert.False(glyphHost.ClipToBounds,
                $"{glyphHost.GetType().Name} clips the diamond glyph to its advance box "
                + $"({glyphHost.Bounds.Width}x{glyphHost.Bounds.Height})");
        }
    }
}