From 5a4a7c80c7861aaf68bd0a9932232ab40bdf0f78 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 9 Jun 2020 17:23:13 +0300 Subject: [PATCH 01/40] .axaml --- packages/Avalonia/Avalonia.csproj | 4 ++++ packages/Avalonia/AvaloniaBuildTasks.props | 11 ++++++++++- packages/Avalonia/AvaloniaBuildTasks.targets | 7 +++++-- packages/Avalonia/AvaloniaItemSchema.xaml | 13 +++++++++++++ .../GenerateAvaloniaResourcesTask.cs | 4 ++-- .../XamlCompilerTaskExecutor.cs | 3 ++- 6 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 packages/Avalonia/AvaloniaItemSchema.xaml diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 2c5a09bee7..cd3ce9adcd 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -41,6 +41,10 @@ true build\ + + true + build\ + diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 30bafa37ee..8b452c7890 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -1,3 +1,12 @@ - + + + + + + + %(Filename) + Code + + diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 537495fcad..6a4db38b28 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -31,9 +31,12 @@ + DependsOnTargets="$(BuildAvaloniaResourcesDependsOn)"> + + + + + + + + + + + + diff --git a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs index 406abe6f99..ae2bf99d1e 100644 --- a/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs +++ b/src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs @@ -107,7 +107,7 @@ namespace Avalonia.Build.Tasks foreach (var s in sources.ToList()) { - if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml")) + if (s.Path.ToLowerInvariant().EndsWith(".xaml") || s.Path.ToLowerInvariant().EndsWith(".paml") || s.Path.ToLowerInvariant().EndsWith(".axaml")) { XamlFileInfo info; try @@ -150,7 +150,7 @@ namespace Avalonia.Build.Tasks BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance); - foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml"))) + foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml") || r.ItemSpec.EndsWith(".axaml"))) BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec, "XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work"); var resources = BuildResourceSources(); diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 3b69109e68..30e8f120d7 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -25,7 +25,8 @@ namespace Avalonia.Build.Tasks public static partial class XamlCompilerTaskExecutor { static bool CheckXamlName(IResource r) => r.Name.ToLowerInvariant().EndsWith(".xaml") - || r.Name.ToLowerInvariant().EndsWith(".paml"); + || r.Name.ToLowerInvariant().EndsWith(".paml") + || r.Name.ToLowerInvariant().EndsWith(".axaml"); public class CompileResult { From 927692763ebcf5abca2cae4607ed34edb2f12e57 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 9 Jun 2020 18:11:33 +0300 Subject: [PATCH 02/40] More msbuild metadata --- packages/Avalonia/AvaloniaBuildTasks.props | 1 + packages/Avalonia/AvaloniaBuildTasks.targets | 1 + packages/Avalonia/AvaloniaItemSchema.xaml | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 8b452c7890..21cc77a1ff 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -1,6 +1,7 @@ + diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 6a4db38b28..68b54c0916 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -82,5 +82,6 @@ + diff --git a/packages/Avalonia/AvaloniaItemSchema.xaml b/packages/Avalonia/AvaloniaItemSchema.xaml index b103e69c4f..a51ea3c0be 100644 --- a/packages/Avalonia/AvaloniaItemSchema.xaml +++ b/packages/Avalonia/AvaloniaItemSchema.xaml @@ -10,4 +10,9 @@ + From 5fd0dfe0b390d2afe2fe3e653880d2f8be60fb93 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 9 Jun 2020 18:28:49 +0300 Subject: [PATCH 03/40] More metadata --- packages/Avalonia/AvaloniaBuildTasks.props | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 21cc77a1ff..8f98f5424a 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -9,5 +9,9 @@ %(Filename) Code + + %(Filename) + Code + From ae47355b70203e454f5d7c64ac830248b1f622a2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 18 Jun 2020 11:50:09 +0300 Subject: [PATCH 04/40] More default metadata --- packages/Avalonia/AvaloniaBuildTasks.props | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 8f98f5424a..53df03f2d6 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -3,8 +3,12 @@ - - + + + + + + %(Filename) Code From d9d33720d85b2d2c5c8c73cd1a1ea27bb1e19ebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 18 Jun 2020 12:22:36 +0300 Subject: [PATCH 05/40] metadata? --- packages/Avalonia/AvaloniaBuildTasks.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 53df03f2d6..3835aa5e97 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -4,7 +4,7 @@ - + From 03a10c26ec837e05cbed8549fdce3af9e44ba8d0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 21 Jun 2020 12:04:08 +0300 Subject: [PATCH 06/40] Update .targets --- packages/Avalonia/AvaloniaBuildTasks.props | 10 ---------- packages/Avalonia/AvaloniaBuildTasks.targets | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/Avalonia/AvaloniaBuildTasks.props b/packages/Avalonia/AvaloniaBuildTasks.props index 3835aa5e97..deea3aa391 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.props +++ b/packages/Avalonia/AvaloniaBuildTasks.props @@ -7,15 +7,5 @@ - - - - %(Filename) - Code - - - %(Filename) - Code - diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index 68b54c0916..84a62bb5c0 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -4,6 +4,20 @@ <_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false low + + + + + %(Filename) + Code + + + %(Filename) + Code + + + + Date: Tue, 23 Jun 2020 01:29:37 +0200 Subject: [PATCH 07/40] Add compact density style --- .../DensityStyles/Compact.xaml | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml diff --git a/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml new file mode 100644 index 0000000000..f0f3e5ea16 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/DensityStyles/Compact.xaml @@ -0,0 +1,23 @@ + + + + From 7694fc048437a172cef98339bc037dd652f1d838 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:40:19 +0200 Subject: [PATCH 08/40] Don't run layout passes on hidden TopLevels. To do this we need to pass the root to `LayoutManager`. Fixes #4161 --- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Layout/ILayoutManager.cs | 10 +++++++ src/Avalonia.Layout/LayoutManager.cs | 28 +++++++++++++++---- .../ItemsPresenterTests_Virtualization.cs | 7 ++++- ...emsPresenterTests_Virtualization_Simple.cs | 7 ++++- .../TopLevelTests.cs | 2 +- .../LayoutableTests.cs | 4 +-- tests/Avalonia.UnitTests/TestRoot.cs | 4 ++- tests/Avalonia.UnitTests/TestTemplatedRoot.cs | 3 +- 9 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5d34444eb8..c738a5ff3b 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -315,7 +315,7 @@ namespace Avalonia.Controls /// /// Creates the layout manager for this . /// - protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(); + protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); /// /// Handles a paint notification from . diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index 6e63d3edbb..688b6b83e5 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -35,6 +35,15 @@ namespace Avalonia.Layout /// void ExecuteLayoutPass(); + /// + /// Executes the initial layout pass on a layout root. + /// + /// + /// You should not usually need to call this method explictly, the layout root will call + /// it to carry out the initial layout of the control. + /// + void ExecuteInitialLayoutPass(); + /// /// Executes the initial layout pass on a layout root. /// @@ -43,6 +52,7 @@ namespace Avalonia.Layout /// You should not usually need to call this method explictly, the layout root will call /// it to carry out the initial layout of the control. /// + [Obsolete("Call ExecuteInitialLayoutPass without parameter")] void ExecuteInitialLayoutPass(ILayoutRoot root); } } diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index aefb319fd0..a81f2b61b7 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -12,14 +12,16 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager { + private readonly ILayoutRoot _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; private bool _queued; private bool _running; - public LayoutManager() + public LayoutManager(ILayoutRoot owner) { + _owner = owner ?? throw new ArgumentNullException(nameof(owner)); _executeLayoutPass = ExecuteLayoutPass; } @@ -73,6 +75,11 @@ namespace Avalonia.Layout Dispatcher.UIThread.VerifyAccess(); + if (!_owner.IsVisible) + { + return; + } + if (!_running) { _running = true; @@ -131,13 +138,13 @@ namespace Avalonia.Layout } /// - public virtual void ExecuteInitialLayoutPass(ILayoutRoot root) + public virtual void ExecuteInitialLayoutPass() { try { _running = true; - Measure(root); - Arrange(root); + Measure(_owner); + Arrange(_owner); } finally { @@ -151,6 +158,17 @@ namespace Avalonia.Layout ExecuteLayoutPass(); } + [Obsolete("Call ExecuteInitialLayoutPass without parameter")] + public void ExecuteInitialLayoutPass(ILayoutRoot root) + { + if (root != _owner) + { + throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root."); + } + + ExecuteInitialLayoutPass(); + } + private void ExecuteMeasurePass() { while (_toMeasure.Count > 0) @@ -228,7 +246,7 @@ namespace Avalonia.Layout private void QueueLayoutPass() { - if (!_queued && !_running) + if (!_queued && !_running && _owner.IsVisible) { Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 9caae89cfe..3320ced8a4 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -324,6 +324,11 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot { + public TestScroller() + { + LayoutManager = new LayoutManager(this); + } + public IRenderer Renderer { get; } public Size ClientSize { get; } public double RenderScaling => 1; @@ -332,7 +337,7 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; } public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index 5a2cb60a56..d3fa565f4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -1062,6 +1062,11 @@ namespace Avalonia.Controls.UnitTests.Presenters private class TestScroller : ScrollContentPresenter, IRenderRoot, ILayoutRoot, ILogicalRoot { + public TestScroller() + { + LayoutManager = new LayoutManager(this); + } + public IRenderer Renderer { get; } public Size ClientSize { get; } public double RenderScaling => 1; @@ -1070,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests.Presenters public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; } public IRenderTarget CreateRenderTarget() => throw new NotImplementedException(); public void Invalidate(Rect rect) => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 6b107b0187..e5ff8d04de 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -323,7 +323,7 @@ namespace Avalonia.Controls.UnitTests public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null) : base(impl) { - _layoutManager = layoutManager ?? new LayoutManager(); + _layoutManager = layoutManager ?? new LayoutManager(this); } protected override ILayoutManager CreateLayoutManager() => _layoutManager; diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs index a21c8d589d..44a5af94b9 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests.cs @@ -208,14 +208,12 @@ namespace Avalonia.Layout.UnitTests { Border border1; Border border2; - var layoutManager = new LayoutManager(); var root = new TestRoot { Child = border1 = new Border { Child = border2 = new Border(), }, - LayoutManager = layoutManager, }; var raised = 0; @@ -233,7 +231,7 @@ namespace Avalonia.Layout.UnitTests root.Measure(new Size(100, 100)); root.Arrange(new Rect(0, 0, 100, 100)); - layoutManager.ExecuteLayoutPass(); + root.LayoutManager.ExecuteLayoutPass(); Assert.Equal(3, raised); Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds); diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs index f291d386aa..b6f3a020e8 100644 --- a/tests/Avalonia.UnitTests/TestRoot.cs +++ b/tests/Avalonia.UnitTests/TestRoot.cs @@ -19,6 +19,8 @@ namespace Avalonia.UnitTests public TestRoot() { Renderer = Mock.Of(); + LayoutManager = new LayoutManager(this); + IsVisible = true; } public TestRoot(IControl child) @@ -44,7 +46,7 @@ namespace Avalonia.UnitTests public double LayoutScaling { get; set; } = 1; - public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; set; } public double RenderScaling => 1; diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs index da4d92ce5e..38ab3c3c5d 100644 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs @@ -16,6 +16,7 @@ namespace Avalonia.UnitTests public TestTemplatedRoot() { + LayoutManager = new LayoutManager(this); Template = new FuncControlTemplate((x, scope) => new ContentPresenter { Name = "PART_ContentPresenter", @@ -28,7 +29,7 @@ namespace Avalonia.UnitTests public double LayoutScaling => 1; - public ILayoutManager LayoutManager { get; set; } = new LayoutManager(); + public ILayoutManager LayoutManager { get; set; } public double RenderScaling => 1; From d3c3741bec5b08925bb8bb0ce325c78f7ed82ea3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:44:33 +0200 Subject: [PATCH 09/40] No longer need to pass root to ExecuteInitialLayoutPass. --- .../Embedding/EmbeddableControlRoot.cs | 2 +- .../Embedding/Offscreen/OffscreenTopLevel.cs | 2 +- src/Avalonia.Controls/Window.cs | 4 +-- src/Avalonia.Controls/WindowBase.cs | 2 +- .../Layout/ControlsBenchmark.cs | 2 +- tests/Avalonia.Benchmarks/Layout/Measure.cs | 2 +- .../Traversal/VisualTreeTraversal.cs | 2 +- .../Avalonia.Controls.UnitTests/GridTests.cs | 2 +- .../ListBoxTests.cs | 2 +- .../ItemsPresenterTests_Virtualization.cs | 2 +- ...emsPresenterTests_Virtualization_Simple.cs | 20 ++++++------ .../ScrollViewerTests.cs | 6 ++-- .../TopLevelTests.cs | 6 ++-- .../LayoutManagerTests.cs | 32 +++++++++---------- tests/Avalonia.LeakTests/ControlTests.cs | 20 ++++++------ .../Rendering/ImmediateRendererTests.cs | 8 ++--- .../Rendering/SceneGraph/SceneBuilderTests.cs | 6 ++-- .../SceneGraph/SceneBuilderTests_Layers.cs | 10 +++--- 18 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index 2d48a7d33b..c56294f9ec 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Embedding { EnsureInitialized(); ApplyTemplate(); - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); } private void EnsureInitialized() diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs index d326ab5734..b037dd9901 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs @@ -18,7 +18,7 @@ namespace Avalonia.Controls.Embedding.Offscreen { EnsureInitialized(); ApplyTemplate(); - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); } private void EnsureInitialized() diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ff7cc41e3b..cedd20ace5 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -519,7 +519,7 @@ namespace Avalonia.Controls } } - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); using (BeginAutoSizing()) { @@ -592,7 +592,7 @@ namespace Avalonia.Controls } } - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); var result = new TaskCompletionSource(); diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index afc01db506..eb6e7319f5 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -162,7 +162,7 @@ namespace Avalonia.Controls if (!_hasExecutedInitialLayoutPass) { - LayoutManager.ExecuteInitialLayoutPass(this); + LayoutManager.ExecuteInitialLayoutPass(); _hasExecutedInitialLayoutPass = true; } PlatformImpl?.Show(); diff --git a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs index de40c247e6..7170f6d7d4 100644 --- a/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs +++ b/tests/Avalonia.Benchmarks/Layout/ControlsBenchmark.cs @@ -24,7 +24,7 @@ namespace Avalonia.Benchmarks.Layout Renderer = new NullRenderer() }; - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark] diff --git a/tests/Avalonia.Benchmarks/Layout/Measure.cs b/tests/Avalonia.Benchmarks/Layout/Measure.cs index d03d17b4d3..fce2cddec9 100644 --- a/tests/Avalonia.Benchmarks/Layout/Measure.cs +++ b/tests/Avalonia.Benchmarks/Layout/Measure.cs @@ -25,7 +25,7 @@ namespace Avalonia.Benchmarks.Layout _controls.Add(panel); _controls = ControlHierarchyCreator.CreateChildren(_controls, panel, 3, 5, 5); - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark, MethodImpl(MethodImplOptions.NoInlining)] diff --git a/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs index fc2380d670..c6da3a941f 100644 --- a/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs +++ b/tests/Avalonia.Benchmarks/Traversal/VisualTreeTraversal.cs @@ -26,7 +26,7 @@ namespace Avalonia.Benchmarks.Traversal _shuffledControls = _controls.OrderBy(r => random.Next()).ToList(); - _root.LayoutManager.ExecuteInitialLayoutPass(_root); + _root.LayoutManager.ExecuteInitialLayoutPass(); } [Benchmark] diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs index b3882c534b..4ffa526b85 100644 --- a/tests/Avalonia.Controls.UnitTests/GridTests.cs +++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs @@ -1194,7 +1194,7 @@ namespace Avalonia.Controls.UnitTests Height = 50, }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); PrintColumnDefinitions(grids[0]); Assert.Equal(5, grids[0].ColumnDefinitions[0].ActualWidth); diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs index a7679ba388..c4346e571b 100644 --- a/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests.cs @@ -329,7 +329,7 @@ namespace Avalonia.Controls.UnitTests return tb; }, true); - lm.ExecuteInitialLayoutPass(wnd); + lm.ExecuteInitialLayoutPass(); target.Items = items; diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs index 3320ced8a4..d529cc4f75 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization.cs @@ -232,7 +232,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroll = (TestScroller)target.Parent; scroll.Width = scroll.Height = 100; - scroll.LayoutManager.ExecuteInitialLayoutPass(scroll); + scroll.LayoutManager.ExecuteInitialLayoutPass(); // Ensure than an intermediate measure pass doesn't add more controls than it // should. This can happen if target gets measured with Size.Infinity which diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs index d3fa565f4c..a467c6dd03 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests_Virtualization_Simple.cs @@ -723,7 +723,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var last = (target.Items as IList)[10]; @@ -740,7 +740,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var last = (target.Items as IList)[10]; @@ -838,7 +838,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[5]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -855,7 +855,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[9]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -874,7 +874,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 100; scroller.Height = 95; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[8]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -893,7 +893,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 100; scroller.Height = 95; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); ((ILogicalScrollable)target).Offset = new Vector(0, 11); var from = target.Panel.Children[1]; @@ -946,7 +946,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[5]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -963,7 +963,7 @@ namespace Avalonia.Controls.UnitTests.Presenters var scroller = (TestScroller)target.Parent; scroller.Width = scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[9]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -982,7 +982,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 95; scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); var from = target.Panel.Children[8]; var result = ((ILogicalScrollable)target).GetControlInDirection( @@ -1001,7 +1001,7 @@ namespace Avalonia.Controls.UnitTests.Presenters scroller.Width = 95; scroller.Height = 100; - scroller.LayoutManager.ExecuteInitialLayoutPass(scroller); + scroller.LayoutManager.ExecuteInitialLayoutPass(); ((ILogicalScrollable)target).Offset = new Vector(11, 0); var from = target.Panel.Children[1]; diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs index deca3cfb75..ab21c5d330 100644 --- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs @@ -158,7 +158,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { @@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { @@ -218,7 +218,7 @@ namespace Avalonia.Controls.UnitTests target.SetValue(ScrollViewer.ViewportProperty, new Size(50, 50)); target.Offset = new Vector(10, 10); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); target.ScrollChanged += (s, e) => { diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index e5ff8d04de..ab272b261b 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests } }; - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Rect(0, 0, 321, 432), target.Bounds); } @@ -282,7 +282,7 @@ namespace Avalonia.Controls.UnitTests Content = child, }; - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Thickness(0), child.BorderThickness); @@ -295,7 +295,7 @@ namespace Avalonia.Controls.UnitTests }; Application.Current.Styles.Add(style); - target.LayoutManager.ExecuteInitialLayoutPass(target); + target.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Thickness(2), child.BorderThickness); diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 332e8a751d..392227d5fe 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -13,7 +13,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateMeasure(); @@ -29,7 +29,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateArrange(); @@ -45,7 +45,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot(); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Child = control; root.Measured = root.Arranged = false; @@ -80,7 +80,7 @@ namespace Avalonia.Layout.UnitTests root.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control2.InvalidateMeasure(); control1.InvalidateMeasure(); @@ -115,7 +115,7 @@ namespace Avalonia.Layout.UnitTests root.DoMeasureOverride = MeasureOverride; control1.DoMeasureOverride = MeasureOverride; control2.DoMeasureOverride = MeasureOverride; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control2.InvalidateMeasure(); root.InvalidateMeasure(); @@ -132,7 +132,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measured = root.Arranged = false; control.Measured = control.Arranged = false; @@ -151,7 +151,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = control.Arranged = false; control.InvalidateMeasure(); @@ -177,7 +177,7 @@ namespace Avalonia.Layout.UnitTests return new Size(100, 100); }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(Size.Infinity, availableSize); } @@ -199,7 +199,7 @@ namespace Avalonia.Layout.UnitTests return s; }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(100, 100), arrangeSize); root.Width = 120; @@ -225,7 +225,7 @@ namespace Avalonia.Layout.UnitTests } }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(0, 0), root.DesiredSize); border.Width = 100; @@ -241,7 +241,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; int cnt = 0; @@ -272,7 +272,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Arranged = false; int cnt = 0; @@ -313,7 +313,7 @@ namespace Avalonia.Layout.UnitTests panel.Children.AddRange(nonArrageableTargets); panel.Children.AddRange(targets); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); foreach (var c in panel.Children.OfType()) { @@ -347,7 +347,7 @@ namespace Avalonia.Layout.UnitTests var control = new LayoutTestControl(); var root = new LayoutTestRoot { Child = control }; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; control.DoMeasureOverride = (l, s) => @@ -380,7 +380,7 @@ namespace Avalonia.Layout.UnitTests var root = new LayoutTestRoot { Child = control }; var count = 0; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); control.Measured = false; control.DoMeasureOverride = (l, s) => @@ -399,7 +399,7 @@ namespace Avalonia.Layout.UnitTests root.InvalidateMeasure(); control.InvalidateMeasure(); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); Assert.Equal(new Size(200, 200), control.Bounds.Size); Assert.Equal(new Size(200, 200), control.DesiredSize); diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs index 9bb9fd7145..f1e4c90947 100644 --- a/tests/Avalonia.LeakTests/ControlTests.cs +++ b/tests/Avalonia.LeakTests/ControlTests.cs @@ -44,7 +44,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Canvas gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); // Clear the content and ensure the Canvas is removed. @@ -82,7 +82,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Canvas gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Find("foo")); Assert.IsType(window.Presenter.Child); @@ -122,7 +122,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that ScrollViewer gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.IsType(((ScrollViewer)window.Presenter.Child).Presenter.Child); @@ -159,7 +159,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.NotEmpty(window.Presenter.Child.GetVisualChildren()); @@ -203,7 +203,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // Text property set. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); Assert.Equal("foo", ((TextBox)window.Presenter.Child).Text); @@ -241,7 +241,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that TextBox gets added to visual tree and its // template applied. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.Same(textBox, window.Presenter.Child); // Get the border from the TextBox template. @@ -295,7 +295,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that TreeViewItems get realized. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.Single(target.ItemContainerGenerator.Containers); // Clear the content and ensure the TreeView is removed. @@ -329,7 +329,7 @@ namespace Avalonia.LeakTests window.Show(); // Do a layout and make sure that Slider gets added to visual tree. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); // Clear the content and ensure the Slider is removed. @@ -403,7 +403,7 @@ namespace Avalonia.LeakTests // Do a layout and make sure that Canvas gets added to visual tree with // its render transform. - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); var canvas = Assert.IsType(window.Presenter.Child); Assert.IsType(canvas.RenderTransform); @@ -512,7 +512,7 @@ namespace Avalonia.LeakTests window.Show(); - window.LayoutManager.ExecuteInitialLayoutPass(window); + window.LayoutManager.ExecuteInitialLayoutPass(); Assert.IsType(window.Presenter.Child); window.Content = null; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs index 52552f0bee..acee9a50f5 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs @@ -134,7 +134,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(child); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measure(new Size(50, 100)); root.Arrange(new Rect(new Size(50, 100))); @@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(child); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); root.Measure(new Size(300, 100)); root.Arrange(new Rect(new Size(300, 100))); @@ -222,7 +222,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(rootGrid); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); root.Measure(rootSize); @@ -277,7 +277,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering var root = new TestRoot(rootGrid); root.Renderer = new ImmediateRenderer(root); - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.LayoutManager.ExecuteInitialLayoutPass(); var rootSize = new Size(RootWidth, RootHeight); root.Measure(rootSize); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index b0f890b484..8527c3a95b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -653,7 +653,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); @@ -696,7 +696,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); @@ -744,7 +744,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var scene = new Scene(tree); var sceneBuilder = new SceneBuilder(); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs index 1bece3ae22..e4d053d813 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -40,7 +40,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -105,7 +105,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -147,7 +147,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -197,7 +197,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); @@ -241,7 +241,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; var layout = tree.LayoutManager; - layout.ExecuteInitialLayoutPass(tree); + layout.ExecuteInitialLayoutPass(); var animation = new BehaviorSubject(0.5); border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation); From 536a2f7c6220d549b4dbfdf20ec792080a39e541 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:46:14 +0200 Subject: [PATCH 10/40] Check correct root in invalidation calls. --- src/Avalonia.Layout/LayoutManager.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index a81f2b61b7..8f964a6041 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -43,6 +43,11 @@ namespace Avalonia.Layout #endif } + if (control.VisualRoot != _owner) + { + throw new ArgumentException("Attempt to call InvalidateMeasure on wrong LayoutManager."); + } + _toMeasure.Enqueue(control); _toArrange.Enqueue(control); QueueLayoutPass(); @@ -64,6 +69,11 @@ namespace Avalonia.Layout #endif } + if (control.VisualRoot != _owner) + { + throw new ArgumentException("Attempt to call InvalidateArrange on wrong LayoutManager."); + } + _toArrange.Enqueue(control); QueueLayoutPass(); } From 259529fbcde9307680c86bc0e8b984b4c3ccfe56 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 16:57:41 +0200 Subject: [PATCH 11/40] Added unit test for not laying out invisible toplevel. --- .../LayoutManagerTests.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs index 392227d5fe..e429adce85 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs @@ -23,6 +23,22 @@ namespace Avalonia.Layout.UnitTests Assert.True(control.Arranged); } + [Fact] + public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible() + { + var control = new LayoutTestControl(); + var root = new LayoutTestRoot { Child = control, IsVisible = false }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + control.Measured = control.Arranged = false; + + control.InvalidateMeasure(); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.False(control.Measured); + Assert.False(control.Arranged); + } + [Fact] public void Arranges_InvalidateArranged_Control() { From f14d73f5c9c1e0e26ea75f068d3edb703897d432 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 23 Jun 2020 17:58:51 +0200 Subject: [PATCH 12/40] Disable render layers. We need a new renderer :/ Fixes #2244 --- .../Rendering/SceneGraph/SceneBuilder.cs | 9 ++------- .../Rendering/DeferredRendererTests.cs | 8 ++++---- .../Rendering/SceneGraph/SceneBuilderTests.cs | 4 +++- .../Rendering/SceneGraph/SceneBuilderTests_Layers.cs | 8 ++++---- 4 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index 5da44c5943..872f69c884 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -394,13 +394,8 @@ namespace Avalonia.Rendering.SceneGraph } } - private static bool ShouldStartLayer(IVisual visual) - { - var o = visual as IAvaloniaObject; - return visual.VisualChildren.Count > 0 && - o != null && - o.IsAnimating(Visual.OpacityProperty); - } + // HACK: Disabled layers because they're broken in current renderer. See #2244. + private static bool ShouldStartLayer(IVisual visual) => false; private static IGeometryImpl CreateLayerGeometryClip(VisualNode node) { diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs index 767111b89b..bfcc341eed 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs @@ -557,7 +557,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -599,7 +599,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -629,7 +629,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -658,7 +658,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Should_Draw_Transparent_Layer_With_Correct_Opacity() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs index b0f890b484..d93e6c990e 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs @@ -810,7 +810,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph }; Assert.Equal(expected, scene.Layers[tree].Dirty.ToArray()); - Assert.Equal(expected, scene.Layers[border].Dirty.ToArray()); + + // Layers are disabled. See #2244 + // Assert.Equal(expected, scene.Layers[border].Dirty.ToArray()); } } diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs index 1bece3ae22..33a0668b64 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs @@ -14,7 +14,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { public partial class SceneBuilderTests { - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -118,7 +118,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -168,7 +168,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void Hiding_Transparent_Control_Should_Remove_Layers() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) @@ -218,7 +218,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph } } - [Fact] + [Fact(Skip = "Layers are disabled. See #2244")] public void GeometryClip_Should_Affect_Child_Layers() { using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) From 6149cd69b5a8a5fb53a731103a75f851c25b8a10 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Jun 2020 22:29:00 +0300 Subject: [PATCH 13/40] Fixed compilation with old compiler versions --- src/Avalonia.X11/XI2Manager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/XI2Manager.cs b/src/Avalonia.X11/XI2Manager.cs index 4e44f55fe0..742973e0da 100644 --- a/src/Avalonia.X11/XI2Manager.cs +++ b/src/Avalonia.X11/XI2Manager.cs @@ -239,13 +239,13 @@ namespace Avalonia.X11 if (ev.Type == XiEventType.XI_ButtonPress && ev.Button >= 4 && ev.Button <= 7 && !ev.Emulated) { - Vector? scrollDelta = ev.Button switch + var scrollDelta = ev.Button switch { 4 => new Vector(0, 1), 5 => new Vector(0, -1), 6 => new Vector(1, 0), 7 => new Vector(-1, 0), - _ => null + _ => (Vector?)null }; if (scrollDelta.HasValue) From 2807cbe6cb923982fa8faa0422caf97e21c43d90 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 10:18:25 +0200 Subject: [PATCH 14/40] Make LayoutManager disposable. And dispose it on `TopLevel` close: this allows layout passes to be run before a window/popup is shown but prevents it being run after close. --- src/Avalonia.Controls/TopLevel.cs | 1 + src/Avalonia.Layout/ILayoutManager.cs | 2 +- src/Avalonia.Layout/LayoutManager.cs | 29 +++++++++++++++++-- src/Avalonia.Layout/LayoutQueue.cs | 9 +++++- .../TopLevelTests.cs | 17 +++++++++++ 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index c738a5ff3b..b4e33e6631 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -348,6 +348,7 @@ namespace Avalonia.Controls OnClosed(EventArgs.Empty); Renderer?.Dispose(); Renderer = null; + LayoutManager?.Dispose(); } /// diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index 688b6b83e5..d996b301f8 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - public interface ILayoutManager + public interface ILayoutManager : IDisposable { /// /// Raised when the layout manager completes a layout pass. diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 8f964a6041..c923aa62e9 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -10,12 +10,13 @@ namespace Avalonia.Layout /// /// Manages measuring and arranging of controls. /// - public class LayoutManager : ILayoutManager + public class LayoutManager : ILayoutManager, IDisposable { private readonly ILayoutRoot _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; + private bool _disposed; private bool _queued; private bool _running; @@ -33,6 +34,11 @@ namespace Avalonia.Layout control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); + if (_disposed) + { + return; + } + if (!control.IsAttachedToVisualTree) { #if DEBUG @@ -59,6 +65,11 @@ namespace Avalonia.Layout control = control ?? throw new ArgumentNullException(nameof(control)); Dispatcher.UIThread.VerifyAccess(); + if (_disposed) + { + return; + } + if (!control.IsAttachedToVisualTree) { #if DEBUG @@ -85,7 +96,7 @@ namespace Avalonia.Layout Dispatcher.UIThread.VerifyAccess(); - if (!_owner.IsVisible) + if (_disposed) { return; } @@ -150,6 +161,11 @@ namespace Avalonia.Layout /// public virtual void ExecuteInitialLayoutPass() { + if (_disposed) + { + return; + } + try { _running = true; @@ -179,6 +195,13 @@ namespace Avalonia.Layout ExecuteInitialLayoutPass(); } + public void Dispose() + { + _disposed = true; + _toMeasure.Dispose(); + _toArrange.Dispose(); + } + private void ExecuteMeasurePass() { while (_toMeasure.Count > 0) @@ -256,7 +279,7 @@ namespace Avalonia.Layout private void QueueLayoutPass() { - if (!_queued && !_running && _owner.IsVisible) + if (!_queued && !_running) { Dispatcher.UIThread.Post(_executeLayoutPass, DispatcherPriority.Layout); _queued = true; diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs index e261a1b48e..1a9eb6b785 100644 --- a/src/Avalonia.Layout/LayoutQueue.cs +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Avalonia.Layout { - internal class LayoutQueue : IReadOnlyCollection + internal class LayoutQueue : IReadOnlyCollection, IDisposable { private struct Info { @@ -84,5 +84,12 @@ namespace Avalonia.Layout _notFinalizedBuffer.Clear(); } + + public void Dispose() + { + _inner.Clear(); + _loopQueueInfo.Clear(); + _notFinalizedBuffer.Clear(); + } } } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index ab272b261b..e49e273bec 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -267,6 +267,23 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Close_Should_Dispose_LayoutManager() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var impl = new Mock(); + impl.SetupAllProperties(); + + var layoutManager = new Mock(); + var target = new TestTopLevel(impl.Object, layoutManager.Object); + + impl.Object.Closed(); + + layoutManager.Verify(x => x.Dispose()); + } + } + [Fact] public void Reacts_To_Changes_In_Global_Styles() { From 4aaa48739cee611d1f9458eec67c1c93623d7e46 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 24 Jun 2020 20:36:58 +0500 Subject: [PATCH 15/40] Fix skip ISolidColorBrush when animation --- .../Animation/Animators/SolidColorBrushAnimator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 8776d3a7b7..8f0b710f63 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -29,13 +29,13 @@ namespace Avalonia.Animation.Animators { foreach (var keyframe in this) { - if (keyframe.Value as ISolidColorBrush == null) + if (!(keyframe.Value is ISolidColorBrush)) return Disposable.Empty; // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. - if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush)) + if (keyframe.Value is ISolidColorBrush colorBrush) { - keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color; + keyframe.Value = colorBrush.Color; } } From be4f4ed1c2d5d34b6bf52b095cf9f6036fa52648 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 24 Jun 2020 20:49:25 +0500 Subject: [PATCH 16/40] Fix using pattern matching --- .../Animation/Animators/SolidColorBrushAnimator.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 8f0b710f63..d60542a6b4 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -51,17 +51,14 @@ namespace Avalonia.Animation.Animators if (_colorAnimator == null) InitializeColorAnimator(); - SolidColorBrush finalTarget; - // If it's ISCB, change it back to SCB. - if (targetVal.GetType() == typeof(ImmutableSolidColorBrush)) + if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) { - var col = (ImmutableSolidColorBrush)targetVal; - targetVal = new SolidColorBrush(col.Color); + targetVal = new SolidColorBrush(immutableSolidColorBrush.Color); control.SetValue(Property, targetVal); } - finalTarget = targetVal as SolidColorBrush; + var finalTarget = targetVal as SolidColorBrush; return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } From cbbbdc3c73220324a15613eaf074b50f282655d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Wed, 24 Jun 2020 19:04:05 +0200 Subject: [PATCH 17/40] Add missing Compact.xaml resource to project --- src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj index a4eab83e4a..84bf799d8d 100644 --- a/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj +++ b/src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj @@ -14,6 +14,7 @@ + From c0d192a8f63d704e097eb3880e9c87617380d6f1 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Wed, 24 Jun 2020 23:57:56 +0500 Subject: [PATCH 18/40] Rewrite getting targetVal --- .../Animation/Animators/SolidColorBrushAnimator.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index d60542a6b4..9c8f72fe88 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -1,6 +1,5 @@ using System; using System.Reactive.Disposables; -using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Immutable; @@ -39,11 +38,13 @@ namespace Avalonia.Animation.Animators } } - // Add SCB if the target prop is empty. - if (control.GetValue(Property) == null) - control.SetValue(Property, new SolidColorBrush(Colors.Transparent)); - var targetVal = control.GetValue(Property); + // Add SCB if the target prop is empty. + if (targetVal is null) + { + targetVal = new SolidColorBrush(Colors.Transparent); + control.SetValue(Property, targetVal); + } // Continue if target prop is not empty & is a SolidColorBrush derivative. if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) From 3157dbd13335a42c6dd6c753d000d44d8c6f3247 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 25 Jun 2020 00:05:23 +0500 Subject: [PATCH 19/40] Rewrite condition by if not ISolidColorBrush --- .../Animators/SolidColorBrushAnimator.cs | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 9c8f72fe88..191c9631b7 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -46,25 +46,22 @@ namespace Avalonia.Animation.Animators control.SetValue(Property, targetVal); } - // Continue if target prop is not empty & is a SolidColorBrush derivative. - if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType())) - { - if (_colorAnimator == null) - InitializeColorAnimator(); - - // If it's ISCB, change it back to SCB. - if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) - { - targetVal = new SolidColorBrush(immutableSolidColorBrush.Color); - control.SetValue(Property, targetVal); - } + if (!(targetVal is ISolidColorBrush)) + return Disposable.Empty; - var finalTarget = targetVal as SolidColorBrush; + if (_colorAnimator == null) + InitializeColorAnimator(); - return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); + // If it's ISCB, change it back to SCB. + if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) + { + targetVal = new SolidColorBrush(immutableSolidColorBrush.Color); + control.SetValue(Property, targetVal); } - return Disposable.Empty; + var finalTarget = targetVal as SolidColorBrush; + + return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null; From d7c3c03ccfefae01042662eb50fd9815a0362ba2 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 25 Jun 2020 00:14:55 +0500 Subject: [PATCH 20/40] Rewrite check targetVal --- .../Animators/SolidColorBrushAnimator.cs | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 191c9631b7..8f3abcf068 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -10,9 +10,9 @@ namespace Avalonia.Animation.Animators /// public class SolidColorBrushAnimator : Animator { - ColorAnimator _colorAnimator; + private ColorAnimator _colorAnimator; - void InitializeColorAnimator() + private void InitializeColorAnimator() { _colorAnimator = new ColorAnimator(); @@ -38,28 +38,29 @@ namespace Avalonia.Animation.Animators } } + SolidColorBrush finalTarget; var targetVal = control.GetValue(Property); - // Add SCB if the target prop is empty. if (targetVal is null) { - targetVal = new SolidColorBrush(Colors.Transparent); - control.SetValue(Property, targetVal); + finalTarget = new SolidColorBrush(Colors.Transparent); + control.SetValue(Property, finalTarget); } - - if (!(targetVal is ISolidColorBrush)) + else if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) + { + finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color); + control.SetValue(Property, finalTarget); + } + else if (!(targetVal is ISolidColorBrush)) + { return Disposable.Empty; - - if (_colorAnimator == null) - InitializeColorAnimator(); - - // If it's ISCB, change it back to SCB. - if (targetVal is ImmutableSolidColorBrush immutableSolidColorBrush) + } + else { - targetVal = new SolidColorBrush(immutableSolidColorBrush.Color); - control.SetValue(Property, targetVal); + finalTarget = targetVal as SolidColorBrush; } - var finalTarget = targetVal as SolidColorBrush; + if (_colorAnimator == null) + InitializeColorAnimator(); return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete); } From b4b54fe6a3fe69869db97d16b07d99df66a0a113 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 25 Jun 2020 11:05:30 +0500 Subject: [PATCH 21/40] Swap condition --- .../Animation/Animators/SolidColorBrushAnimator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 8f3abcf068..9c94823608 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -50,13 +50,13 @@ namespace Avalonia.Animation.Animators finalTarget = new SolidColorBrush(immutableSolidColorBrush.Color); control.SetValue(Property, finalTarget); } - else if (!(targetVal is ISolidColorBrush)) + else if (targetVal is ISolidColorBrush) { - return Disposable.Empty; + finalTarget = targetVal as SolidColorBrush; } else { - finalTarget = targetVal as SolidColorBrush; + return Disposable.Empty; } if (_colorAnimator == null) From 8e7d785419f20f7fd41ef366d6cb282ecf8443d3 Mon Sep 17 00:00:00 2001 From: Rustam Sayfutdinov Date: Thu, 25 Jun 2020 11:12:40 +0500 Subject: [PATCH 22/40] Merge condition --- .../Animation/Animators/SolidColorBrushAnimator.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 9c94823608..a8e618af27 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -26,16 +26,17 @@ namespace Avalonia.Animation.Animators public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) { + // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. foreach (var keyframe in this) { - if (!(keyframe.Value is ISolidColorBrush)) - return Disposable.Empty; - - // Preprocess keyframe values to Color if the xaml parser converts them to ISCB. if (keyframe.Value is ISolidColorBrush colorBrush) { keyframe.Value = colorBrush.Color; } + else + { + return Disposable.Empty; + } } SolidColorBrush finalTarget; From 7921512a7bb07fe71a14077251351e0ddc2bdd97 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 25 Jun 2020 13:15:18 +0300 Subject: [PATCH 23/40] Updated numerge --- nukebuild/Numerge | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nukebuild/Numerge b/nukebuild/Numerge index 4464343aef..aef10ae67d 160000 --- a/nukebuild/Numerge +++ b/nukebuild/Numerge @@ -1 +1 @@ -Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8 +Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5 From 9c7aeaf71311f14e404112d3630fe0219a80da0c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 11:34:39 +0200 Subject: [PATCH 24/40] Initial implementation of EffectiveViewportChanged. --- .../EffectiveViewportChangedEventArgs.cs | 24 ++ src/Avalonia.Layout/ILayoutManager.cs | 12 + src/Avalonia.Layout/ILayoutable.cs | 7 + src/Avalonia.Layout/LayoutManager.cs | 124 ++++++- src/Avalonia.Layout/Layoutable.cs | 70 +++- ...ayoutableTests_EffectiveViewportChanged.cs | 333 ++++++++++++++++++ 6 files changed, 544 insertions(+), 26 deletions(-) create mode 100644 src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs create mode 100644 tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs diff --git a/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs new file mode 100644 index 0000000000..1cdc775b13 --- /dev/null +++ b/src/Avalonia.Layout/EffectiveViewportChangedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Avalonia.Layout +{ + /// + /// Provides data for the event. + /// + public class EffectiveViewportChangedEventArgs : EventArgs + { + public EffectiveViewportChangedEventArgs(Rect effectiveViewport) + { + EffectiveViewport = effectiveViewport; + } + + /// + /// Gets the representing the effective viewport. + /// + /// + /// The viewport is expressed in coordinates relative to the control that the event is + /// raised on. + /// + public Rect EffectiveViewport { get; } + } +} diff --git a/src/Avalonia.Layout/ILayoutManager.cs b/src/Avalonia.Layout/ILayoutManager.cs index d996b301f8..614670a53b 100644 --- a/src/Avalonia.Layout/ILayoutManager.cs +++ b/src/Avalonia.Layout/ILayoutManager.cs @@ -54,5 +54,17 @@ namespace Avalonia.Layout /// [Obsolete("Call ExecuteInitialLayoutPass without parameter")] void ExecuteInitialLayoutPass(ILayoutRoot root); + + /// + /// Registers a control as wanting to receive effective viewport notifications. + /// + /// The control. + void RegisterEffectiveViewportListener(ILayoutable control); + + /// + /// Registers a control as no longer wanting to receive effective viewport notifications. + /// + /// The control. + void UnregisterEffectiveViewportListener(ILayoutable control); } } diff --git a/src/Avalonia.Layout/ILayoutable.cs b/src/Avalonia.Layout/ILayoutable.cs index 316a017f1d..54d3ba6a11 100644 --- a/src/Avalonia.Layout/ILayoutable.cs +++ b/src/Avalonia.Layout/ILayoutable.cs @@ -111,5 +111,12 @@ namespace Avalonia.Layout /// /// The child control. void ChildDesiredSizeChanged(ILayoutable control); + + /// + /// Used by the to notify the control that its effective + /// viewport is changed. + /// + /// The viewport information. + void EffectiveViewportChanged(EffectiveViewportChangedEventArgs e); } } diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index c923aa62e9..888c8a4910 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; +using Avalonia.VisualTree; #nullable enable @@ -12,10 +14,12 @@ namespace Avalonia.Layout /// public class LayoutManager : ILayoutManager, IDisposable { + private const int MaxPasses = 3; private readonly ILayoutRoot _owner; private readonly LayoutQueue _toMeasure = new LayoutQueue(v => !v.IsMeasureValid); private readonly LayoutQueue _toArrange = new LayoutQueue(v => !v.IsArrangeValid); private readonly Action _executeLayoutPass; + private List? _effectiveViewportChangedListeners; private bool _disposed; private bool _queued; private bool _running; @@ -92,8 +96,6 @@ namespace Avalonia.Layout /// public virtual void ExecuteLayoutPass() { - const int MaxPasses = 3; - Dispatcher.UIThread.VerifyAccess(); if (_disposed) @@ -125,23 +127,15 @@ namespace Avalonia.Layout _toMeasure.BeginLoop(MaxPasses); _toArrange.BeginLoop(MaxPasses); - try + for (var pass = 0; pass < MaxPasses; ++pass) { - for (var pass = 0; pass < MaxPasses; ++pass) - { - ExecuteMeasurePass(); - ExecuteArrangePass(); + InnerLayoutPass(); - if (_toMeasure.Count == 0) - { - break; - } + if (!RaiseEffectiveViewportChanged()) + { + break; } } - finally - { - _running = false; - } _toMeasure.EndLoop(); _toArrange.EndLoop(); @@ -202,6 +196,47 @@ namespace Avalonia.Layout _toArrange.Dispose(); } + void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control) + { + _effectiveViewportChangedListeners ??= new List(); + _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(control)); + } + + void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control) + { + if (_effectiveViewportChangedListeners is object) + { + for (var i = 0; i < _effectiveViewportChangedListeners.Count; ++i) + { + if (_effectiveViewportChangedListeners[i].Listener == control) + { + _effectiveViewportChangedListeners.RemoveAt(i); + } + } + } + } + + private void InnerLayoutPass() + { + try + { + for (var pass = 0; pass < MaxPasses; ++pass) + { + ExecuteMeasurePass(); + ExecuteArrangePass(); + + if (_toMeasure.Count == 0) + { + break; + } + } + } + finally + { + _running = false; + } + } + private void ExecuteMeasurePass() { while (_toMeasure.Count > 0) @@ -285,5 +320,64 @@ namespace Avalonia.Layout _queued = true; } } + + private bool RaiseEffectiveViewportChanged() + { + var startCount = _toMeasure.Count + _toArrange.Count; + + if (_effectiveViewportChangedListeners is object) + { + // TODO: This may not work correctly if listener is removed in event handler. + for (var i = 0; i < _effectiveViewportChangedListeners.Count; ++i) + { + var l = _effectiveViewportChangedListeners[i]; + var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); + CalculateEffectiveViewport(l.Listener, ref viewport); + + if (viewport != l.Viewport) + { + l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); + _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport); + } + } + } + + return startCount != _toMeasure.Count + _toMeasure.Count; + } + + private void CalculateEffectiveViewport(IVisual control, ref Rect viewport) + { + if (control.VisualParent is object) + { + CalculateEffectiveViewport(control.VisualParent, ref viewport); + } + + if (control.ClipToBounds || control.VisualParent is null) + { + viewport = control.Bounds; + } + else + { + viewport = viewport.Translate(-control.Bounds.Position); + } + } + + private readonly struct EffectiveViewportChangedListener + { + public EffectiveViewportChangedListener(ILayoutable listener) + { + Listener = listener; + Viewport = new Rect(double.NaN, double.NaN, double.NaN, double.NaN); + } + + public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) + { + Listener = listener; + Viewport = viewport; + } + + public ILayoutable Listener { get; } + public Rect Viewport { get; } + } } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 8d2a825fa0..e62e22f8ec 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -132,6 +132,7 @@ namespace Avalonia.Layout private bool _measuring; private Size? _previousMeasure; private Rect? _previousArrange; + private EventHandler? _effectiveViewportChanged; private EventHandler? _layoutUpdated; /// @@ -152,6 +153,32 @@ namespace Avalonia.Layout VerticalAlignmentProperty); } + /// + /// Occurs when the element's effective viewport changes. + /// + public event EventHandler? EffectiveViewportChanged + { + add + { + if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r) + { + r.LayoutManager.RegisterEffectiveViewportListener(this); + } + + _effectiveViewportChanged += value; + } + + remove + { + _effectiveViewportChanged -= value; + + if (_effectiveViewportChanged is null && VisualRoot is ILayoutRoot r) + { + r.LayoutManager.UnregisterEffectiveViewportListener(this); + } + } + } + /// /// Occurs when a layout pass completes for the control. /// @@ -384,13 +411,6 @@ namespace Avalonia.Layout } } - /// - /// Called by InvalidateMeasure - /// - protected virtual void OnMeasureInvalidated() - { - } - /// /// Invalidates the measurement of the control and queues a new layout pass. /// @@ -436,6 +456,11 @@ namespace Avalonia.Layout } } + void ILayoutable.EffectiveViewportChanged(EffectiveViewportChangedEventArgs e) + { + _effectiveViewportChanged?.Invoke(this, e); + } + /// /// Marks a property as affecting the control's measurement. /// @@ -717,9 +742,17 @@ namespace Avalonia.Layout { base.OnAttachedToVisualTreeCore(e); - if (_layoutUpdated is object && e.Root is ILayoutRoot r) + if (e.Root is ILayoutRoot r) { - r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; + if (_layoutUpdated is object) + { + r.LayoutManager.LayoutUpdated += LayoutManagedLayoutUpdated; + } + + if (_effectiveViewportChanged is object) + { + r.LayoutManager.RegisterEffectiveViewportListener(this); + } } } @@ -727,12 +760,27 @@ namespace Avalonia.Layout { base.OnDetachedFromVisualTreeCore(e); - if (_layoutUpdated is object && e.Root is ILayoutRoot r) + if (e.Root is ILayoutRoot r) { - r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; + if (_layoutUpdated is object) + { + r.LayoutManager.LayoutUpdated -= LayoutManagedLayoutUpdated; + } + + if (_effectiveViewportChanged is object) + { + r.LayoutManager.UnregisterEffectiveViewportListener(this); + } } } + /// + /// Called by InvalidateMeasure + /// + protected virtual void OnMeasureInvalidated() + { + } + /// protected sealed override void OnVisualParentChanged(IVisual oldParent, IVisual newParent) { diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs new file mode 100644 index 0000000000..8226eb9c2a --- /dev/null +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs @@ -0,0 +1,333 @@ +using Avalonia.Controls; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Layout.UnitTests +{ + public class LayoutableTests_EffectiveViewportChanged + { + [Fact] + public void EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree() + { + var root = new TestRoot(); + var canvas = new Canvas(); + var raised = 0; + + canvas.EffectiveViewportChanged += (s, e) => + { + ++raised; + }; + + root.Child = canvas; + + Assert.Equal(0, raised); + } + + [Fact] + public void EffectiveViewportChanged_Raised_Before_LayoutUpdated() + { + var root = new TestRoot(); + var canvas = new Canvas(); + var raised = 0; + var layoutUpdatedRaised = 0; + + canvas.LayoutUpdated += (s, e) => + { + Assert.Equal(1, raised); + ++layoutUpdatedRaised; + }; + + canvas.EffectiveViewportChanged += (s, e) => + { + ++raised; + }; + + root.Child = canvas; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + Assert.Equal(1, layoutUpdatedRaised); + Assert.Equal(1, raised); + } + + [Fact] + public void Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated() + { + var root = new TestRoot(); + var canvas = new TestCanvas(); + var raised = 0; + var layoutUpdatedRaised = 0; + + canvas.LayoutUpdated += (s, e) => + { + Assert.Equal(2, canvas.MeasureCount); + Assert.Equal(2, canvas.ArrangeCount); + ++layoutUpdatedRaised; + }; + + canvas.EffectiveViewportChanged += (s, e) => + { + canvas.InvalidateMeasure(); + ++raised; + }; + + root.Child = canvas; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + Assert.Equal(1, layoutUpdatedRaised); + } + + [Fact] + public void Viewport_Extends_Beyond_Centered_Control() + { + var root = new TestRoot + { + Width = 1200, + Height = 900, + }; + + var canvas = new Canvas + { + Width = 52, + Height = 52, + }; + var raised = 0; + + canvas.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); + ++raised; + }; + + root.Child = canvas; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + } + + [Fact] + public void Viewport_Extends_Beyond_Nested_Centered_Control() + { + var root = new TestRoot + { + Width = 1200, + Height = 900, + }; + + var canvas = new Canvas + { + Width = 52, + Height = 52, + }; + + var outer = new Border + { + Width = 100, + Height = 100, + Child = canvas, + }; + + var raised = 0; + + canvas.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); + ++raised; + }; + + root.Child = outer; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + } + + [Fact] + public void ScrollViewer_Determines_EffectiveViewport() + { + var root = new TestRoot + { + Width = 1200, + Height = 900, + }; + + var canvas = new Canvas + { + Width = 200, + Height = 200, + }; + + var outer = new ScrollViewer + { + Width = 100, + Height = 100, + Content = canvas, + Template = ScrollViewerTemplate(), + }; + + var raised = 0; + + canvas.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport); + ++raised; + }; + + root.Child = outer; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + } + + [Fact] + public void Scrolled_ScrollViewer_Determines_EffectiveViewport() + { + var root = new TestRoot + { + Width = 1200, + Height = 900, + }; + + var canvas = new Canvas + { + Width = 200, + Height = 200, + }; + + var outer = new ScrollViewer + { + Width = 100, + Height = 100, + Content = canvas, + Template = ScrollViewerTemplate(), + }; + + var raised = 0; + + root.Child = outer; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + canvas.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport); + ++raised; + }; + + outer.Offset = new Vector(0, 10); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.Equal(1, raised); + } + + [Fact] + public void Moving_Parent_Updates_EffectiveViewport() + { + var root = new TestRoot + { + Width = 1200, + Height = 900, + }; + + var canvas = new Canvas + { + Width = 100, + Height = 100, + }; + + var outer = new Border + { + Width = 200, + Height = 200, + Child = canvas, + }; + + var raised = 0; + + root.Child = outer; + root.LayoutManager.ExecuteInitialLayoutPass(root); + + canvas.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport); + ++raised; + }; + + // Change the parent margin to move it. + outer.Margin = new Thickness(8, 0, 0, 0); + root.LayoutManager.ExecuteLayoutPass(); + + Assert.Equal(1, raised); + } + + private IControlTemplate ScrollViewerTemplate() + { + return new FuncControlTemplate((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 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); + } + } + } +} From b5da0310f15abca4299a1e3ee676be30c7956d35 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 17:56:34 +0200 Subject: [PATCH 25/40] Update tests from UWP tests. UWP tests are at: https://github.com/grokys/EffectiveBoundsTestsUWP Try to make the Avalonia tests match them as closely as possible in order to easily port them across. --- src/Avalonia.Layout/LayoutManager.cs | 20 +- ...ayoutableTests_EffectiveViewportChanged.cs | 400 ++++++++++-------- 2 files changed, 239 insertions(+), 181 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 888c8a4910..e7551d8e87 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -199,7 +199,9 @@ namespace Avalonia.Layout void ILayoutManager.RegisterEffectiveViewportListener(ILayoutable control) { _effectiveViewportChangedListeners ??= new List(); - _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener(control)); + _effectiveViewportChangedListeners.Add(new EffectiveViewportChangedListener( + control, + CalculateEffectiveViewport(control))); } void ILayoutManager.UnregisterEffectiveViewportListener(ILayoutable control) @@ -331,8 +333,7 @@ namespace Avalonia.Layout for (var i = 0; i < _effectiveViewportChangedListeners.Count; ++i) { var l = _effectiveViewportChangedListeners[i]; - var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); - CalculateEffectiveViewport(l.Listener, ref viewport); + var viewport = CalculateEffectiveViewport(l.Listener); if (viewport != l.Viewport) { @@ -345,6 +346,13 @@ namespace Avalonia.Layout return startCount != _toMeasure.Count + _toMeasure.Count; } + private Rect CalculateEffectiveViewport(IVisual control) + { + var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); + CalculateEffectiveViewport(control, ref viewport); + return viewport; + } + private void CalculateEffectiveViewport(IVisual control, ref Rect viewport) { if (control.VisualParent is object) @@ -364,12 +372,6 @@ namespace Avalonia.Layout private readonly struct EffectiveViewportChangedListener { - public EffectiveViewportChangedListener(ILayoutable listener) - { - Listener = listener; - Viewport = new Rect(double.NaN, double.NaN, double.NaN, double.NaN); - } - public EffectiveViewportChangedListener(ILayoutable listener, Rect viewport) { Listener = listener; diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs index 8226eb9c2a..0cd0f6c96a 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs @@ -1,7 +1,10 @@ -using Avalonia.Controls; +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; @@ -10,257 +13,302 @@ namespace Avalonia.Layout.UnitTests public class LayoutableTests_EffectiveViewportChanged { [Fact] - public void EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree() + public async Task EffectiveViewportChanged_Not_Raised_When_Control_Added_To_Tree() { - var root = new TestRoot(); - var canvas = new Canvas(); - var raised = 0; - - canvas.EffectiveViewportChanged += (s, e) => + await RunOnUIThread.Execute(async () => { - ++raised; - }; + var root = CreateRoot(); + var target = new Canvas(); + var raised = 0; + + target.EffectiveViewportChanged += (s, e) => + { + ++raised; + }; - root.Child = canvas; + root.Child = target; - Assert.Equal(0, raised); + Assert.Equal(0, raised); + }); } [Fact] - public void EffectiveViewportChanged_Raised_Before_LayoutUpdated() + public async Task EffectiveViewportChanged_Raised_Before_LayoutUpdated() { - var root = new TestRoot(); - var canvas = new Canvas(); - var raised = 0; - var layoutUpdatedRaised = 0; - - canvas.LayoutUpdated += (s, e) => + await RunOnUIThread.Execute(async () => { - Assert.Equal(1, raised); - ++layoutUpdatedRaised; - }; + var root = CreateRoot(); + var target = new Canvas(); + var raised = 0; - canvas.EffectiveViewportChanged += (s, e) => - { - ++raised; - }; + target.EffectiveViewportChanged += (s, e) => + { + ++raised; + }; - root.Child = canvas; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.Child = target; - Assert.Equal(1, layoutUpdatedRaised); - Assert.Equal(1, raised); + await ExecuteInitialLayoutPass(root); + + Assert.Equal(1, raised); + }); } [Fact] - public void Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated() + public async Task Parent_Affects_EffectiveViewport() { - var root = new TestRoot(); - var canvas = new TestCanvas(); - var raised = 0; - var layoutUpdatedRaised = 0; - - canvas.LayoutUpdated += (s, e) => + await RunOnUIThread.Execute(async () => { - Assert.Equal(2, canvas.MeasureCount); - Assert.Equal(2, canvas.ArrangeCount); - ++layoutUpdatedRaised; - }; + var root = CreateRoot(); + var target = new Canvas { Width = 100, Height = 100 }; + var parent = new Border { Width = 200, Height = 200, Child = target }; + var raised = 0; - canvas.EffectiveViewportChanged += (s, e) => - { - canvas.InvalidateMeasure(); - ++raised; - }; + root.Child = parent; - root.Child = canvas; - root.LayoutManager.ExecuteInitialLayoutPass(root); + target.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-550, -400, 1200, 900), e.EffectiveViewport); + ++raised; + }; - Assert.Equal(1, raised); - Assert.Equal(1, layoutUpdatedRaised); + await ExecuteInitialLayoutPass(root); + }); } [Fact] - public void Viewport_Extends_Beyond_Centered_Control() + public async Task Invalidating_In_Handler_Causes_Layout_To_Be_Rerun_Before_LayoutUpdated_Raised() { - var root = new TestRoot + await RunOnUIThread.Execute(async () => { - Width = 1200, - Height = 900, - }; + var root = CreateRoot(); + var target = new TestCanvas(); + var raised = 0; + var layoutUpdatedRaised = 0; - var canvas = new Canvas - { - Width = 52, - Height = 52, - }; - var raised = 0; + root.LayoutUpdated += (s, e) => + { + Assert.Equal(2, target.MeasureCount); + Assert.Equal(2, target.ArrangeCount); + ++layoutUpdatedRaised; + }; - canvas.EffectiveViewportChanged += (s, e) => - { - Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); - ++raised; - }; + target.EffectiveViewportChanged += (s, e) => + { + target.InvalidateMeasure(); + ++raised; + }; - root.Child = canvas; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.Child = target; + + await ExecuteInitialLayoutPass(root); - Assert.Equal(1, raised); + Assert.Equal(1, raised); + Assert.Equal(1, layoutUpdatedRaised); + }); } [Fact] - public void Viewport_Extends_Beyond_Nested_Centered_Control() + public async Task Viewport_Extends_Beyond_Centered_Control() { - var root = new TestRoot + await RunOnUIThread.Execute(async () => { - Width = 1200, - Height = 900, - }; + var root = CreateRoot(); + var target = new Canvas { Width = 52, Height = 52, }; + var raised = 0; - var canvas = new Canvas - { - Width = 52, - Height = 52, - }; + target.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); + ++raised; + }; - var outer = new Border - { - Width = 100, - Height = 100, - Child = canvas, - }; + root.Child = target; - var raised = 0; + await ExecuteInitialLayoutPass(root); + Assert.Equal(1, raised); + }); + } - canvas.EffectiveViewportChanged += (s, e) => + [Fact] + public async Task Viewport_Extends_Beyond_Nested_Centered_Control() + { + await RunOnUIThread.Execute(async () => { - Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); - ++raised; - }; + var root = CreateRoot(); + var target = new Canvas { Width = 52, Height = 52 }; + var parent = new Border { Width = 100, Height = 100, Child = target }; + var raised = 0; - root.Child = outer; - root.LayoutManager.ExecuteInitialLayoutPass(root); + target.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-574, -424, 1200, 900), e.EffectiveViewport); + ++raised; + }; + + root.Child = parent; - Assert.Equal(1, raised); + await ExecuteInitialLayoutPass(root); + Assert.Equal(1, raised); + }); } [Fact] - public void ScrollViewer_Determines_EffectiveViewport() + public async Task ScrollViewer_Determines_EffectiveViewport() { - var root = new TestRoot + await RunOnUIThread.Execute(async () => { - Width = 1200, - Height = 900, - }; + 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; - var canvas = new Canvas - { - Width = 200, - Height = 200, - }; + target.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport); + ++raised; + }; - var outer = new ScrollViewer - { - Width = 100, - Height = 100, - Content = canvas, - Template = ScrollViewerTemplate(), - }; + root.Child = scroller; - var raised = 0; + await ExecuteInitialLayoutPass(root); + Assert.Equal(1, raised); + }); + } - canvas.EffectiveViewportChanged += (s, e) => + [Fact] + public async Task Scrolled_ScrollViewer_Determines_EffectiveViewport() + { + await RunOnUIThread.Execute(async () => { - Assert.Equal(new Rect(0, 0, 100, 100), e.EffectiveViewport); - ++raised; - }; + 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 = outer; - root.LayoutManager.ExecuteInitialLayoutPass(root); + root.Child = scroller; + + await ExecuteInitialLayoutPass(root); + scroller.Offset = new Vector(0, 10); - Assert.Equal(1, raised); + await ExecuteScrollerLayoutPass(root, scroller, target, (s, e) => + { + Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport); + ++raised; + }); + + Assert.Equal(1, raised); + }); } [Fact] - public void Scrolled_ScrollViewer_Determines_EffectiveViewport() + public async Task Moving_Parent_Updates_EffectiveViewport() { - var root = new TestRoot + await RunOnUIThread.Execute(async () => { - Width = 1200, - Height = 900, - }; + var root = CreateRoot(); + var target = new Canvas { Width = 100, Height = 100 }; + var parent = new Border { Width = 200, Height = 200, Child = target }; + var raised = 0; - var canvas = new Canvas - { - Width = 200, - Height = 200, - }; + root.Child = parent; - var outer = new ScrollViewer - { - Width = 100, - Height = 100, - Content = canvas, - Template = ScrollViewerTemplate(), - }; + await ExecuteInitialLayoutPass(root); - var raised = 0; + target.EffectiveViewportChanged += (s, e) => + { + Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport); + ++raised; + }; - root.Child = outer; - root.LayoutManager.ExecuteInitialLayoutPass(root); + parent.Margin = new Thickness(8, 0, 0, 0); + await ExecuteLayoutPass(root); - canvas.EffectiveViewportChanged += (s, e) => + Assert.Equal(1, raised); + }); + } + + [Fact] + public async Task Translate_Transform_Doesnt_Affect_EffectiveViewport() + { + await RunOnUIThread.Execute(async () => { - Assert.Equal(new Rect(0, 10, 100, 100), e.EffectiveViewport); - ++raised; - }; - - outer.Offset = new Vector(0, 10); - root.LayoutManager.ExecuteLayoutPass(); + var root = CreateRoot(); + var target = new Canvas { Width = 100, Height = 100 }; + var parent = new Border { Width = 200, Height = 200, Child = target }; + var raised = 0; - Assert.Equal(1, raised); + 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 void Moving_Parent_Updates_EffectiveViewport() + public async Task Translate_Transform_On_Parent_Affects_EffectiveViewport() { - var root = new TestRoot + await RunOnUIThread.Execute(async () => { - Width = 1200, - Height = 900, - }; + var root = CreateRoot(); + var target = new Canvas { Width = 100, Height = 100 }; + var parent = new Border { Width = 200, Height = 200, Child = target }; + var raised = 0; - var canvas = new Canvas - { - Width = 100, - Height = 100, - }; + root.Child = parent; - var outer = new Border - { - Width = 200, - Height = 200, - Child = canvas, - }; + await ExecuteInitialLayoutPass(root); - var raised = 0; + 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); + }); + } - root.Child = outer; + private TestRoot CreateRoot() => new TestRoot { Width = 1200, Height = 900 }; + + private Task ExecuteInitialLayoutPass(TestRoot root) + { root.LayoutManager.ExecuteInitialLayoutPass(root); + return Task.CompletedTask; + } + + private Task ExecuteLayoutPass(TestRoot root) + { + root.LayoutManager.ExecuteLayoutPass(); + return Task.CompletedTask; + } - canvas.EffectiveViewportChanged += (s, e) => + private Task ExecuteScrollerLayoutPass( + TestRoot root, + ScrollViewer scroller, + Control target, + Action handler) + { + void ViewportChanged(object sender, EffectiveViewportChangedEventArgs e) { - Assert.Equal(new Rect(-554, -400, 1200, 900), e.EffectiveViewport); - ++raised; - }; + handler(sender, e); + } - // Change the parent margin to move it. - outer.Margin = new Thickness(8, 0, 0, 0); + target.EffectiveViewportChanged += ViewportChanged; root.LayoutManager.ExecuteLayoutPass(); - - Assert.Equal(1, raised); + return Task.CompletedTask; } - private IControlTemplate ScrollViewerTemplate() { return new FuncControlTemplate((control, scope) => new Grid @@ -329,5 +377,13 @@ namespace Avalonia.Layout.UnitTests return base.ArrangeOverride(finalSize); } } + + private static class RunOnUIThread + { + public static async Task Execute(Func func) + { + await func(); + } + } } } From d0e74b7dbd1d6692fbf78251fc468cf230c3f0c2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 18:11:33 +0200 Subject: [PATCH 26/40] Include transforms in effective bounds. --- src/Avalonia.Layout/LayoutManager.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index e7551d8e87..8a1a9fc2fb 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -349,15 +349,15 @@ namespace Avalonia.Layout private Rect CalculateEffectiveViewport(IVisual control) { var viewport = new Rect(0, 0, double.PositiveInfinity, double.PositiveInfinity); - CalculateEffectiveViewport(control, ref viewport); + CalculateEffectiveViewport(control, control, ref viewport); return viewport; } - private void CalculateEffectiveViewport(IVisual control, ref Rect viewport) + private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport) { if (control.VisualParent is object) { - CalculateEffectiveViewport(control.VisualParent, ref viewport); + CalculateEffectiveViewport(target, control.VisualParent, ref viewport); } if (control.ClipToBounds || control.VisualParent is null) @@ -368,6 +368,14 @@ namespace Avalonia.Layout { viewport = viewport.Translate(-control.Bounds.Position); } + + if (control != target && control.RenderTransform is object) + { + var origin = control.RenderTransformOrigin.ToPixels(control.Bounds.Size); + var offset = Matrix.CreateTranslation(origin); + var renderTransform = (-offset) * control.RenderTransform.Value.Invert() * (offset); + viewport = viewport.TransformToAABB(renderTransform); + } } private readonly struct EffectiveViewportChangedListener From 64174f37af5b0d738935b46abdb7d2c2ee7db18c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 23:13:05 +0200 Subject: [PATCH 27/40] Tweak effective viewport calculation. This logic isn't in the UWP version as it only clips to scrollports. --- src/Avalonia.Layout/LayoutManager.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 8a1a9fc2fb..4a2c95db5b 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -355,20 +355,27 @@ namespace Avalonia.Layout private void CalculateEffectiveViewport(IVisual target, IVisual control, ref Rect viewport) { + // Recurse until the top level control. if (control.VisualParent is object) { CalculateEffectiveViewport(target, control.VisualParent, ref viewport); } - - if (control.ClipToBounds || control.VisualParent is null) + else { - viewport = control.Bounds; + viewport = new Rect(control.Bounds.Size); } - else + + // Apply the control clip bounds if it's not the target control. We don't apply it to + // the target control because it may itself be clipped to bounds and if so the viewport + // we calculate would be of no use. + if (control != target && control.ClipToBounds) { - viewport = viewport.Translate(-control.Bounds.Position); + viewport = control.Bounds.Intersect(viewport); } + // Translate the viewport into this control's coordinate space. + viewport = viewport.Translate(-control.Bounds.Position); + if (control != target && control.RenderTransform is object) { var origin = control.RenderTransformOrigin.ToPixels(control.Bounds.Size); From 4b503f8b373aba70ee6910562f2531ef63d3d9c8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 23:14:46 +0200 Subject: [PATCH 28/40] Use EffectiveViewportChanged in ViewportManager. --- .../Repeater/ViewportManager.cs | 45 ++++++++----------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index b705a518ff..fa85ceec5d 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -49,8 +49,8 @@ namespace Avalonia.Controls // For non-virtualizing layouts, we do not need to keep // updating viewports and invalidating measure often. So when // a non virtualizing layout is used, we stop doing all that work. - bool _managingViewportDisabled; - private IDisposable _effectiveViewportChangedRevoker; + private bool _managingViewportDisabled; + private bool _effectiveViewportChangedSubscribed; private bool _layoutUpdatedSubscribed; public ViewportManager(ItemsRepeater owner) @@ -228,11 +228,15 @@ namespace Avalonia.Controls _pendingViewportShift = default; _unshiftableShift = default; - _effectiveViewportChangedRevoker?.Dispose(); - - if (!_managingViewportDisabled) + if (_managingViewportDisabled && _effectiveViewportChangedSubscribed) { - _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner); + _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged; + _effectiveViewportChangedSubscribed = false; + } + else if (!_managingViewportDisabled && !_effectiveViewportChangedSubscribed) + { + _owner.EffectiveViewportChanged += OnEffectiveViewportChanged; + _effectiveViewportChangedSubscribed = true; } } @@ -415,15 +419,15 @@ namespace Avalonia.Controls _scroller = null; } - _effectiveViewportChangedRevoker?.Dispose(); - _effectiveViewportChangedRevoker = null; + _owner.EffectiveViewportChanged -= OnEffectiveViewportChanged; + _effectiveViewportChangedSubscribed = false; _ensuredScroller = false; } - private void OnEffectiveViewportChanged(Rect effectiveViewport) + private void OnEffectiveViewportChanged(object sender, EffectiveViewportChangedEventArgs e) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: EffectiveViewportChanged event callback", _owner.Layout.LayoutId); - UpdateViewport(effectiveViewport); + UpdateViewport(e.EffectiveViewport); _pendingViewportShift = default; _unshiftableShift = default; @@ -468,8 +472,8 @@ namespace Avalonia.Controls } else if (!_managingViewportDisabled) { - _effectiveViewportChangedRevoker?.Dispose(); - _effectiveViewportChangedRevoker = SubscribeToEffectiveViewportChanged(_owner); + _owner.EffectiveViewportChanged += OnEffectiveViewportChanged; + _effectiveViewportChangedSubscribed = true; } _ensuredScroller = true; @@ -541,26 +545,15 @@ namespace Avalonia.Controls // UWP uses the EffectiveViewportChanged event (which I think was implemented specially // for this case): we need to implement that in Avalonia, but the semantics of it aren't // clear to me. Hopefully the source for this event will be released with WinUI 3. - if (control.VisualParent is ScrollContentPresenter scp) + if (control.VisualParent is Layoutable layoutable) { - scp.PreArrange += ScrollContentPresenterPreArrange; - return Disposable.Create(() => scp.PreArrange -= ScrollContentPresenterPreArrange); + layoutable.EffectiveViewportChanged += OnEffectiveViewportChanged; + return Disposable.Create(() => layoutable.EffectiveViewportChanged -= OnEffectiveViewportChanged); } return Disposable.Empty; } - private void ScrollContentPresenterPreArrange(object sender, VectorEventArgs e) - { - var scp = (ScrollContentPresenter)sender; - var effectiveViewport = new Rect((Point)scp.Offset, new Size(e.Vector.X, e.Vector.Y)); - - if (effectiveViewport != _visibleWindow) - { - OnEffectiveViewportChanged(effectiveViewport); - } - } - private class ScrollerInfo { public ScrollerInfo(ScrollViewer scroller) From a0e8afe8411a5daa2aec916980aa6a1060dcaaf9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 25 Jun 2020 12:47:04 +0200 Subject: [PATCH 29/40] Added rotate transform test. --- ...ayoutableTests_EffectiveViewportChanged.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs index 0cd0f6c96a..504e3fa585 100644 --- a/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs +++ b/tests/Avalonia.Layout.UnitTests/LayoutableTests_EffectiveViewportChanged.cs @@ -280,11 +280,40 @@ namespace Avalonia.Layout.UnitTests }); } + [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(root); + root.LayoutManager.ExecuteInitialLayoutPass(); return Task.CompletedTask; } @@ -359,6 +388,12 @@ namespace Avalonia.Layout.UnitTests }); } + 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 { From 400523da951c818405e83117887f4fc2924da795 Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 25 Jun 2020 21:40:05 +0800 Subject: [PATCH 30/40] Fix dev tools crashing on X11. --- src/Avalonia.Controls/TreeView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index a91655855c..a6cbb96bc2 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -347,7 +347,7 @@ namespace Avalonia.Controls if (container != null) { - DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.Zero); + DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.FromTicks(1)); } } } From 441590f549459df9223ac588d9b7fb761f1ad25c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Thu, 25 Jun 2020 22:10:15 +0800 Subject: [PATCH 31/40] change in DispatcherTimer instead. --- src/Avalonia.Base/Threading/DispatcherTimer.cs | 6 ++++-- src/Avalonia.Controls/TreeView.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Threading/DispatcherTimer.cs b/src/Avalonia.Base/Threading/DispatcherTimer.cs index ebafc8b946..56cde9738e 100644 --- a/src/Avalonia.Base/Threading/DispatcherTimer.cs +++ b/src/Avalonia.Base/Threading/DispatcherTimer.cs @@ -14,7 +14,7 @@ namespace Avalonia.Threading private readonly DispatcherPriority _priority; private TimeSpan _interval; - + /// /// Initializes a new instance of the class. /// @@ -154,6 +154,8 @@ namespace Avalonia.Threading TimeSpan interval, DispatcherPriority priority = DispatcherPriority.Normal) { + interval = (interval != TimeSpan.Zero) ? interval : TimeSpan.FromTicks(1); + var timer = new DispatcherTimer(priority) { Interval = interval }; timer.Tick += (s, e) => @@ -197,7 +199,7 @@ namespace Avalonia.Threading } } - + /// /// Raises the event on the dispatcher thread. diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index a6cbb96bc2..a91655855c 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -347,7 +347,7 @@ namespace Avalonia.Controls if (container != null) { - DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.FromTicks(1)); + DispatcherTimer.RunOnce(container.BringIntoView, TimeSpan.Zero); } } } From 02b2f3dc1527d3257a107a7a509ccbb1a5ef7cb9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Jun 2020 13:08:23 +0200 Subject: [PATCH 32/40] Remove listeners backwards in case of duplicates. --- src/Avalonia.Layout/LayoutManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 4a2c95db5b..a4da887f2c 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -208,7 +208,7 @@ namespace Avalonia.Layout { if (_effectiveViewportChangedListeners is object) { - for (var i = 0; i < _effectiveViewportChangedListeners.Count; ++i) + for (var i = _effectiveViewportChangedListeners.Count - 1; i >= 0; --i) { if (_effectiveViewportChangedListeners[i].Listener == control) { From 13d828cdccbf5a05ae0f363304c53d7c66ede086 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Jun 2020 13:08:48 +0200 Subject: [PATCH 33/40] Duplicate listeners before raising EffectiveViewportChanged. --- src/Avalonia.Layout/LayoutManager.cs | 35 +++++++++++++++++++++------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index a4da887f2c..792de774d1 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using Avalonia.Logging; @@ -329,18 +330,36 @@ namespace Avalonia.Layout if (_effectiveViewportChangedListeners is object) { - // TODO: This may not work correctly if listener is removed in event handler. - for (var i = 0; i < _effectiveViewportChangedListeners.Count; ++i) - { - var l = _effectiveViewportChangedListeners[i]; - var viewport = CalculateEffectiveViewport(l.Listener); + var count = _effectiveViewportChangedListeners.Count; + var pool = ArrayPool.Shared; + var listeners = pool.Rent(count); + + _effectiveViewportChangedListeners.CopyTo(listeners); - if (viewport != l.Viewport) + try + { + for (var i = 0; i < count; ++i) { - l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); - _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport); + var l = _effectiveViewportChangedListeners[i]; + + if (!l.Listener.IsAttachedToVisualTree) + { + continue; + } + + var viewport = CalculateEffectiveViewport(l.Listener); + + if (viewport != l.Viewport) + { + l.Listener.EffectiveViewportChanged(new EffectiveViewportChangedEventArgs(viewport)); + _effectiveViewportChangedListeners[i] = new EffectiveViewportChangedListener(l.Listener, viewport); + } } } + finally + { + pool.Return(listeners, clearArray: true); + } } return startCount != _toMeasure.Count + _toMeasure.Count; From 50af733eac0c8ff2ccc276013de667d2e2876df6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Jun 2020 13:09:25 +0200 Subject: [PATCH 34/40] Remove unused method. --- .../Repeater/ViewportManager.cs | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index fa85ceec5d..a01a7d113a 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -533,27 +533,6 @@ namespace Avalonia.Controls } } - private IDisposable SubscribeToEffectiveViewportChanged(IControl control) - { - // HACK: This is a bit of a hack. We need the effective viewport of the ItemsRepeater - - // we can get this from TransformedBounds, but this property is updated after layout has - // run, which is too late. Instead, for now lets just hook into an internal event on - // ScrollContentPresenter to find out what the offset and viewport will be after arrange - // and use those values. Note that this doesn't handle nested ScrollViewers at all, but - // it's enough to get scrolling to non-uniformly sized items working for now. - // - // UWP uses the EffectiveViewportChanged event (which I think was implemented specially - // for this case): we need to implement that in Avalonia, but the semantics of it aren't - // clear to me. Hopefully the source for this event will be released with WinUI 3. - if (control.VisualParent is Layoutable layoutable) - { - layoutable.EffectiveViewportChanged += OnEffectiveViewportChanged; - return Disposable.Create(() => layoutable.EffectiveViewportChanged -= OnEffectiveViewportChanged); - } - - return Disposable.Empty; - } - private class ScrollerInfo { public ScrollerInfo(ScrollViewer scroller) From 5f77d26c42f1ad08ab7017701d55e1dd8bd67e04 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 Jun 2020 14:08:48 +0200 Subject: [PATCH 35/40] Don't throw when a BringIntoView received from non-descendent. The event can come from a child `Popup`. Fixes #4176. --- src/Avalonia.Controls/Repeater/ViewportManager.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls/Repeater/ViewportManager.cs index b705a518ff..e4e03d1be9 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls/Repeater/ViewportManager.cs @@ -340,6 +340,11 @@ namespace Avalonia.Controls // Note that the element being brought into view could be a descendant. var targetChild = GetImmediateChildOfRepeater((IControl)args.TargetObject); + if (targetChild is null) + { + return; + } + // Make sure that only the target child can be the anchor during the bring into view operation. foreach (var child in _owner.Children) { @@ -373,7 +378,7 @@ namespace Avalonia.Controls if (parent == null) { - throw new InvalidOperationException("OnBringIntoViewRequested called with args.target element not under the ItemsRepeater that recieved the call"); + return null; } return targetChild; From f6c36d7c490e9172c31c851f97fd10671ebc82ec Mon Sep 17 00:00:00 2001 From: Raj Kumar Mondol Date: Sat, 27 Jun 2020 01:01:38 +0530 Subject: [PATCH 36/40] Correct url for [Contribute] in README --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a9263d4816..6a04c7e31e 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,7 @@ Avalonia is licenced under the [MIT licence](licence.md). ## Contributors -This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)]. +This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing)]. ### Backers From 7c77b3ee915a89880dd13a3300be4028bc89a65b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 27 Jun 2020 11:53:10 +0200 Subject: [PATCH 37/40] Update ncrunch ignore rules. --- .ncrunch/NativeEmbedSample.v3.ncrunchproject | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .ncrunch/NativeEmbedSample.v3.ncrunchproject diff --git a/.ncrunch/NativeEmbedSample.v3.ncrunchproject b/.ncrunch/NativeEmbedSample.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/NativeEmbedSample.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From cc2ddce5abc52413fcf6d7149abd2394d8dacddd Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Sat, 27 Jun 2020 19:59:20 +0800 Subject: [PATCH 38/40] Fix access denied scenario in windows managed dialog --- .../WindowsMountedVolumeInfoListener.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs index db4c916052..ba1bfda949 100644 --- a/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs +++ b/src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoListener.cs @@ -5,12 +5,13 @@ using System.Linq; using System.Reactive.Disposables; using System.Reactive.Linq; using Avalonia.Controls.Platform; +using Avalonia.Logging; namespace Avalonia.Win32 { internal class WindowsMountedVolumeInfoListener : IDisposable { - private readonly CompositeDisposable _disposables; + private readonly CompositeDisposable _disposables; private bool _beenDisposed = false; private ObservableCollection mountedDrives; @@ -32,10 +33,22 @@ namespace Avalonia.Win32 var allDrives = DriveInfo.GetDrives(); var mountVolInfos = allDrives - .Where(p => p.IsReady) + .Where(p => + { + try + { + var ret = p.IsReady; + return ret; + } + catch (Exception e) + { + Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log(this, $"Error in Windows drive enumeration: {e.Message}"); + } + return false; + }) .Select(p => new MountedVolumeInfo() { - VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName + VolumeLabel = string.IsNullOrEmpty(p.VolumeLabel.Trim()) ? p.RootDirectory.FullName : $"{p.VolumeLabel} ({p.Name})", VolumePath = p.RootDirectory.FullName, VolumeSizeBytes = (ulong)p.TotalSize From 5cfc20abb2f165906323b4fdadbff1fc5f86e8c7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 29 Jun 2020 14:20:29 +0200 Subject: [PATCH 39/40] Removed unused call to IsSelectedWithPartialAt. --- src/Avalonia.Controls/SelectionModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs index ff1c0260bb..aa6552579f 100644 --- a/src/Avalonia.Controls/SelectionModel.cs +++ b/src/Avalonia.Controls/SelectionModel.cs @@ -189,8 +189,6 @@ namespace Avalonia.Controls } set { - var isSelected = IsSelectedWithPartialAt(value); - if (!IsSelectedAt(value) || SelectedItems.Count > 1) { using var operation = new Operation(this); From be73db7bebc51232d4168a7f660cf0aff5ab9f0c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 29 Jun 2020 17:24:56 +0300 Subject: [PATCH 40/40] Dispose the renderer before detaching visuals from the tree (perf) See https://github.com/AvaloniaUI/Avalonia/issues/3622 --- src/Avalonia.Controls/TopLevel.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 6c3a2d4a03..f058942116 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -340,6 +340,9 @@ namespace Avalonia.Controls _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved; } + Renderer?.Dispose(); + Renderer = null; + var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); @@ -349,8 +352,7 @@ namespace Avalonia.Controls (this as IInputRoot).MouseDevice?.TopLevelClosed(this); PlatformImpl = null; OnClosed(EventArgs.Empty); - Renderer?.Dispose(); - Renderer = null; + LayoutManager?.Dispose(); }