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