123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- using System;
- using System.Threading.Tasks;
- using Avalonia.Controls;
- using Avalonia.Controls.Presenters;
- using Avalonia.Controls.Primitives;
- using Avalonia.Controls.Templates;
- using Avalonia.Media;
- using Avalonia.UnitTests;
- using Xunit;
- namespace Avalonia.Layout.UnitTests
- {
- public class LayoutableTests_EffectiveViewportChanged
- {
- [Fact]
- public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree()
- {
- #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
- await RunOnUIThread.Execute(async () =>
- #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
- {
- var root = CreateRoot();
- var target = new Canvas();
- var raised = 0;
- target.EffectiveViewportChanged += (s, e) =>
- {
- ++raised;
- };
- root.Child = target;
- Assert.Equal(0, raised);
- });
- }
- [Fact]
- public async Task EffectiveViewportChanged_Raised_Before_LayoutUpdated()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas();
- var raised = 0;
- target.EffectiveViewportChanged += (s, e) =>
- {
- ++raised;
- };
- root.Child = target;
- await ExecuteInitialLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Parent_Affects_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 100, Height = 100 };
- var parent = new Border { Width = 200, Height = 200, Child = target };
- var raised = 0;
- root.Child = parent;
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(-550, -400, 1200, 900), e.EffectiveViewport);
- ++raised;
- };
- await ExecuteInitialLayoutPass(root);
- });
- }
- [Fact]
- public async Task Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated_Raised()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new TestCanvas();
- var raised = 0;
- var layoutUpdatedRaised = 0;
- root.LayoutUpdated += (s, e) =>
- {
- Assert.Equal(2, target.MeasureCount);
- Assert.Equal(2, target.ArrangeCount);
- ++layoutUpdatedRaised;
- };
- target.EffectiveViewportChanged += (s, e) =>
- {
- target.InvalidateMeasure();
- ++raised;
- };
- root.Child = target;
- await ExecuteInitialLayoutPass(root);
- Assert.Equal(1, raised);
- Assert.Equal(1, layoutUpdatedRaised);
- });
- }
- [Fact]
- public async Task Viewport_Extends_Beyond_Centered_Control()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 52, Height = 52, };
- var raised = 0;
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
- ++raised;
- };
- root.Child = target;
- await ExecuteInitialLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Viewport_Extends_Beyond_Nested_Centered_Control()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 52, Height = 52 };
- var parent = new Border { Width = 100, Height = 100, Child = target };
- var raised = 0;
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport);
- ++raised;
- };
- root.Child = parent;
- await ExecuteInitialLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task ScrollViewer_Determines_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 200, Height = 200 };
- var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
- var raised = 0;
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport);
- ++raised;
- };
- root.Child = scroller;
- await ExecuteInitialLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Scrolled_ScrollViewer_Determines_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 200, Height = 200 };
- var scroller = new ScrollViewer { Width = 100, Height = 100, Content = target, Template = ScrollViewerTemplate() };
- var raised = 0;
- root.Child = scroller;
- await ExecuteInitialLayoutPass(root);
- scroller.Offset = new Vector(0, 10);
- await ExecuteScrollerLayoutPass(root, scroller, target, (s, e) =>
- {
- Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport);
- ++raised;
- });
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Moving_Parent_Updates_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 100, Height = 100 };
- var parent = new Border { Width = 200, Height = 200, Child = target };
- var raised = 0;
- root.Child = parent;
- await ExecuteInitialLayoutPass(root);
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport);
- ++raised;
- };
- parent.Margin = new Thickness(8, 0, 0, 0);
- await ExecuteLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Translate_Transform_Doesnt_Affect_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 100, Height = 100 };
- var parent = new Border { Width = 200, Height = 200, Child = target };
- var raised = 0;
- root.Child = parent;
- await ExecuteInitialLayoutPass(root);
- target.EffectiveViewportChanged += (s, e) => ++raised;
- target.RenderTransform = new TranslateTransform { X = 8 };
- target.InvalidateMeasure();
- await ExecuteLayoutPass(root);
- Assert.Equal(0, raised);
- });
- }
- [Fact]
- public async Task Translate_Transform_On_Parent_Affects_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 100, Height = 100 };
- var parent = new Border { Width = 200, Height = 200, Child = target };
- var raised = 0;
- root.Child = parent;
- await ExecuteInitialLayoutPass(root);
- target.EffectiveViewportChanged += (s, e) =>
- {
- Assert.Equal(new Rect(-558, -400, 1200, 900), e.EffectiveViewport);
- ++raised;
- };
- // Change the parent render transform to move it. A layout is then needed before
- // EffectiveViewportChanged is raised.
- parent.RenderTransform = new TranslateTransform { X = 8 };
- parent.InvalidateMeasure();
- await ExecuteLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- [Fact]
- public async Task Rotate_Transform_On_Parent_Affects_EffectiveViewport()
- {
- await RunOnUIThread.Execute(async () =>
- {
- var root = CreateRoot();
- var target = new Canvas { Width = 100, Height = 100 };
- var parent = new Border { Width = 200, Height = 200, Child = target };
- var raised = 0;
- root.Child = parent;
- await ExecuteInitialLayoutPass(root);
- target.EffectiveViewportChanged += (s, e) =>
- {
- AssertArePixelEqual(new Rect(-651, -792, 1484, 1484), e.EffectiveViewport);
- ++raised;
- };
- parent.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
- parent.RenderTransform = new RotateTransform { Angle = 45 };
- parent.InvalidateMeasure();
- await ExecuteLayoutPass(root);
- Assert.Equal(1, raised);
- });
- }
- private TestRoot CreateRoot() => new TestRoot { Width = 1200, Height = 900 };
- private Task ExecuteInitialLayoutPass(TestRoot root)
- {
- root.LayoutManager.ExecuteInitialLayoutPass();
- return Task.CompletedTask;
- }
- private Task ExecuteLayoutPass(TestRoot root)
- {
- root.LayoutManager.ExecuteLayoutPass();
- return Task.CompletedTask;
- }
- private Task ExecuteScrollerLayoutPass(
- TestRoot root,
- ScrollViewer scroller,
- Control target,
- Action<object, EffectiveViewportChangedEventArgs> handler)
- {
- void ViewportChanged(object sender, EffectiveViewportChangedEventArgs e)
- {
- handler(sender, e);
- }
- target.EffectiveViewportChanged += ViewportChanged;
- root.LayoutManager.ExecuteLayoutPass();
- return Task.CompletedTask;
- }
- private IControlTemplate ScrollViewerTemplate()
- {
- return new FuncControlTemplate<ScrollViewer>((control, scope) => new Grid
- {
- ColumnDefinitions = new ColumnDefinitions
- {
- new ColumnDefinition(1, GridUnitType.Star),
- new ColumnDefinition(GridLength.Auto),
- },
- RowDefinitions = new RowDefinitions
- {
- new RowDefinition(1, GridUnitType.Star),
- new RowDefinition(GridLength.Auto),
- },
- Children =
- {
- new ScrollContentPresenter
- {
- Name = "PART_ContentPresenter",
- [~ContentPresenter.ContentProperty] = control[~ContentControl.ContentProperty],
- [~~ScrollContentPresenter.ExtentProperty] = control[~~ScrollViewer.ExtentProperty],
- [~~ScrollContentPresenter.OffsetProperty] = control[~~ScrollViewer.OffsetProperty],
- [~~ScrollContentPresenter.ViewportProperty] = control[~~ScrollViewer.ViewportProperty],
- [~ScrollContentPresenter.CanHorizontallyScrollProperty] = control[~ScrollViewer.CanHorizontallyScrollProperty],
- [~ScrollContentPresenter.CanVerticallyScrollProperty] = control[~ScrollViewer.CanVerticallyScrollProperty],
- }.RegisterInNameScope(scope),
- new ScrollBar
- {
- Name = "horizontalScrollBar",
- Orientation = Orientation.Horizontal,
- [~RangeBase.MaximumProperty] = control[~ScrollViewer.HorizontalScrollBarMaximumProperty],
- [~~RangeBase.ValueProperty] = control[~~ScrollViewer.HorizontalScrollBarValueProperty],
- [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.HorizontalScrollBarViewportSizeProperty],
- [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.HorizontalScrollBarVisibilityProperty],
- [Grid.RowProperty] = 1,
- }.RegisterInNameScope(scope),
- new ScrollBar
- {
- Name = "verticalScrollBar",
- Orientation = Orientation.Vertical,
- [~RangeBase.MaximumProperty] = control[~ScrollViewer.VerticalScrollBarMaximumProperty],
- [~~RangeBase.ValueProperty] = control[~~ScrollViewer.VerticalScrollBarValueProperty],
- [~ScrollBar.ViewportSizeProperty] = control[~ScrollViewer.VerticalScrollBarViewportSizeProperty],
- [~ScrollBar.VisibilityProperty] = control[~ScrollViewer.VerticalScrollBarVisibilityProperty],
- [Grid.ColumnProperty] = 1,
- }.RegisterInNameScope(scope),
- },
- });
- }
- private void AssertArePixelEqual(Rect expected, Rect actual)
- {
- var expectedRounded = new Rect((int)expected.X, (int)expected.Y, (int)expected.Width, (int)expected.Height);
- var actualRounded = new Rect((int)actual.X, (int)actual.Y, (int)actual.Width, (int)actual.Height);
- Assert.Equal(expectedRounded, actualRounded);
- }
- private class TestCanvas : Canvas
- {
- public int MeasureCount { get; private set; }
- public int ArrangeCount { get; private set; }
- protected override Size MeasureOverride(Size availableSize)
- {
- ++MeasureCount;
- return base.MeasureOverride(availableSize);
- }
- protected override Size ArrangeOverride(Size finalSize)
- {
- ++ArrangeCount;
- return base.ArrangeOverride(finalSize);
- }
- }
- private static class RunOnUIThread
- {
- public static async Task Execute(Func<Task> func)
- {
- await func();
- }
- }
- }
- }
|