From 5a4a7c80c7861aaf68bd0a9932232ab40bdf0f78 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 9 Jun 2020 17:23:13 +0300 Subject: [PATCH 01/47] .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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 26863755d8628957f0a675077d897c3a259e1cb2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 23 Jun 2020 22:26:48 +0300 Subject: [PATCH 12/47] Synchronize native control size even if its not visible (X11/Win32) --- src/Avalonia.Controls/NativeControlHost.cs | 89 +++++++++++++++---- .../Platform/INativeControlHostImpl.cs | 4 +- src/Avalonia.Native/NativeControlHostImpl.cs | 6 +- src/Avalonia.Visuals/Rect.cs | 15 ++++ src/Avalonia.X11/X11NativeControlHost.cs | 22 +++-- .../Avalonia.Win32/Win32NativeControlHost.cs | 12 ++- 6 files changed, 116 insertions(+), 32 deletions(-) diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 94ef0b2284..20eac11c2c 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -1,7 +1,9 @@ +using System; +using System.Collections.Generic; using Avalonia.Controls.Platform; -using Avalonia.LogicalTree; using Avalonia.Platform; using Avalonia.Threading; +using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -12,14 +14,18 @@ namespace Avalonia.Controls private INativeControlHostControlTopLevelAttachment _attachment; private IPlatformHandle _nativeControlHandle; private bool _queuedForDestruction; + private bool _queuedForMoveResize; + private readonly List _propertyChangedSubscriptions = new List(); + private readonly EventHandler _propertyChangedHandler; static NativeControlHost() { IsVisibleProperty.Changed.AddClassHandler(OnVisibleChanged); - TransformedBoundsProperty.Changed.AddClassHandler(OnBoundsChanged); } - private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) - => host.UpdateHost(); + public NativeControlHost() + { + _propertyChangedHandler = PropertyChangedHandler; + } private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) => host.UpdateHost(); @@ -27,21 +33,46 @@ namespace Avalonia.Controls protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; + var visual = (IVisual)this; + while (visual != _currentRoot) + { + + if (visual is Visual v) + { + v.PropertyChanged += _propertyChangedHandler; + _propertyChangedSubscriptions.Add(v); + } + + visual = visual.GetVisualParent(); + } + UpdateHost(); } + private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.IsEffectiveValueChange && e.Property == BoundsProperty) + EnqueueForMoveResize(); + } + protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; + if (_propertyChangedSubscriptions != null) + { + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= _propertyChangedHandler; + _propertyChangedSubscriptions.Clear(); + } UpdateHost(); } - void UpdateHost() + private void UpdateHost() { + _queuedForMoveResize = false; _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost; var needsAttachment = _currentHost != null; - var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue; if (needsAttachment) { @@ -93,22 +124,46 @@ namespace Avalonia.Controls } } - if (needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - else if (needsAttachment) - _attachment?.Hide(); + if (_attachment?.AttachedTo != _currentHost) + return; + + TryUpdateNativeControlPosition(); + } + + + private Rect? GetAbsoluteBounds() + { + var bounds = Bounds; + var position = this.TranslatePoint(bounds.Position, _currentRoot); + if (position == null) + return null; + return new Rect(position.Value, bounds.Size); + } + + void EnqueueForMoveResize() + { + if(_queuedForMoveResize) + return; + _queuedForMoveResize = true; + Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render); } public bool TryUpdateNativeControlPosition() { - var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue; + if (_currentHost == null) + return false; + + var bounds = GetAbsoluteBounds(); + var needsShow = IsEffectivelyVisible && bounds.HasValue; - if(needsShow) - _attachment?.ShowInBounds(TransformedBounds.Value); - return needsShow; + if (needsShow) + _attachment?.ShowInBounds(bounds.Value); + else + _attachment?.HideWithSize(Bounds.Size); + return false; } - void CheckDestruction() + private void CheckDestruction() { _queuedForDestruction = false; if (_currentRoot == null) @@ -117,10 +172,12 @@ namespace Avalonia.Controls protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent) { + if (_currentHost == null) + throw new InvalidOperationException(); return _currentHost.CreateDefaultChild(parent); } - void DestroyNativeControl() + private void DestroyNativeControl() { if (_nativeControlHandle != null) { diff --git a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs index 7a4568abc6..c6b1d09849 100644 --- a/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs +++ b/src/Avalonia.Controls/Platform/INativeControlHostImpl.cs @@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform { INativeControlHostImpl AttachedTo { get; set; } bool IsCompatibleWith(INativeControlHostImpl host); - void Hide(); - void ShowInBounds(TransformedBounds transformedBounds); + void HideWithSize(Size size); + void ShowInBounds(Rect rect); } public interface ITopLevelImplWithNativeControlHost diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 0777c6416b..4045ce8816 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -114,16 +114,16 @@ namespace Avalonia.Native public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl; - public void Hide() + public void HideWithSize(Size size) { + //TODO _native?.Hide(); } - public void ShowInBounds(TransformedBounds transformedBounds) + public void ShowInBounds(Rect bounds) { if (_attachedTo == null) throw new InvalidOperationException("Native control isn't attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform); bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), Math.Max(1, bounds.Height)); _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index d1110e0613..d2a72db6ae 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -211,6 +211,21 @@ namespace Avalonia rect.Width * scale.X, rect.Height * scale.Y); } + + /// + /// Multiplies a rectangle by a scale. + /// + /// The rectangle. + /// The scale. + /// The scaled rectangle. + public static Rect operator *(Rect rect, double scale) + { + return new Rect( + rect.X * scale, + rect.Y * scale, + rect.Width * scale, + rect.Height * scale); + } /// /// Divides a rectangle by a vector. diff --git a/src/Avalonia.X11/X11NativeControlHost.cs b/src/Avalonia.X11/X11NativeControlHost.cs index add3bc6585..23fb27f72b 100644 --- a/src/Avalonia.X11/X11NativeControlHost.cs +++ b/src/Avalonia.X11/X11NativeControlHost.cs @@ -157,21 +157,30 @@ namespace Avalonia.X11 public bool IsCompatibleWith(INativeControlHostImpl host) => host is X11NativeControlHost; - public void Hide() + public void HideWithSize(Size size) { if(_attachedTo == null || _child == null) return; - _mapped = false; - XUnmapWindow(_display, _holder.Handle); + if (_mapped) + { + _mapped = false; + XUnmapWindow(_display, _holder.Handle); + } + + size *= _attachedTo.Window.Scaling; + XResizeWindow(_display, _child.Handle, + Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height)); } - public void ShowInBounds(TransformedBounds transformedBounds) + + + public void ShowInBounds(Rect bounds) { CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * - new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling); + bounds *= _attachedTo.Window.Scaling; + var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); XMoveResizeWindow(_display, _child.Handle, 0, 0, pixelRect.Width, pixelRect.Height); @@ -183,7 +192,6 @@ namespace Avalonia.X11 XRaiseWindow(_display, _holder.Handle); _mapped = true; } - Console.WriteLine($"Moved {_child.Handle} to {pixelRect}"); } } } diff --git a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs index 69fbe30068..d7bb2c037e 100644 --- a/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs +++ b/src/Windows/Avalonia.Win32/Win32NativeControlHost.cs @@ -168,21 +168,25 @@ namespace Avalonia.Win32 public bool IsCompatibleWith(INativeControlHostImpl host) => host is Win32NativeControlHost; - public void Hide() + public void HideWithSize(Size size) { UnmanagedMethods.SetWindowPos(_holder.Handle, IntPtr.Zero, -100, -100, 1, 1, UnmanagedMethods.SetWindowPosFlags.SWP_HIDEWINDOW | UnmanagedMethods.SetWindowPosFlags.SWP_NOACTIVATE); + if (_attachedTo == null || _child == null) + return; + size *= _attachedTo.Window.Scaling; + UnmanagedMethods.MoveWindow(_child.Handle, 0, 0, + Math.Max(1, (int)size.Width), Math.Max(1, (int)size.Height), false); } - public unsafe void ShowInBounds(TransformedBounds transformedBounds) + public unsafe void ShowInBounds(Rect bounds) { CheckDisposed(); if (_attachedTo == null) throw new InvalidOperationException("The control isn't currently attached to a toplevel"); - var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform) * - new Vector(_attachedTo.Window.Scaling, _attachedTo.Window.Scaling); + bounds *= _attachedTo.Window.Scaling; var pixelRect = new PixelRect((int)bounds.X, (int)bounds.Y, Math.Max(1, (int)bounds.Width), Math.Max(1, (int)bounds.Height)); From 2807cbe6cb923982fa8faa0422caf97e21c43d90 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 Jun 2020 10:18:25 +0200 Subject: [PATCH 13/47] 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 14/47] 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 15/47] 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 16/47] 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 17/47] 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 18/47] 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 19/47] 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 20/47] 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 21/47] 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 22/47] 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 23/47] 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 24/47] 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 25/47] 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 26/47] 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 27/47] 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 28/47] 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 29/47] 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 30/47] 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 31/47] 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 32/47] 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 33/47] 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 34/47] 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 35/47] 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 36/47] 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 37/47] 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 38/47] 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 39/47] 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(); } From cba236d9a35758952eac4ab7989ce11b534a3d2f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 29 Jun 2020 18:43:01 +0300 Subject: [PATCH 40/47] OSX impl for HideWithSize --- native/Avalonia.Native/inc/avalonia-native.h | 4 ++-- native/Avalonia.Native/src/OSX/controlhost.mm | 21 ++++++++++++++++--- src/Avalonia.Native/NativeControlHostImpl.cs | 5 ++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 1cf3bc75b0..22247df9b2 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -493,8 +493,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown virtual void* GetParentHandle() = 0; virtual HRESULT InitializeWithChildHandle(void* child) = 0; virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0; - virtual void MoveTo(float x, float y, float width, float height) = 0; - virtual void Hide() = 0; + virtual void ShowInBounds(float x, float y, float width, float height) = 0; + virtual void HideWithSize(float width, float height) = 0; virtual void ReleaseChild() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/controlhost.mm b/native/Avalonia.Native/src/OSX/controlhost.mm index 315ec2f310..5ee2344ac7 100644 --- a/native/Avalonia.Native/src/OSX/controlhost.mm +++ b/native/Avalonia.Native/src/OSX/controlhost.mm @@ -97,7 +97,7 @@ public: return S_OK; }; - virtual void MoveTo(float x, float y, float width, float height) override + virtual void ShowInBounds(float x, float y, float width, float height) override { if(_child == nil) return; @@ -106,7 +106,7 @@ public: IAvnNativeControlHostTopLevelAttachment* slf = this; slf->AddRef(); dispatch_async(dispatch_get_main_queue(), ^{ - slf->MoveTo(x, y, width, height); + slf->ShowInBounds(x, y, width, height); slf->Release(); }); return; @@ -122,9 +122,24 @@ public: [[_holder superview] setNeedsDisplay:true]; } - virtual void Hide() override + virtual void HideWithSize(float width, float height) override { + if(_child == nil) + return; + if(AvnInsidePotentialDeadlock::IsInside()) + { + IAvnNativeControlHostTopLevelAttachment* slf = this; + slf->AddRef(); + dispatch_async(dispatch_get_main_queue(), ^{ + slf->HideWithSize(width, height); + slf->Release(); + }); + return; + } + + NSRect frame = {0, 0, width, height}; [_holder setHidden: true]; + [_child setFrame: frame]; } virtual void ReleaseChild() override diff --git a/src/Avalonia.Native/NativeControlHostImpl.cs b/src/Avalonia.Native/NativeControlHostImpl.cs index 4045ce8816..a46528dc48 100644 --- a/src/Avalonia.Native/NativeControlHostImpl.cs +++ b/src/Avalonia.Native/NativeControlHostImpl.cs @@ -116,8 +116,7 @@ namespace Avalonia.Native public void HideWithSize(Size size) { - //TODO - _native?.Hide(); + _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height)); } public void ShowInBounds(Rect bounds) @@ -126,7 +125,7 @@ namespace Avalonia.Native throw new InvalidOperationException("Native control isn't attached to a toplevel"); bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width), Math.Max(1, bounds.Height)); - _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); + _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height); } public void InitWithChild(IPlatformHandle handle) From 8c851b440e3abcbe4b1664dc807c918dd07fd97c Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 29 Jun 2020 19:32:44 +0300 Subject: [PATCH 41/47] OSX various popup focus shenanigans --- native/Avalonia.Native/inc/avalonia-native.h | 1 + native/Avalonia.Native/src/OSX/window.mm | 33 ++++++++++++++++--- .../interop/NativeEmbedSample/MainWindow.xaml | 9 +++++ src/Avalonia.Native/PopupImpl.cs | 12 +++++++ src/Avalonia.Native/WindowImplBase.cs | 2 +- 5 files changed, 51 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 22247df9b2..6800ff7d68 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -278,6 +278,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; + virtual HRESULT TakeFocusFromChildren() = 0; }; AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 86b3584681..7f8a6e1393 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -116,10 +116,15 @@ public: { SetPosition(lastPositionSet); UpdateStyle(); - - [Window makeKeyAndOrderFront:Window]; - [NSApp activateIgnoringOtherApps:YES]; - + if(ShouldTakeFocusOnShow()) + { + [Window makeKeyAndOrderFront:Window]; + [NSApp activateIgnoringOtherApps:YES]; + } + else + { + [Window orderFront: Window]; + } [Window setTitle:_lastTitle]; _shown = true; @@ -128,6 +133,11 @@ public: } } + virtual bool ShouldTakeFocusOnShow() + { + return true; + } + virtual HRESULT Hide () override { @autoreleasepool @@ -774,6 +784,15 @@ private: } } + virtual HRESULT TakeFocusFromChildren () override + { + if(Window == nil) + return S_OK; + if([Window isKeyWindow]) + [Window makeFirstResponder: View]; + return S_OK; + } + void EnterFullScreenMode () { _fullScreenActive = true; @@ -1858,7 +1877,6 @@ private: WindowEvents = events; [Window setLevel:NSPopUpMenuWindowLevel]; } - protected: virtual NSWindowStyleMask GetStyle() override { @@ -1876,6 +1894,11 @@ protected: return S_OK; } } +public: + virtual bool ShouldTakeFocusOnShow() override + { + return false; + } }; extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl) diff --git a/samples/interop/NativeEmbedSample/MainWindow.xaml b/samples/interop/NativeEmbedSample/MainWindow.xaml index dcec9035e0..f2161a1bea 100644 --- a/samples/interop/NativeEmbedSample/MainWindow.xaml +++ b/samples/interop/NativeEmbedSample/MainWindow.xaml @@ -20,7 +20,16 @@ + + + + Text + + + Tooltip + + diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs index c41be1723b..b0da5fdc43 100644 --- a/src/Avalonia.Native/PopupImpl.cs +++ b/src/Avalonia.Native/PopupImpl.cs @@ -10,6 +10,7 @@ namespace Avalonia.Native private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; private readonly GlPlatformFeature _glFeature; + private readonly IWindowBaseImpl _parent; public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, @@ -19,6 +20,7 @@ namespace Avalonia.Native _factory = factory; _opts = opts; _glFeature = glFeature; + _parent = parent; using (var e = new PopupEvents(this)) { var context = _opts.UseGpu ? glFeature?.DeferredContext : null; @@ -58,6 +60,16 @@ namespace Avalonia.Native } } + public override void Show() + { + var parent = _parent; + while (parent is PopupImpl p) + parent = p._parent; + if (parent is WindowImpl w) + w.Native.TakeFocusFromChildren(); + base.Show(); + } + public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this); public void SetWindowManagerAddShadowHint(bool enabled) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 930b5800ba..9a90f65d1b 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -319,7 +319,7 @@ namespace Avalonia.Native } - public void Show() + public virtual void Show() { _native.Show(); } From bd29bc333afc9eb9858fd3f3730dcb6fdf2cd0b9 Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Mon, 29 Jun 2020 23:44:45 -0400 Subject: [PATCH 42/47] Remove duplicated colors from fluent theme editor with non-standard values --- .../Accents/FluentBaseDark.xaml | 38 +----------------- .../Accents/FluentBaseLight.xaml | 39 +------------------ 2 files changed, 4 insertions(+), 73 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml index b12a639d31..b5d502787d 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml @@ -2,35 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> - #FF0078D7 - #FF000000 - #FF000000 - #FF000000 - #FF000000 - #FF000000 - #FFFFFFFF - #FF333333 - #FF9A9A9A - #FFB4B4B4 - #FF676767 - #FFB4B4B4 - #FF000000 - #FFB4B4B4 - #FF000000 - #FF000000 - #FF333333 - #FF808080 - #FF808080 - #FF151515 - #FF1D1D1D - #FF2C2C2C - #FFFFFFFF - #FF1D1D1D - #FF333333 - #CC000000 - #FF333333 - #FF1D1D1D - #FF333333 + #18FFFFFF + #30FFFFFF @@ -67,13 +40,6 @@ 1,1,1,1 1 - - #FF005A9E - #FF004275 - #FF002642 - #FF429CE3 - #FF76B9ED - #FFA6D8FF #FF000000 diff --git a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml index 31c2f592b9..0806a6e9ef 100644 --- a/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml @@ -2,36 +2,8 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=netstandard"> - #FF0078D7 - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FFFFFFFF - #FF000000 - #FFCCCCCC - #FF898989 - #FF5D5D5D - #FF737373 - #FF5D5D5D - #FF000000 - #FFCCCCCC - #FF5D5D5D - #FF898989 - #FFCCCCCC - #FF898989 - #FF737373 - #FFCCCCCC - #FFECECEC - #FFE6E6E6 - #FFECECEC - #FFFFFFFF - #FFE6E6E6 - #FFCCCCCC - #CCFFFFFF - #FFCCCCCC - #FFE6E6E6 - #FFCCCCCC + #17000000 + #2E000000 @@ -68,13 +40,6 @@ 1,1,1,1 1 - - #FF005A9E - #FF004275 - #FF002642 - #FF429CE3 - #FF76B9ED - #FFA6D8FF #FFFFFFFF From 4fe771786e5b44279af6c5bb7237d04c1d8c7e8f Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 30 Jun 2020 07:57:17 +0200 Subject: [PATCH 43/47] Update to Unicode 13.0 --- Avalonia.sln | 8 +- .../Assets/GraphemeBreak.trie | Bin 2549 -> 2460 bytes src/Avalonia.Visuals/Assets/UnicodeData.trie | Bin 11506 -> 11728 bytes .../Media/TextFormatting/Unicode/BiDiClass.cs | 2 +- .../TextFormatting/Unicode/BreakPairTable.cs | 65 +++---- .../Unicode/GraphemeBreakClass.cs | 34 ++-- .../TextFormatting/Unicode/LineBreakClass.cs | 4 +- .../Unicode/PropertyValueAliasHelper.cs | 178 +++++++++++++++++ .../Media/TextFormatting/Unicode/Script.cs | 10 +- .../Media/TextFormatting/BreakPairTable.txt | 67 +++---- .../GraphemeBreakClassTrieGenerator.cs | 61 +++--- .../GraphemeBreakClassTrieGeneratorTests.cs | 20 +- .../Media/TextFormatting/LineBreakerTests.cs | 2 +- .../TextFormatting/UnicodeDataGenerator.cs | 61 +++--- .../UnicodeDataGeneratorTests.cs | 2 +- .../TextFormatting/UnicodeEnumsGenerator.cs | 181 +++++++++++------- 16 files changed, 455 insertions(+), 240 deletions(-) create mode 100644 src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs diff --git a/Avalonia.sln b/Avalonia.sln index 3a2c619d5b..f6dc039c2f 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}" EndProject @@ -211,8 +211,8 @@ Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4 + src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 diff --git a/src/Avalonia.Visuals/Assets/GraphemeBreak.trie b/src/Avalonia.Visuals/Assets/GraphemeBreak.trie index 704dea4e86307ec2e3dfa8c7f9271e35b1c9ac5e..482bf9b44dacb6b225e0c59267571319bdbe36e8 100644 GIT binary patch literal 2460 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 L00960V`cyVU3%*? diff --git a/src/Avalonia.Visuals/Assets/UnicodeData.trie b/src/Avalonia.Visuals/Assets/UnicodeData.trie index 2e39745646a068d1c6d254656338c0f27e28456c..f96106a5fadbaf7451963aca766dde9d3cd9aa8e 100644 GIT binary patch literal 11728 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 YpWd;77|;I)0096000030|6^tV0LIs)tN;K2 diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs index 03576a4c40..ad3cc9141b 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BiDiClass.cs @@ -2,6 +2,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { public enum BiDiClass { + LeftToRight, //L ArabicLetter, //AL ArabicNumber, //AN ParagraphSeparator, //B @@ -11,7 +12,6 @@ namespace Avalonia.Media.TextFormatting.Unicode EuropeanSeparator, //ES EuropeanTerminator, //ET FirstStrongIsolate, //FSI - LeftToRight, //L LeftToRightEmbedding, //LRE LeftToRightIsolate, //LRI LeftToRightOverride, //LRO diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs index c13074711e..76c94b324a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs @@ -4,38 +4,39 @@ namespace Avalonia.Media.TextFormatting.Unicode { private static readonly byte[][] s_breakPairTable = { - new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4}, - new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1}, - new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1}, - new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,1,0,1,1,0,0,4,2,4,0,0,0,0,0,0,1,1,1}, + new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4}, + new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,0,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,1,0,1,1,0,0,4,2,4,1,1,1,1,1,0,1,1,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,0,0,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,0,1,4,4,4,0,0,1,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,0,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,4,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,0,0,0,0,0,0,0,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,1,1,1,1,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,1,1,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,1,0,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,1,0,0,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,1,1,0}, + new byte[] {0,4,4,1,1,1,4,4,4,0,1,0,0,0,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,0,1,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,0,4,4,4,0,0,0,0,0,0,0,0,0,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, }; public static PairBreakType Map(LineBreakClass first, LineBreakClass second) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs index 684baae51f..71e4bce106 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/GraphemeBreakClass.cs @@ -2,24 +2,20 @@ namespace Avalonia.Media.TextFormatting.Unicode { public enum GraphemeBreakClass { - Control, //CN - CR, //CR - EBase, //EB - EBaseGAZ, //EBG - EModifier, //EM - Extend, //EX - GlueAfterZwj, //GAZ - L, //L - LF, //LF - LV, //LV - LVT, //LVT - Prepend, //PP - RegionalIndicator, //RI - SpacingMark, //SM - T, //T - V, //V - Other, //XX - ZWJ, //ZWJ - ExtendedPictographic + Other, + CR, + LF, + Control, + Extend, + ZWJ, + RegionalIndicator, + Prepend, + SpacingMark, + L, + V, + T, + LV, + LVT, + ExtendedPictographic, } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs index 925706dd4f..0cf135ba21 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs @@ -35,9 +35,10 @@ namespace Avalonia.Media.TextFormatting.Unicode EModifier, //EM ZWJ, //ZWJ + ContingentBreak, //CB + Unknown, //XX Ambiguous, //AI MandatoryBreak, //BK - ContingentBreak, //CB ConditionalJapaneseStarter, //CJ CarriageReturn, //CR LineFeed, //LF @@ -45,6 +46,5 @@ namespace Avalonia.Media.TextFormatting.Unicode ComplexContext, //SA Surrogate, //SG Space, //SP - Unknown, //XX } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs new file mode 100644 index 0000000000..388a7d257d --- /dev/null +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/PropertyValueAliasHelper.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; + +namespace Avalonia.Media.TextFormatting.Unicode +{ + internal static class PropertyValueAliasHelper + { + private static readonly Dictionary s_scriptToTag = + new Dictionary{ + { Script.Unknown, "Zzzz"}, + { Script.Common, "Zyyy"}, + { Script.Inherited, "Zinh"}, + { Script.Adlam, "Adlm"}, + { Script.CaucasianAlbanian, "Aghb"}, + { Script.Ahom, "Ahom"}, + { Script.Arabic, "Arab"}, + { Script.ImperialAramaic, "Armi"}, + { Script.Armenian, "Armn"}, + { Script.Avestan, "Avst"}, + { Script.Balinese, "Bali"}, + { Script.Bamum, "Bamu"}, + { Script.BassaVah, "Bass"}, + { Script.Batak, "Batk"}, + { Script.Bengali, "Beng"}, + { Script.Bhaiksuki, "Bhks"}, + { Script.Bopomofo, "Bopo"}, + { Script.Brahmi, "Brah"}, + { Script.Braille, "Brai"}, + { Script.Buginese, "Bugi"}, + { Script.Buhid, "Buhd"}, + { Script.Chakma, "Cakm"}, + { Script.CanadianAboriginal, "Cans"}, + { Script.Carian, "Cari"}, + { Script.Cham, "Cham"}, + { Script.Cherokee, "Cher"}, + { Script.Chorasmian, "Chrs"}, + { Script.Coptic, "Copt"}, + { Script.Cypriot, "Cprt"}, + { Script.Cyrillic, "Cyrl"}, + { Script.Devanagari, "Deva"}, + { Script.DivesAkuru, "Diak"}, + { Script.Dogra, "Dogr"}, + { Script.Deseret, "Dsrt"}, + { Script.Duployan, "Dupl"}, + { Script.EgyptianHieroglyphs, "Egyp"}, + { Script.Elbasan, "Elba"}, + { Script.Elymaic, "Elym"}, + { Script.Ethiopic, "Ethi"}, + { Script.Georgian, "Geor"}, + { Script.Glagolitic, "Glag"}, + { Script.GunjalaGondi, "Gong"}, + { Script.MasaramGondi, "Gonm"}, + { Script.Gothic, "Goth"}, + { Script.Grantha, "Gran"}, + { Script.Greek, "Grek"}, + { Script.Gujarati, "Gujr"}, + { Script.Gurmukhi, "Guru"}, + { Script.Hangul, "Hang"}, + { Script.Han, "Hani"}, + { Script.Hanunoo, "Hano"}, + { Script.Hatran, "Hatr"}, + { Script.Hebrew, "Hebr"}, + { Script.Hiragana, "Hira"}, + { Script.AnatolianHieroglyphs, "Hluw"}, + { Script.PahawhHmong, "Hmng"}, + { Script.NyiakengPuachueHmong, "Hmnp"}, + { Script.KatakanaOrHiragana, "Hrkt"}, + { Script.OldHungarian, "Hung"}, + { Script.OldItalic, "Ital"}, + { Script.Javanese, "Java"}, + { Script.KayahLi, "Kali"}, + { Script.Katakana, "Kana"}, + { Script.Kharoshthi, "Khar"}, + { Script.Khmer, "Khmr"}, + { Script.Khojki, "Khoj"}, + { Script.KhitanSmallScript, "Kits"}, + { Script.Kannada, "Knda"}, + { Script.Kaithi, "Kthi"}, + { Script.TaiTham, "Lana"}, + { Script.Lao, "Laoo"}, + { Script.Latin, "Latn"}, + { Script.Lepcha, "Lepc"}, + { Script.Limbu, "Limb"}, + { Script.LinearA, "Lina"}, + { Script.LinearB, "Linb"}, + { Script.Lisu, "Lisu"}, + { Script.Lycian, "Lyci"}, + { Script.Lydian, "Lydi"}, + { Script.Mahajani, "Mahj"}, + { Script.Makasar, "Maka"}, + { Script.Mandaic, "Mand"}, + { Script.Manichaean, "Mani"}, + { Script.Marchen, "Marc"}, + { Script.Medefaidrin, "Medf"}, + { Script.MendeKikakui, "Mend"}, + { Script.MeroiticCursive, "Merc"}, + { Script.MeroiticHieroglyphs, "Mero"}, + { Script.Malayalam, "Mlym"}, + { Script.Modi, "Modi"}, + { Script.Mongolian, "Mong"}, + { Script.Mro, "Mroo"}, + { Script.MeeteiMayek, "Mtei"}, + { Script.Multani, "Mult"}, + { Script.Myanmar, "Mymr"}, + { Script.Nandinagari, "Nand"}, + { Script.OldNorthArabian, "Narb"}, + { Script.Nabataean, "Nbat"}, + { Script.Newa, "Newa"}, + { Script.Nko, "Nkoo"}, + { Script.Nushu, "Nshu"}, + { Script.Ogham, "Ogam"}, + { Script.OlChiki, "Olck"}, + { Script.OldTurkic, "Orkh"}, + { Script.Oriya, "Orya"}, + { Script.Osage, "Osge"}, + { Script.Osmanya, "Osma"}, + { Script.Palmyrene, "Palm"}, + { Script.PauCinHau, "Pauc"}, + { Script.OldPermic, "Perm"}, + { Script.PhagsPa, "Phag"}, + { Script.InscriptionalPahlavi, "Phli"}, + { Script.PsalterPahlavi, "Phlp"}, + { Script.Phoenician, "Phnx"}, + { Script.Miao, "Plrd"}, + { Script.InscriptionalParthian, "Prti"}, + { Script.Rejang, "Rjng"}, + { Script.HanifiRohingya, "Rohg"}, + { Script.Runic, "Runr"}, + { Script.Samaritan, "Samr"}, + { Script.OldSouthArabian, "Sarb"}, + { Script.Saurashtra, "Saur"}, + { Script.SignWriting, "Sgnw"}, + { Script.Shavian, "Shaw"}, + { Script.Sharada, "Shrd"}, + { Script.Siddham, "Sidd"}, + { Script.Khudawadi, "Sind"}, + { Script.Sinhala, "Sinh"}, + { Script.Sogdian, "Sogd"}, + { Script.OldSogdian, "Sogo"}, + { Script.SoraSompeng, "Sora"}, + { Script.Soyombo, "Soyo"}, + { Script.Sundanese, "Sund"}, + { Script.SylotiNagri, "Sylo"}, + { Script.Syriac, "Syrc"}, + { Script.Tagbanwa, "Tagb"}, + { Script.Takri, "Takr"}, + { Script.TaiLe, "Tale"}, + { Script.NewTaiLue, "Talu"}, + { Script.Tamil, "Taml"}, + { Script.Tangut, "Tang"}, + { Script.TaiViet, "Tavt"}, + { Script.Telugu, "Telu"}, + { Script.Tifinagh, "Tfng"}, + { Script.Tagalog, "Tglg"}, + { Script.Thaana, "Thaa"}, + { Script.Thai, "Thai"}, + { Script.Tibetan, "Tibt"}, + { Script.Tirhuta, "Tirh"}, + { Script.Ugaritic, "Ugar"}, + { Script.Vai, "Vaii"}, + { Script.WarangCiti, "Wara"}, + { Script.Wancho, "Wcho"}, + { Script.OldPersian, "Xpeo"}, + { Script.Cuneiform, "Xsux"}, + { Script.Yezidi, "Yezi"}, + { Script.Yi, "Yiii"}, + { Script.ZanabazarSquare, "Zanb"}, + }; + + public static string GetTag(Script script) + { + if(!s_scriptToTag.ContainsKey(script)) + { + return "Zzzz"; + } + return s_scriptToTag[script]; + } + } +} diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs index e9681d4c24..2593a77848 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/Script.cs @@ -2,6 +2,9 @@ namespace Avalonia.Media.TextFormatting.Unicode { public enum Script { + Unknown, //Zzzz + Common, //Zyyy + Inherited, //Zinh Adlam, //Adlm CaucasianAlbanian, //Aghb Ahom, //Ahom @@ -25,10 +28,12 @@ namespace Avalonia.Media.TextFormatting.Unicode Carian, //Cari Cham, //Cham Cherokee, //Cher + Chorasmian, //Chrs Coptic, //Copt Cypriot, //Cprt Cyrillic, //Cyrl Devanagari, //Deva + DivesAkuru, //Diak Dogra, //Dogr Deseret, //Dsrt Duployan, //Dupl @@ -63,6 +68,7 @@ namespace Avalonia.Media.TextFormatting.Unicode Kharoshthi, //Khar Khmer, //Khmr Khojki, //Khoj + KhitanSmallScript, //Kits Kannada, //Knda Kaithi, //Kthi TaiTham, //Lana @@ -151,10 +157,8 @@ namespace Avalonia.Media.TextFormatting.Unicode Wancho, //Wcho OldPersian, //Xpeo Cuneiform, //Xsux + Yezidi, //Yezi Yi, //Yiii ZanabazarSquare, //Zanb - Inherited, //Zinh - Common, //Zyyy - Unknown, //Zzzz } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt index 90c1e2cee1..93d531c700 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt @@ -1,33 +1,34 @@ - OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ -OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % -GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % -NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % -PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % -B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % -ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ -CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % -H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % -H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % -JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % -JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % -JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % -RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % -EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % -EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % -ZWJ _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ % _ % % _ _ ^ # ^ _ _ _ _ _ _ % % % + OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB +OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ +CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % +GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % +NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +EX _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +SY _ ^ ^ % % % ^ ^ ^ _ _ % _ % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +IS _ ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +PR % ^ ^ % % % ^ ^ ^ _ _ % % % % _ % % _ _ ^ # ^ % % % % % _ % % % _ +PO % ^ ^ % % % ^ ^ ^ _ _ % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +NU % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +AL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +HL % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +ID _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +IN _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +HY _ ^ ^ % _ % ^ ^ ^ _ _ % _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +BA _ ^ ^ % _ % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +BB % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % _ +B2 _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ ^ ^ # ^ _ _ _ _ _ _ _ _ % _ +ZW _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ ^ _ _ _ _ _ _ _ _ _ _ _ _ +CM % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +WJ % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % +H2 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _ +H3 _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _ +JL _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ % % % % _ _ _ _ % _ +JV _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ % % _ _ _ % _ +JT _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ % _ _ _ % _ +RI _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ % _ _ % _ +EB _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ % % _ +EM _ ^ ^ % % % ^ ^ ^ _ % _ _ _ _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +ZWJ % ^ ^ % % % ^ ^ ^ % % % % % _ % % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CB _ ^ ^ % % _ ^ ^ ^ _ _ _ _ _ _ _ _ _ _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs index 94ab615130..c074e5a1f1 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGenerator.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net.Http; using System.Text.RegularExpressions; using Avalonia.Media.TextFormatting.Unicode; @@ -12,6 +11,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { public static void Execute() { + if (!Directory.Exists("Generated")) + { + Directory.CreateDirectory("Generated"); + } + using (var stream = File.Create("Generated\\GraphemeBreak.trie")) { var trie = GenerateBreakTypeTrie(); @@ -22,48 +26,29 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting private static UnicodeTrie GenerateBreakTypeTrie() { - var graphemeBreakClassValues = UnicodeEnumsGenerator.GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)"); - - var graphemeBreakClassMapping = graphemeBreakClassValues.Select(x => x.name).ToList(); - var trieBuilder = new UnicodeTrieBuilder(); - var graphemeBreakData = ReadBreakData( - "https://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt"); - - foreach (var (start, end, graphemeBreakType) in graphemeBreakData) - { - if (!graphemeBreakClassMapping.Contains(graphemeBreakType)) - { - continue; - } - - if (start == end) - { - trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); - } - else - { - trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); - } - } + var graphemeBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakProperty.txt")); - var emojiBreakData = ReadBreakData("https://unicode.org/Public/emoji/12.0/emoji-data.txt"); + var emojiBreakData = ReadBreakData(Path.Combine(UnicodeDataGenerator.Ucd, "emoji/emoji-data.txt")); - foreach (var (start, end, graphemeBreakType) in emojiBreakData) + foreach (var breakData in new [] { graphemeBreakData, emojiBreakData }) { - if (!graphemeBreakClassMapping.Contains(graphemeBreakType)) + foreach (var (start, end, graphemeBreakType) in breakData) { - continue; - } + if (!Enum.TryParse(graphemeBreakType, out var value)) + { + continue; + } - if (start == end) - { - trieBuilder.Set(start, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); - } - else - { - trieBuilder.SetRange(start, end, (uint)graphemeBreakClassMapping.IndexOf(graphemeBreakType)); + if (start == end) + { + trieBuilder.Set(start, (uint)value); + } + else + { + trieBuilder.SetRange(start, end, (uint)value); + } } } @@ -113,7 +98,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting end = Convert.ToInt32(match.Groups[2].Value, 16); } - data.Add((start, end, match.Groups[3].Value)); + var breakType = match.Groups[3].Value; + + data.Add((start, end, breakType)); } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs index d9a9c82f85..5ae0b1448b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs @@ -15,11 +15,13 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting /// public class GraphemeBreakClassTrieGeneratorTests { - [Theory(Skip = "Only run when we update the trie.")] + [Theory/*(Skip = "Only run when we update the trie.")*/] [ClassData(typeof(GraphemeEnumeratorTestDataGenerator))] public void Should_Enumerate(string text, int expectedLength) { - var enumerator = new GraphemeEnumerator(text.AsMemory()); + var textMemory = text.AsMemory(); + + var enumerator = new GraphemeEnumerator(textMemory); Assert.True(enumerator.MoveNext()); @@ -31,7 +33,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { const string text = "ABCDEFGHIJ"; - var enumerator = new GraphemeEnumerator(text.AsMemory()); + var textMemory = text.AsMemory(); + + var enumerator = new GraphemeEnumerator(textMemory); var count = 0; @@ -45,7 +49,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting Assert.Equal(10, count); } - [Fact(Skip = "Only run when we update the trie.")] + [Fact/*(Skip = "Only run when we update the trie.")*/] public void Should_Generate_Trie() { GraphemeBreakClassTrieGenerator.Execute(); @@ -76,7 +80,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting using (var client = new HttpClient()) { - using (var result = client.GetAsync("https://www.unicode.org/Public/UNIDATA/auxiliary/GraphemeBreakTest.txt").GetAwaiter().GetResult()) + var url = Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakTest.txt"); + + using (var result = client.GetAsync(url).GetAwaiter().GetResult()) { if (!result.IsSuccessStatusCode) return testData; @@ -98,7 +104,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting continue; } - var elements = line.Split('#')[0].Replace("÷\t", "÷").Trim('÷').Split('÷'); + var elements = line.Split('#'); + + elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷'); var chars = elements[0].Replace(" × ", " ").Split(' '); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs index fe7d7adc17..3ed5cfb0b2 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/LineBreakerTests.cs @@ -3,7 +3,7 @@ using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Utility; using Xunit; -namespace Avalonia.Visuals.UnitTests.Media.Text +namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { public class LineBreakerTests { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs index f7b8b68ab3..cbe8edefb6 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGenerator.cs @@ -9,13 +9,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { internal static class UnicodeDataGenerator { + public const string Ucd = "https://www.unicode.org/Public/13.0.0/ucd/"; + public static void Execute() { var codepoints = new Dictionary(); - var generalCategoryValues = UnicodeEnumsGenerator.CreateGeneralCategoryEnum(); + var generalCategoryEntries = + UnicodeEnumsGenerator.CreateGeneralCategoryEnum(); - var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryValues); + var generalCategoryMappings = CreateTagToIndexMappings(generalCategoryEntries); var generalCategoryData = ReadGeneralCategoryData(); @@ -26,23 +29,23 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting AddGeneralCategoryRange(codepoints, range, generalCategory); } - var scriptValues = UnicodeEnumsGenerator.CreateScriptEnum(); + var scriptEntries = UnicodeEnumsGenerator.CreateScriptEnum(); - var scriptMappings = CreateNameToIndexMappings(scriptValues); + var scriptMappings = CreateNameToIndexMappings(scriptEntries); var scriptData = ReadScriptData(); foreach (var (range, name) in scriptData) { - var script = scriptMappings[name.Replace("_", "")]; + var script = scriptMappings[name]; AddScriptRange(codepoints, range, script); - } - var biDiClassValues = UnicodeEnumsGenerator.CreateBiDiClassEnum(); + var biDiClassEntries = + UnicodeEnumsGenerator.CreateBiDiClassEnum(); - var biDiClassMappings = CreateTagToIndexMappings(biDiClassValues); + var biDiClassMappings = CreateTagToIndexMappings(biDiClassEntries); var biDiData = ReadBiDiData(); @@ -53,9 +56,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting AddBiDiClassRange(codepoints, range, biDiClass); } - var lineBreakClassValues = UnicodeEnumsGenerator.CreateLineBreakClassEnum(); + var lineBreakClassEntries = + UnicodeEnumsGenerator.CreateLineBreakClassEnum(); - var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassValues); + var lineBreakClassMappings = CreateTagToIndexMappings(lineBreakClassEntries); var lineBreakClassData = ReadLineBreakClassData(); @@ -66,11 +70,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting AddLineBreakClassRange(codepoints, range, lineBreakClass); } - const int initialValue = ((int)LineBreakClass.Unknown << UnicodeData.LINEBREAK_SHIFT) | - ((int)BiDiClass.LeftToRight << UnicodeData.BIDI_SHIFT) | - ((int)Script.Unknown << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other; + //const int initialValue = (0 << UnicodeData.LINEBREAK_SHIFT) | + // (0 << UnicodeData.BIDI_SHIFT) | + // (0 << UnicodeData.SCRIPT_SHIFT) | (int)GeneralCategory.Other; - var builder = new UnicodeTrieBuilder(initialValue); + var builder = new UnicodeTrieBuilder(/*initialValue*/); foreach (var properties in codepoints.Values) { @@ -88,27 +92,30 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting trie.Save(stream); } + + UnicodeEnumsGenerator.CreatePropertyValueAliasHelper(scriptEntries, generalCategoryEntries, + biDiClassEntries, lineBreakClassEntries); } - private static Dictionary CreateTagToIndexMappings(List<(string name, string tag, string comment)> values) + private static Dictionary CreateTagToIndexMappings(List entries) { var mappings = new Dictionary(); - for (var i = 0; i < values.Count; i++) + for (var i = 0; i < entries.Count; i++) { - mappings.Add(values[i].tag, i); + mappings.Add(entries[i].Tag, i); } return mappings; } - private static Dictionary CreateNameToIndexMappings(List<(string name, string tag, string comment)> values) + private static Dictionary CreateNameToIndexMappings(List entries) { var mappings = new Dictionary(); - for (var i = 0; i < values.Count; i++) + for (var i = 0; i < entries.Count; i++) { - mappings.Add(values[i].name, i); + mappings.Add(entries[i].Name, i); } return mappings; @@ -180,24 +187,22 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting public static List<(CodepointRange, string)> ReadGeneralCategoryData() { - return ReadUnicodeData( - "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedGeneralCategory.txt"); + return ReadUnicodeData("extracted/DerivedGeneralCategory.txt"); } public static List<(CodepointRange, string)> ReadScriptData() { - return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt"); + return ReadUnicodeData("Scripts.txt"); } public static List<(CodepointRange, string)> ReadBiDiData() { - return ReadUnicodeData("https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedBidiClass.txt"); + return ReadUnicodeData("extracted/DerivedBidiClass.txt"); } public static List<(CodepointRange, string)> ReadLineBreakClassData() { - return ReadUnicodeData( - "https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedLineBreak.txt"); + return ReadUnicodeData("extracted/DerivedLineBreak.txt"); } private static List<(CodepointRange, string)> ReadUnicodeData(string file) @@ -208,7 +213,9 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting using (var client = new HttpClient()) { - using (var result = client.GetAsync(file).GetAwaiter().GetResult()) + var url = Path.Combine(Ucd, file); + + using (var result = client.GetAsync(url).GetAwaiter().GetResult()) { if (!result.IsSuccessStatusCode) { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs index 47aef84533..c063cd296b 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs @@ -8,7 +8,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting /// This test is used to generate all Unicode related types. /// We only need to run this when the Unicode spec changes. /// - [Fact(Skip = "Only run when the Unicode spec changes.")] + [Fact/*(Skip = "Only run when the Unicode spec changes.")*/] public void Should_Generate_Data() { UnicodeDataGenerator.Execute(); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs index e141204d4c..413ebd645e 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs @@ -8,9 +8,16 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { internal static class UnicodeEnumsGenerator { - public static List<(string name, string tag, string comment)> CreateScriptEnum() + public static List CreateScriptEnum() { - var scriptValues = GetPropertyValueAliases("# Script (sc)"); + var entries = new List + { + new DataEntry("Unknown", "Zzzz", string.Empty), + new DataEntry("Common", "Zyyy", string.Empty), + new DataEntry("Inherited", "Zinh", string.Empty) + }; + + ParseDataEntries("# Script (sc)", entries); using (var stream = File.Create("Generated\\Script.cs")) using (var writer = new StreamWriter(stream)) @@ -20,22 +27,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(" public enum Script"); writer.WriteLine(" {"); - foreach (var (name, tag, comment) in scriptValues) + foreach (var entry in entries) { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } - return scriptValues; + return entries; } - public static List<(string name, string tag, string comment)> CreateGeneralCategoryEnum() + public static List CreateGeneralCategoryEnum() { - var generalCategoryValues = GetPropertyValueAliases("# General_Category (gc)"); + var entries = new List { new DataEntry("Other", "C#", string.Empty) }; + + ParseDataEntries("# General_Category (gc)", entries); using (var stream = File.Create("Generated\\GeneralCategory.cs")) using (var writer = new StreamWriter(stream)) @@ -45,22 +54,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(" public enum GeneralCategory"); writer.WriteLine(" {"); - foreach (var (name, tag, comment) in generalCategoryValues) + foreach (var entry in entries) { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } - return generalCategoryValues; + return entries; } - public static List<(string name, string tag, string comment)> CreateGraphemeBreakTypeEnum() + public static List CreateGraphemeBreakTypeEnum() { - var graphemeClusterBreakValues = GetPropertyValueAliases("# Grapheme_Cluster_Break (GCB)"); + var entries = new List { new DataEntry("Other", "XX", string.Empty) }; + + ParseDataEntries("# Grapheme_Cluster_Break (GCB)", entries); using (var stream = File.Create("Generated\\GraphemeBreakClass.cs")) using (var writer = new StreamWriter(stream)) @@ -70,10 +81,10 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(" public enum GraphemeBreakClass"); writer.WriteLine(" {"); - foreach (var (name, tag, comment) in graphemeClusterBreakValues) + foreach (var entry in entries) { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" ExtendedPictographic"); @@ -82,7 +93,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine("}"); } - return graphemeClusterBreakValues; + return entries; } private static List GenerateBreakPairTable() @@ -185,20 +196,32 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting } } - public static List<(string name, string tag, string comment)> CreateLineBreakClassEnum() + public static List CreateLineBreakClassEnum() { var usedLineBreakClasses = GenerateBreakPairTable(); - var lineBreakValues = GetPropertyValueAliases("# Line_Break (lb)"); + var entries = new List { new DataEntry("Unknown", "XX", string.Empty) }; + + ParseDataEntries("# Line_Break (lb)", entries); - var lineBreakClassMappings = lineBreakValues.ToDictionary(x => x.tag, x => (x.name, x.tag, x.comment)); + var orderedLineBreakEntries = new Dictionary(); - var orderedLineBreakValues = usedLineBreakClasses.Select(x => + foreach (var tag in usedLineBreakClasses) { - var value = lineBreakClassMappings[x]; - lineBreakClassMappings.Remove(x); - return value; - }).ToList(); + var entry = entries.Single(x => x.Tag == tag); + + orderedLineBreakEntries.Add(tag, entry); + } + + foreach (var entry in entries) + { + if (orderedLineBreakEntries.ContainsKey(entry.Tag)) + { + continue; + } + + orderedLineBreakEntries.Add(entry.Tag, entry); + } using (var stream = File.Create("Generated\\LineBreakClass.cs")) using (var writer = new StreamWriter(stream)) @@ -208,32 +231,24 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(" public enum LineBreakClass"); writer.WriteLine(" {"); - foreach (var (name, tag, comment) in orderedLineBreakValues) - { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); - } - - writer.WriteLine(); - - foreach (var (name, tag, comment) in lineBreakClassMappings.Values) + foreach (var entry in orderedLineBreakEntries.Values) { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } - orderedLineBreakValues.AddRange(lineBreakClassMappings.Values); - - return orderedLineBreakValues; + return orderedLineBreakEntries.Values.ToList(); } - public static List<(string name, string tag, string comment)> CreateBiDiClassEnum() + public static List CreateBiDiClassEnum() { - var biDiClassValues = GetPropertyValueAliases("# Bidi_Class (bc)"); + var entries = new List { new DataEntry("Left_To_Right", "L", string.Empty) }; + + ParseDataEntries("# Bidi_Class (bc)", entries); using (var stream = File.Create("Generated\\BiDiClass.cs")) using (var writer = new StreamWriter(stream)) @@ -243,23 +258,21 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(" public enum BiDiClass"); writer.WriteLine(" {"); - foreach (var (name, tag, comment) in biDiClassValues) + foreach (var entry in entries) { - writer.WriteLine(" " + name + ", //" + tag + - (string.IsNullOrEmpty(comment) ? string.Empty : "#" + comment)); + writer.WriteLine(" " + entry.Name.Replace("_", "") + ", //" + entry.Tag + + (string.IsNullOrEmpty(entry.Comment) ? string.Empty : "#" + entry.Comment)); } writer.WriteLine(" }"); writer.WriteLine("}"); } - return biDiClassValues; + return entries; } - public static void CreatePropertyValueAliasHelper(List<(string name, string tag, string comment)> scriptValues, - List<(string name, string tag, string comment)> generalCategoryValues, - List<(string name, string tag, string comment)> biDiClassValues, - List<(string name, string tag, string comment)> lineBreakValues) + public static void CreatePropertyValueAliasHelper(List scriptEntries, IEnumerable generalCategoryEntries, + IEnumerable biDiClassEntries, IEnumerable lineBreakClassEntries) { using (var stream = File.Create("Generated\\PropertyValueAliasHelper.cs")) using (var writer = new StreamWriter(stream)) @@ -269,35 +282,35 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine("namespace Avalonia.Media.TextFormatting.Unicode"); writer.WriteLine("{"); - writer.WriteLine(" public static class PropertyValueAliasHelper"); + writer.WriteLine(" internal static class PropertyValueAliasHelper"); writer.WriteLine(" {"); - WritePropertyValueAliasGetTag(writer, scriptValues, "Script", "Zzzz"); + WritePropertyValueAliasGetTag(writer, scriptEntries, "Script", "Zzzz"); - WritePropertyValueAlias(writer, scriptValues, "Script", "Unknown"); + WritePropertyValueAlias(writer, scriptEntries, "Script", "Unknown"); - WritePropertyValueAlias(writer, generalCategoryValues, "GeneralCategory", "Other"); + WritePropertyValueAlias(writer, generalCategoryEntries, "GeneralCategory", "Other"); - WritePropertyValueAlias(writer, biDiClassValues, "BiDiClass", "LeftToRight"); + WritePropertyValueAlias(writer, biDiClassEntries, "BiDiClass", "LeftToRight"); - WritePropertyValueAlias(writer, lineBreakValues, "LineBreakClass", "Unknown"); + WritePropertyValueAlias(writer, lineBreakClassEntries, "LineBreakClass", "Unknown"); writer.WriteLine(" }"); writer.WriteLine("}"); } } - public static List<(string name, string tag, string comment)> GetPropertyValueAliases(string property) + public static void ParseDataEntries(string property, List entries) { - var data = new List<(string name, string tag, string comment)>(); - using (var client = new HttpClient()) { - using (var result = client.GetAsync("https://www.unicode.org/Public/UCD/latest/ucd/PropertyValueAliases.txt").GetAwaiter().GetResult()) + var url = Path.Combine(UnicodeDataGenerator.Ucd, "PropertyValueAliases.txt"); + + using (var result = client.GetAsync(url).GetAwaiter().GetResult()) { if (!result.IsSuccessStatusCode) { - return data; + return; } using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) @@ -337,7 +350,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting elements = elements[2].Split('#'); - var name = elements[0].Trim().Replace("_", string.Empty); + var name = elements[0].Trim(); + + if (entries.Any(x => x.Name == name)) + { + continue; + } var comment = string.Empty; @@ -346,24 +364,25 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting comment = elements[1]; } - data.Add((name, tag, comment)); + var entry = new DataEntry(name, tag, comment); + + entries.Add(entry); } } } } - - return data; } - private static void WritePropertyValueAliasGetTag(TextWriter writer, - IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) + private static void WritePropertyValueAliasGetTag(TextWriter writer, IEnumerable entries, + string typeName, string defaultValue) { - writer.WriteLine($" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = "); + writer.WriteLine( + $" private static readonly Dictionary<{typeName}, string> s_{typeName.ToLower()}ToTag = "); writer.WriteLine($" new Dictionary<{typeName}, string>{{"); - foreach (var (name, tag, comment) in values) + foreach (var entry in entries) { - writer.WriteLine($" {{ {typeName}.{name}, \"{tag}\"}},"); + writer.WriteLine($" {{ {typeName}.{entry.Name.Replace("_", "")}, \"{entry.Tag}\"}},"); } writer.WriteLine(" };"); @@ -382,15 +401,15 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(); } - private static void WritePropertyValueAlias(TextWriter writer, - IEnumerable<(string name, string tag, string comment)> values, string typeName, string defaultValue) + private static void WritePropertyValueAlias(TextWriter writer, IEnumerable entries, string typeName, + string defaultValue) { writer.WriteLine($" private static readonly Dictionary s_tagTo{typeName} = "); writer.WriteLine($" new Dictionary{{"); - foreach (var (name, tag, comment) in values) + foreach (var entry in entries) { - writer.WriteLine($" {{ \"{tag}\", {typeName}.{name}}},"); + writer.WriteLine($" {{ \"{entry.Tag}\", {typeName}.{entry.Name.Replace("_", "")}}},"); } writer.WriteLine(" };"); @@ -409,4 +428,18 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting writer.WriteLine(); } } + + public readonly struct DataEntry + { + public DataEntry(string name, string tag, string comment) + { + Name = name; + Tag = tag; + Comment = comment; + } + + public string Name { get; } + public string Tag { get; } + public string Comment { get; } + } } From f20039070d1f7d1255dc5ed77763b48484529b36 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 30 Jun 2020 11:42:03 +0200 Subject: [PATCH 44/47] Fix a couple of bugs in LayoutManager. - Clear `_running` at the end of all layout passes, not at the end of the first set of 3 attempts - Fix a bizarre anti-copy-pasta in `RaiseEffectiveViewportChanged` where we were comparing `startCount` to the size of the measure queue twice instead of the arrange queue --- src/Avalonia.Layout/LayoutManager.cs | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 792de774d1..fc988a8d6c 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -106,8 +106,6 @@ namespace Avalonia.Layout if (!_running) { - _running = true; - Stopwatch? stopwatch = null; const LogEventLevel timingLogLevel = LogEventLevel.Information; @@ -128,15 +126,24 @@ namespace Avalonia.Layout _toMeasure.BeginLoop(MaxPasses); _toArrange.BeginLoop(MaxPasses); - for (var pass = 0; pass < MaxPasses; ++pass) + try { - InnerLayoutPass(); + _running = true; - if (!RaiseEffectiveViewportChanged()) + for (var pass = 0; pass < MaxPasses; ++pass) { - break; + InnerLayoutPass(); + + if (!RaiseEffectiveViewportChanged()) + { + break; + } } } + finally + { + _running = false; + } _toMeasure.EndLoop(); _toArrange.EndLoop(); @@ -221,23 +228,16 @@ namespace Avalonia.Layout private void InnerLayoutPass() { - try + for (var pass = 0; pass < MaxPasses; ++pass) { - for (var pass = 0; pass < MaxPasses; ++pass) - { - ExecuteMeasurePass(); - ExecuteArrangePass(); + ExecuteMeasurePass(); + ExecuteArrangePass(); - if (_toMeasure.Count == 0) - { - break; - } + if (_toMeasure.Count == 0) + { + break; } } - finally - { - _running = false; - } } private void ExecuteMeasurePass() @@ -362,7 +362,7 @@ namespace Avalonia.Layout } } - return startCount != _toMeasure.Count + _toMeasure.Count; + return startCount != _toMeasure.Count + _toArrange.Count; } private Rect CalculateEffectiveViewport(IVisual control) From f377172947ea44a5ddeaa2cb79d47b197f2d3fbc Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 30 Jun 2020 13:42:27 +0200 Subject: [PATCH 45/47] Introduce line break tests --- .../TextFormatting/Unicode/BreakPairTable.cs | 4 +- .../TextFormatting/Unicode/LineBreakClass.cs | 2 +- .../Unicode/LineBreakEnumerator.cs | 2 + .../Media/TextFormatting/BreakPairTable.txt | 4 +- .../GraphemeBreakClassTrieGeneratorTests.cs | 79 +---------------- .../Media/TextFormatting/TestDataGenerator.cs | 85 +++++++++++++++++++ .../UnicodeDataGeneratorTests.cs | 25 +++++- .../TextFormatting/UnicodeEnumsGenerator.cs | 2 +- 8 files changed, 121 insertions(+), 82 deletions(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs index 76c94b324a..86d39a4283 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/BreakPairTable.cs @@ -5,8 +5,8 @@ namespace Avalonia.Media.TextFormatting.Unicode private static readonly byte[][] s_breakPairTable = { new byte[] {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,4}, - new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, - new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,4,4,4,4,1,1,0,0,0,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, + new byte[] {0,4,4,1,1,4,4,4,4,1,1,1,1,1,0,4,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, new byte[] {4,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1}, new byte[] {1,4,4,1,1,1,4,4,4,1,1,1,1,1,1,1,1,1,1,1,4,2,4,1,1,1,1,1,1,1,1,1,1}, new byte[] {0,4,4,1,1,1,4,4,4,0,0,0,0,0,0,0,1,1,0,0,4,2,4,0,0,0,0,0,0,0,0,1,0}, diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs index 0cf135ba21..8b2e3f41e3 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakClass.cs @@ -34,8 +34,8 @@ namespace Avalonia.Media.TextFormatting.Unicode EBase, //EB EModifier, //EM ZWJ, //ZWJ - ContingentBreak, //CB + Unknown, //XX Ambiguous, //AI MandatoryBreak, //BK diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index a11c008409..79c140a957 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -95,6 +95,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (_nextClass.Value == LineBreakClass.MandatoryBreak) { + _lastPos = _pos; Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos); return true; } @@ -108,6 +109,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { case PairBreakType.DI: // Direct break shouldBreak = true; + _lastPos = _pos; break; case PairBreakType.IN: // possible indirect break diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt index 93d531c700..814ce15d0a 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/BreakPairTable.txt @@ -1,7 +1,7 @@ OP CL CP QU GL NS EX SY IS PR PO NU AL HL ID IN HY BA BB B2 ZW CM WJ H2 H3 JL JV JT RI EB EM ZWJ CB OP ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ @ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ -CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ -CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CL _ ^ ^ % % ^ ^ ^ ^ % % _ _ _ _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ +CP _ ^ ^ % % ^ ^ ^ ^ % % % % % _ ^ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ QU ^ ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % GL % ^ ^ % % % ^ ^ ^ % % % % % % % % % % % ^ # ^ % % % % % % % % % % NS _ ^ ^ % % % ^ ^ ^ _ _ _ _ _ _ _ % % _ _ ^ # ^ _ _ _ _ _ _ _ _ % _ diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs index 5ae0b1448b..d7e2128f69 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs @@ -1,9 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; using Avalonia.Media.TextFormatting.Unicode; using Xunit; @@ -16,7 +11,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting public class GraphemeBreakClassTrieGeneratorTests { [Theory/*(Skip = "Only run when we update the trie.")*/] - [ClassData(typeof(GraphemeEnumeratorTestDataGenerator))] + [ClassData(typeof(GraphemeBreakTestDataGenerator))] public void Should_Enumerate(string text, int expectedLength) { var textMemory = text.AsMemory(); @@ -55,77 +50,11 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting GraphemeBreakClassTrieGenerator.Execute(); } - public class GraphemeEnumeratorTestDataGenerator : IEnumerable + private class GraphemeBreakTestDataGenerator : TestDataGenerator { - private readonly List _testData; - - public GraphemeEnumeratorTestDataGenerator() - { - _testData = ReadTestData(); - } - - public IEnumerator GetEnumerator() - { - return _testData.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - private static List ReadTestData() + public GraphemeBreakTestDataGenerator() + : base("auxiliary/GraphemeBreakTest.txt") { - var testData = new List(); - - using (var client = new HttpClient()) - { - var url = Path.Combine(UnicodeDataGenerator.Ucd, "auxiliary/GraphemeBreakTest.txt"); - - using (var result = client.GetAsync(url).GetAwaiter().GetResult()) - { - if (!result.IsSuccessStatusCode) - return testData; - - using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) - using (var reader = new StreamReader(stream)) - { - while (!reader.EndOfStream) - { - var line = reader.ReadLine(); - - if (line == null) - { - break; - } - - if (line.StartsWith("#") || string.IsNullOrEmpty(line)) - { - continue; - } - - var elements = line.Split('#'); - - elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷'); - - var chars = elements[0].Replace(" × ", " ").Split(' '); - - var codepoints = chars.Where(x => x != "" && x != "×") - .Select(x => Convert.ToInt32(x, 16)).ToArray(); - - var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32)); - - var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum(); - - var data = new object[] { text, length }; - - testData.Add(data); - } - } - } - } - - return testData; } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs new file mode 100644 index 0000000000..058de909df --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/TestDataGenerator.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; + +namespace Avalonia.Visuals.UnitTests.Media.TextFormatting +{ + public abstract class TestDataGenerator : IEnumerable + { + private readonly string _fileName; + private readonly List _testData; + + protected TestDataGenerator(string fileName) + { + _fileName = fileName; + _testData = ReadTestData(); + } + + public IEnumerator GetEnumerator() + { + return _testData.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private List ReadTestData() + { + var testData = new List(); + + using (var client = new HttpClient()) + { + var url = Path.Combine(UnicodeDataGenerator.Ucd, _fileName); + + using (var result = client.GetAsync(url).GetAwaiter().GetResult()) + { + if (!result.IsSuccessStatusCode) + return testData; + + using (var stream = result.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) + using (var reader = new StreamReader(stream)) + { + while (!reader.EndOfStream) + { + var line = reader.ReadLine(); + + if (line == null) + { + break; + } + + if (line.StartsWith("#") || string.IsNullOrEmpty(line)) + { + continue; + } + + var elements = line.Split('#'); + + elements = elements[0].Replace("÷\t", "÷").Trim('÷').Split('÷'); + + var chars = elements[0].Replace(" × ", " ").Split(' '); + + var codepoints = chars.Where(x => x != "" && x != "×") + .Select(x => Convert.ToInt32(x, 16)).ToArray(); + + var text = string.Join(null, codepoints.Select(char.ConvertFromUtf32)); + + var length = codepoints.Select(x => x > ushort.MaxValue ? 2 : 1).Sum(); + + var data = new object[] { text, length }; + + testData.Add(data); + } + } + } + } + + return testData; + } + } +} diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs index c063cd296b..c79852e830 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs @@ -1,4 +1,6 @@ -using Xunit; +using System; +using Avalonia.Media.TextFormatting.Unicode; +using Xunit; namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { @@ -13,5 +15,26 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting { UnicodeDataGenerator.Execute(); } + [Theory/*(Skip = "Only run when we update the trie.")*/] + [ClassData(typeof(LineBreakTestDataGenerator))] + + public void Should_Enumerate_LineBreaks(string text, int expectedLength) + { + var textMemory = text.AsMemory(); + + var enumerator = new LineBreakEnumerator(textMemory); + + Assert.True(enumerator.MoveNext()); + + Assert.Equal(expectedLength, enumerator.Current.PositionWrap); + } + + private class LineBreakTestDataGenerator : TestDataGenerator + { + public LineBreakTestDataGenerator() + : base("auxiliary/LineBreakTest.txt") + { + } + } } } diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs index 413ebd645e..3a936ff3b0 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeEnumsGenerator.cs @@ -42,7 +42,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting public static List CreateGeneralCategoryEnum() { - var entries = new List { new DataEntry("Other", "C#", string.Empty) }; + var entries = new List { new DataEntry("Other", "C", " Cc | Cf | Cn | Co | Cs") }; ParseDataEntries("# General_Category (gc)", entries); From f211e3c724fd4fb9126ae6ec80aabc8cd47d8515 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 30 Jun 2020 14:08:40 +0200 Subject: [PATCH 46/47] Disable generators Remove vertical tab and form feed --- tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs | 2 -- .../TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs | 4 ++-- .../Media/TextFormatting/UnicodeDataGeneratorTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs index a2c9f8b8cd..53fef5982b 100644 --- a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs @@ -409,8 +409,6 @@ namespace Avalonia.Skia.UnitTests [InlineData("abcde\r\n", 7)] // Carriage Return + Line Feed [InlineData("abcde\n\r", 7)] // This isn't valid but we somehow have to support it. [InlineData("abcde\u000A", 6)] // Line Feed - [InlineData("abcde\u000B", 6)] // Vertical Tab - [InlineData("abcde\u000C", 6)] // Form Feed [InlineData("abcde\u000D", 6)] // Carriage Return public void Should_Break_With_BreakChar(string text, int expectedLength) { diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs index d7e2128f69..848b60b341 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/GraphemeBreakClassTrieGeneratorTests.cs @@ -10,7 +10,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting /// public class GraphemeBreakClassTrieGeneratorTests { - [Theory/*(Skip = "Only run when we update the trie.")*/] + [Theory(Skip = "Only run when we update the trie.")] [ClassData(typeof(GraphemeBreakTestDataGenerator))] public void Should_Enumerate(string text, int expectedLength) { @@ -44,7 +44,7 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting Assert.Equal(10, count); } - [Fact/*(Skip = "Only run when we update the trie.")*/] + [Fact(Skip = "Only run when we update the trie.")] public void Should_Generate_Trie() { GraphemeBreakClassTrieGenerator.Execute(); diff --git a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs index c79852e830..5c705ba0c7 100644 --- a/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Media/TextFormatting/UnicodeDataGeneratorTests.cs @@ -10,12 +10,12 @@ namespace Avalonia.Visuals.UnitTests.Media.TextFormatting /// This test is used to generate all Unicode related types. /// We only need to run this when the Unicode spec changes. /// - [Fact/*(Skip = "Only run when the Unicode spec changes.")*/] + [Fact(Skip = "Only run when the Unicode spec changes.")] public void Should_Generate_Data() { UnicodeDataGenerator.Execute(); } - [Theory/*(Skip = "Only run when we update the trie.")*/] + [Theory(Skip = "Only run when we update the trie.")] [ClassData(typeof(LineBreakTestDataGenerator))] public void Should_Enumerate_LineBreaks(string text, int expectedLength) From c9a98da01ad8b66e512112c8a2d3f8bf57788641 Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Tue, 30 Jun 2020 14:50:41 +0200 Subject: [PATCH 47/47] Fix mandatory break --- .../Media/TextFormatting/Unicode/LineBreakEnumerator.cs | 2 +- tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 79c140a957..25a32bb1a3 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -96,7 +96,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (_nextClass.Value == LineBreakClass.MandatoryBreak) { _lastPos = _pos; - Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos); + Current = new LineBreak(FindPriorNonWhitespace(_lastPos), _lastPos, true); return true; } diff --git a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs index 53fef5982b..a2c9f8b8cd 100644 --- a/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/TextLayoutTests.cs @@ -409,6 +409,8 @@ namespace Avalonia.Skia.UnitTests [InlineData("abcde\r\n", 7)] // Carriage Return + Line Feed [InlineData("abcde\n\r", 7)] // This isn't valid but we somehow have to support it. [InlineData("abcde\u000A", 6)] // Line Feed + [InlineData("abcde\u000B", 6)] // Vertical Tab + [InlineData("abcde\u000C", 6)] // Form Feed [InlineData("abcde\u000D", 6)] // Carriage Return public void Should_Break_With_BreakChar(string text, int expectedLength) {