12 changed files with 834 additions and 74 deletions
@ -0,0 +1,6 @@ |
|||||
|
namespace Avalonia.Rendering.Composition; |
||||
|
|
||||
|
internal interface ICompositionTargetDebugEvents |
||||
|
{ |
||||
|
void RectInvalidated(Rect rc); |
||||
|
} |
||||
@ -0,0 +1,417 @@ |
|||||
|
using System; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Controls.Presenters; |
||||
|
using Avalonia.Controls.Shapes; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Media; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.UnitTests; |
||||
|
using Avalonia.VisualTree; |
||||
|
using Moq; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests.Rendering; |
||||
|
|
||||
|
public class CompositorHitTestingTests : CompositorTestsBase |
||||
|
{ |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Find_Controls_At_Point() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
var border = new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}; |
||||
|
|
||||
|
s.TopLevel.Content = border; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null, border); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Empty_Controls_At_Point() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
var border = new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}; |
||||
|
|
||||
|
s.TopLevel.Content = border; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Invisible_Controls_At_Point() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Border visible, border; |
||||
|
s.TopLevel.Content = border = new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center, |
||||
|
IsVisible = false, |
||||
|
Child = visible = new Border |
||||
|
{ |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Stretch, |
||||
|
VerticalAlignment = VerticalAlignment.Stretch, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Control_Outside_Point() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
var border = new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
|
||||
|
}; |
||||
|
s.TopLevel.Content = border; |
||||
|
|
||||
|
s.AssertHitTest(new Point(10, 10), null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Return_Top_Controls_First() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Panel container = new Panel |
||||
|
{ |
||||
|
Width = 200, |
||||
|
Height = 200, |
||||
|
Children = |
||||
|
{ |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}, |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 50, |
||||
|
Height = 50, |
||||
|
Background = Brushes.Blue, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
s.TopLevel.Content = container; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null, container.Children[1], container.Children[0]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Return_Top_Controls_First_With_ZIndex() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Panel container = new Panel |
||||
|
{ |
||||
|
Width = 200, |
||||
|
Height = 200, |
||||
|
Children = |
||||
|
{ |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
ZIndex = 1, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}, |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 50, |
||||
|
Height = 50, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}, |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 75, |
||||
|
Height = 75, |
||||
|
ZIndex = 2, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
s.TopLevel.Content = container; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null, new[] { container.Children[2], container.Children[0], container.Children[1] }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Find_Control_Translated_Outside_Parent_Bounds() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Border target; |
||||
|
Panel container = new Panel |
||||
|
{ |
||||
|
Width = 200, |
||||
|
Height = 200, |
||||
|
Background = Brushes.Red, |
||||
|
ClipToBounds = false, |
||||
|
Children = |
||||
|
{ |
||||
|
new Border |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
ZIndex = 1, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Left, |
||||
|
VerticalAlignment = VerticalAlignment.Top, |
||||
|
Child = target = new Border |
||||
|
{ |
||||
|
Width = 50, |
||||
|
Height = 50, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Left, |
||||
|
VerticalAlignment = VerticalAlignment.Top, |
||||
|
RenderTransform = new TranslateTransform(110, 110), |
||||
|
} |
||||
|
}, |
||||
|
} |
||||
|
}; |
||||
|
s.TopLevel.Content = container; |
||||
|
|
||||
|
s.AssertHitTest(new Point(120, 120), null, target, container); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Control_Outside_Parent_Bounds_When_Clipped() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Border target; |
||||
|
Panel container = new Panel |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 200, |
||||
|
Background = Brushes.Red, |
||||
|
Children = |
||||
|
{ |
||||
|
new Panel() |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
Margin = new Thickness(0, 100, 0, 0), |
||||
|
ClipToBounds = true, |
||||
|
Children = |
||||
|
{ |
||||
|
(target = new Border() |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
Margin = new Thickness(0, -100, 0, 0) |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
s.TopLevel.Content = container; |
||||
|
|
||||
|
s.AssertHitTest(new Point(50, 50), null, container); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Control_Outside_Scroll_Viewport() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(100, 200))) |
||||
|
{ |
||||
|
Border target; |
||||
|
Border item1; |
||||
|
Border item2; |
||||
|
ScrollContentPresenter scroll; |
||||
|
Panel container = new Panel |
||||
|
{ |
||||
|
Width = 100, |
||||
|
Height = 200, |
||||
|
Background = Brushes.Red, |
||||
|
Children = |
||||
|
{ |
||||
|
(target = new Border() |
||||
|
{ |
||||
|
Name = "b1", |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
}), |
||||
|
new Border() |
||||
|
{ |
||||
|
Name = "b2", |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
Margin = new Thickness(0, 100, 0, 0), |
||||
|
Child = scroll = new ScrollContentPresenter() |
||||
|
{ |
||||
|
CanHorizontallyScroll = true, |
||||
|
CanVerticallyScroll = true, |
||||
|
Content = new StackPanel() |
||||
|
{ |
||||
|
Children = |
||||
|
{ |
||||
|
(item1 = new Border() |
||||
|
{ |
||||
|
Name = "b3", |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
}), |
||||
|
(item2 = new Border() |
||||
|
{ |
||||
|
Name = "b4", |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
}), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
s.TopLevel.Content = container; |
||||
|
|
||||
|
scroll.UpdateChild(); |
||||
|
|
||||
|
s.AssertHitTestFirst(new Point(50, 150), null, item1); |
||||
|
|
||||
|
s.AssertHitTestFirst(new Point(50,50), null, target); |
||||
|
|
||||
|
scroll.Offset = new Vector(0, 100); |
||||
|
|
||||
|
s.AssertHitTestFirst(new Point(50, 150), null, item2); |
||||
|
|
||||
|
s.AssertHitTestFirst(new Point(50,50), null, target); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Not_Find_Path_When_Outside_Fill() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(200, 200))) |
||||
|
{ |
||||
|
Path path = new Path |
||||
|
{ |
||||
|
Width = 200, |
||||
|
Height = 200, |
||||
|
Fill = Brushes.Red, |
||||
|
Data = StreamGeometry.Parse("M100,0 L0,100 100,100") |
||||
|
}; |
||||
|
s.TopLevel.Content = path; |
||||
|
|
||||
|
s.AssertHitTest(new Point(100, 100), null, path); |
||||
|
s.AssertHitTest(new Point(10, 10), null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Respect_Geometry_Clip() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(400, 400))) |
||||
|
{ |
||||
|
Canvas canvas; |
||||
|
Border border = new Border |
||||
|
{ |
||||
|
Background = Brushes.Red, |
||||
|
Clip = StreamGeometry.Parse("M100,0 L0,100 100,100"), |
||||
|
Width = 200, |
||||
|
Height = 200, |
||||
|
Child = canvas = new Canvas |
||||
|
{ |
||||
|
Background = Brushes.Yellow, |
||||
|
Margin = new Thickness(10), |
||||
|
} |
||||
|
|
||||
|
}; |
||||
|
s.TopLevel.Content = border; |
||||
|
|
||||
|
s.RunJobs(); |
||||
|
Assert.Equal(new Rect(100, 100, 200, 200), border.Bounds); |
||||
|
|
||||
|
s.AssertHitTest(new Point(200,200), null, canvas, border); |
||||
|
|
||||
|
s.AssertHitTest(new Point(110, 110), null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void HitTest_Should_Accommodate_ICustomHitTest() |
||||
|
{ |
||||
|
using (var s = new CompositorServices(new Size(300, 200))) |
||||
|
{ |
||||
|
Border border = new CustomHitTestBorder |
||||
|
{ |
||||
|
ClipToBounds = false, |
||||
|
Width = 100, |
||||
|
Height = 100, |
||||
|
Background = Brushes.Red, |
||||
|
HorizontalAlignment = HorizontalAlignment.Center, |
||||
|
VerticalAlignment = VerticalAlignment.Center |
||||
|
}; |
||||
|
|
||||
|
s.TopLevel.Content = border; |
||||
|
|
||||
|
s.AssertHitTest(75, 100, null, border); |
||||
|
s.AssertHitTest(125, 100, null, border); |
||||
|
s.AssertHitTest(175, 100, null); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private IDisposable TestApplication() |
||||
|
{ |
||||
|
return UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Media; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests.Rendering; |
||||
|
|
||||
|
public class CompositorInvalidationTests : CompositorTestsBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Control_Should_Invalidate_Own_Rect_When_Added() |
||||
|
{ |
||||
|
using (var s = new CompositorCanvas()) |
||||
|
{ |
||||
|
var control = new Border() |
||||
|
{ |
||||
|
Background = Brushes.Red, Width = 20, Height = 10, |
||||
|
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
||||
|
}; |
||||
|
s.Canvas.Children.Add(control); |
||||
|
s.AssertRects(new Rect(30, 50, 20, 10)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Control_Should_Invalidate_Own_Rect_When_Removed() |
||||
|
{ |
||||
|
using (var s = new CompositorCanvas()) |
||||
|
{ |
||||
|
var control = new Border() |
||||
|
{ |
||||
|
Background = Brushes.Red, Width = 20, Height = 10, |
||||
|
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
||||
|
}; |
||||
|
s.Canvas.Children.Add(control); |
||||
|
s.RunJobs(); |
||||
|
s.Events.Rects.Clear(); |
||||
|
s.Canvas.Children.Remove(control); |
||||
|
s.AssertRects(new Rect(30, 50, 20, 10)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Control_Should_Invalidate_Both_Own_Rects_When_Moved() |
||||
|
{ |
||||
|
using (var s = new CompositorCanvas()) |
||||
|
{ |
||||
|
var control = new Border() |
||||
|
{ |
||||
|
Background = Brushes.Red, Width = 20, Height = 10, |
||||
|
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50 |
||||
|
}; |
||||
|
s.Canvas.Children.Add(control); |
||||
|
s.RunJobs(); |
||||
|
s.Events.Rects.Clear(); |
||||
|
control[Canvas.LeftProperty] = 55; |
||||
|
s.AssertRects(new Rect(30, 50, 20, 10), |
||||
|
new Rect(55, 50, 20, 10) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Control_Should_Invalidate_Child_Rects_When_Moved() |
||||
|
{ |
||||
|
using (var s = new CompositorCanvas()) |
||||
|
{ |
||||
|
var control = new Decorator() |
||||
|
{ |
||||
|
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, |
||||
|
Padding = new Thickness(10), |
||||
|
Child = new Border() |
||||
|
{ |
||||
|
Width = 20, Height = 10, |
||||
|
Background = Brushes.Red |
||||
|
} |
||||
|
}; |
||||
|
s.Canvas.Children.Add(control); |
||||
|
s.RunJobs(); |
||||
|
s.Events.Rects.Clear(); |
||||
|
control[Canvas.LeftProperty] = 55; |
||||
|
s.AssertRects(new Rect(40, 60, 20, 10), |
||||
|
new Rect(65, 60, 20, 10) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Control_Should_Invalidate_Child_Rects_When_Becomes_Invisible() |
||||
|
{ |
||||
|
using (var s = new CompositorCanvas()) |
||||
|
{ |
||||
|
var control = new Decorator() |
||||
|
{ |
||||
|
[Canvas.LeftProperty] = 30, [Canvas.TopProperty] = 50, |
||||
|
Padding = new Thickness(10), |
||||
|
Child = new Border() |
||||
|
{ |
||||
|
Width = 20, Height = 10, |
||||
|
Background = Brushes.Red |
||||
|
} |
||||
|
}; |
||||
|
s.Canvas.Children.Add(control); |
||||
|
s.RunJobs(); |
||||
|
s.Events.Rects.Clear(); |
||||
|
control.IsVisible = false; |
||||
|
s.AssertRects(new Rect(40, 60, 20, 10)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,208 @@ |
|||||
|
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<Rect> Rects = new(); |
||||
|
|
||||
|
public void RectInvalidated(Rect rc) |
||||
|
{ |
||||
|
Rects.Add(rc); |
||||
|
} |
||||
|
|
||||
|
public void Reset() |
||||
|
{ |
||||
|
Rects.Clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ManualRenderTimer : IRenderTimer |
||||
|
{ |
||||
|
public event Action<TimeSpan> 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<object> Surfaces { get; } = Array.Empty<object>(); |
||||
|
public Action<RawInputEventArgs> Input { get; set; } |
||||
|
public Action<Rect> Paint { get; set; } |
||||
|
public Action<Size, PlatformResizeReason> Resized { get; set; } |
||||
|
public Action<double> ScalingChanged { get; set; } |
||||
|
public Action<WindowTransparencyLevel> 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<IRenderTimer>().ToConstant(Timer); |
||||
|
AvaloniaLocator.CurrentMutable.Bind<IRenderLoop>() |
||||
|
.ToConstant(new RenderLoop(Timer, Dispatcher.UIThread)); |
||||
|
|
||||
|
Compositor = new Compositor(AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(), 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<IVisual, bool> filter, params object[] expected) |
||||
|
=> AssertHitTest(new Point(x, y), filter, expected); |
||||
|
public void AssertHitTest(Point pt, Func<IVisual, bool> filter, params object[] expected) |
||||
|
{ |
||||
|
RunJobs(); |
||||
|
var tested = Renderer.HitTest(pt, TopLevel, filter); |
||||
|
Assert.Equal(expected, tested); |
||||
|
} |
||||
|
|
||||
|
public void AssertHitTestFirst(Point pt, Func<IVisual, bool> 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(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue