using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Embedding; using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Threading; using Avalonia.UnitTests; using Avalonia.VisualTree; using JetBrains.Annotations; using Xunit; namespace Avalonia.Base.UnitTests.Rendering; public class CompositorTestsBase { public class DebugEvents : ICompositionTargetDebugEvents { public List Rects = new(); public void RectInvalidated(Rect rc) { Rects.Add(rc); } public void Reset() { Rects.Clear(); } } public class ManualRenderTimer : IRenderTimer { public event Action Tick; public bool RunsInBackground => false; public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero); public Task TriggerBackgroundTick() => Task.Run(TriggerTick); } class TopLevelImpl : ITopLevelImpl { private readonly Compositor _compositor; public CompositingRenderer Renderer { get; private set; } public TopLevelImpl(Compositor compositor, Size clientSize) { ClientSize = clientSize; _compositor = compositor; } public void Dispose() { } public Size ClientSize { get; } public Size? FrameSize { get; } public double RenderScaling => 1; public IEnumerable Surfaces { get; } = Array.Empty(); public Action Input { get; set; } public Action Paint { get; set; } public Action Resized { get; set; } public Action ScalingChanged { get; set; } public Action TransparencyLevelChanged { get; set; } public IRenderer CreateRenderer(IRenderRoot root) { return Renderer = new CompositingRenderer(root, _compositor); } public void Invalidate(Rect rect) { } public void SetInputRoot(IInputRoot inputRoot) { } public Point PointToClient(PixelPoint point) => default; public PixelPoint PointToScreen(Point point) => new(); public void SetCursor(ICursorImpl cursor) { } public Action Closed { get; set; } public Action LostFocus { get; set; } public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IPopupImpl CreatePopup() => throw new NotImplementedException(); public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } public WindowTransparencyLevel TransparencyLevel { get; } public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } } protected class CompositorServices : IDisposable { private readonly IDisposable _app; public Compositor Compositor { get; } public ManualRenderTimer Timer { get; } = new(); public EmbeddableControlRoot TopLevel { get; } public CompositingRenderer Renderer { get; } = null!; public DebugEvents Events { get; } = new(); public void Dispose() { TopLevel.Renderer.Stop(); TopLevel.Dispose(); _app.Dispose(); } public CompositorServices(Size? size = null) { _app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); try { AvaloniaLocator.CurrentMutable.Bind().ToConstant(Timer); AvaloniaLocator.CurrentMutable.Bind() .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread)); Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService(), null); var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000)); TopLevel = new EmbeddableControlRoot(impl) { Template = new FuncControlTemplate((parent, scope) => { var presenter = new ContentPresenter { [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty) }; scope.Register("PART_ContentPresenter", presenter); return presenter; }) }; Renderer = impl.Renderer; TopLevel.Prepare(); TopLevel.Renderer.Start(); RunJobs(); Renderer.CompositionTarget.Server.DebugEvents = Events; } catch { _app.Dispose(); throw; } } public void RunJobs() { Dispatcher.UIThread.RunJobs(); Timer.TriggerTick(); Dispatcher.UIThread.RunJobs(); } public void AssertRects(params Rect[] rects) { RunJobs(); var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x); Assert.Equal(toAssert, invalidated); Events.Rects.Clear(); } public void AssertHitTest(double x, double y, Func filter, params object[] expected) => AssertHitTest(new Point(x, y), filter, expected); public void AssertHitTest(Point pt, Func filter, params object[] expected) { RunJobs(); var tested = Renderer.HitTest(pt, TopLevel, filter); Assert.Equal(expected, tested); } public void AssertHitTestFirst(Point pt, Func filter, object expected) { RunJobs(); var tested = Renderer.HitTest(pt, TopLevel, filter).First(); Assert.Equal(expected, tested); } } protected class CompositorCanvas : CompositorServices { public Canvas Canvas { get; } = new(); public CompositorCanvas() { TopLevel.Content = Canvas; RunJobs(); Events.Reset(); } } }