CompositorTestServices.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Threading.Tasks;
  6. using Avalonia.Controls;
  7. using Avalonia.Controls.Embedding;
  8. using Avalonia.Controls.Platform.Surfaces;
  9. using Avalonia.Controls.Presenters;
  10. using Avalonia.Controls.Templates;
  11. using Avalonia.Data;
  12. using Avalonia.Input;
  13. using Avalonia.Input.Raw;
  14. using Avalonia.Platform;
  15. using Avalonia.Rendering;
  16. using Avalonia.Rendering.Composition;
  17. using Avalonia.Threading;
  18. using Xunit;
  19. namespace Avalonia.UnitTests;
  20. public class CompositorTestServices : IDisposable
  21. {
  22. private readonly IDisposable _app;
  23. public Compositor Compositor { get; }
  24. public ManualRenderTimer Timer { get; } = new();
  25. public EmbeddableControlRoot TopLevel { get; }
  26. public CompositingRenderer Renderer { get; } = null!;
  27. public DebugEvents Events { get; } = new();
  28. public void Dispose()
  29. {
  30. TopLevel.Renderer.Stop();
  31. TopLevel.Dispose();
  32. _app.Dispose();
  33. }
  34. public CompositorTestServices(Size? size = null, IPlatformRenderInterface renderInterface = null)
  35. {
  36. var services = TestServices.MockPlatformRenderInterface;
  37. if (renderInterface != null)
  38. services = services.With(renderInterface: renderInterface);
  39. _app = UnitTestApplication.Start(services);
  40. try
  41. {
  42. AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(Timer);
  43. AvaloniaLocator.CurrentMutable.Bind<IRenderLoop>()
  44. .ToConstant(new RenderLoop(Timer, Dispatcher.UIThread));
  45. Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), null);
  46. var impl = new TopLevelImpl(Compositor, size ?? new Size(1000, 1000));
  47. TopLevel = new EmbeddableControlRoot(impl)
  48. {
  49. Template = new FuncControlTemplate((parent, scope) =>
  50. {
  51. var presenter = new ContentPresenter
  52. {
  53. [~ContentPresenter.ContentProperty] = new TemplateBinding(ContentControl.ContentProperty)
  54. };
  55. scope.Register("PART_ContentPresenter", presenter);
  56. return presenter;
  57. })
  58. };
  59. Renderer = impl.Renderer;
  60. TopLevel.Prepare();
  61. TopLevel.Renderer.Start();
  62. RunJobs();
  63. Renderer.CompositionTarget.Server.DebugEvents = Events;
  64. }
  65. catch
  66. {
  67. _app.Dispose();
  68. throw;
  69. }
  70. }
  71. public void RunJobs()
  72. {
  73. Dispatcher.UIThread.RunJobs();
  74. Timer.TriggerTick();
  75. Dispatcher.UIThread.RunJobs();
  76. }
  77. public void AssertRects(params Rect[] rects)
  78. {
  79. RunJobs();
  80. var toAssert = rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
  81. var invalidated = Events.Rects.Select(x => x.ToString()).Distinct().OrderBy(x => x);
  82. Assert.Equal(toAssert, invalidated);
  83. Events.Rects.Clear();
  84. }
  85. public void AssertHitTest(double x, double y, Func<Visual, bool> filter, params object[] expected)
  86. => AssertHitTest(new Point(x, y), filter, expected);
  87. public void AssertHitTest(Point pt, Func<Visual, bool> filter, params object[] expected)
  88. {
  89. RunJobs();
  90. var tested = Renderer.HitTest(pt, TopLevel, filter);
  91. Assert.Equal(expected, tested);
  92. }
  93. public void AssertHitTestFirst(Point pt, Func<Visual, bool> filter, object expected)
  94. {
  95. RunJobs();
  96. var tested = Renderer.HitTest(pt, TopLevel, filter).First();
  97. Assert.Equal(expected, tested);
  98. }
  99. public class DebugEvents : ICompositionTargetDebugEvents
  100. {
  101. public List<Rect> Rects = new();
  102. public void RectInvalidated(Rect rc)
  103. {
  104. Rects.Add(rc);
  105. }
  106. public void Reset()
  107. {
  108. Rects.Clear();
  109. }
  110. }
  111. public class ManualRenderTimer : IRenderTimer
  112. {
  113. public event Action<TimeSpan> Tick;
  114. public bool RunsInBackground => false;
  115. public void TriggerTick() => Tick?.Invoke(TimeSpan.Zero);
  116. public Task TriggerBackgroundTick() => Task.Run(TriggerTick);
  117. }
  118. class TopLevelImpl : ITopLevelImpl
  119. {
  120. private readonly Compositor _compositor;
  121. public CompositingRenderer Renderer { get; private set; }
  122. public TopLevelImpl(Compositor compositor, Size clientSize)
  123. {
  124. ClientSize = clientSize;
  125. _compositor = compositor;
  126. }
  127. public void Dispose()
  128. {
  129. }
  130. public Size ClientSize { get; }
  131. public Size? FrameSize { get; }
  132. public double RenderScaling => 1;
  133. public IEnumerable<object> Surfaces { get; } = new[] { new DummyFramebufferSurface() };
  134. public Action<RawInputEventArgs> Input { get; set; }
  135. public Action<Rect> Paint { get; set; }
  136. public Action<Size, PlatformResizeReason> Resized { get; set; }
  137. public Action<double> ScalingChanged { get; set; }
  138. public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
  139. class DummyFramebufferSurface : IFramebufferPlatformSurface
  140. {
  141. public ILockedFramebuffer Lock()
  142. {
  143. var ptr = Marshal.AllocHGlobal(128);
  144. return new LockedFramebuffer(ptr, new PixelSize(1, 1), 4, new Vector(96, 96),
  145. PixelFormat.Rgba8888, () => Marshal.FreeHGlobal(ptr));
  146. }
  147. }
  148. public IRenderer CreateRenderer(IRenderRoot root)
  149. {
  150. return Renderer = new CompositingRenderer(root, _compositor, () => Surfaces);
  151. }
  152. public void Invalidate(Rect rect)
  153. {
  154. }
  155. public void SetInputRoot(IInputRoot inputRoot)
  156. {
  157. }
  158. public Point PointToClient(PixelPoint point) => default;
  159. public PixelPoint PointToScreen(Point point) => new();
  160. public void SetCursor(ICursorImpl cursor)
  161. {
  162. }
  163. public Action Closed { get; set; }
  164. public Action LostFocus { get; set; }
  165. public IMouseDevice MouseDevice { get; } = new MouseDevice();
  166. public IPopupImpl CreatePopup() => throw new NotImplementedException();
  167. public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
  168. {
  169. }
  170. public WindowTransparencyLevel TransparencyLevel { get; }
  171. public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
  172. {
  173. }
  174. public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; }
  175. public object TryGetFeature(Type featureType) => null;
  176. }
  177. }