From 9365bbefa359aa9415060cf0a63d255b426ceaa2 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 27 Jan 2023 15:52:41 -0500 Subject: [PATCH 001/120] Make AnonymousObserver public and LightweightObservableBase internal --- src/Avalonia.Base/Reactive/AnonymousObserver.cs | 7 ++++++- src/Avalonia.Base/Reactive/LightweightObservableBase.cs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Reactive/AnonymousObserver.cs b/src/Avalonia.Base/Reactive/AnonymousObserver.cs index c2e02ae879..6c458713dc 100644 --- a/src/Avalonia.Base/Reactive/AnonymousObserver.cs +++ b/src/Avalonia.Base/Reactive/AnonymousObserver.cs @@ -1,9 +1,14 @@ using System; +using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Avalonia.Reactive; -internal class AnonymousObserver : IObserver +/// +/// Class to create an instance from delegate-based implementations of the On* methods. +/// +/// The type of the elements in the sequence. +public class AnonymousObserver : IObserver { private static readonly Action ThrowsOnError = ex => throw ex; private static readonly Action NoOpCompleted = () => { }; diff --git a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs index 263109972f..cf20f20172 100644 --- a/src/Avalonia.Base/Reactive/LightweightObservableBase.cs +++ b/src/Avalonia.Base/Reactive/LightweightObservableBase.cs @@ -14,7 +14,7 @@ namespace Avalonia.Reactive /// usage. This class provides a more lightweight base for some internal observable types /// in the Avalonia framework. /// - public abstract class LightweightObservableBase : IObservable + internal abstract class LightweightObservableBase : IObservable { private Exception? _error; private List>? _observers = new List>(); From 0065a962acf3fa8738b53f8426fad9d0c3bb762e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 28 Jan 2023 13:08:24 +0100 Subject: [PATCH 002/120] Move ItemsRepeater to separate assembly. --- Avalonia.Desktop.slnf | 1 + Avalonia.sln | 13 +++++++++++ samples/ControlCatalog/ControlCatalog.csproj | 1 + .../Avalonia.Controls.ItemsRepeater.csproj | 20 +++++++++++++++++ .../Controls}/ElementFactory.cs | 0 .../Controls}/IElementFactory.cs | 0 .../Controls}/ItemTemplateWrapper.cs | 0 .../Controls}/ItemsRepeater.cs | 0 .../ItemsRepeaterElementClearingEventArgs.cs | 0 ...emsRepeaterElementIndexChangedEventArgs.cs | 0 .../ItemsRepeaterElementPreparedEventArgs.cs | 0 .../Controls}/RecyclePool.cs | 0 .../Controls}/RecyclingElementFactory.cs | 0 .../Controls}/RepeaterLayoutContext.cs | 0 .../Controls}/UniqueIdElementPool.cs | 0 .../Controls}/ViewManager.cs | 10 ++++----- .../Controls}/ViewportManager.cs | 10 ++++----- .../Controls}/VirtualizationInfo.cs | 0 .../Layout/AttachedLayout.cs | 0 .../Layout/ElementManager.cs | 0 .../Layout/FlowLayoutAlgorithm.cs | 0 .../Layout/IFlowLayoutAlgorithmDelegates.cs | 0 .../Layout/LayoutContext.cs | 0 .../Layout/LayoutContextAdapter.cs | 0 .../Layout/NonVirtualizingLayout.cs | 0 .../Layout/NonVirtualizingLayoutContext.cs | 0 .../Layout/NonVirtualizingStackLayout.cs | 0 .../Layout/OrientationBasedMeasures.cs | 0 .../Layout/StackLayout.cs | 5 +++-- .../Layout/StackLayoutState.cs | 0 .../Layout/UniformGridLayout.cs | 0 .../Layout/UniformGridLayoutState.cs | 0 .../Layout/Utils/ListUtils.cs | 0 .../Layout}/UvBounds.cs | 0 .../Layout}/UvMeasure.cs | 0 .../Layout/VirtualLayoutContextAdapter.cs | 0 .../Layout/VirtualizingLayout.cs | 0 .../Layout/VirtualizingLayoutContext.cs | 0 .../Layout}/WrapItem.cs | 0 .../Layout}/WrapLayout.cs | 0 .../Layout}/WrapLayoutState.cs | 0 .../Properties/AssemblyInfo.cs | 4 ++++ .../Avalonia.Controls.csproj | 1 + src/Avalonia.Controls/StackPanel.cs | 4 ++-- .../VirtualizingStackPanel.cs | 2 +- ...valoniaXamlIlDataContextTypeTransformer.cs | 3 +-- .../AvaloniaXamlIlWellKnownTypes.cs | 2 -- ...ia.Controls.ItemsRepeater.UnitTests.csproj | 22 +++++++++++++++++++ .../ItemsRepeaterTests.cs | 0 .../NonVirtualizingStackLayoutTests.cs | 0 .../Avalonia.LeakTests.csproj | 1 + .../Avalonia.Markup.Xaml.UnitTests.csproj | 1 + 52 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 src/Avalonia.Controls.ItemsRepeater/Avalonia.Controls.ItemsRepeater.csproj rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ElementFactory.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/IElementFactory.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ItemTemplateWrapper.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ItemsRepeater.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ItemsRepeaterElementClearingEventArgs.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ItemsRepeaterElementIndexChangedEventArgs.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ItemsRepeaterElementPreparedEventArgs.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/RecyclePool.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/RecyclingElementFactory.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/RepeaterLayoutContext.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/UniqueIdElementPool.cs (100%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ViewManager.cs (99%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/ViewportManager.cs (98%) rename src/{Avalonia.Controls/Repeater => Avalonia.Controls.ItemsRepeater/Controls}/VirtualizationInfo.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/AttachedLayout.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/ElementManager.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/FlowLayoutAlgorithm.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/IFlowLayoutAlgorithmDelegates.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/LayoutContext.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/LayoutContextAdapter.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/NonVirtualizingLayout.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/NonVirtualizingLayoutContext.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/NonVirtualizingStackLayout.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/OrientationBasedMeasures.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/StackLayout.cs (98%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/StackLayoutState.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/UniformGridLayout.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/UniformGridLayoutState.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/Utils/ListUtils.cs (100%) rename src/{Avalonia.Base/Layout/WrapLayout => Avalonia.Controls.ItemsRepeater/Layout}/UvBounds.cs (100%) rename src/{Avalonia.Base/Layout/WrapLayout => Avalonia.Controls.ItemsRepeater/Layout}/UvMeasure.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/VirtualLayoutContextAdapter.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/VirtualizingLayout.cs (100%) rename src/{Avalonia.Base => Avalonia.Controls.ItemsRepeater}/Layout/VirtualizingLayoutContext.cs (100%) rename src/{Avalonia.Base/Layout/WrapLayout => Avalonia.Controls.ItemsRepeater/Layout}/WrapItem.cs (100%) rename src/{Avalonia.Base/Layout/WrapLayout => Avalonia.Controls.ItemsRepeater/Layout}/WrapLayout.cs (100%) rename src/{Avalonia.Base/Layout/WrapLayout => Avalonia.Controls.ItemsRepeater/Layout}/WrapLayoutState.cs (100%) create mode 100644 src/Avalonia.Controls.ItemsRepeater/Properties/AssemblyInfo.cs create mode 100644 tests/Avalonia.Controls.ItemsRepeater.UnitTests/Avalonia.Controls.ItemsRepeater.UnitTests.csproj rename tests/{Avalonia.Controls.UnitTests => Avalonia.Controls.ItemsRepeater.UnitTests}/ItemsRepeaterTests.cs (100%) rename tests/{Avalonia.Base.UnitTests/Layout => Avalonia.Controls.ItemsRepeater.UnitTests}/NonVirtualizingStackLayoutTests.cs (100%) diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 2f034bd083..3acd4bf9f2 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -15,6 +15,7 @@ "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", "src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj", + "src\\Avalonia.Controls.ItemsRepeater\\Avalonia.Controls.ItemsRepeater.csproj", "src\\Avalonia.Controls\\Avalonia.Controls.csproj", "src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj", "src\\Avalonia.Desktop\\Avalonia.Desktop.csproj", diff --git a/Avalonia.sln b/Avalonia.sln index ce9a37a3ce..525e01c891 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -233,6 +233,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GpuInterop", "samples\GpuInterop\GpuInterop.csproj", "{C810060E-3809-4B74-A125-F11533AF9C1B}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater", "src\Avalonia.Controls.ItemsRepeater\Avalonia.Controls.ItemsRepeater.csproj", "{EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ItemsRepeater.UnitTests", "tests\Avalonia.Controls.ItemsRepeater.UnitTests\Avalonia.Controls.ItemsRepeater.UnitTests.csproj", "{F4E36AA8-814E-4704-BC07-291F70F45193}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -548,6 +552,14 @@ Global {C810060E-3809-4B74-A125-F11533AF9C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU {C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU {C810060E-3809-4B74-A125-F11533AF9C1B}.Release|Any CPU.Build.0 = Release|Any CPU + {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE0F0DD4-A70D-472B-BD5D-B7D32D0E9386}.Release|Any CPU.Build.0 = Release|Any CPU + {F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4E36AA8-814E-4704-BC07-291F70F45193}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4E36AA8-814E-4704-BC07-291F70F45193}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -613,6 +625,7 @@ Global {90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098} {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C810060E-3809-4B74-A125-F11533AF9C1B} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {F4E36AA8-814E-4704-BC07-291F70F45193} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index 18f0dd16ba..c223bfe1a9 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Avalonia.Controls.ItemsRepeater/Avalonia.Controls.ItemsRepeater.csproj b/src/Avalonia.Controls.ItemsRepeater/Avalonia.Controls.ItemsRepeater.csproj new file mode 100644 index 0000000000..1ec0ee33a7 --- /dev/null +++ b/src/Avalonia.Controls.ItemsRepeater/Avalonia.Controls.ItemsRepeater.csproj @@ -0,0 +1,20 @@ + + + net6.0;netstandard2.0 + Avalonia.Controls.ItemsRepeater + Avalonia + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Repeater/ElementFactory.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ElementFactory.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ElementFactory.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/IElementFactory.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/IElementFactory.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/IElementFactory.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/IElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemTemplateWrapper.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ItemTemplateWrapper.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ItemTemplateWrapper.cs diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ItemsRepeater.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeater.cs diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementClearingEventArgs.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ItemsRepeaterElementClearingEventArgs.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementClearingEventArgs.cs diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementIndexChangedEventArgs.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ItemsRepeaterElementIndexChangedEventArgs.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementIndexChangedEventArgs.cs diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementPreparedEventArgs.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/ItemsRepeaterElementPreparedEventArgs.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ItemsRepeaterElementPreparedEventArgs.cs diff --git a/src/Avalonia.Controls/Repeater/RecyclePool.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/RecyclePool.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/RecyclePool.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/RecyclePool.cs diff --git a/src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/RecyclingElementFactory.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/RecyclingElementFactory.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/RecyclingElementFactory.cs diff --git a/src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/RepeaterLayoutContext.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/RepeaterLayoutContext.cs diff --git a/src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/UniqueIdElementPool.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/UniqueIdElementPool.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/UniqueIdElementPool.cs diff --git a/src/Avalonia.Controls/Repeater/ViewManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs similarity index 99% rename from src/Avalonia.Controls/Repeater/ViewManager.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs index 2dff18cd04..2d302a95dd 100644 --- a/src/Avalonia.Controls/Repeater/ViewManager.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewManager.cs @@ -256,7 +256,7 @@ namespace Avalonia.Controls public void UpdatePin(Control element, bool addPin) { - var parent = element.VisualParent; + var parent = element.GetVisualParent(); var child = (Visual)element; while (parent != null) @@ -283,7 +283,7 @@ namespace Avalonia.Controls } child = parent; - parent = child.VisualParent; + parent = child.GetVisualParent(); } } @@ -656,7 +656,7 @@ namespace Avalonia.Controls // that handlers can walk up the tree in case they want to find their IndexPath in the // nested case. var children = repeater.Children; - if (element.VisualParent != repeater) + if (element.GetVisualParent() != repeater) { children.Add(element); } @@ -701,7 +701,7 @@ namespace Avalonia.Controls if (FocusManager.Instance?.Current is Visual child) { - var parent = child.VisualParent; + var parent = child.GetVisualParent(); var owner = _owner; // Find out if the focused element belongs to one of our direct @@ -722,7 +722,7 @@ namespace Avalonia.Controls } child = parent; - parent = child?.VisualParent; + parent = child?.GetVisualParent(); } } diff --git a/src/Avalonia.Controls/Repeater/ViewportManager.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs similarity index 98% rename from src/Avalonia.Controls/Repeater/ViewportManager.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs index 56e0cda8fe..336fb2d228 100644 --- a/src/Avalonia.Controls/Repeater/ViewportManager.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Controls/ViewportManager.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls // be a direct child of ours, or even an indirect child. We need to walk up the tree starting // from anchorElement to figure out what child of ours (if any) to use as the suggested element. var child = anchorElement; - var parent = child.VisualParent as Control; + var parent = child.GetVisualParent() as Control; while (parent != null) { @@ -78,7 +78,7 @@ namespace Avalonia.Controls } child = parent; - parent = parent.VisualParent as Control; + parent = parent.GetVisualParent() as Control; } } } @@ -369,11 +369,11 @@ namespace Avalonia.Controls private Control? GetImmediateChildOfRepeater(Control descendant) { var targetChild = descendant; - var parent = (Control?)descendant.VisualParent; + var parent = (Control?)descendant.GetVisualParent(); while (parent != null && parent != _owner) { targetChild = parent; - parent = (Control?)parent.VisualParent; + parent = (Control?)parent.GetVisualParent(); } if (parent == null) @@ -471,7 +471,7 @@ namespace Avalonia.Controls break; } - parent = parent.VisualParent; + parent = parent.GetVisualParent(); } if (!_managingViewportDisabled) diff --git a/src/Avalonia.Controls/Repeater/VirtualizationInfo.cs b/src/Avalonia.Controls.ItemsRepeater/Controls/VirtualizationInfo.cs similarity index 100% rename from src/Avalonia.Controls/Repeater/VirtualizationInfo.cs rename to src/Avalonia.Controls.ItemsRepeater/Controls/VirtualizationInfo.cs diff --git a/src/Avalonia.Base/Layout/AttachedLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/AttachedLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/AttachedLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/AttachedLayout.cs diff --git a/src/Avalonia.Base/Layout/ElementManager.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/ElementManager.cs similarity index 100% rename from src/Avalonia.Base/Layout/ElementManager.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/ElementManager.cs diff --git a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/FlowLayoutAlgorithm.cs similarity index 100% rename from src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/FlowLayoutAlgorithm.cs diff --git a/src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/IFlowLayoutAlgorithmDelegates.cs similarity index 100% rename from src/Avalonia.Base/Layout/IFlowLayoutAlgorithmDelegates.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/IFlowLayoutAlgorithmDelegates.cs diff --git a/src/Avalonia.Base/Layout/LayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/LayoutContext.cs similarity index 100% rename from src/Avalonia.Base/Layout/LayoutContext.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/LayoutContext.cs diff --git a/src/Avalonia.Base/Layout/LayoutContextAdapter.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/LayoutContextAdapter.cs similarity index 100% rename from src/Avalonia.Base/Layout/LayoutContextAdapter.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/LayoutContextAdapter.cs diff --git a/src/Avalonia.Base/Layout/NonVirtualizingLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/NonVirtualizingLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingLayout.cs diff --git a/src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingLayoutContext.cs similarity index 100% rename from src/Avalonia.Base/Layout/NonVirtualizingLayoutContext.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingLayoutContext.cs diff --git a/src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingStackLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/NonVirtualizingStackLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/NonVirtualizingStackLayout.cs diff --git a/src/Avalonia.Base/Layout/OrientationBasedMeasures.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/OrientationBasedMeasures.cs similarity index 100% rename from src/Avalonia.Base/Layout/OrientationBasedMeasures.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/OrientationBasedMeasures.cs diff --git a/src/Avalonia.Base/Layout/StackLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs similarity index 98% rename from src/Avalonia.Base/Layout/StackLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs index e9093cc146..5e2b2b8574 100644 --- a/src/Avalonia.Base/Layout/StackLayout.cs +++ b/src/Avalonia.Controls.ItemsRepeater/Layout/StackLayout.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Specialized; +using Avalonia.Controls; using Avalonia.Data; using Avalonia.Logging; @@ -25,13 +26,13 @@ namespace Avalonia.Layout /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical); + StackPanel.OrientationProperty.AddOwner(); /// /// Defines the property. /// public static readonly StyledProperty SpacingProperty = - AvaloniaProperty.Register(nameof(Spacing)); + StackPanel.SpacingProperty.AddOwner(); private readonly OrientationBasedMeasures _orientation = new OrientationBasedMeasures(); diff --git a/src/Avalonia.Base/Layout/StackLayoutState.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/StackLayoutState.cs similarity index 100% rename from src/Avalonia.Base/Layout/StackLayoutState.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/StackLayoutState.cs diff --git a/src/Avalonia.Base/Layout/UniformGridLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/UniformGridLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayout.cs diff --git a/src/Avalonia.Base/Layout/UniformGridLayoutState.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayoutState.cs similarity index 100% rename from src/Avalonia.Base/Layout/UniformGridLayoutState.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/UniformGridLayoutState.cs diff --git a/src/Avalonia.Base/Layout/Utils/ListUtils.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/Utils/ListUtils.cs similarity index 100% rename from src/Avalonia.Base/Layout/Utils/ListUtils.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/Utils/ListUtils.cs diff --git a/src/Avalonia.Base/Layout/WrapLayout/UvBounds.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/UvBounds.cs similarity index 100% rename from src/Avalonia.Base/Layout/WrapLayout/UvBounds.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/UvBounds.cs diff --git a/src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/UvMeasure.cs similarity index 100% rename from src/Avalonia.Base/Layout/WrapLayout/UvMeasure.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/UvMeasure.cs diff --git a/src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/VirtualLayoutContextAdapter.cs similarity index 100% rename from src/Avalonia.Base/Layout/VirtualLayoutContextAdapter.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/VirtualLayoutContextAdapter.cs diff --git a/src/Avalonia.Base/Layout/VirtualizingLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/VirtualizingLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/VirtualizingLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/VirtualizingLayout.cs diff --git a/src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/VirtualizingLayoutContext.cs similarity index 100% rename from src/Avalonia.Base/Layout/VirtualizingLayoutContext.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/VirtualizingLayoutContext.cs diff --git a/src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/WrapItem.cs similarity index 100% rename from src/Avalonia.Base/Layout/WrapLayout/WrapItem.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/WrapItem.cs diff --git a/src/Avalonia.Base/Layout/WrapLayout/WrapLayout.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/WrapLayout.cs similarity index 100% rename from src/Avalonia.Base/Layout/WrapLayout/WrapLayout.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/WrapLayout.cs diff --git a/src/Avalonia.Base/Layout/WrapLayout/WrapLayoutState.cs b/src/Avalonia.Controls.ItemsRepeater/Layout/WrapLayoutState.cs similarity index 100% rename from src/Avalonia.Base/Layout/WrapLayout/WrapLayoutState.cs rename to src/Avalonia.Controls.ItemsRepeater/Layout/WrapLayoutState.cs diff --git a/src/Avalonia.Controls.ItemsRepeater/Properties/AssemblyInfo.cs b/src/Avalonia.Controls.ItemsRepeater/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..d8023b0853 --- /dev/null +++ b/src/Avalonia.Controls.ItemsRepeater/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using Avalonia.Metadata; + +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")] diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 42c577041a..3195c38eef 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index aa63ac975e..9362dab553 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -22,13 +22,13 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty SpacingProperty = - StackLayout.SpacingProperty.AddOwner(); + AvaloniaProperty.Register(nameof(Spacing)); /// /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - StackLayout.OrientationProperty.AddOwner(); + AvaloniaProperty.Register(nameof(Orientation), Orientation.Vertical); /// /// Defines the property. diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs index c5276741b6..634efbd699 100644 --- a/src/Avalonia.Controls/VirtualizingStackPanel.cs +++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs @@ -23,7 +23,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - StackLayout.OrientationProperty.AddOwner(); + StackPanel.OrientationProperty.AddOwner(); /// /// Defines the property. diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index 574d46e737..f7c3089214 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -79,8 +79,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { var parentType = parentObject.Type.GetClrType(); - if (context.GetAvaloniaTypes().ItemsControl.IsDirectlyAssignableFrom(parentType) - || context.GetAvaloniaTypes().ItemsRepeater.IsDirectlyAssignableFrom(parentType)) + if (context.GetAvaloniaTypes().ItemsControl.IsDirectlyAssignableFrom(parentType)) { inferredDataContextTypeNode = InferDataContextOfPresentedItem(context, on, parentObject); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 0b61316603..8f487b5552 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -55,7 +55,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType DataTemplate { get; } public IXamlType IDataTemplate { get; } public IXamlType ItemsControl { get; } - public IXamlType ItemsRepeater { get; } public IXamlType ReflectionBindingExtension { get; } public IXamlType RelativeSource { get; } @@ -179,7 +178,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate"); ItemsControl = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsControl"); - ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater"); ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension"); RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource"); UInt = cfg.TypeSystem.GetType("System.UInt32"); diff --git a/tests/Avalonia.Controls.ItemsRepeater.UnitTests/Avalonia.Controls.ItemsRepeater.UnitTests.csproj b/tests/Avalonia.Controls.ItemsRepeater.UnitTests/Avalonia.Controls.ItemsRepeater.UnitTests.csproj new file mode 100644 index 0000000000..6f9815757e --- /dev/null +++ b/tests/Avalonia.Controls.ItemsRepeater.UnitTests/Avalonia.Controls.ItemsRepeater.UnitTests.csproj @@ -0,0 +1,22 @@ + + + net6.0 + Library + true + + + + + + + + + + + + + + + + + diff --git a/tests/Avalonia.Controls.UnitTests/ItemsRepeaterTests.cs b/tests/Avalonia.Controls.ItemsRepeater.UnitTests/ItemsRepeaterTests.cs similarity index 100% rename from tests/Avalonia.Controls.UnitTests/ItemsRepeaterTests.cs rename to tests/Avalonia.Controls.ItemsRepeater.UnitTests/ItemsRepeaterTests.cs diff --git a/tests/Avalonia.Base.UnitTests/Layout/NonVirtualizingStackLayoutTests.cs b/tests/Avalonia.Controls.ItemsRepeater.UnitTests/NonVirtualizingStackLayoutTests.cs similarity index 100% rename from tests/Avalonia.Base.UnitTests/Layout/NonVirtualizingStackLayoutTests.cs rename to tests/Avalonia.Controls.ItemsRepeater.UnitTests/NonVirtualizingStackLayoutTests.cs diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 4572f7ae7c..15815c81b6 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -11,6 +11,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index fa4957c24c..ade6010bae 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -17,6 +17,7 @@ + From 04ed8e9eef3be1603d7476e4a25da647d80a4ad7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 30 Jan 2023 10:11:49 +0100 Subject: [PATCH 003/120] fix: xml comment --- src/Avalonia.Base/Data/InstancedBinding.cs | 2 +- .../Metadata/InheritDataTypeFromItemsAttribute.cs | 4 ++-- src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs | 2 +- src/Avalonia.Controls/TreeViewItem.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index 00e5c3d8e6..c09c31632e 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -23,7 +23,7 @@ namespace Avalonia.Data /// The priority of the binding. /// /// This constructor can be used to create any type of binding and as such requires an - /// as the binding source because this is the only binding + /// as the binding source because this is the only binding /// source which can be used for all binding modes. If you wish to create an instance with /// something other than a subject, use one of the static creation methods on this class. /// diff --git a/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs b/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs index 6bb820d214..fac8cd8737 100644 --- a/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs +++ b/src/Avalonia.Base/Metadata/InheritDataTypeFromItemsAttribute.cs @@ -25,9 +25,9 @@ public sealed class InheritDataTypeFromItemsAttribute : Attribute /// The name of the property whose item type should be used on the target property. /// public string AncestorItemsProperty { get; } - + /// - /// The ancestor type to be used in a lookup for the . + /// The ancestor type to be used in a lookup for the . /// If null, the declaring type of the target property is used. /// public Type? AncestorType { get; set; } diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs index 6d30358119..112dd436de 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The point in global coordinates. /// True if the point hits the node's geometry; otherwise false. /// - /// This method does not recurse to child s, if you want + /// This method does not recurse to child s, if you want /// to hit test children they must be hit tested manually. /// bool HitTest(Point p); diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 022e1a74b1..e6b1016696 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -264,9 +264,9 @@ namespace Avalonia.Controls Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll } } - + /// - /// Invoked when the event occurs in the header. + /// Invoked when the event occurs in the header. /// protected virtual void OnHeaderDoubleTapped(TappedEventArgs e) { From f43fc66ae3386373b687a945449e5552e87177ed Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 31 Jan 2023 16:16:45 +0100 Subject: [PATCH 004/120] Fix failing integration tests on macOS. Looks like maybe macOS 13.1 changed behavior around fullscreen windows, breaking our integration tests. --- .../ElementExtensions.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index e7837a6971..b9df420270 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -118,7 +118,16 @@ namespace Avalonia.IntegrationTests.Appium Thread.Sleep(1000); var newWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")); - var newWindowTitles = newWindows.ToDictionary(x => x.Text); + + // Try to find the new window by looking for a window with a title that didn't exist before the button + // was clicked. Sometimes it seems that when a window becomes fullscreen, all other windows in the + // application lose their titles, so filter out windows with no title (this may have started happening + // with macOS 13.1?) + var newWindowTitles = newWindows + .Select(x => (x.Text, x)) + .Where(x => !string.IsNullOrEmpty(x.Text)) + .ToDictionary(x => x.Text, x => x.x); + var newWindowTitle = Assert.Single(newWindowTitles.Keys.Except(oldWindowTitles.Keys)); return Disposable.Create(() => From 6ded9c6e7fd091ca575569780c17969a67713a9d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 14:43:23 +0100 Subject: [PATCH 005/120] Update actual window state after showing window. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index f345043f61..ce82f7d83f 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -66,7 +66,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { _isModal = isDialog; WindowBaseImpl::Show(activate, isDialog); - + GetWindowState(&_actualWindowState); HideOrShowTrafficLights(); return SetWindowState(_lastWindowState); From ffcec80f80cd705b9300cdd7783937a89f312cf9 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Feb 2023 16:31:26 +0100 Subject: [PATCH 006/120] fix(GpuInterop): CS0618 member is obsolete, due to the renaming of some members of the Silk.NET.Vulkan enums --- .../VulkanDemo/VulkanBufferHelper.cs | 6 +-- .../VulkanDemo/VulkanCommandBufferPool.cs | 7 ++- .../GpuInterop/VulkanDemo/VulkanContent.cs | 50 +++++++++---------- .../GpuInterop/VulkanDemo/VulkanContext.cs | 12 ++--- samples/GpuInterop/VulkanDemo/VulkanImage.cs | 18 +++---- .../VulkanDemo/VulkanMemoryHelper.cs | 8 +-- .../GpuInterop/VulkanDemo/VulkanSwapchain.cs | 5 +- 7 files changed, 51 insertions(+), 55 deletions(-) diff --git a/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs b/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs index 949a951a36..290eb06b1e 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanBufferHelper.cs @@ -38,8 +38,8 @@ static class VulkanBufferHelper MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api, physicalDevice, memoryRequirements.MemoryTypeBits, - MemoryPropertyFlags.MemoryPropertyHostCoherentBit | - MemoryPropertyFlags.MemoryPropertyHostVisibleBit) + MemoryPropertyFlags.HostCoherentBit | + MemoryPropertyFlags.HostVisibleBit) }; api.AllocateMemory(device, memoryAllocateInfo, null, out memory).ThrowOnError(); @@ -77,4 +77,4 @@ static class VulkanBufferHelper return -1; } -} \ No newline at end of file +} diff --git a/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs index 65a05c5226..2f018171dc 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanCommandBufferPool.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Avalonia.Input; using Silk.NET.Vulkan; using SilkNetDemo; @@ -25,7 +24,7 @@ namespace Avalonia.Vulkan var commandPoolCreateInfo = new CommandPoolCreateInfo { SType = StructureType.CommandPoolCreateInfo, - Flags = CommandPoolCreateFlags.CommandPoolCreateResetCommandBufferBit, + Flags = CommandPoolCreateFlags.ResetCommandBufferBit, QueueFamilyIndex = queueFamilyIndex }; @@ -109,7 +108,7 @@ namespace Avalonia.Vulkan var fenceCreateInfo = new FenceCreateInfo() { SType = StructureType.FenceCreateInfo, - Flags = FenceCreateFlags.FenceCreateSignaledBit + Flags = FenceCreateFlags.SignaledBit }; api.CreateFence(device, fenceCreateInfo, null, out _fence); @@ -134,7 +133,7 @@ namespace Avalonia.Vulkan var beginInfo = new CommandBufferBeginInfo { SType = StructureType.CommandBufferBeginInfo, - Flags = CommandBufferUsageFlags.CommandBufferUsageOneTimeSubmitBit + Flags = CommandBufferUsageFlags.OneTimeSubmitBit }; _api.BeginCommandBuffer(InternalHandle, beginInfo); diff --git a/samples/GpuInterop/VulkanDemo/VulkanContent.cs b/samples/GpuInterop/VulkanDemo/VulkanContent.cs index b16343190a..5805604f1b 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanContent.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanContent.cs @@ -208,7 +208,7 @@ unsafe class VulkanContent : IDisposable api.CmdBindDescriptorSets(commandBufferHandle, PipelineBindPoint.Graphics, _pipelineLayout,0,1, &dset, null); - api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.ShaderStageVertexBit | ShaderStageFlags.FragmentBit, 0, + api.CmdPushConstants(commandBufferHandle, _pipelineLayout, ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit, 0, (uint)Marshal.SizeOf(), &vertexConstant); api.CmdBindVertexBuffers(commandBufferHandle, 0, 1, _vertexBuffer, 0); api.CmdBindIndexBuffer(commandBufferHandle, _indexBuffer, 0, IndexType.Uint16); @@ -237,14 +237,14 @@ unsafe class VulkanContent : IDisposable SrcSubresource = new ImageSubresourceLayers { - AspectMask = ImageAspectFlags.ImageAspectColorBit, + AspectMask = ImageAspectFlags.ColorBit, BaseArrayLayer = 0, LayerCount = 1, MipLevel = 0 }, DstSubresource = new ImageSubresourceLayers { - AspectMask = ImageAspectFlags.ImageAspectColorBit, + AspectMask = ImageAspectFlags.ColorBit, BaseArrayLayer = 0, LayerCount = 1, MipLevel = 0 @@ -326,19 +326,19 @@ unsafe class VulkanContent : IDisposable var imageCreateInfo = new ImageCreateInfo { SType = StructureType.ImageCreateInfo, - ImageType = ImageType.ImageType2D, + ImageType = ImageType.Type2D, Format = Format.D32Sfloat, Extent = new Extent3D((uint?)size.Width, (uint?)size.Height, 1), MipLevels = 1, ArrayLayers = 1, - Samples = SampleCountFlags.SampleCount1Bit, + Samples = SampleCountFlags.Count1Bit, Tiling = ImageTiling.Optimal, - Usage = ImageUsageFlags.ImageUsageDepthStencilAttachmentBit, + Usage = ImageUsageFlags.DepthStencilAttachmentBit, SharingMode = SharingMode.Exclusive, InitialLayout = ImageLayout.Undefined, - Flags = ImageCreateFlags.ImageCreateMutableFormatBit + Flags = ImageCreateFlags.CreateMutableFormatBit }; var api = _context.Api; @@ -355,7 +355,7 @@ unsafe class VulkanContent : IDisposable AllocationSize = memoryRequirements.Size, MemoryTypeIndex = (uint)FindSuitableMemoryTypeIndex(api, _context.PhysicalDevice, - memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit) + memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) }; api.AllocateMemory(device, memoryAllocateInfo, null, @@ -369,14 +369,14 @@ unsafe class VulkanContent : IDisposable ComponentSwizzle.B, ComponentSwizzle.A); - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectDepthBit, + var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.DepthBit, 0, 1, 0, 1); var imageViewCreateInfo = new ImageViewCreateInfo { SType = StructureType.ImageViewCreateInfo, Image = _depthImage, - ViewType = ImageViewType.ImageViewType2D, + ViewType = ImageViewType.Type2D, Format = Format.D32Sfloat, Components = componentMapping, SubresourceRange = subresourceRange @@ -406,7 +406,7 @@ unsafe class VulkanContent : IDisposable var colorAttachment = new AttachmentDescription() { Format = Format.R8G8B8A8Unorm, - Samples = SampleCountFlags.SampleCount1Bit, + Samples = SampleCountFlags.Count1Bit, LoadOp = AttachmentLoadOp.Clear, StoreOp = AttachmentStoreOp.Store, InitialLayout = ImageLayout.Undefined, @@ -418,7 +418,7 @@ unsafe class VulkanContent : IDisposable var depthAttachment = new AttachmentDescription() { Format = Format.D32Sfloat, - Samples = SampleCountFlags.SampleCount1Bit, + Samples = SampleCountFlags.Count1Bit, LoadOp = AttachmentLoadOp.Clear, StoreOp = AttachmentStoreOp.DontCare, InitialLayout = ImageLayout.Undefined, @@ -431,10 +431,10 @@ unsafe class VulkanContent : IDisposable { SrcSubpass = Vk.SubpassExternal, DstSubpass = 0, - SrcStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit, + SrcStageMask = PipelineStageFlags.ColorAttachmentOutputBit, SrcAccessMask = 0, - DstStageMask = PipelineStageFlags.PipelineStageColorAttachmentOutputBit, - DstAccessMask = AccessFlags.AccessColorAttachmentWriteBit + DstStageMask = PipelineStageFlags.ColorAttachmentOutputBit, + DstAccessMask = AccessFlags.ColorAttachmentWriteBit }; var colorAttachmentReference = new AttachmentReference() @@ -498,14 +498,14 @@ unsafe class VulkanContent : IDisposable var vertShaderStageInfo = new PipelineShaderStageCreateInfo() { SType = StructureType.PipelineShaderStageCreateInfo, - Stage = ShaderStageFlags.ShaderStageVertexBit, + Stage = ShaderStageFlags.VertexBit, Module = _vertShader, PName = (byte*)pname, }; var fragShaderStageInfo = new PipelineShaderStageCreateInfo() { SType = StructureType.PipelineShaderStageCreateInfo, - Stage = ShaderStageFlags.ShaderStageFragmentBit, + Stage = ShaderStageFlags.FragmentBit, Module = _fragShader, PName = (byte*)pname, }; @@ -564,7 +564,7 @@ unsafe class VulkanContent : IDisposable RasterizerDiscardEnable = false, PolygonMode = PolygonMode.Fill, LineWidth = 1, - CullMode = CullModeFlags.CullModeNone, + CullMode = CullModeFlags.None, DepthBiasEnable = false }; @@ -572,7 +572,7 @@ unsafe class VulkanContent : IDisposable { SType = StructureType.PipelineMultisampleStateCreateInfo, SampleShadingEnable = false, - RasterizationSamples = SampleCountFlags.SampleCount1Bit + RasterizationSamples = SampleCountFlags.Count1Bit }; var depthStencilCreateInfo = new PipelineDepthStencilStateCreateInfo() @@ -587,10 +587,10 @@ unsafe class VulkanContent : IDisposable var colorBlendAttachmentState = new PipelineColorBlendAttachmentState() { - ColorWriteMask = ColorComponentFlags.ColorComponentABit | - ColorComponentFlags.ColorComponentRBit | - ColorComponentFlags.ColorComponentGBit | - ColorComponentFlags.ColorComponentBBit, + ColorWriteMask = ColorComponentFlags.ABit | + ColorComponentFlags.RBit | + ColorComponentFlags.GBit | + ColorComponentFlags.BBit, BlendEnable = false }; @@ -617,14 +617,14 @@ unsafe class VulkanContent : IDisposable { Offset = 0, Size = (uint)Marshal.SizeOf(), - StageFlags = ShaderStageFlags.ShaderStageVertexBit + StageFlags = ShaderStageFlags.VertexBit }; var fragPushConstantRange = new PushConstantRange() { //Offset = vertexPushConstantRange.Size, Size = (uint)Marshal.SizeOf(), - StageFlags = ShaderStageFlags.ShaderStageFragmentBit + StageFlags = ShaderStageFlags.FragmentBit }; var layoutBindingInfo = new DescriptorSetLayoutBinding diff --git a/samples/GpuInterop/VulkanDemo/VulkanContext.cs b/samples/GpuInterop/VulkanDemo/VulkanContext.cs index 56041d6965..3fdd9695f2 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanContext.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanContext.cs @@ -86,12 +86,12 @@ public unsafe class VulkanContext : IDisposable var debugCreateInfo = new DebugUtilsMessengerCreateInfoEXT { SType = StructureType.DebugUtilsMessengerCreateInfoExt, - MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityVerboseBitExt | - DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt | - DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt, - MessageType = DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt | - DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt | - DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt, + MessageSeverity = DebugUtilsMessageSeverityFlagsEXT.VerboseBitExt | + DebugUtilsMessageSeverityFlagsEXT.WarningBitExt | + DebugUtilsMessageSeverityFlagsEXT.ErrorBitExt, + MessageType = DebugUtilsMessageTypeFlagsEXT.GeneralBitExt | + DebugUtilsMessageTypeFlagsEXT.ValidationBitExt | + DebugUtilsMessageTypeFlagsEXT.PerformanceBitExt, PfnUserCallback = new PfnDebugUtilsMessengerCallbackEXT(LogCallback), }; diff --git a/samples/GpuInterop/VulkanDemo/VulkanImage.cs b/samples/GpuInterop/VulkanDemo/VulkanImage.cs index e8854bfeb2..59b2ef7e30 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanImage.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanImage.cs @@ -54,8 +54,8 @@ public unsafe class VulkanImage : IDisposable Size = size; MipLevels = 1;//mipLevels; _imageUsageFlags = - ImageUsageFlags.ImageUsageColorAttachmentBit | ImageUsageFlags.ImageUsageTransferDstBit | - ImageUsageFlags.ImageUsageTransferSrcBit | ImageUsageFlags.ImageUsageSampledBit; + ImageUsageFlags.ColorAttachmentBit | ImageUsageFlags.TransferDstBit | + ImageUsageFlags.TransferSrcBit | ImageUsageFlags.SampledBit; //MipLevels = MipLevels != 0 ? MipLevels : (uint)Math.Floor(Math.Log(Math.Max(Size.Width, Size.Height), 2)); @@ -72,19 +72,19 @@ public unsafe class VulkanImage : IDisposable { PNext = exportable ? &externalMemoryCreateInfo : null, SType = StructureType.ImageCreateInfo, - ImageType = ImageType.ImageType2D, + ImageType = ImageType.Type2D, Format = Format, Extent = new Extent3D((uint?)Size.Width, (uint?)Size.Height, 1), MipLevels = MipLevels, ArrayLayers = 1, - Samples = SampleCountFlags.SampleCount1Bit, + Samples = SampleCountFlags.Count1Bit, Tiling = Tiling, Usage = _imageUsageFlags, SharingMode = SharingMode.Exclusive, InitialLayout = ImageLayout.Undefined, - Flags = ImageCreateFlags.ImageCreateMutableFormatBit + Flags = ImageCreateFlags.CreateMutableFormatBit }; Api @@ -128,7 +128,7 @@ public unsafe class VulkanImage : IDisposable MemoryTypeIndex = (uint)VulkanMemoryHelper.FindSuitableMemoryTypeIndex( Api, _physicalDevice, - memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.MemoryPropertyDeviceLocalBit) + memoryRequirements.MemoryTypeBits, MemoryPropertyFlags.DeviceLocalBit) }; Api.AllocateMemory(_device, memoryAllocateInfo, null, @@ -146,7 +146,7 @@ public unsafe class VulkanImage : IDisposable ComponentSwizzle.Identity, ComponentSwizzle.Identity); - AspectFlags = ImageAspectFlags.ImageAspectColorBit; + AspectFlags = ImageAspectFlags.ColorBit; var subresourceRange = new ImageSubresourceRange(AspectFlags, 0, MipLevels, 0, 1); @@ -154,7 +154,7 @@ public unsafe class VulkanImage : IDisposable { SType = StructureType.ImageViewCreateInfo, Image = InternalHandle.Value, - ViewType = ImageViewType.ImageViewType2D, + ViewType = ImageViewType.Type2D, Format = Format, Components = componentMapping, SubresourceRange = subresourceRange @@ -168,7 +168,7 @@ public unsafe class VulkanImage : IDisposable _currentLayout = ImageLayout.Undefined; - TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessNoneKhr); + TransitionLayout(ImageLayout.ColorAttachmentOptimal, AccessFlags.NoneKhr); } public int ExportFd() diff --git a/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs b/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs index f6778610dc..b7c7b9cf44 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanMemoryHelper.cs @@ -29,7 +29,7 @@ internal static class VulkanMemoryHelper AccessFlags destinationAccessMask, uint mipLevels) { - var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ImageAspectColorBit, 0, mipLevels, 0, 1); + var subresourceRange = new ImageSubresourceRange(ImageAspectFlags.ColorBit, 0, mipLevels, 0, 1); var barrier = new ImageMemoryBarrier { @@ -46,8 +46,8 @@ internal static class VulkanMemoryHelper api.CmdPipelineBarrier( commandBuffer, - PipelineStageFlags.PipelineStageAllCommandsBit, - PipelineStageFlags.PipelineStageAllCommandsBit, + PipelineStageFlags.AllCommandsBit, + PipelineStageFlags.AllCommandsBit, 0, 0, null, @@ -56,4 +56,4 @@ internal static class VulkanMemoryHelper 1, barrier); } -} \ No newline at end of file +} diff --git a/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs b/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs index 325c815ccb..fc0e98b3e0 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanSwapchain.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia; @@ -7,9 +6,7 @@ using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Rendering.Composition; using Avalonia.Vulkan; -using Metsys.Bson; using Silk.NET.Vulkan; -using SkiaSharp; namespace GpuInterop.VulkanDemo; @@ -84,7 +81,7 @@ class VulkanSwapchainImage : ISwapchainImage _image.TransitionLayout(buffer.InternalHandle, ImageLayout.Undefined, AccessFlags.None, - ImageLayout.ColorAttachmentOptimal, AccessFlags.AccessColorAttachmentReadBit); + ImageLayout.ColorAttachmentOptimal, AccessFlags.ColorAttachmentReadBit); if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) buffer.Submit(null,null,null, null, new VulkanCommandBufferPool.VulkanCommandBuffer.KeyedMutexSubmitInfo From 86dc4042ab99b2340365fa57c629c2c2dad95e8f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Feb 2023 16:52:58 +0100 Subject: [PATCH 007/120] fix: misc warning - Warning CA1815 PlatformGraphicsExternalImageProperties should override Equals Avalonia.Base (net6.0) \src\Avalonia.Base\Platform\PlatformGraphicsExternalMemory.cs 7 N/A - Warning CA1823 Unused field 'ProximityPoints' Avalonia.Controls (net6.0) src\Avalonia.Controls\Presenters\ScrollContentPresenter.cs 18 N/A - Warning CS0169 The field 'VulkanDemoControl._vkInstance' is never used GpuInterop samples\GpuInterop\VulkanDemo\VulkanDemoControl.cs 19 N/A - Warning CS0169 The field 'VulkanDemoControl._api' is never used GpuInterop samples\GpuInterop\VulkanDemo\VulkanDemoControl.cs 20 N/A - Warning CS0219 The variable 'isZooming' is assigned but its value is never used ControlCatalog (netstandard2.0) samples\ControlCatalog\Pages\GesturePage.cs 73 N/A - Warning xUnit2004 Do not use Assert.Equal() to check for boolean conditions. Avalonia.IntegrationTests.Appium tests\Avalonia.IntegrationTests.Appium\WindowTests_MacOS.cs 267 N/A --- samples/ControlCatalog/Pages/GesturePage.cs | 1 - samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs | 12 ------------ .../Platform/PlatformGraphicsExternalMemory.cs | 6 +----- .../Presenters/ScrollContentPresenter.cs | 1 - .../WindowTests_MacOS.cs | 2 +- 5 files changed, 2 insertions(+), 20 deletions(-) diff --git a/samples/ControlCatalog/Pages/GesturePage.cs b/samples/ControlCatalog/Pages/GesturePage.cs index 0bb8f38219..cc4429f414 100644 --- a/samples/ControlCatalog/Pages/GesturePage.cs +++ b/samples/ControlCatalog/Pages/GesturePage.cs @@ -70,7 +70,6 @@ namespace ControlCatalog.Pages _currentScale = 1; Vector3 currentOffset = default; - bool isZooming = false; CompositionVisual? compositionVisual = null; diff --git a/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs b/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs index 6a1cd641b3..962b4e433a 100644 --- a/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs +++ b/samples/GpuInterop/VulkanDemo/VulkanDemoControl.cs @@ -1,24 +1,12 @@ using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; using System.Threading.Tasks; using Avalonia; -using Avalonia.Platform; using Avalonia.Rendering.Composition; -using Silk.NET.Core; -using Silk.NET.Vulkan; -using Silk.NET.Vulkan.Extensions.KHR; -using SilkNetDemo; namespace GpuInterop.VulkanDemo; public class VulkanDemoControl : DrawingSurfaceDemoBase { - private Instance _vkInstance; - private Vk _api; - class VulkanResources : IAsyncDisposable { public VulkanContext Context { get; } diff --git a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs b/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs index cad4ab2051..4b47c93eb5 100644 --- a/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs +++ b/src/Avalonia.Base/Platform/PlatformGraphicsExternalMemory.cs @@ -1,10 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace Avalonia.Platform; -public struct PlatformGraphicsExternalImageProperties +public record struct PlatformGraphicsExternalImageProperties { public int Width { get; set; } public int Height { get; set; } diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 762702efcc..454f7eac9d 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -15,7 +15,6 @@ namespace Avalonia.Controls.Presenters public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable, IScrollAnchorProvider { private const double EdgeDetectionTolerance = 0.1; - private const int ProximityPoints = 10; /// /// Defines the property. diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 6c61a85561..d9817ecdd1 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -264,7 +264,7 @@ namespace Avalonia.IntegrationTests.Appium var secondaryWindow = GetWindow("SecondaryWindow"); var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons(); - Assert.Equal(false, miniaturizeButton.Enabled); + Assert.False(miniaturizeButton.Enabled); } } From 6d49ffc95550d29f5c64f874c9163d4c86559016 Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Wed, 1 Feb 2023 17:33:16 +0100 Subject: [PATCH 008/120] Added tests for binding value types to null --- .../Data/BindingTests.cs | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 3ba8e8354d..c312a71d44 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -648,16 +648,69 @@ namespace Avalonia.Markup.UnitTests.Data }; } + [Fact] + public void Binding_Producing_Default_Value_Should_Result_In_Correct_Priority() + { + var defaultValue = StyledPropertyClass.NullableDoubleProperty.GetDefaultValue(typeof(StyledPropertyClass)); + + var vm = new NullableValuesViewModel() { NullableDouble = defaultValue }; + var target = new StyledPropertyClass(); + + target.Bind(StyledPropertyClass.NullableDoubleProperty, new Binding(nameof(NullableValuesViewModel.NullableDouble)) { Source = vm }); + + Assert.Equal(BindingPriority.LocalValue, target.GetDiagnosticInternal(StyledPropertyClass.NullableDoubleProperty).Priority); + Assert.Equal(defaultValue, target.GetValue(StyledPropertyClass.NullableDoubleProperty)); + } + + [Fact] + public void Binding_Non_Nullable_ValueType_To_Null_Reverts_To_Default_Value() + { + var source = new NullableValuesViewModel { NullableDouble = 42 }; + var target = new StyledPropertyClass(); + var binding = new Binding(nameof(source.NullableDouble)) { Source = source }; + + target.Bind(StyledPropertyClass.DoubleValueProperty, binding); + Assert.Equal(42, target.DoubleValue); + + source.NullableDouble = null; + + Assert.Equal(12.3, target.DoubleValue); + } + + [Fact] + public void Binding_Nullable_ValueType_To_Null_Sets_Value_To_Null() + { + var source = new NullableValuesViewModel { NullableDouble = 42 }; + var target = new StyledPropertyClass(); + var binding = new Binding(nameof(source.NullableDouble)) { Source = source }; + + target.Bind(StyledPropertyClass.NullableDoubleProperty, binding); + Assert.Equal(42, target.NullableDouble); + + source.NullableDouble = null; + + Assert.Null(target.NullableDouble); + } + private class StyledPropertyClass : AvaloniaObject { public static readonly StyledProperty DoubleValueProperty = - AvaloniaProperty.Register(nameof(DoubleValue)); + AvaloniaProperty.Register(nameof(DoubleValue), 12.3); public double DoubleValue { get { return GetValue(DoubleValueProperty); } set { SetValue(DoubleValueProperty, value); } } + + public static StyledProperty NullableDoubleProperty = + AvaloniaProperty.Register(nameof(NullableDoubleProperty), -1); + + public double? NullableDouble + { + get => GetValue(NullableDoubleProperty); + set => SetValue(NullableDoubleProperty, value); + } } private class DirectPropertyClass : AvaloniaObject @@ -676,6 +729,21 @@ namespace Avalonia.Markup.UnitTests.Data } } + private class NullableValuesViewModel : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private double? _nullableDouble; + public double? NullableDouble + { + get => _nullableDouble; set + { + _nullableDouble = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(NullableDouble))); + } + } + } + private class TestStackOverflowViewModel : INotifyPropertyChanged { public int SetterInvokedCount { get; private set; } From 796722f31944df2f62c2f56bffeec5ad6e39d32a Mon Sep 17 00:00:00 2001 From: Tom Edwards Date: Sat, 21 Jan 2023 16:17:10 +0100 Subject: [PATCH 009/120] Never convert null to UnsetValue in bindings Revert ToggleButton.IsChecked hack --- .../Data/Converters/DefaultValueConverter.cs | 2 +- .../Primitives/ToggleButton.cs | 2 +- .../Data/Core/BindingExpressionTests.cs | 25 ------------------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs index f5c135459d..aeb71d16ae 100644 --- a/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs +++ b/src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs @@ -30,7 +30,7 @@ namespace Avalonia.Data.Converters { if (value == null) { - return targetType.IsValueType ? AvaloniaProperty.UnsetValue : null; + return null; } if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1) diff --git a/src/Avalonia.Controls/Primitives/ToggleButton.cs b/src/Avalonia.Controls/Primitives/ToggleButton.cs index dfb436a55e..158c5d875b 100644 --- a/src/Avalonia.Controls/Primitives/ToggleButton.cs +++ b/src/Avalonia.Controls/Primitives/ToggleButton.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls.Primitives nameof(IsChecked), o => o.IsChecked, (o, v) => o.IsChecked = v, - unsetValue: null, + unsetValue: false, defaultBindingMode: BindingMode.TwoWay); /// diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs index 339cf8a334..924e844ec5 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/BindingExpressionTests.cs @@ -78,18 +78,6 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } - [Fact] - public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue() - { - var data = new Class1 { StringValue = null }; - var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double)); - var result = await target.Take(1); - - Assert.Equal(AvaloniaProperty.UnsetValue, result); - - GC.KeepAlive(data); - } - [Fact] public void Should_Convert_Set_String_To_Double() { @@ -249,19 +237,6 @@ namespace Avalonia.Base.UnitTests.Data.Core GC.KeepAlive(data); } - [Fact] - public void Should_Coerce_Setting_Null_Double_To_Default_Value() - { - var data = new Class1 { DoubleValue = 5.6 }; - var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string)); - - target.OnNext(null); - - Assert.Equal(0, data.DoubleValue); - - GC.KeepAlive(data); - } - [Fact] public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value() { From f2c3805d6a0086ce27ac9ecb55680def9f31b9ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:08:13 +0100 Subject: [PATCH 010/120] Update ncrunch config. --- .ncrunch/Avalonia.UnitTests.v3.ncrunchproject | 5 +++++ .ncrunch/GpuInterop.v3.ncrunchproject | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .ncrunch/Avalonia.UnitTests.v3.ncrunchproject create mode 100644 .ncrunch/GpuInterop.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..cff5044edf --- /dev/null +++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/GpuInterop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From 1e926dac72b4c666d4c213cd72693d4cbf551e27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:19:40 +0100 Subject: [PATCH 011/120] Added failing tests for #10110. --- .../AvaloniaObjectTests_Binding.cs | 18 +++++++++++++ .../Xaml/ResourceDictionaryTests.cs | 25 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 030d6ba215..f0386615c5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1285,6 +1285,24 @@ namespace Avalonia.Base.UnitTests subscription.Dispose(); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Binding_Producing_UnsetValue_Does_Not_Cause_Unsubscribe(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + + source.OnNext("foo"); + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + source.OnNext(BindingValue.Unset); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); + source.OnNext("bar"); + Assert.Equal("bar", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 6cab83751f..bea6bc4dc8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -307,7 +307,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(buttonResources.ContainsDeferredKey("Red")); } } - + + [Fact] + public void Dynamically_Changing_Referenced_Resources_Works_With_DynamicResource() + { + var xaml = @" + + + Red + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Remove("color"); + Assert.Equal(default, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Add("color", Colors.Blue); + Assert.Equal(Colors.Blue, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From a22249b898aa621679bc79ea7fb1406a74dd0942 Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Thu, 2 Feb 2023 09:45:36 +0100 Subject: [PATCH 012/120] fix: remarks --- src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs index 112dd436de..2bfd2080c3 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/IDrawOperation.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The point in global coordinates. /// True if the point hits the node's geometry; otherwise false. /// - /// This method does not recurse to child s, if you want + /// This method does not recurse to childs, if you want /// to hit test children they must be hit tested manually. /// bool HitTest(Point p); From b0a2ae99e44151f58ef6e77ca731f78bd5b3d660 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 2 Feb 2023 10:37:20 +0100 Subject: [PATCH 013/120] Fix default GlyphRun.BaselineOrigin Add a unit test --- src/Avalonia.Base/Media/GlyphRun.cs | 2 +- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 2 +- .../Controls/TextBlockTests.cs | 51 ++++++++++++++++++ .../Should_Draw_TextDecorations.expected.png | Bin 0 -> 1279 bytes .../Should_Draw_TextDecorations.expected.png | Bin 0 -> 1516 bytes 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_TextDecorations.expected.png create mode 100644 tests/TestFiles/Skia/Controls/TextBlock/Should_Draw_TextDecorations.expected.png diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 0ec7152359..2966ceee8d 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -166,7 +166,7 @@ namespace Avalonia.Media /// public Point BaselineOrigin { - get => _baselineOrigin ?? default; + get => PlatformImpl.Item.BaselineOrigin; set => Set(ref _baselineOrigin, value); } diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index d12db39ad6..e795f3d304 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -86,7 +86,7 @@ namespace Avalonia.Skia SKPath path = new SKPath(); - var (currentX, currentY) = glyphRun.PlatformImpl.Item.BaselineOrigin; + var (currentX, currentY) = glyphRun.BaselineOrigin; for (var i = 0; i < glyphRun.GlyphInfos.Count; i++) { diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index eb3f9911df..99c01dd111 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -257,7 +257,7 @@ namespace Avalonia.Direct2D1 sink.Close(); } - var (baselineOriginX, baselineOriginY) = glyphRun.PlatformImpl.Item.BaselineOrigin; + var (baselineOriginX, baselineOriginY) = glyphRun.BaselineOrigin; var transformedGeometry = new SharpDX.Direct2D1.TransformedGeometry( Direct2D1Factory, diff --git a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs index c11bd2b816..4210ee8238 100644 --- a/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs +++ b/tests/Avalonia.RenderTests/Controls/TextBlockTests.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Layout; @@ -17,6 +18,56 @@ namespace Avalonia.Direct2D1.RenderTests.Controls { } + [Win32Fact("Has text")] + public async Task Should_Draw_TextDecorations() + { + Border target = new Border + { + Padding = new Thickness(8), + Width = 200, + Height = 30, + Background = Brushes.White, + Child = new TextBlock + { + FontFamily = TestFontFamily, + FontSize = 12, + Foreground = Brushes.Black, + Text = "Neque porro quisquam est qui dolorem", + VerticalAlignment = VerticalAlignment.Top, + TextWrapping = TextWrapping.NoWrap, + TextDecorations = new TextDecorationCollection + { + new TextDecoration + { + Location = TextDecorationLocation.Overline, + StrokeThickness= 1.5, + StrokeThicknessUnit = TextDecorationUnit.Pixel, + Stroke = new SolidColorBrush(Colors.Red) + }, + new TextDecoration + { + Location = TextDecorationLocation.Baseline, + StrokeThickness= 1.5, + StrokeThicknessUnit = TextDecorationUnit.Pixel, + Stroke = new SolidColorBrush(Colors.Green) + }, + new TextDecoration + { + Location = TextDecorationLocation.Underline, + StrokeThickness= 1.5, + StrokeThicknessUnit = TextDecorationUnit.Pixel, + Stroke = new SolidColorBrush(Colors.Blue), + StrokeOffset = 2, + StrokeOffsetUnit = TextDecorationUnit.Pixel + } + } + } + }; + + await RenderToFile(target); + CompareImages(); + } + [Win32Fact("Has text")] public async Task Wrapping_NoWrap() { diff --git a/tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_TextDecorations.expected.png b/tests/TestFiles/Direct2D1/Controls/TextBlock/Should_Draw_TextDecorations.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..494c8a900252641099b21c89d680145adde9046f GIT binary patch literal 1279 zcmVPx#1ZP1_K>z@;j|==^1poj9sYygZRCr$Pnma6QK@^6ELPRvT5|Kza3WZ8UL_9*` z*;1fVNoa0IqtqPH+=fJ@aunp2UWIr>M3f2=uSTJ|2_h=4f9|Yp8T-thvmN(!)|X87 znpt~ZYu1{X?Z|t6K?#BoO1^mtf)IiPh#)wS01*TS5+H)$KmtS%97uo&f&&QMmwmhfq^qk-KRd$d&`nQI>xc)C`MbC#;9Y{Qc9eXt5oiN#Q$6aBh#D*Jm+K*O9Um(; z4k|~=CYe!LzMZJ2lmb*Vj?lkbK~b&>d-0oNhX zT+t`;=jZ1tkw|FSUh8!5b&SF*K+ew2)WE=inwy(5C9hym2p$~uNR^e9fsvu>?CcD5 z^Yin%JPH_=w+SOOH#Zw?G*?$wL0#-HpH> zd3o7r)8q0V3I^UUT_mHz`vd`)w7omR7;p%RNCXia6u?PZ zT3WOWMn*=AP3)5cAXvB!FjimS%(NzUV3n7b2R7igh{EMR2nLK7<@PBV7xE$B z@3xErq`$vkO-@dl+$$7~lzb!E1-^TDn@E`$K9|Ab!(MPcIG25Gi39EGv=(8TMO<9| zg8)GpHZIJ?cazK(83hO&6jy@v^>tICbbfJh(d6EO_i6VAe7DAq!;gO6Mtyy~?jx=- z>^^Ci##s0F_q7brC(1qooDruTA1ii2zDuVgu6=w#`*1G4UN}{_4fwSg@6+xLTvtAq zE)!h-!@+eIryXt=9|3a&z9&-T4~4z+y}Gf!zCNA5xw%oRtE;NDwN+JAROqtJ%}sTE zeXV8C)6=6$N=mf6va+J??(Vc)U0to84Gj(Y$#r#gb?Wr=^j$whSy`Ft=;$!H^q@_= zdvbE3^XQZM1AMG@o2Xk`TN})agL?%?1+_lrL84!$XlkB{|xVfL~~=RcR1muh=^ z+mwd;0QxyPI?^)c!q++-+_#>;yT3ys1LRB3HzR$V$HzzY^z@{Pii%80_PM>iRfUCx zs-U32l>9sV4*S?VJUpoU{CsV*T;#{h#;gzsh^u4Ek3jSWm^CM002ovPDHLkV1l~1QeprA literal 0 HcmV?d00001 diff --git a/tests/TestFiles/Skia/Controls/TextBlock/Should_Draw_TextDecorations.expected.png b/tests/TestFiles/Skia/Controls/TextBlock/Should_Draw_TextDecorations.expected.png new file mode 100644 index 0000000000000000000000000000000000000000..297bd592ff27631f9c0c5738da922f49e7f3dd25 GIT binary patch literal 1516 zcmVR9JKak1zRd8c^6@WOQo{X5km70J(RM=3R0%aLc%IoM2ZM99+FHZ z$z(FwCf#j$AM9cB&-?%X{qOz%dv9ho<@>&mLZOIc$5uO_P;6&Zf+!R*s02|cVo(X9 zP{g1TM4^a5C5S>1gGvyEB8F!M-f`r}V{k*E*dnf9j}@oXi$aB>KZCbebwNIr!jZub zhx74N9bG!AQYp3_ z(zV8_KVgDs%oTNn8=WE2Ns&r5|MG%*ADJTr`KG)s^5nxAC!Nl=&8=O~Her>JP97ng zME-QjBu>gf%23;oQR$>`q{In)a%k^5H|Oi4#HTLu&2|x6tS)~#``Oqc2W^ED^fT6G zx6V-irIW{T8vf*kXvQe{)-UP&>4R+T2p3t1`bE7%vz2u*0-uWLfv*Cy7WOsSa!ulntNESa=+ zVbaM^&|5LM2qIwxPMa+GZJkxy!U;NAMrs%FYb7Xn7`{)fMBjoMCT{*v7dWB#_`Xjt zBzh6wuTjc@UG42>ekP-OVr2qU$V=gsUS9iE=+o%}wO}dVu7bpbPqfvpZK2Nwb zEk{|nK^5yhPz9BuTc#V>WkG@>97$NO=atE6TBNUaS5UT%-c?~3dVH0*Ru0n`oXwk# zPg2q*OMVqQSS8bFI-wXeD){g=C8IeT$IqWIdNKL7E#9Ptjz!XkvIAcf&Wj_g+<@t$ z2iXnq@E$9|W#92ryuPn-&&KV`EZ=OmITR)P5$um_EU8gjGWo)t=jw&aw3;0<3^wld z+F5HUois38vIwSMm}dUXd8CuV#m4&7g4ckVc@_@u5AIv9EOP0m=EcjioHM7GXh^6m zR@igSWNM;U|9WMS%ARwWQNq7h5ubg>ug%Mu`2|8IV+s6`U@x@e^X1PUu>AQ~ed4ph z?aRyD?9eocVIJk+?uXo45x8q@Nq9VzSqjyW~KdSww$Tj22ibt>WkZdT+t--f# S6vVXv0000 Date: Thu, 2 Feb 2023 10:06:57 +0000 Subject: [PATCH 014/120] fix scroll snap points attached properties --- src/Avalonia.Controls/ScrollViewer.cs | 88 +++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index 1c23919d0e..ab114da933 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -154,15 +154,15 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty HorizontalSnapPointsTypeProperty = - AvaloniaProperty.Register( + public static readonly AttachedProperty HorizontalSnapPointsTypeProperty = + AvaloniaProperty.RegisterAttached( nameof(HorizontalSnapPointsType)); /// /// Defines the property. /// - public static readonly StyledProperty VerticalSnapPointsTypeProperty = - AvaloniaProperty.Register( + public static readonly AttachedProperty VerticalSnapPointsTypeProperty = + AvaloniaProperty.RegisterAttached( nameof(VerticalSnapPointsType)); /// @@ -625,6 +625,86 @@ namespace Avalonia.Controls control.SetValue(HorizontalScrollBarVisibilityProperty, value); } + /// + /// Gets the value of the HorizontalSnapPointsType attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static SnapPointsType GetHorizontalSnapPointsType(Control control) + { + return control.GetValue(HorizontalSnapPointsTypeProperty); + } + + /// + /// Gets the value of the HorizontalSnapPointsType attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetHorizontalSnapPointsType(Control control, SnapPointsType value) + { + control.SetValue(HorizontalSnapPointsTypeProperty, value); + } + + /// + /// Gets the value of the VerticalSnapPointsType attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static SnapPointsType GetVerticalSnapPointsType(Control control) + { + return control.GetValue(VerticalSnapPointsTypeProperty); + } + + /// + /// Gets the value of the VerticalSnapPointsType attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetVerticalSnapPointsType(Control control, SnapPointsType value) + { + control.SetValue(VerticalSnapPointsTypeProperty, value); + } + + /// + /// Gets the value of the HorizontalSnapPointsAlignment attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static SnapPointsAlignment GetHorizontalSnapPointsAlignment(Control control) + { + return control.GetValue(HorizontalSnapPointsAlignmentProperty); + } + + /// + /// Gets the value of the HorizontalSnapPointsAlignment attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetHorizontalSnapPointsAlignment(Control control, SnapPointsAlignment value) + { + control.SetValue(HorizontalSnapPointsAlignmentProperty, value); + } + + /// + /// Gets the value of the VerticalSnapPointsAlignment attached property. + /// + /// The control to read the value from. + /// The value of the property. + public static SnapPointsAlignment GetVerticalSnapPointsAlignment(Control control) + { + return control.GetValue(VerticalSnapPointsAlignmentProperty); + } + + /// + /// Gets the value of the VerticalSnapPointsAlignment attached property. + /// + /// The control to set the value on. + /// The value of the property. + public static void SetVerticalSnapPointsAlignment(Control control, SnapPointsAlignment value) + { + control.SetValue(VerticalSnapPointsAlignmentProperty, value); + } + /// /// Gets the value of the VerticalScrollBarVisibility attached property. /// From cd90a9c0c239c4a67ae8dd348358f52b56658084 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 2 Feb 2023 14:21:22 +0100 Subject: [PATCH 015/120] feat(HamburgerMenu): Auto close on SelectedItem Changed when HamburgerMenu is display as Overlay --- samples/ControlCatalog/MainView.xaml | 4 ++-- .../SampleControls/HamburgerMenu/HamburgerMenu.cs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 0695d9d17a..3681298a72 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -14,8 +14,8 @@ - - + + diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs b/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs index ab61dcde91..7ff8160720 100644 --- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs +++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.cs @@ -52,6 +52,14 @@ namespace ControlSamples var (oldBounds, newBounds) = change.GetOldAndNewValue(); EnsureSplitViewMode(oldBounds, newBounds); } + + if (change.Property == SelectedItemProperty) + { + if (_splitView is not null && _splitView.DisplayMode == SplitViewDisplayMode.Overlay) + { + _splitView.SetValue(SplitView.IsPaneOpenProperty, false, Avalonia.Data.BindingPriority.Animation); + } + } } private void EnsureSplitViewMode(Rect oldBounds, Rect newBounds) @@ -60,12 +68,12 @@ namespace ControlSamples { var threshold = ExpandedModeThresholdWidth; - if (newBounds.Width >= threshold && oldBounds.Width < threshold) + if (newBounds.Width >= threshold) { _splitView.DisplayMode = SplitViewDisplayMode.Inline; _splitView.IsPaneOpen = true; } - else if (newBounds.Width < threshold && oldBounds.Width >= threshold) + else if (newBounds.Width < threshold) { _splitView.DisplayMode = SplitViewDisplayMode.Overlay; _splitView.IsPaneOpen = false; From 69b3db42a2f25e0dd878918911ec136565bbd7e2 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Thu, 2 Feb 2023 13:20:17 +0300 Subject: [PATCH 016/120] Cleanup attributes Enable rules CA1018: Mark attributes with AttributeUsageAttribute; CA1813: Avoid unsealed attributes --- .editorconfig | 4 ++++ src/Avalonia.Base/Metadata/AmbientAttribute.cs | 4 ++-- src/Avalonia.Base/Metadata/ContentAttribute.cs | 2 +- src/Avalonia.Base/Metadata/DataTypeAttribute.cs | 4 ++-- src/Avalonia.Base/Metadata/DependsOnAttribute.cs | 2 +- .../Metadata/NotClientImplementableAttribute.cs | 2 +- src/Avalonia.Base/Metadata/TemplateContent.cs | 2 +- .../Metadata/TrimSurroundingWhitespaceAttribute.cs | 2 +- src/Avalonia.Base/Metadata/UnstableAttribute.cs | 3 ++- .../Metadata/UsableDuringInitializationAttribute.cs | 4 ++-- .../WhitespaceSignificantCollectionAttribute.cs | 2 +- .../Metadata/XmlnsDefinitionAttribute.cs | 2 +- .../Rendering/Composition/Expressions/Expression.cs | 3 ++- .../Platform/ExportAvaloniaModuleAttribute.cs | 2 +- src/Avalonia.Controls/ResolveByNameAttribute.cs | 3 ++- src/Avalonia.OpenGL/GlEntryPointAttribute.cs | 4 ++-- .../AvaloniaRemoteMessageGuidAttribute.cs | 2 +- src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs | 3 ++- src/Shared/ModuleInitializer.cs | 3 ++- src/Shared/SourceGeneratorAttributes.cs | 11 ++++++++--- 20 files changed, 39 insertions(+), 25 deletions(-) diff --git a/.editorconfig b/.editorconfig index eac5870f96..e6ae266849 100644 --- a/.editorconfig +++ b/.editorconfig @@ -145,10 +145,14 @@ dotnet_diagnostic.CS1591.severity = suggestion # CS0162: Remove unreachable code dotnet_diagnostic.CS0162.severity = error +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = error # CA1304: Specify CultureInfo dotnet_diagnostic.CA1304.severity = warning # CA1802: Use literals where appropriate dotnet_diagnostic.CA1802.severity = warning +# CA1813: Avoid unsealed attributes +dotnet_diagnostic.CA1813.severity = error # CA1815: Override equals and operator equals on value types dotnet_diagnostic.CA1815.severity = warning # CA1820: Test for empty strings using string length diff --git a/src/Avalonia.Base/Metadata/AmbientAttribute.cs b/src/Avalonia.Base/Metadata/AmbientAttribute.cs index 85ca6c4ec9..1c85a67641 100644 --- a/src/Avalonia.Base/Metadata/AmbientAttribute.cs +++ b/src/Avalonia.Base/Metadata/AmbientAttribute.cs @@ -3,10 +3,10 @@ using System; namespace Avalonia.Metadata { /// - /// Defines the ambient class/property + /// Defines the ambient class/property /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, Inherited = true)] - public class AmbientAttribute : Attribute + public sealed class AmbientAttribute : Attribute { } } diff --git a/src/Avalonia.Base/Metadata/ContentAttribute.cs b/src/Avalonia.Base/Metadata/ContentAttribute.cs index a0b2fa0e1d..f32c8e78f6 100644 --- a/src/Avalonia.Base/Metadata/ContentAttribute.cs +++ b/src/Avalonia.Base/Metadata/ContentAttribute.cs @@ -6,7 +6,7 @@ namespace Avalonia.Metadata /// Defines the property that contains the object's content in markup. /// [AttributeUsage(AttributeTargets.Property)] - public class ContentAttribute : Attribute + public sealed class ContentAttribute : Attribute { } } diff --git a/src/Avalonia.Base/Metadata/DataTypeAttribute.cs b/src/Avalonia.Base/Metadata/DataTypeAttribute.cs index ac46a0d30a..dd9603b4a9 100644 --- a/src/Avalonia.Base/Metadata/DataTypeAttribute.cs +++ b/src/Avalonia.Base/Metadata/DataTypeAttribute.cs @@ -9,7 +9,7 @@ namespace Avalonia.Metadata; /// Used on DataTemplate.DataType property so it can be inherited in compiled bindings inside of the template. /// [AttributeUsage(AttributeTargets.Property)] -public class DataTypeAttribute : Attribute +public sealed class DataTypeAttribute : Attribute { - + } diff --git a/src/Avalonia.Base/Metadata/DependsOnAttribute.cs b/src/Avalonia.Base/Metadata/DependsOnAttribute.cs index caee71ebfd..ca58a91eb9 100644 --- a/src/Avalonia.Base/Metadata/DependsOnAttribute.cs +++ b/src/Avalonia.Base/Metadata/DependsOnAttribute.cs @@ -6,7 +6,7 @@ namespace Avalonia.Metadata /// Indicates that the property depends on the value of another property in markup. /// [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] - public class DependsOnAttribute : Attribute + public sealed class DependsOnAttribute : Attribute { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs b/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs index 348c983c03..75fe7b8031 100644 --- a/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs +++ b/src/Avalonia.Base/Metadata/NotClientImplementableAttribute.cs @@ -11,7 +11,7 @@ namespace Avalonia.Metadata /// may be added to its API. /// [AttributeUsage(AttributeTargets.Interface)] - public class NotClientImplementableAttribute : Attribute + public sealed class NotClientImplementableAttribute : Attribute { } } diff --git a/src/Avalonia.Base/Metadata/TemplateContent.cs b/src/Avalonia.Base/Metadata/TemplateContent.cs index 258154aba4..78bcc2ff29 100644 --- a/src/Avalonia.Base/Metadata/TemplateContent.cs +++ b/src/Avalonia.Base/Metadata/TemplateContent.cs @@ -6,7 +6,7 @@ namespace Avalonia.Metadata /// Defines the property that contains the object's content in markup. /// [AttributeUsage(AttributeTargets.Property)] - public class TemplateContentAttribute : Attribute + public sealed class TemplateContentAttribute : Attribute { public Type? TemplateResultType { get; set; } } diff --git a/src/Avalonia.Base/Metadata/TrimSurroundingWhitespaceAttribute.cs b/src/Avalonia.Base/Metadata/TrimSurroundingWhitespaceAttribute.cs index c46891b3ad..a644c9afe6 100644 --- a/src/Avalonia.Base/Metadata/TrimSurroundingWhitespaceAttribute.cs +++ b/src/Avalonia.Base/Metadata/TrimSurroundingWhitespaceAttribute.cs @@ -3,7 +3,7 @@ namespace Avalonia.Metadata { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class TrimSurroundingWhitespaceAttribute : Attribute + public sealed class TrimSurroundingWhitespaceAttribute : Attribute { } diff --git a/src/Avalonia.Base/Metadata/UnstableAttribute.cs b/src/Avalonia.Base/Metadata/UnstableAttribute.cs index 3b6fa5168a..361f6d30fd 100644 --- a/src/Avalonia.Base/Metadata/UnstableAttribute.cs +++ b/src/Avalonia.Base/Metadata/UnstableAttribute.cs @@ -6,7 +6,8 @@ namespace Avalonia.Metadata /// This API is unstable and is not covered by API compatibility guarantees between minor and /// patch releases. /// - public class UnstableAttribute : Attribute + [AttributeUsage(AttributeTargets.All)] + public sealed class UnstableAttribute : Attribute { } } diff --git a/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs index 753a96b9ce..d2d163b368 100644 --- a/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs +++ b/src/Avalonia.Base/Metadata/UsableDuringInitializationAttribute.cs @@ -3,8 +3,8 @@ using System; namespace Avalonia.Metadata { [AttributeUsage(AttributeTargets.Class)] - public class UsableDuringInitializationAttribute : Attribute + public sealed class UsableDuringInitializationAttribute : Attribute { - + } } diff --git a/src/Avalonia.Base/Metadata/WhitespaceSignificantCollectionAttribute.cs b/src/Avalonia.Base/Metadata/WhitespaceSignificantCollectionAttribute.cs index aeaa38dad9..2fd2b1da3b 100644 --- a/src/Avalonia.Base/Metadata/WhitespaceSignificantCollectionAttribute.cs +++ b/src/Avalonia.Base/Metadata/WhitespaceSignificantCollectionAttribute.cs @@ -6,7 +6,7 @@ namespace Avalonia.Metadata /// Indicates that a collection type should be processed as being whitespace significant by a XAML processor. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public class WhitespaceSignificantCollectionAttribute : Attribute + public sealed class WhitespaceSignificantCollectionAttribute : Attribute { } } diff --git a/src/Avalonia.Base/Metadata/XmlnsDefinitionAttribute.cs b/src/Avalonia.Base/Metadata/XmlnsDefinitionAttribute.cs index d43fa55f5c..c6b79ba987 100644 --- a/src/Avalonia.Base/Metadata/XmlnsDefinitionAttribute.cs +++ b/src/Avalonia.Base/Metadata/XmlnsDefinitionAttribute.cs @@ -6,7 +6,7 @@ namespace Avalonia.Metadata /// Maps an XML namespace to a CLR namespace for use in XAML. /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class XmlnsDefinitionAttribute : Attribute + public sealed class XmlnsDefinitionAttribute : Attribute { /// /// Initializes a new instance of the class. diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index ff2069e71e..560ee05c10 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -39,7 +39,8 @@ namespace Avalonia.Rendering.Composition.Expressions } } - internal class PrettyPrintStringAttribute : Attribute + [AttributeUsage(AttributeTargets.Field)] + internal sealed class PrettyPrintStringAttribute : Attribute { public string Name { get; } diff --git a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs b/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs index 5a34c5c0e1..f271abb59a 100644 --- a/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs +++ b/src/Avalonia.Controls/Platform/ExportAvaloniaModuleAttribute.cs @@ -41,7 +41,7 @@ namespace Avalonia.Platform /// The fallback module will only be initialized if the Skia-specific module is not applicable. /// [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] - public class ExportAvaloniaModuleAttribute : Attribute + public sealed class ExportAvaloniaModuleAttribute : Attribute { public ExportAvaloniaModuleAttribute(string name, Type moduleType) { diff --git a/src/Avalonia.Controls/ResolveByNameAttribute.cs b/src/Avalonia.Controls/ResolveByNameAttribute.cs index a13b10d630..3c56c20db0 100644 --- a/src/Avalonia.Controls/ResolveByNameAttribute.cs +++ b/src/Avalonia.Controls/ResolveByNameAttribute.cs @@ -7,7 +7,8 @@ namespace Avalonia.Controls /// When applying this to attached properties, ensure to put on both /// the Getter and Setter methods. /// - public class ResolveByNameAttribute : Attribute + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + public sealed class ResolveByNameAttribute : Attribute { } } diff --git a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs index 3e31de6995..386db30f92 100644 --- a/src/Avalonia.OpenGL/GlEntryPointAttribute.cs +++ b/src/Avalonia.OpenGL/GlEntryPointAttribute.cs @@ -3,7 +3,7 @@ using System; namespace Avalonia.OpenGL { [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - class GlMinVersionEntryPoint : Attribute + sealed class GlMinVersionEntryPoint : Attribute { public GlMinVersionEntryPoint(string entry, int minVersionMajor, int minVersionMinor) { @@ -28,7 +28,7 @@ namespace Avalonia.OpenGL } [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - class GlExtensionEntryPoint : Attribute + sealed class GlExtensionEntryPoint : Attribute { public GlExtensionEntryPoint(string entry, string extension) { diff --git a/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs b/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs index 98a843bad1..44605a2ffb 100644 --- a/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs +++ b/src/Avalonia.Remote.Protocol/AvaloniaRemoteMessageGuidAttribute.cs @@ -3,7 +3,7 @@ namespace Avalonia.Remote.Protocol { [AttributeUsage(AttributeTargets.Class)] - public class AvaloniaRemoteMessageGuidAttribute : Attribute + public sealed class AvaloniaRemoteMessageGuidAttribute : Attribute { public Guid Guid { get; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs index 8d6f8cdf3a..da4d7374d4 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlTypes.cs @@ -34,7 +34,8 @@ namespace Avalonia.Markup.Xaml } - public class ConstructorArgumentAttribute : Attribute + [AttributeUsage(AttributeTargets.Property)] + public sealed class ConstructorArgumentAttribute : Attribute { public ConstructorArgumentAttribute(string name) { diff --git a/src/Shared/ModuleInitializer.cs b/src/Shared/ModuleInitializer.cs index a72929e06f..e58b296474 100644 --- a/src/Shared/ModuleInitializer.cs +++ b/src/Shared/ModuleInitializer.cs @@ -1,7 +1,8 @@ namespace System.Runtime.CompilerServices { #if NETSTANDARD2_0 - internal class ModuleInitializerAttribute : Attribute + [AttributeUsage(AttributeTargets.Method)] + internal sealed class ModuleInitializerAttribute : Attribute { } diff --git a/src/Shared/SourceGeneratorAttributes.cs b/src/Shared/SourceGeneratorAttributes.cs index 3f00fbef57..bdd21d0426 100644 --- a/src/Shared/SourceGeneratorAttributes.cs +++ b/src/Shared/SourceGeneratorAttributes.cs @@ -16,7 +16,9 @@ namespace Avalonia.SourceGenerator } - internal class GetProcAddressAttribute : Attribute + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class GetProcAddressAttribute : Attribute { public GetProcAddressAttribute(string proc) { @@ -39,11 +41,14 @@ namespace Avalonia.SourceGenerator } } - internal class GenerateEnumValueDictionaryAttribute : Attribute + [AttributeUsage(AttributeTargets.Method)] + internal sealed class GenerateEnumValueDictionaryAttribute : Attribute { } - internal class GenerateEnumValueListAttribute : Attribute + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class GenerateEnumValueListAttribute : Attribute { } } From 936d4c3448108f793002adb87f09f1eede21dbb1 Mon Sep 17 00:00:00 2001 From: Dmitry Zhelnin Date: Thu, 2 Feb 2023 18:15:35 +0300 Subject: [PATCH 017/120] Animatable: handle transitions subscription when contol is added or removed from visual tree --- src/Avalonia.Base/Animation/Animatable.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index edaa76233e..eddb89c3e8 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -27,6 +27,7 @@ namespace Avalonia.Animation AvaloniaProperty.Register(nameof(Transitions)); private bool _transitionsEnabled = true; + private bool _isSubscribedToTransitionsCollection = false; private Dictionary? _transitionState; /// @@ -62,6 +63,11 @@ namespace Avalonia.Animation if (Transitions is object) { + if (!_isSubscribedToTransitionsCollection) + { + _isSubscribedToTransitionsCollection = true; + Transitions.CollectionChanged += TransitionsCollectionChanged; + } AddTransitions(Transitions); } } @@ -72,7 +78,7 @@ namespace Avalonia.Animation /// /// /// This method should not be called from user code, it will be called automatically by the framework - /// when a control is added to the visual tree. + /// when a control is removed from the visual tree. /// protected void DisableTransitions() { @@ -82,6 +88,11 @@ namespace Avalonia.Animation if (Transitions is object) { + if (_isSubscribedToTransitionsCollection) + { + _isSubscribedToTransitionsCollection = false; + Transitions.CollectionChanged -= TransitionsCollectionChanged; + } RemoveTransitions(Transitions); } } @@ -110,6 +121,7 @@ namespace Avalonia.Animation } newTransitions.CollectionChanged += TransitionsCollectionChanged; + _isSubscribedToTransitionsCollection = true; AddTransitions(toAdd); } From 7b00ef69892358650c10e0f27fb0d5dd1477e3f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 12:00:40 +0100 Subject: [PATCH 018/120] Make untyped bindings produce default value. This changes the behavior of bindings slightly, in that previously a binding which produced `UnsetValue` (or a binding error) caused the value of any lower priority value of value to take effect. After this change, the binding reverts to the property's default value. This behavior more closely matches WPF and fixes #10110. --- src/Avalonia.Base/AvaloniaObject.cs | 4 +- .../PropertyStore/BindingEntryBase.cs | 47 +++++++++---------- .../LocalValueBindingObserver.cs | 42 ++++++++++++----- .../LocalValueUntypedBindingObserver.cs | 25 +++++++--- .../SourceUntypedBindingEntry.cs | 2 + .../PropertyStore/TypedBindingEntry.cs | 2 + .../PropertyStore/UntypedBindingEntry.cs | 5 ++ .../AvaloniaObjectTests_Binding.cs | 18 ++++++- .../AvaloniaObjectTests_Validation.cs | 4 +- 9 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1946d4ba5c..db6dbe98e0 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -270,8 +270,8 @@ namespace Avalonia /// The property. /// True if the property is set, otherwise false. /// - /// Checks whether a value is assigned to the property, or that there is a binding to the - /// property that is producing a value other than . + /// Returns true if is a styled property which has a value + /// assigned to it or a binding targeting it; otherwise false. /// public bool IsSet(AvaloniaProperty property) { diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 11dc80ef8f..e1ff0970c2 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore private IDisposable? _subscription; private bool _hasValue; private TValue? _value; + private TValue? _defaultValue; + private bool _isDefaultValueInitialized; protected BindingEntryBase( ValueFrame frame, @@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); + protected abstract TValue GetDefaultValue(Type ownerType); protected virtual void Start(bool produceValue) { @@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore }; } - private void ClearValue() - { - if (_hasValue) - { - _hasValue = false; - _value = default; - if (_subscription is not null) - Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); - } - } - private void SetValue(BindingValue value) { static void Execute(BindingEntryBase instance, BindingValue value) @@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); - if (value.HasValue) - { - if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value)) - { - instance._value = value.Value; - instance._hasValue = true; - if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); - } - } - else if (value.Type != BindingValueType.DoNothing) + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + + if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, effectiveValue)) { - instance.ClearValue(); + instance._value = effectiveValue; + instance._hasValue = true; if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority); + instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); } } + if (value.Type == BindingValueType.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore _subscription = null; Frame.OnBindingCompleted(this); } + + private TValue GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index f89cb029b6..0771ade647 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { @@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledProperty property, T value) + static void Execute(LocalValueBindingObserver instance, T value) { - if (property.ValidateValue?.Invoke(value) != false) - owner.SetValue(property, value, BindingPriority.LocalValue); - else - owner.ClearLocalValue(property); + var owner = instance._owner; + var property = instance.Property; + + if (property.ValidateValue?.Invoke(value) == false) + value = instance.GetCachedDefaultValue(); + + owner.SetValue(property, value, BindingPriority.LocalValue); } if (Dispatcher.UIThread.CheckAccess()) { - Execute(_owner, Property, value); + Execute(this, value); } else { // To avoid allocating closure in the outer scope we need to capture variables // locally. This allows us to skip most of the allocations when on UI thread. - var instance = _owner; - var property = Property; + var instance = this; var newValue = value; - Dispatcher.UIThread.Post(() => Execute(instance, property, newValue)); + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } @@ -73,12 +77,13 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - if (value.HasValue) - owner.SetValue(property, value.Value, BindingPriority.LocalValue); - else if (value.Type != BindingValueType.DataValidationError) - owner.ClearLocalValue(property); + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); } + if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -92,5 +97,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 2d157b2519..46e6ed810a 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using Avalonia.Data; using Avalonia.Threading; @@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { @@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore if (value == AvaloniaProperty.UnsetValue) { - owner.ClearLocalValue(property); - } - else if (value == BindingOperations.DoNothing) - { - // Do nothing! + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); } else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) { @@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore } else { - owner.ClearLocalValue(property); + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); } } + if (value == BindingOperations.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b56d0d4529..b82714817b 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 697725c87b..550f5c0001 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore return value; } + + protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs index f8becb2e06..a77d7fddb6 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs @@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override object? GetDefaultValue(Type ownerType) + { + return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue; + } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index f0386615c5..baaed5104d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -372,6 +372,20 @@ namespace Avalonia.Base.UnitTests Assert.Null(target.GetValue(property)); } + [Fact] + public void LocalValue_Bind_Generic_To_ValueType_Accepts_UnsetValue() + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.QuxProperty, source); + source.OnNext(6.7); + source.OnNext(BindingValue.Unset); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); + } + [Fact] public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() { @@ -383,7 +397,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] @@ -397,7 +411,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index e8175cf477..3fc950573d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -66,7 +66,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Reverts_To_Lower_Priority_If_Style_Binding_Fails_Validation() + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_2() { var target = new Class1(); var source = new Subject(); @@ -75,7 +75,7 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); source.OnNext(150); - Assert.Equal(10, target.GetValue(Class1.FooProperty)); + Assert.Equal(11, target.GetValue(Class1.FooProperty)); } [Fact] From bca57ea0e9c65c5b17422303261b1a7a058d842b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:27:48 +0100 Subject: [PATCH 019/120] Added additional failing test. --- .../AvaloniaObjectTests_Validation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index 3fc950573d..513aeb65ab 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -78,6 +78,20 @@ namespace Avalonia.Base.UnitTests Assert.Equal(11, target.GetValue(Class1.FooProperty)); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_3(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + [Fact] public void Reverts_To_DefaultValue_Even_In_Presence_Of_Other_Bindings() { From b2c1d80f5cfdf3c0dccbed07cb908b69f7db3e2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:32:02 +0100 Subject: [PATCH 020/120] Fix validation of local value bindings. Local value bindings with a type of `BindingValue` were not being validated. --- .../PropertyStore/LocalValueBindingObserver.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 0771ade647..5908d9e535 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -77,8 +77,17 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); - owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + if (value.HasValue) + { + var effectiveValue = value.Value; + if (property.ValidateValue?.Invoke(effectiveValue) == false) + effectiveValue = instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + } + else + { + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); + } } if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) From 96bda931196bab8368969d0a1e1151a9427d618d Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 21 Jan 2023 11:43:14 +0100 Subject: [PATCH 021/120] fix: DevGenerator Nullable --- .../DevGenerators/CompositionGenerator/Generator.ListProxy.cs | 2 +- src/tools/DevGenerators/CompositionGenerator/Generator.cs | 4 ++-- src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs | 2 +- src/tools/DevGenerators/GetProcAddressInitialization.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.ListProxy.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.ListProxy.cs index 135ab0426e..c293a9101d 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.ListProxy.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.ListProxy.cs @@ -112,7 +112,7 @@ class Template var defs = cl.Members.OfType().First(m => m.Identifier.Text == "InitializeDefaults"); - cl = cl.ReplaceNode(defs.Body, defs.Body.AddStatements( + cl = cl.ReplaceNode(defs.Body!, defs.Body!.AddStatements( ParseStatement($"_list = new ServerListProxyHelper<{itemType}, {serverItemType}>(this);"))); diff --git a/src/tools/DevGenerators/CompositionGenerator/Generator.cs b/src/tools/DevGenerators/CompositionGenerator/Generator.cs index 3b5d3d8c3f..dfc8b45579 100644 --- a/src/tools/DevGenerators/CompositionGenerator/Generator.cs +++ b/src/tools/DevGenerators/CompositionGenerator/Generator.cs @@ -297,8 +297,8 @@ namespace Avalonia.SourceGenerator.CompositionGenerator server = server.WithBaseList( server.BaseList?.AddTypes(SimpleBaseType(ParseTypeName(impl.ServerName)))); - client = client.AddMembers( - ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;")); + if(ParseMemberDeclaration($"{impl.ServerName} {impl.Name}.Server => Server;") is { } member) + client = client.AddMembers(member); } diff --git a/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs index 86dbb3a452..c975bb8444 100644 --- a/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs +++ b/src/tools/DevGenerators/EnumMemberDictionaryGenerator.cs @@ -32,7 +32,7 @@ public class EnumMemberDictionaryGenerator : IIncrementalGenerator ).Collect(); context.RegisterSourceOutput(all, static (context, methods) => { - foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { var classBuilder = new StringBuilder(); if (typeGroup.Key.ContainingNamespace != null) diff --git a/src/tools/DevGenerators/GetProcAddressInitialization.cs b/src/tools/DevGenerators/GetProcAddressInitialization.cs index aedc13e7f6..e8d7c251fa 100644 --- a/src/tools/DevGenerators/GetProcAddressInitialization.cs +++ b/src/tools/DevGenerators/GetProcAddressInitialization.cs @@ -34,7 +34,7 @@ public class GetProcAddressInitializationGenerator : IIncrementalGenerator var all = fieldsWithAttribute.Collect(); context.RegisterSourceOutput(all, static (context, methods) => { - foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) + foreach (var typeGroup in methods.GroupBy(f => f.ContainingType, SymbolEqualityComparer.Default)) { var nextContext = 0; var contexts = new Dictionary(); From 14fba2e53ac5348b1a86a5b28980ecd18bddc9ee Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 2 Feb 2023 17:39:36 +0100 Subject: [PATCH 022/120] Rework HitTestTextRange --- src/Avalonia.Base/Media/FormattedText.cs | 32 +- .../Media/TextFormatting/ITextSource.cs | 4 +- .../Media/TextFormatting/TextFormatter.cs | 2 +- .../Media/TextFormatting/TextFormatterImpl.cs | 18 +- .../Media/TextFormatting/TextLayout.cs | 37 +- .../Media/TextFormatting/TextLineImpl.cs | 684 ++++++++++-------- src/Avalonia.Controls/TextBlock.cs | 51 +- .../TextFormatting/TextFormatterTests.cs | 84 +++ .../Media/TextFormatting/TextLayoutTests.cs | 60 +- .../Media/TextFormatting/TextLineTests.cs | 13 +- 10 files changed, 633 insertions(+), 352 deletions(-) diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 0bab473442..28757b1a1d 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -741,6 +741,11 @@ namespace Avalonia.Media null // no previous line break ); + if(Current is null) + { + return false; + } + // check if this line fits the text height if (_totalHeight + Current.Height > _that._maxTextHeight) { @@ -779,7 +784,7 @@ namespace Avalonia.Media // maybe there is no next line at all if (Position + Current.Length < _that._text.Length) { - bool nextLineFits; + bool nextLineFits = false; if (_lineCount + 1 >= _that._maxLineCount) { @@ -795,7 +800,10 @@ namespace Avalonia.Media currentLineBreak ); - nextLineFits = (_totalHeight + Current.Height + _nextLine.Height <= _that._maxTextHeight); + if(_nextLine != null) + { + nextLineFits = (_totalHeight + Current.Height + _nextLine.Height <= _that._maxTextHeight); + } } if (!nextLineFits) @@ -819,16 +827,22 @@ namespace Avalonia.Media _previousLineBreak ); - currentLineBreak = Current.TextLineBreak; + if(Current != null) + { + currentLineBreak = Current.TextLineBreak; + } _that._defaultParaProps.SetTextWrapping(currentWrap); } } } - _previousHeight = Current.Height; + if(Current != null) + { + _previousHeight = Current.Height; - Length = Current.Length; + Length = Current.Length; + } _previousLineBreak = currentLineBreak; @@ -838,7 +852,7 @@ namespace Avalonia.Media /// /// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed. /// - private TextLine FormatLine(ITextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak? lineBreak) + private TextLine? FormatLine(ITextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak? lineBreak) { var line = _formatter.FormatLine( textSource, @@ -848,7 +862,7 @@ namespace Avalonia.Media lineBreak ); - if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0) + if (line != null && _that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0) { // what I really need here is the last displayed text run of the line // textSourcePosition + line.Length - 1 works except the end of paragraph case, @@ -1601,11 +1615,11 @@ namespace Avalonia.Media } /// - public TextRun? GetTextRun(int textSourceCharacterIndex) + public TextRun GetTextRun(int textSourceCharacterIndex) { if (textSourceCharacterIndex >= _that._text.Length) { - return null; + return new TextEndOfParagraph(); } var thatFormatRider = new SpanRider(_that._formatRuns, _that._latestPosition, textSourceCharacterIndex); diff --git a/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs b/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs index 26966b37bc..32012ab8e9 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ITextSource.cs @@ -1,6 +1,4 @@ -using Avalonia.Metadata; - -namespace Avalonia.Media.TextFormatting +namespace Avalonia.Media.TextFormatting { /// /// Produces objects that are used by the . diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs index 0b5d7649d7..ff8c1c4860 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatter.cs @@ -38,7 +38,7 @@ /// A value that specifies the text formatter state, /// in terms of where the previous line in the paragraph was broken by the text formatting process. /// The formatted line. - public abstract TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, + public abstract TextLine? FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null); } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 812c4e9eb8..7f74f49982 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -18,7 +18,7 @@ namespace Avalonia.Media.TextFormatting [ThreadStatic] private static BidiAlgorithm? t_bidiAlgorithm; /// - public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, + public override TextLine? FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null) { TextLineBreak? nextLineBreak = null; @@ -41,6 +41,11 @@ namespace Avalonia.Media.TextFormatting fetchedRuns = FetchTextRuns(textSource, firstTextSourceIndex, objectPool, out var textEndOfLine, out var textSourceLength); + if (fetchedRuns.Count == 0) + { + return null; + } + shapedTextRuns = ShapeTextRuns(fetchedRuns, paragraphProperties, objectPool, fontManager, out var resolvedFlowDirection); @@ -491,16 +496,7 @@ namespace Avalonia.Media.TextFormatting while (textRunEnumerator.MoveNext()) { - var textRun = textRunEnumerator.Current; - - if (textRun == null) - { - textRuns.Add(new TextEndOfParagraph()); - - textSourceLength += TextRun.DefaultTextSourceLength; - - break; - } + TextRun textRun = textRunEnumerator.Current!; if (textRun is TextEndOfLine textEndOfLine) { diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 8e85c10bba..4dbc472133 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -238,7 +238,7 @@ namespace Avalonia.Media.TextFormatting foreach (var textLine in _textLines) { //Current line isn't covered. - if (textLine.FirstTextSourceIndex + textLine.Length < start) + if (textLine.FirstTextSourceIndex + textLine.Length <= start) { currentY += textLine.Height; @@ -348,14 +348,36 @@ namespace Avalonia.Media.TextFormatting { var (x, y) = point; - var lastTrailingIndex = textLine.FirstTextSourceIndex + textLine.Length; - var isInside = x >= 0 && x <= textLine.Width && y >= 0 && y <= textLine.Height; - if (x >= textLine.Width && textLine.Length > 0 && textLine.NewLineLength > 0) + var lastTrailingIndex = 0; + + if(_paragraphProperties.FlowDirection== FlowDirection.LeftToRight) { - lastTrailingIndex -= textLine.NewLineLength; + lastTrailingIndex = textLine.FirstTextSourceIndex + textLine.Length; + + if (x >= textLine.Width && textLine.Length > 0 && textLine.NewLineLength > 0) + { + lastTrailingIndex -= textLine.NewLineLength; + } + + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfLine textEndOfLine) + { + lastTrailingIndex -= textEndOfLine.Length; + } } + else + { + if (x <= textLine.WidthIncludingTrailingWhitespace - textLine.Width && textLine.Length > 0 && textLine.NewLineLength > 0) + { + lastTrailingIndex += textLine.NewLineLength; + } + + if (textLine.TextLineBreak?.TextEndOfLine is TextEndOfLine textEndOfLine) + { + lastTrailingIndex += textEndOfLine.Length; + } + } var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; @@ -391,7 +413,7 @@ namespace Avalonia.Media.TextFormatting /// private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping, - TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, + TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, double letterSpacing) { var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); @@ -456,7 +478,7 @@ namespace Avalonia.Media.TextFormatting var textLine = textFormatter.FormatLine(_textSource, _textSourceLength, MaxWidth, _paragraphProperties, previousLine?.TextLineBreak); - if (textLine.Length == 0) + if (textLine is null) { if (previousLine != null && previousLine.NewLineLength > 0) { @@ -518,7 +540,6 @@ namespace Avalonia.Media.TextFormatting } } - //Make sure the TextLayout always contains at least on empty line if (textLines.Count == 0) { var textLine = TextFormatterImpl.CreateEmptyTextLine(0, MaxWidth, _paragraphProperties); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index d29063e07d..187b3154ad 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -10,6 +10,7 @@ namespace Avalonia.Media.TextFormatting private readonly double _paragraphWidth; private readonly TextParagraphProperties _paragraphProperties; private TextLineMetrics _textLineMetrics; + private TextLineBreak? _textLineBreak; private readonly FlowDirection _resolvedFlowDirection; public TextLineImpl(TextRun[] textRuns, int firstTextSourceIndex, int length, double paragraphWidth, @@ -18,7 +19,7 @@ namespace Avalonia.Media.TextFormatting { FirstTextSourceIndex = firstTextSourceIndex; Length = length; - TextLineBreak = lineBreak; + _textLineBreak = lineBreak; HasCollapsed = hasCollapsed; _textRuns = textRuns; @@ -38,7 +39,7 @@ namespace Avalonia.Media.TextFormatting public override int Length { get; } /// - public override TextLineBreak? TextLineBreak { get; } + public override TextLineBreak? TextLineBreak => _textLineBreak; /// public override bool HasCollapsed { get; } @@ -167,50 +168,54 @@ namespace Avalonia.Media.TextFormatting { if (_textRuns.Length == 0) { - return new CharacterHit(); + return new CharacterHit(FirstTextSourceIndex); } distance -= Start; - var firstRunIndex = 0; + var lastIndex = _textRuns.Length - 1; - if (_textRuns[firstRunIndex] is TextEndOfLine) + if (_textRuns[lastIndex] is TextEndOfLine) { - firstRunIndex++; + lastIndex--; } - if(firstRunIndex >= _textRuns.Length) + var currentPosition = FirstTextSourceIndex; + + if (lastIndex < 0) { - return new CharacterHit(FirstTextSourceIndex); + return new CharacterHit(currentPosition); } if (distance <= 0) { - var firstRun = _textRuns[firstRunIndex]; + var firstRun = _textRuns[0]; - return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0); + if (_paragraphProperties.FlowDirection == FlowDirection.RightToLeft) + { + currentPosition = Length - firstRun.Length; + } + + return GetRunCharacterHit(firstRun, currentPosition, 0); } if (distance >= WidthIncludingTrailingWhitespace) { - var lastRun = _textRuns[_textRuns.Length - 1]; - - var size = 0.0; + var lastRun = _textRuns[lastIndex]; - if (lastRun is DrawableTextRun drawableTextRun) + if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) { - size = drawableTextRun.Size.Width; + currentPosition = Length - lastRun.Length; } - return GetRunCharacterHit(lastRun, FirstTextSourceIndex + Length - lastRun.Length, size); + return GetRunCharacterHit(lastRun, currentPosition, distance); } // process hit that happens within the line var characterHit = new CharacterHit(); - var currentPosition = FirstTextSourceIndex; var currentDistance = 0.0; - for (var i = 0; i < _textRuns.Length; i++) + for (var i = 0; i <= lastIndex; i++) { var currentRun = _textRuns[i]; @@ -242,7 +247,7 @@ namespace Avalonia.Media.TextFormatting currentRun = _textRuns[j]; - if(currentRun is not ShapedTextRun) + if (currentRun is not ShapedTextRun) { continue; } @@ -274,10 +279,6 @@ namespace Avalonia.Media.TextFormatting continue; } } - else - { - continue; - } break; } @@ -422,10 +423,10 @@ namespace Avalonia.Media.TextFormatting { if (currentGlyphRun != null) { - distance = currentGlyphRun.Size.Width - distance; + currentDistance -= currentGlyphRun.Size.Width; } - return Math.Max(0, currentDistance - distance); + return currentDistance + distance; } if (currentRun is DrawableTextRun drawableTextRun) @@ -575,386 +576,505 @@ namespace Avalonia.Media.TextFormatting return GetPreviousCaretCharacterHit(characterHit); } - private IReadOnlyList GetTextBoundsLeftToRight(int firstTextSourceIndex, int textLength) + public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength) { - var characterIndex = firstTextSourceIndex + textLength; + if (_textRuns.Length == 0) + { + return Array.Empty(); + } - var result = new List(_textRuns.Length); - var lastDirection = FlowDirection.LeftToRight; - var currentDirection = lastDirection; + var result = new List(); var currentPosition = FirstTextSourceIndex; var remainingLength = textLength; - var startX = Start; - double currentWidth = 0; - var currentRect = default(Rect); - - TextRunBounds lastRunBounds = default; - - for (var index = 0; index < _textRuns.Length; index++) + static FlowDirection GetDirection(TextRun textRun, FlowDirection currentDirection) { - if (_textRuns[index] is not DrawableTextRun currentRun) + if (textRun is ShapedTextRun shapedTextRun) { - continue; + return shapedTextRun.ShapedBuffer.IsLeftToRight ? + FlowDirection.LeftToRight : + FlowDirection.RightToLeft; } - var characterLength = 0; - var endX = startX; - - TextRunBounds currentRunBounds; + return currentDirection; + } - double combinedWidth; + if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) + { + var currentX = Start; - if (currentRun is ShapedTextRun currentShapedRun) + for (int i = 0; i < _textRuns.Length; i++) { - var firstCluster = currentShapedRun.GlyphRun.Metrics.FirstCluster; + var currentRun = _textRuns[i]; + + var firstRunIndex = i; + var lastRunIndex = firstRunIndex; + var currentDirection = GetDirection(currentRun, FlowDirection.LeftToRight); + var directionalWidth = 0.0; - if (currentPosition + currentRun.Length <= firstTextSourceIndex) + if (currentRun is DrawableTextRun currentDrawable) { - startX += currentRun.Size.Width; + directionalWidth = currentDrawable.Size.Width; + } - currentPosition += currentRun.Length; + // Find consecutive runs of same direction + for (; lastRunIndex + 1 < _textRuns.Length; lastRunIndex++) + { + var nextRun = _textRuns[lastRunIndex + 1]; - continue; + var nextDirection = GetDirection(nextRun, currentDirection); + + if (currentDirection != nextDirection) + { + break; + } + + if (nextRun is DrawableTextRun nextDrawable) + { + directionalWidth += nextDrawable.Size.Width; + } } - if (currentShapedRun.ShapedBuffer.IsLeftToRight) + //Skip runs that are not part of the hit test range + switch (currentDirection) { - var startIndex = firstCluster + Math.Max(0, firstTextSourceIndex - currentPosition); + case FlowDirection.RightToLeft: + { + for (; lastRunIndex >= firstRunIndex; lastRunIndex--) + { + currentRun = _textRuns[lastRunIndex]; + + if (currentPosition + currentRun.Length > firstTextSourceIndex) + { + break; + } + + currentPosition += currentRun.Length; - double startOffset; + if (currentRun is DrawableTextRun drawableTextRun) + { + directionalWidth -= drawableTextRun.Size.Width; + currentX += drawableTextRun.Size.Width; + } - double endOffset; + if(lastRunIndex - 1 < 0) + { + break; + } + } - startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + break; + } + default: + { + for (; firstRunIndex <= lastRunIndex; firstRunIndex++) + { + currentRun = _textRuns[firstRunIndex]; - endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + if (currentPosition + currentRun.Length > firstTextSourceIndex) + { + break; + } - startX += startOffset; + currentPosition += currentRun.Length; - endX += endOffset; + if (currentRun is DrawableTextRun drawableTextRun) + { + currentX += drawableTextRun.Size.Width; + directionalWidth -= drawableTextRun.Size.Width; + } - var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); + if(firstRunIndex + 1 == _textRuns.Length) + { + break; + } + } - var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); + break; + } + } - characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength); + i = lastRunIndex; - currentDirection = FlowDirection.LeftToRight; + if (directionalWidth == 0) + { + continue; } - else + + var coveredLength = 0; + TextBounds? textBounds = null; + + switch (currentDirection) { - var rightToLeftIndex = index; - var rightToLeftWidth = currentShapedRun.Size.Width; - while (rightToLeftIndex + 1 <= _textRuns.Length - 1 && _textRuns[rightToLeftIndex + 1] is ShapedTextRun nextShapedRun) - { - if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight) + case FlowDirection.RightToLeft: { + textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX + directionalWidth, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); + + currentX += directionalWidth; + break; } + default: + { + textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); - rightToLeftIndex++; - - rightToLeftWidth += nextShapedRun.Size.Width; + currentX = textBounds.Rectangle.Right; - if (currentPosition + nextShapedRun.Length > firstTextSourceIndex + textLength) - { break; } + } - currentShapedRun = nextShapedRun; - } + if (coveredLength > 0) + { + result.Add(textBounds); + + remainingLength -= coveredLength; + } + + if (remainingLength <= 0) + { + break; + } + } + } + else + { + var currentX = Start + WidthIncludingTrailingWhitespace; - startX += rightToLeftWidth; + for (int i = _textRuns.Length - 1; i >= 0; i--) + { + var currentRun = _textRuns[i]; + var firstRunIndex = i; + var lastRunIndex = firstRunIndex; + var currentDirection = GetDirection(currentRun, FlowDirection.RightToLeft); + var directionalWidth = 0.0; - currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength); + if (currentRun is DrawableTextRun currentDrawable) + { + directionalWidth = currentDrawable.Size.Width; + } - remainingLength -= currentRunBounds.Length; - currentPosition = currentRunBounds.TextSourceCharacterIndex + currentRunBounds.Length; - endX = currentRunBounds.Rectangle.Right; - startX = currentRunBounds.Rectangle.Left; + // Find consecutive runs of same direction + for (; firstRunIndex - 1 > 0; firstRunIndex--) + { + var previousRun = _textRuns[firstRunIndex - 1]; - var rightToLeftRunBounds = new List { currentRunBounds }; + var previousDirection = GetDirection(previousRun, currentDirection); - for (int i = rightToLeftIndex - 1; i >= index; i--) + if (currentDirection != previousDirection) { - if (_textRuns[i] is not ShapedTextRun shapedRun) + break; + } + + if (currentRun is DrawableTextRun previousDrawable) + { + directionalWidth += previousDrawable.Size.Width; + } + } + + //Skip runs that are not part of the hit test range + switch (currentDirection) + { + case FlowDirection.RightToLeft: { - continue; - } + for (; lastRunIndex >= firstRunIndex; lastRunIndex--) + { + currentRun = _textRuns[lastRunIndex]; - currentShapedRun = shapedRun; + if (currentPosition + currentRun.Length <= firstTextSourceIndex) + { + currentPosition += currentRun.Length; - currentRunBounds = GetRightToLeftTextRunBounds(currentShapedRun, startX, firstTextSourceIndex, characterIndex, currentPosition, remainingLength); + if (currentRun is DrawableTextRun drawableTextRun) + { + currentX -= drawableTextRun.Size.Width; + directionalWidth -= drawableTextRun.Size.Width; + } - rightToLeftRunBounds.Insert(0, currentRunBounds); + continue; + } - remainingLength -= currentRunBounds.Length; - startX = currentRunBounds.Rectangle.Left; + break; + } - currentPosition += currentRunBounds.Length; - } + break; + } + default: + { + for (; firstRunIndex <= lastRunIndex; firstRunIndex++) + { + currentRun = _textRuns[firstRunIndex]; - combinedWidth = endX - startX; + if (currentPosition + currentRun.Length <= firstTextSourceIndex) + { + currentPosition += currentRun.Length; - currentRect = new Rect(startX, 0, combinedWidth, Height); + if (currentRun is DrawableTextRun drawableTextRun) + { + currentX += drawableTextRun.Size.Width; + directionalWidth -= drawableTextRun.Size.Width; + } - currentDirection = FlowDirection.RightToLeft; + continue; + } - if (!MathUtilities.IsZero(combinedWidth)) - { - result.Add(new TextBounds(currentRect, currentDirection, rightToLeftRunBounds)); - } + break; + } - startX = endX; + break; + } } - } - else - { - if (currentPosition + currentRun.Length <= firstTextSourceIndex) - { - startX += currentRun.Size.Width; - currentPosition += currentRun.Length; + i = firstRunIndex; + if (directionalWidth == 0) + { continue; } - if (currentPosition < firstTextSourceIndex) - { - startX += currentRun.Size.Width; - } + var coveredLength = 0; - if (currentPosition + currentRun.Length <= characterIndex) + TextBounds? textBounds = null; + + switch (currentDirection) { - endX += currentRun.Size.Width; + case FlowDirection.LeftToRight: + { + textBounds = GetTextBoundsLeftToRight(firstRunIndex, lastRunIndex, currentX - directionalWidth, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); + + currentX -= directionalWidth; - characterLength = currentRun.Length; + break; + } + default: + { + textBounds = GetTextRunBoundsRightToLeft(firstRunIndex, lastRunIndex, currentX, firstTextSourceIndex, + currentPosition, remainingLength, out coveredLength, out currentPosition); + + currentX = textBounds.Rectangle.Left; + + break; + } } - } - if (endX < startX) - { - (endX, startX) = (startX, endX); - } + //Visual order is always left to right so we need to insert + result.Insert(0, textBounds); - //Lines that only contain a linebreak need to be covered here - if (characterLength == 0) - { - characterLength = NewLineLength; + remainingLength -= coveredLength; + + if (remainingLength <= 0) + { + break; + } } + } - combinedWidth = endX - startX; + return result; + } - currentRunBounds = new TextRunBounds(new Rect(startX, 0, combinedWidth, Height), currentPosition, characterLength, currentRun); + private TextBounds GetTextRunBoundsRightToLeft(int firstRunIndex, int lastRunIndex, double endX, + int firstTextSourceIndex, int currentPosition, int remainingLength, out int coveredLength, out int newPosition) + { + coveredLength = 0; + var textRunBounds = new List(); + var startX = endX; - currentPosition += characterLength; + for (int i = lastRunIndex; i >= firstRunIndex; i--) + { + var currentRun = _textRuns[i]; - remainingLength -= characterLength; + if (currentRun is ShapedTextRun shapedTextRun) + { + var runBounds = GetRunBoundsRightToLeft(shapedTextRun, startX, firstTextSourceIndex, remainingLength, currentPosition, out var offset); - startX = endX; + textRunBounds.Insert(0, runBounds); - if (currentRunBounds.TextRun != null && !MathUtilities.IsZero(combinedWidth) || NewLineLength > 0) - { - if (result.Count > 0 && lastDirection == currentDirection && MathUtilities.AreClose(currentRect.Left, lastRunBounds.Rectangle.Right)) + if (offset > 0) { - currentRect = currentRect.WithWidth(currentWidth + combinedWidth); + endX = runBounds.Rectangle.Right; - var textBounds = result[result.Count - 1]; + startX = endX; + } - textBounds.Rectangle = currentRect; + startX -= runBounds.Rectangle.Width; - textBounds.TextRunBounds.Add(currentRunBounds); - } - else + currentPosition += runBounds.Length + offset; + + coveredLength += runBounds.Length; + + remainingLength -= runBounds.Length; + } + else + { + if (currentRun is DrawableTextRun drawableTextRun) { - currentRect = currentRunBounds.Rectangle; + startX -= drawableTextRun.Size.Width; - result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); + textRunBounds.Insert(0, + new TextRunBounds( + new Rect(startX, 0, drawableTextRun.Size.Width, Height), currentPosition, currentRun.Length, currentRun)); } - } - lastRunBounds = currentRunBounds; + currentPosition += currentRun.Length; + + coveredLength += currentRun.Length; - currentWidth += combinedWidth; + remainingLength -= currentRun.Length; + } - if (remainingLength <= 0 || currentPosition >= characterIndex) + if (remainingLength <= 0) { break; } - - lastDirection = currentDirection; } - return result; - } + newPosition = currentPosition; - private IReadOnlyList GetTextBoundsRightToLeft(int firstTextSourceIndex, int textLength) - { - var characterIndex = firstTextSourceIndex + textLength; + var runWidth = endX - startX; - var result = new List(_textRuns.Length); - var lastDirection = FlowDirection.LeftToRight; - var currentDirection = lastDirection; + var bounds = new Rect(startX, 0, runWidth, Height); - var currentPosition = FirstTextSourceIndex; - var remainingLength = textLength; + return new TextBounds(bounds, FlowDirection.RightToLeft, textRunBounds); + } - var startX = WidthIncludingTrailingWhitespace; - double currentWidth = 0; - var currentRect = default(Rect); + private TextBounds GetTextBoundsLeftToRight(int firstRunIndex, int lastRunIndex, double startX, + int firstTextSourceIndex, int currentPosition, int remainingLength, out int coveredLength, out int newPosition) + { + coveredLength = 0; + var textRunBounds = new List(); + var endX = startX; - for (var index = _textRuns.Length - 1; index >= 0; index--) + for (int i = firstRunIndex; i <= lastRunIndex; i++) { - if (_textRuns[index] is not DrawableTextRun currentRun) - { - continue; - } - - if (currentPosition + currentRun.Length < firstTextSourceIndex) - { - startX -= currentRun.Size.Width; - - currentPosition += currentRun.Length; - - continue; - } - - var characterLength = 0; - var endX = startX; + var currentRun = _textRuns[i]; - if (currentRun is ShapedTextRun currentShapedRun) + if (currentRun is ShapedTextRun shapedTextRun) { - var offset = Math.Max(0, firstTextSourceIndex - currentPosition); - - currentPosition += offset; - - var startIndex = currentPosition; - double startOffset; - double endOffset; + var runBounds = GetRunBoundsLeftToRight(shapedTextRun, endX, firstTextSourceIndex, remainingLength, currentPosition, out var offset); - if (currentShapedRun.ShapedBuffer.IsLeftToRight) - { - if (currentPosition < startIndex) - { - startOffset = endOffset = 0; - } - else - { - endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + textRunBounds.Add(runBounds); - startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - } - } - else + if (offset > 0) { - endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + startX = runBounds.Rectangle.Left; - startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + endX = startX; } - startX -= currentRun.Size.Width - startOffset; - endX -= currentRun.Size.Width - endOffset; + currentPosition += runBounds.Length + offset; - var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); - var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); + endX += runBounds.Rectangle.Width; - characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength); + coveredLength += runBounds.Length; - currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ? - FlowDirection.LeftToRight : - FlowDirection.RightToLeft; + remainingLength -= runBounds.Length; } else { - if (currentPosition + currentRun.Length <= characterIndex) + if (currentRun is DrawableTextRun drawableTextRun) { - endX -= currentRun.Size.Width; + textRunBounds.Add( + new TextRunBounds( + new Rect(endX, 0, drawableTextRun.Size.Width, Height), currentPosition, currentRun.Length, currentRun)); + + endX += drawableTextRun.Size.Width; } - if (currentPosition < firstTextSourceIndex) - { - startX -= currentRun.Size.Width; + currentPosition += currentRun.Length; - characterLength = currentRun.Length; - } - } + coveredLength += currentRun.Length; - if (endX < startX) - { - (endX, startX) = (startX, endX); + remainingLength -= currentRun.Length; } - //Lines that only contain a linebreak need to be covered here - if (characterLength == 0) + if (remainingLength <= 0) { - characterLength = NewLineLength; + break; } + } - var runWidth = endX - startX; + newPosition = currentPosition; - var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); + var runWidth = endX - startX; - if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0) - { - if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX)) - { - currentRect = currentRect.WithWidth(currentWidth + runWidth); + var bounds = new Rect(startX, 0, runWidth, Height); - var textBounds = result[result.Count - 1]; + return new TextBounds(bounds, FlowDirection.LeftToRight, textRunBounds); + } - textBounds.Rectangle = currentRect; + private TextRunBounds GetRunBoundsLeftToRight(ShapedTextRun currentRun, double startX, + int firstTextSourceIndex, int remainingLength, int currentPosition, out int offset) + { + var startIndex = currentPosition; - textBounds.TextRunBounds.Add(currentRunBounds); - } - else - { - currentRect = currentRunBounds.Rectangle; + offset = Math.Max(0, firstTextSourceIndex - currentPosition); - result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds })); - } - } + var firstCluster = currentRun.GlyphRun.Metrics.FirstCluster; - currentWidth += runWidth; - currentPosition += characterLength; + if (currentPosition != firstCluster) + { + startIndex = firstCluster + offset; + } + else + { + startIndex += offset; + } - if (currentPosition > characterIndex) - { - break; - } + var startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + var endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); - lastDirection = currentDirection; - remainingLength -= characterLength; + var endX = startX + endOffset; + startX += startOffset; - if (remainingLength <= 0) - { - break; - } + var startHit = currentRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _); + var endHit = currentRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _); + + var characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength); + + if (endX < startX) + { + (endX, startX) = (startX, endX); + } + + //Lines that only contain a linebreak need to be covered here + if (characterLength == 0) + { + characterLength = NewLineLength; } - result.Reverse(); + var runWidth = endX - startX; - return result; + return new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); } - private TextRunBounds GetRightToLeftTextRunBounds(ShapedTextRun currentRun, double endX, int firstTextSourceIndex, int characterIndex, int currentPosition, int remainingLength) + private TextRunBounds GetRunBoundsRightToLeft(ShapedTextRun currentRun, double endX, + int firstTextSourceIndex, int remainingLength, int currentPosition, out int offset) { var startX = endX; - var offset = Math.Max(0, firstTextSourceIndex - currentPosition); + var startIndex = currentPosition; - currentPosition += offset; + offset = Math.Max(0, firstTextSourceIndex - currentPosition); - var startIndex = currentPosition; + var firstCluster = currentRun.GlyphRun.Metrics.FirstCluster; - double startOffset; - double endOffset; + if (currentPosition != firstCluster) + { + startIndex = firstCluster + offset; + } + else + { + startIndex += offset; + } - endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); + var endOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex)); - startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); + var startOffset = currentRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength)); startX -= currentRun.Size.Width - startOffset; endX -= currentRun.Size.Width - endOffset; @@ -980,16 +1100,6 @@ namespace Avalonia.Media.TextFormatting return new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun); } - public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength) - { - if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) - { - return GetTextBoundsLeftToRight(firstTextSourceIndex, textLength); - } - - return GetTextBoundsRightToLeft(firstTextSourceIndex, textLength); - } - public override void Dispose() { for (int i = 0; i < _textRuns.Length; i++) @@ -1005,6 +1115,11 @@ namespace Avalonia.Media.TextFormatting { _textLineMetrics = CreateLineMetrics(); + if (_textLineBreak is null && _textRuns.Length > 1 && _textRuns[_textRuns.Length - 1] is TextEndOfLine textEndOfLine) + { + _textLineBreak = new TextLineBreak(textEndOfLine); + } + BidiReorderer.Instance.BidiReorder(_textRuns, _resolvedFlowDirection); } @@ -1328,7 +1443,7 @@ namespace Avalonia.Media.TextFormatting { width = widthIncludingWhitespace + textRun.GlyphRun.Metrics.Width; trailingWhitespaceLength = textRun.GlyphRun.Metrics.TrailingWhitespaceLength; - newLineLength = textRun.GlyphRun.Metrics.NewLineLength; + newLineLength += textRun.GlyphRun.Metrics.NewLineLength; } widthIncludingWhitespace += textRun.Size.Width; @@ -1340,31 +1455,10 @@ namespace Avalonia.Media.TextFormatting { widthIncludingWhitespace += drawableTextRun.Size.Width; - switch (_paragraphProperties.FlowDirection) + if (index == lastRunIndex) { - case FlowDirection.LeftToRight: - { - if (index == lastRunIndex) - { - width = widthIncludingWhitespace; - trailingWhitespaceLength = 0; - newLineLength = 0; - } - - break; - } - - case FlowDirection.RightToLeft: - { - if (index == lastRunIndex) - { - width = widthIncludingWhitespace; - trailingWhitespaceLength = 0; - newLineLength = 0; - } - - break; - } + width = widthIncludingWhitespace; + trailingWhitespaceLength = 0; } if (drawableTextRun.Size.Height > height) diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 9bd1dc95f9..df9a3eb8f3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -720,6 +720,16 @@ namespace Avalonia.Controls var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); + if (HasComplexContent) + { + ArrangeComplexContent(TextLayout, padding); + } + + if (MathUtilities.AreClose(_constraint.Inflate(padding).Width, finalSize.Width)) + { + return finalSize; + } + _constraint = new Size(Math.Ceiling(finalSize.Deflate(padding).Width), double.PositiveInfinity); _textLayout?.Dispose(); @@ -727,31 +737,36 @@ namespace Avalonia.Controls if (HasComplexContent) { - var currentY = padding.Top; + ArrangeComplexContent(TextLayout, padding); + } - foreach (var textLine in TextLayout.TextLines) - { - var currentX = padding.Left + textLine.Start; + return finalSize; + } - foreach (var run in textLine.TextRuns) + private static void ArrangeComplexContent(TextLayout textLayout, Thickness padding) + { + var currentY = padding.Top; + + foreach (var textLine in textLayout.TextLines) + { + var currentX = padding.Left + textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) { - if (run is DrawableTextRun drawable) + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) { - if (drawable is EmbeddedControlRun controlRun - && controlRun.Control is Control control) - { - control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); - } - - currentX += drawable.Size.Width; + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); } - } - currentY += textLine.Height; + currentX += drawable.Size.Width; + } } - } - return finalSize; + currentY += textLine.Height; + } } protected override AutomationPeer OnCreateAutomationPeer() @@ -892,7 +907,7 @@ namespace Avalonia.Controls return textRun; } - return null; + return new TextEndOfParagraph(); } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index 954169f975..8a2d4ecc6b 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -660,6 +660,90 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [Fact] + public void Should_Return_Null_For_Empty_TextSource() + { + using (Start()) + { + var defaultRunProperties = new GenericTextRunProperties(Typeface.Default); + var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties); + var textSource = new EmptyTextSource(); + + var textLine = TextFormatter.Current.FormatLine(textSource, 0, double.PositiveInfinity, paragraphProperties); + + Assert.Null(textLine); + } + } + + [Fact] + public void Should_Retain_TextEndOfParagraph_With_TextWrapping() + { + using (Start()) + { + var defaultRunProperties = new GenericTextRunProperties(Typeface.Default); + var paragraphProperties = new GenericTextParagraphProperties(defaultRunProperties, textWrap: TextWrapping.Wrap); + + var text = "Hello World"; + + var textSource = new SimpleTextSource(text, defaultRunProperties); + + var pos = 0; + + TextLineBreak previousLineBreak = null; + TextLine textLine = null; + + while (pos < text.Length) + { + textLine = TextFormatter.Current.FormatLine(textSource, pos, 30, paragraphProperties, previousLineBreak); + + pos += textLine.Length; + + previousLineBreak = textLine.TextLineBreak; + } + + Assert.NotNull(textLine); + + Assert.NotNull(textLine.TextLineBreak.TextEndOfLine); + } + } + + protected readonly record struct SimpleTextSource : ITextSource + { + private readonly string _text; + private readonly TextRunProperties _defaultProperties; + + public SimpleTextSource(string text, TextRunProperties defaultProperties) + { + _text = text; + _defaultProperties = defaultProperties; + } + + public TextRun? GetTextRun(int textSourceIndex) + { + if (textSourceIndex > _text.Length) + { + return new TextEndOfParagraph(); + } + + var runText = _text.AsMemory(textSourceIndex); + + if (runText.IsEmpty) + { + return new TextEndOfParagraph(); + } + + return new TextCharacters(runText, _defaultProperties); + } + } + + private class EmptyTextSource : ITextSource + { + public TextRun GetTextRun(int textSourceIndex) + { + return null; + } + } + private class EndOfLineTextSource : ITextSource { public TextRun GetTextRun(int textSourceIndex) diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 2b63f24cf6..3735e9f6d7 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -9,7 +9,6 @@ using Avalonia.Media.TextFormatting.Unicode; using Avalonia.UnitTests; using Avalonia.Utilities; using Xunit; - namespace Avalonia.Skia.UnitTests.Media.TextFormatting { public class TextLayoutTests @@ -1028,6 +1027,65 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting } } + [InlineData("mgfg🧐df f sdf", "g🧐d", 20, 40)] + [InlineData("وه. وقد تعرض لانتقادات", "دات", 5, 30)] + [InlineData("وه. وقد تعرض لانتقادات", "تعرض", 20, 50)] + [InlineData(" علمية 😱ومضللة ،", " علمية 😱ومضللة ،", 40, 100)] + [InlineData("في عام 2018 ، رفعت ل", "في عام 2018 ، رفعت ل", 100, 120)] + [Theory] + public void HitTestTextRange_Range_ValidLength(string text, string textToSelect, double minWidth, double maxWidth) + { + using (Start()) + { + var layout = new TextLayout(text, Typeface.Default, 12, Brushes.Black); + var start = text.IndexOf(textToSelect); + var selectionRectangles = layout.HitTestTextRange(start, textToSelect.Length); + Assert.Equal(1, selectionRectangles.Count()); + var rect = selectionRectangles.First(); + Assert.InRange(rect.Width, minWidth, maxWidth); + } + } + + [InlineData("012🧐210", 2, 4, FlowDirection.LeftToRight, "14.40234375,40.8046875")] + [InlineData("210🧐012", 2, 4, FlowDirection.RightToLeft, "0,7.201171875;21.603515625,33.603515625;48.005859375,55.20703125")] + [InlineData("שנב🧐שנב", 2, 4, FlowDirection.LeftToRight, "11.63671875,39.779296875")] + [InlineData("שנב🧐שנב", 2, 4, FlowDirection.RightToLeft, "11.63671875,39.779296875")] + [Theory] + public void Should_HitTextTextRangeBetweenRuns(string text, int start, int length, + FlowDirection flowDirection, string expected) + { + using (Start()) + { + var expectedRects = expected.Split(';').Select(x => + { + var startEnd = x.Split(','); + + var start = double.Parse(startEnd[0], CultureInfo.InvariantCulture); + + var end = double.Parse(startEnd[1], CultureInfo.InvariantCulture); + + return new Rect(start, 0, end - start, 0); + }).ToArray(); + + var textLayout = new TextLayout(text, Typeface.Default, 12, Brushes.Black, flowDirection: flowDirection); + + var rects = textLayout.HitTestTextRange(start, length).ToArray(); + + Assert.Equal(expectedRects.Length, rects.Length); + + var endX = textLayout.TextLines[0].GetDistanceFromCharacterHit(new CharacterHit(2)); + var startX = textLayout.TextLines[0].GetDistanceFromCharacterHit(new CharacterHit(5, 1)); + + for (int i = 0; i < expectedRects.Length; i++) + { + var expectedRect = expectedRects[i]; + + Assert.Equal(expectedRect.Left, rects[i].Left); + + Assert.Equal(expectedRect.Right, rects[i].Right); + } + } + } private static IDisposable Start() diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 544b84912e..e47542af7a 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -604,19 +604,19 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textBounds = textLine.GetTextBounds(0, 20); - Assert.Equal(2, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(144.0234375, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(0, 30); - Assert.Equal(3, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(216.03515625, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(0, 40); - Assert.Equal(4, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); } @@ -847,7 +847,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textBounds = textLine.GetTextBounds(0, textLine.Length); - Assert.Equal(6, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(0, 1); @@ -857,7 +857,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textBounds = textLine.GetTextBounds(0, firstRun.Length + 1); - Assert.Equal(2, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width)); textBounds = textLine.GetTextBounds(1, firstRun.Length); @@ -867,7 +867,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting textBounds = textLine.GetTextBounds(0, 1 + firstRun.Length); - Assert.Equal(2, textBounds.Count); + Assert.Equal(1, textBounds.Count); Assert.Equal(firstRun.Size.Width + 14, textBounds.Sum(x => x.Rectangle.Width)); } } @@ -958,6 +958,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width); Assert.Equal(7.201171875, textBounds[0].Rectangle.Width); + Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right); Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left); From 84cd896cd80a39668fee46c16285f3c0f102efa4 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 2 Feb 2023 17:49:14 +0100 Subject: [PATCH 023/120] fix: Waring CS0618 ListItemAutomationPeer --- .../Automation/Peers/ListItemAutomationPeer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs index 85f139a6a3..aea91b5e26 100644 --- a/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ListItemAutomationPeer.cs @@ -1,5 +1,4 @@ -using System; -using Avalonia.Automation.Provider; +using Avalonia.Automation.Provider; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Selection; @@ -64,7 +63,7 @@ namespace Avalonia.Automation.Peers if (Owner.Parent is ItemsControl parent && parent.GetValue(ListBox.SelectionProperty) is ISelectionModel selectionModel) { - var index = parent.ItemContainerGenerator.IndexFromContainer(Owner); + var index = parent.IndexFromContainer(Owner); if (index != -1) selectionModel.Deselect(index); From 5ff8dc97fd8949fdb43dcb2a9dfbfa8163a5c28f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 21 Jan 2023 11:42:52 +0100 Subject: [PATCH 024/120] fix: Linux Framebuffer nullable --- .../Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs | 2 +- src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 2dcce12df9..c3e90f5fd7 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -154,7 +154,7 @@ public static class LinuxFramebufferPlatformExtensions var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend); builder.SetupWithLifetime(lifetime); lifetime.Start(args); - builder.Instance.Run(lifetime.Token); + builder.Instance!.Run(lifetime.Token); return lifetime.ExitCode; } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index d61dcd4f91..0135cb3d1f 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -43,13 +43,13 @@ namespace Avalonia.LinuxFramebuffer.Output public IPlatformGraphics PlatformGraphics { get; private set; } public DrmOutput(DrmCard card, DrmResources resources, DrmConnector connector, DrmModeInfo modeInfo, - DrmOutputOptions? options = null) + DrmOutputOptions options = null) { if(options != null) _outputOptions = options; Init(card, resources, connector, modeInfo); } - public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null) + public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions options = null) { if(options != null) _outputOptions = options; @@ -63,7 +63,7 @@ namespace Avalonia.LinuxFramebuffer.Output if(connector == null) throw new InvalidOperationException("Unable to find connected DRM connector"); - DrmModeInfo? mode = null; + DrmModeInfo mode = null; if (options?.VideoMode != null) { From 3913fb333eadab53f070e4def78f59e52917a80a Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:31 -0500 Subject: [PATCH 025/120] Implement SelectedValue --- .../Primitives/SelectingItemsControl.cs | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..2fac60c8d8 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Xml.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; +using Avalonia.Controls.Utils; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; @@ -66,6 +67,19 @@ namespace Avalonia.Controls.Primitives (o, v) => o.SelectedItem = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueProperty = + AvaloniaProperty.Register(nameof(SelectedValue), + defaultBindingMode: BindingMode.TwoWay); + + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueBindingProperty = + AvaloniaProperty.Register(nameof(SelectedValueBinding)); + /// /// Defines the property. /// @@ -129,6 +143,8 @@ namespace Avalonia.Controls.Primitives private bool _ignoreContainerSelectionChanged; private UpdateState? _updateState; private bool _hasScrolledToSelectedItem; + private BindingHelper? _bindingHelper; + private bool _isSelectionChangeActive; /// /// Initializes static members of the class. @@ -209,6 +225,19 @@ namespace Avalonia.Controls.Primitives } } + [AssignBinding] + public IBinding? SelectedValueBinding + { + get => GetValue(SelectedValueBindingProperty); + set => SetValue(SelectedValueBindingProperty, value); + } + + public object? SelectedValue + { + get => GetValue(SelectedValueProperty); + set => SetValue(SelectedValueProperty, value); + } + /// /// Gets or sets the selected items. /// @@ -609,6 +638,60 @@ namespace Avalonia.Controls.Primitives { WrapFocus = WrapSelection; } + else if (change.Property == SelectedValueProperty) + { + if (_isSelectionChangeActive) + return; + + if (_updateState is not null) + { + _updateState.SelectedValue = change.NewValue; + return; + } + + SelectItemWithValue(change.NewValue); + } + else if (change.Property == SelectedValueBindingProperty) + { + var idx = SelectedIndex; + + // If no selection is active, don't do anything as SelectedValue is already null + if (idx == -1) + { + return; + } + + var value = change.GetNewValue(); + if (value is null) + { + // Clearing SelectedValueBinding makes the SelectedValue the item itself + SelectedValue = SelectedItem; + return; + } + + var selectedItem = SelectedItem; + + try + { + _isSelectionChangeActive = true; + + if (_bindingHelper is null) + { + _bindingHelper = new BindingHelper(value); + } + else + { + _bindingHelper.UpdateBinding(value); + } + + // Re-evaluate SelectedValue with the new binding + SelectedValue = _bindingHelper.Evaluate(selectedItem); + } + finally + { + _isSelectionChangeActive = false; + } + } } /// @@ -815,6 +898,10 @@ namespace Avalonia.Controls.Primitives new BindingValue(SelectedItems)); _oldSelectedItems = SelectedItems; } + else if (e.PropertyName == nameof(ISelectionModel.Source)) + { + ClearValue(SelectedValueProperty); + } } /// @@ -845,6 +932,11 @@ namespace Avalonia.Controls.Primitives Mark(i, false); } + if (!_isSelectionChangeActive) + { + UpdateSelectedValueFromItem(); + } + var route = BuildEventRoute(SelectionChangedEvent); if (route.HasHandlers) @@ -871,6 +963,109 @@ namespace Avalonia.Controls.Primitives } } + private void SelectItemWithValue(object? value) + { + if (ItemCount == 0 || _isSelectionChangeActive) + return; + + try + { + _isSelectionChangeActive = true; + var si = FindItemWithValue(value); + if (si != AvaloniaProperty.UnsetValue) + { + SelectedItem = si; + } + else + { + SelectedItem = null; + } + } + finally + { + _isSelectionChangeActive = false; + } + } + + private object FindItemWithValue(object? value) + { + if (ItemCount == 0 || value is null) + { + return AvaloniaProperty.UnsetValue; + } + + var items = Items; + var binding = SelectedValueBinding; + + if (binding is null) + { + // No SelectedValueBinding set, SelectedValue is the item itself + // Still verify the value passed in is in the Items list + var index = items!.IndexOf(value); + + if (index >= 0) + { + return value; + } + else + { + return AvaloniaProperty.UnsetValue; + } + } + + _bindingHelper ??= new BindingHelper(binding); + + // Matching UWP behavior, if duplicates are present, return the first item matching + // the SelectedValue provided + foreach (var item in items!) + { + var itemValue = _bindingHelper.Evaluate(item); + + if (itemValue.Equals(value)) + { + return item; + } + } + + return AvaloniaProperty.UnsetValue; + } + + private void UpdateSelectedValueFromItem() + { + if (_isSelectionChangeActive) + return; + + var binding = SelectedValueBinding; + var item = SelectedItem; + + if (binding is null || item is null) + { + // No SelectedValueBinding, SelectedValue is Item itself + try + { + _isSelectionChangeActive = true; + SelectedValue = item; + } + finally + { + _isSelectionChangeActive = false; + } + return; + } + + _bindingHelper ??= new BindingHelper(binding); + + try + { + _isSelectionChangeActive = true; + SelectedValue = _bindingHelper.Evaluate(item); + } + finally + { + _isSelectionChangeActive = false; + } + } + private void AutoScrollToSelectedItemIfNecessary() { if (AutoScrollToSelectedItem && @@ -1037,6 +1232,13 @@ namespace Avalonia.Controls.Primitives Selection.Clear(); } + if (state.SelectedValue.HasValue) + { + var item = FindItemWithValue(state.SelectedValue.Value); + if (item != AvaloniaProperty.UnsetValue) + state.SelectedItem = item; + } + if (state.SelectedIndex.HasValue) { SelectedIndex = state.SelectedIndex.Value; @@ -1098,6 +1300,7 @@ namespace Avalonia.Controls.Primitives { private Optional _selectedIndex; private Optional _selectedItem; + private Optional _selectedValue; public int UpdateCount { get; set; } public Optional Selection { get; set; } @@ -1122,6 +1325,54 @@ namespace Avalonia.Controls.Primitives _selectedIndex = default; } } + + public Optional SelectedValue + { + get => _selectedValue; + set + { + _selectedValue = value; + } + } + } + + /// + /// Helper class for evaluating a binding from an Item and IBinding instance + /// + private class BindingHelper : StyledElement + { + public BindingHelper(IBinding binding) + { + UpdateBinding(binding); + } + + public static readonly StyledProperty ValueProperty = + AvaloniaProperty.Register("Value"); + + public object Evaluate(object? dataContext) + { + dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); + + // Only update the DataContext if necessary + if (!dataContext.Equals(DataContext)) + DataContext = dataContext; + + return GetValue(ValueProperty); + } + + public void UpdateBinding(IBinding binding) + { + _lastBinding = binding; + var ib = binding.Initiate(this, ValueProperty); + if (ib is null) + { + throw new InvalidOperationException("Unable to create binding"); + } + + BindingOperations.Apply(this, ValueProperty, ib, null); + } + + private IBinding? _lastBinding; } } } From b9c666e1935182c657436c9e8cd909a262e2cca8 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:38 -0500 Subject: [PATCH 026/120] Add Tests --- ...electingItemsControlTests_SelectedValue.cs | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs new file mode 100644 index 0000000000..df81b1faae --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Primitives +{ + public class SelectingItemsControlTests_SelectedValue + { + [Fact] + public void Setting_SelectedItem_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItem = items[0]; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedIndex_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedItems_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new ListBox + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItems = new List + { + items[1], + items[3], + items[4] + }; + + // When interacting, SelectedItem is the first item in the SelectedItems collection + // But when set here, it's the last + Assert.Equal(items[4].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedIndex() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = items[1].Name; + + Assert.Equal(1, sic.SelectedIndex); + } + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedItem() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = "Item2"; + + Assert.Equal(items[1], sic.SelectedItem); + } + } + + [Fact] + public void Changing_SelectedValueBinding_Updates_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedValue = "Item2"; + + sic.SelectedValueBinding = new Binding("AltProperty"); + + // Ensure SelectedItem didn't change + Assert.Equal(items[1], sic.SelectedItem); + + + Assert.Equal("Alt2", sic.SelectedValue); + } + } + + [Fact] + public void SelectedValue_With_Null_SelectedValueBinding_Is_Item() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0], sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.EndInit(); + + Assert.Equal(items[1].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.SelectedValue = "Item1"; + sic.EndInit(); + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Changing_Items_Should_Clear_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + Prepare(sic); + + sic.Items = new List + { + new TestClass("NewItem", string.Empty) + }; + + Assert.Equal(null, sic.SelectedValue); + } + } + + [Fact] + public void Setting_SelectedValue_Should_Raise_SelectionChanged_Event() + { + // Unlike SelectedIndex/SelectedItem tests, we need the ItemsControl to + // initialize so that SelectedValue can actually be looked up + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + }; + + Prepare(sic); + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.AddedItems.Cast().Single()); + Assert.Empty(e.RemovedItems); + called = true; + }; + + sic.SelectedValue = "Item2"; + Assert.True(called); + } + } + + [Fact] + public void Clearing_SelectedValue_Should_Raise_SelectionChanged_Event() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.RemovedItems.Cast().Single()); + Assert.Empty(e.AddedItems); + called = true; + }; + + sic.SelectedValue = null; + Assert.True(called); + } + + private static FuncControlTemplate Template() + { + return new FuncControlTemplate((control, scope) => + new ItemsPresenter + { + Name = "itemsPresenter", + [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], + }.RegisterInNameScope(scope)); + } + + private static void Prepare(SelectingItemsControl target) + { + var root = new TestRoot + { + Child = target, + Width = 100, + Height = 100, + Styles = + { + new Style(x => x.Is()) + { + Setters = + { + new Setter(ListBox.TemplateProperty, Template()), + }, + }, + }, + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + } + } + + internal class TestClass + { + public TestClass(string name, string alt) + { + Name = name; + AltProperty = alt; + } + + public string Name { get; set; } + + public string AltProperty { get; set; } + + public static List GetItems() + { + return new List + { + new TestClass("Item1", "Alt1"), + new TestClass("Item2", "Alt2"), + new TestClass("Item3", "Alt3"), + new TestClass("Item4", "Alt4"), + new TestClass("Item5", "Alt5"), + }; + } + } +} + + From a8cb64160957920b85c9c865753c42f829a5b294 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 18:02:32 -0500 Subject: [PATCH 027/120] Missing xml docs --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 2fac60c8d8..67ad1963ff 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -225,6 +225,10 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets the instance used to obtain the + /// property + /// [AssignBinding] public IBinding? SelectedValueBinding { @@ -232,6 +236,10 @@ namespace Avalonia.Controls.Primitives set => SetValue(SelectedValueBindingProperty, value); } + /// + /// Gets or sets the value of the selected item, obtained using + /// + /// public object? SelectedValue { get => GetValue(SelectedValueProperty); From 07adf1d6dc82bf776b2adef730797fd585662def Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 3 Feb 2023 09:12:18 +0100 Subject: [PATCH 028/120] Try a different font --- .../Assets/NotoSansHebrew-Regular.ttf | Bin 0 -> 42284 bytes .../Avalonia.Skia.RenderTests.csproj | 2 +- .../Avalonia.Skia.UnitTests.csproj | 2 +- .../Media/CustomFontManagerImpl.cs | 4 +++- 4 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 tests/Avalonia.RenderTests/Assets/NotoSansHebrew-Regular.ttf diff --git a/tests/Avalonia.RenderTests/Assets/NotoSansHebrew-Regular.ttf b/tests/Avalonia.RenderTests/Assets/NotoSansHebrew-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..703cfa472d19b1ee2b371da8b88e69caf2ba9c26 GIT binary patch literal 42284 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 literal 0 HcmV?d00001 diff --git a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj index ba45bbbc2e..0d182678ef 100644 --- a/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj +++ b/tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj @@ -8,7 +8,7 @@ - + diff --git a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj index ea91b8c196..86a680fac5 100644 --- a/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj +++ b/tests/Avalonia.Skia.UnitTests/Avalonia.Skia.UnitTests.csproj @@ -9,7 +9,7 @@ - + diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index a748f6cf00..24ecb21d18 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -17,6 +17,8 @@ namespace Avalonia.Skia.UnitTests.Media new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Mono"); private readonly Typeface _arabicTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Arabic"); + private readonly Typeface _hebrewTypeface = + new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans Hebrew"); private readonly Typeface _italicTypeface = new Typeface("resm:Avalonia.Skia.UnitTests.Assets?assembly=Avalonia.Skia.UnitTests#Noto Sans", FontStyle.Italic); private readonly Typeface _emojiTypeface = @@ -24,7 +26,7 @@ namespace Avalonia.Skia.UnitTests.Media public CustomFontManagerImpl() { - _customTypefaces = new[] { _emojiTypeface, _italicTypeface, _arabicTypeface, _defaultTypeface }; + _customTypefaces = new[] { _emojiTypeface, _italicTypeface, _arabicTypeface, _hebrewTypeface, _defaultTypeface }; _defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName; } From 467ef300bf559123f3dc355185ad78efdf0675ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Feb 2023 10:43:26 +0100 Subject: [PATCH 029/120] Remove unneeded method. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index f36a96992b..a758360545 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -380,23 +380,6 @@ namespace Avalonia.PropertyStore } } - /// - /// Called by non-LocalValue binding entries to re-evaluate the effective value when the - /// binding produces an unset value. - /// - /// The bound property. - /// The priority of binding which produced a new value. - public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority) - { - Debug.Assert(priority != BindingPriority.LocalValue); - - if (TryGetEffectiveValue(property, out var existing)) - { - if (priority <= existing.Priority) - ReevaluateEffectiveValue(property, existing); - } - } - /// /// Called by a when its /// state changes. From 42f8c58aef5930cb5ec3def93c981c39beab5143 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 3 Feb 2023 10:45:53 +0100 Subject: [PATCH 030/120] Update teste for the new typeface --- .../Media/TextFormatting/TextCharacters.cs | 21 +++++-------------- .../Media/CustomFontManagerImpl.cs | 6 ++++++ .../Media/TextFormatting/TextLayoutTests.cs | 14 ++++++------- .../Media/TextFormatting/TextLineTests.cs | 14 ++++++------- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 82cf3297fd..c9dafaced7 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -82,24 +82,15 @@ namespace Avalonia.Media.TextFormatting var previousGlyphTypeface = previousProperties?.CachedGlyphTypeface; var textSpan = text.Span; - if (TryGetShapeableLength(textSpan, defaultGlyphTypeface, null, out var count, out var script)) + if (TryGetShapeableLength(textSpan, defaultGlyphTypeface, null, out var count)) { - if (script == Script.Common && previousGlyphTypeface is not null) - { - if (TryGetShapeableLength(textSpan, previousGlyphTypeface, null, out var fallbackCount, out _)) - { - return new UnshapedTextRun(text.Slice(0, fallbackCount), - defaultProperties.WithTypeface(previousTypeface!.Value), biDiLevel); - } - } - return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(defaultTypeface), biDiLevel); } if (previousGlyphTypeface is not null) { - if (TryGetShapeableLength(textSpan, previousGlyphTypeface, defaultGlyphTypeface, out count, out _)) + if (TryGetShapeableLength(textSpan, previousGlyphTypeface, defaultGlyphTypeface, out count)) { return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(previousTypeface!.Value), biDiLevel); @@ -130,7 +121,7 @@ namespace Avalonia.Media.TextFormatting var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count, out _)) + if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) { //Fallback found return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), @@ -160,17 +151,15 @@ namespace Avalonia.Media.TextFormatting /// The typeface that is used to find matching characters. /// The default typeface. /// The shapeable length. - /// /// internal static bool TryGetShapeableLength( ReadOnlySpan text, IGlyphTypeface glyphTypeface, IGlyphTypeface? defaultGlyphTypeface, - out int length, - out Script script) + out int length) { length = 0; - script = Script.Unknown; + var script = Script.Unknown; if (text.IsEmpty) { diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs index 24ecb21d18..5a6d7f2cdf 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs @@ -90,6 +90,12 @@ namespace Avalonia.Skia.UnitTests.Media skTypeface = typefaceCollection.Get(typeface); break; } + case "Noto Sans Hebrew": + { + var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_hebrewTypeface.FontFamily); + skTypeface = typefaceCollection.Get(typeface); + break; + } case FontFamily.DefaultFontFamilyName: case "Noto Mono": { diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs index 3735e9f6d7..9a7460c218 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs @@ -724,7 +724,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var selectedRect = rects[0]; - Assert.Equal(selectedText.Bounds.Width, selectedRect.Width); + Assert.Equal(selectedText.Bounds.Width, selectedRect.Width, 2); } } @@ -885,7 +885,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = hitRange.First().Left; - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 2); currentX += advance; } @@ -915,7 +915,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var distance = hitRange.First().Left + 0.5; - Assert.Equal(currentX, distance); + Assert.Equal(currentX, distance, 2); currentX += advance; } @@ -1048,8 +1048,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting [InlineData("012🧐210", 2, 4, FlowDirection.LeftToRight, "14.40234375,40.8046875")] [InlineData("210🧐012", 2, 4, FlowDirection.RightToLeft, "0,7.201171875;21.603515625,33.603515625;48.005859375,55.20703125")] - [InlineData("שנב🧐שנב", 2, 4, FlowDirection.LeftToRight, "11.63671875,39.779296875")] - [InlineData("שנב🧐שנב", 2, 4, FlowDirection.RightToLeft, "11.63671875,39.779296875")] + [InlineData("שנב🧐שנב", 2, 4, FlowDirection.LeftToRight, "11.268,38.208")] + [InlineData("שנב🧐שנב", 2, 4, FlowDirection.RightToLeft, "11.268,38.208")] [Theory] public void Should_HitTextTextRangeBetweenRuns(string text, int start, int length, FlowDirection flowDirection, string expected) @@ -1080,9 +1080,9 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting { var expectedRect = expectedRects[i]; - Assert.Equal(expectedRect.Left, rects[i].Left); + Assert.Equal(expectedRect.Left, rects[i].Left, 2); - Assert.Equal(expectedRect.Right, rects[i].Right); + Assert.Equal(expectedRect.Right, rects[i].Right, 2); } } } diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index e47542af7a..70e74cdf83 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -658,7 +658,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(TextTestHelper.GetStartCharIndex(run.Text), bounds.TextSourceCharacterIndex); Assert.Equal(run, bounds.TextRun); - Assert.Equal(run.Size.Width, bounds.Rectangle.Width); + Assert.Equal(run.Size.Width, bounds.Rectangle.Width, 2); } for (var i = 0; i < textBounds.Count; i++) @@ -667,19 +667,19 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting if (lastBounds != null) { - Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left); + Assert.Equal(lastBounds.Rectangle.Right, currentBounds.Rectangle.Left, 2); } var sumOfRunWidth = currentBounds.TextRunBounds.Sum(x => x.Rectangle.Width); - Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width); + Assert.Equal(sumOfRunWidth, currentBounds.Rectangle.Width, 2); lastBounds = currentBounds; } var sumOfBoundsWidth = textBounds.Sum(x => x.Rectangle.Width); - Assert.Equal(lineWidth, sumOfBoundsWidth); + Assert.Equal(lineWidth, sumOfBoundsWidth, 2); } } @@ -959,14 +959,14 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting Assert.Equal(secondRun.Size.Width, textBounds[1].Rectangle.Width); Assert.Equal(7.201171875, textBounds[0].Rectangle.Width); - Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right); - Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left); + Assert.Equal(textLine.Start + 7.201171875, textBounds[0].Rectangle.Right, 2); + Assert.Equal(textLine.Start + firstRun.Size.Width, textBounds[1].Rectangle.Left, 2); textBounds = textLine.GetTextBounds(0, text.Length); Assert.Equal(2, textBounds.Count); Assert.Equal(7, textBounds.Sum(x => x.TextRunBounds.Sum(x => x.Length))); - Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width)); + Assert.Equal(textLine.WidthIncludingTrailingWhitespace, textBounds.Sum(x => x.Rectangle.Width), 2); } } From 73cede0bf5f36c72ae918e758858b5b815c11bc3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Feb 2023 13:24:12 +0100 Subject: [PATCH 031/120] Added integration tests for window transparency. Failing on macOS, passing on win32. --- samples/IntegrationTestApp/MainWindow.axaml | 54 ++++++----- .../IntegrationTestApp/MainWindow.axaml.cs | 91 +++++++++++++++++++ .../Avalonia.IntegrationTests.Appium.csproj | 1 + .../WindowTests.cs | 45 ++++++++- 4 files changed, 166 insertions(+), 25 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 54c0cb0655..b116e4c789 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -120,30 +120,36 @@ - - - - NonOwned - Owned - Modal - - - Manual - CenterScreen - CenterOwner - - - Normal - Minimized - Maximized - FullScreen - - - - - - - + + + + + NonOwned + Owned + Modal + + + Manual + CenterScreen + CenterOwner + + + Normal + Minimized + Maximized + FullScreen + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 841947673a..3cd5350cce 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -7,9 +7,13 @@ using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Media; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; using Microsoft.CodeAnalysis; +using Avalonia.Controls.Primitives; +using Avalonia.Threading; +using Avalonia.Controls.Primitives.PopupPositioning; namespace IntegrationTestApp { @@ -103,6 +107,89 @@ namespace IntegrationTestApp } } + private void ShowTransparentWindow() + { + // Show a background window to make sure the color behind the transparent window is + // a known color (green). + var backgroundWindow = new Window + { + Title = "Transparent Window Background", + Name = "TransparentWindowBackground", + Width = 300, + Height = 300, + Background = Brushes.Green, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + }; + + // This is the transparent window with a red circle. + var window = new Window + { + Title = "Transparent Window", + Name = "TransparentWindow", + SystemDecorations = SystemDecorations.None, + Background = Brushes.Transparent, + TransparencyLevelHint = WindowTransparencyLevel.Transparent, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Width = 200, + Height = 200, + Content = new Border + { + Background = Brushes.Red, + CornerRadius = new CornerRadius(100), + } + }; + + window.PointerPressed += (_, _) => + { + window.Close(); + backgroundWindow.Close(); + }; + + backgroundWindow.Show(this); + window.Show(backgroundWindow); + } + + private void ShowTransparentPopup() + { + var popup = new Popup + { + WindowManagerAddShadowHint = false, + PlacementMode = PlacementMode.AnchorAndGravity, + PlacementAnchor = PopupAnchor.Top, + PlacementGravity = PopupGravity.Bottom, + Width= 200, + Height= 200, + Child = new Border + { + Background = Brushes.Red, + CornerRadius = new CornerRadius(100), + } + }; + + // Show a background window to make sure the color behind the transparent window is + // a known color (green). + var backgroundWindow = new Window + { + Title = "Transparent Popup Background", + Name = "TransparentPopupBackground", + Width = 200, + Height = 200, + Background = Brushes.Green, + WindowStartupLocation = WindowStartupLocation.CenterOwner, + Content = new Border + { + Name = "PopupContainer", + Child = popup, + [AutomationProperties.AccessibilityViewProperty] = AccessibilityView.Content, + } + }; + + backgroundWindow.PointerPressed += (_, _) => backgroundWindow.Close(); + backgroundWindow.Show(this); + + popup.Open(); + } + private void SendToBack() { var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; @@ -175,6 +262,10 @@ namespace IntegrationTestApp this.Get("BasicListBox").SelectedIndex = -1; if (source?.Name == "MenuClickedMenuItemReset") this.Get("ClickedMenuItem").Text = "None"; + if (source?.Name == "ShowTransparentWindow") + ShowTransparentWindow(); + if (source?.Name == "ShowTransparentPopup") + ShowTransparentPopup(); if (source?.Name == "ShowWindow") ShowWindow(); if (source?.Name == "SendToBack") diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 57338a1e08..3ff91139f1 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -16,4 +16,5 @@ + diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 4d833cdb1f..3b74ed314b 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,11 +1,14 @@ using System; +using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using Avalonia.Controls; +using Avalonia.Media.Imaging; using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; +using SixLabors.ImageSharp.PixelFormats; using Xunit; using Xunit.Sdk; @@ -141,7 +144,6 @@ namespace Avalonia.IntegrationTests.Appium } } - [Theory] [InlineData(ShowWindowMode.NonOwned)] [InlineData(ShowWindowMode.Owned)] @@ -187,6 +189,47 @@ namespace Avalonia.IntegrationTests.Appium } } + [Fact] + public void TransparentWindow() + { + var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentWindow"); + showTransparentWindow.Click(); + Thread.Sleep(1000); + + var window = _session.FindElementByAccessibilityId("TransparentWindow"); + var screenshot = window.GetScreenshot(); + + window.Click(); + + var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray); + var topLeftColor = img[1, 1]; + var centerColor = img[img.Width / 2, img.Height / 2]; + + Assert.Equal(new Rgba32(0, 128, 0), topLeftColor); + Assert.Equal(new Rgba32(255, 0, 0), centerColor); + } + + [Fact] + public void TransparentPopup() + { + var showTransparentWindow = _session.FindElementByAccessibilityId("ShowTransparentPopup"); + showTransparentWindow.Click(); + Thread.Sleep(1000); + + var window = _session.FindElementByAccessibilityId("TransparentPopupBackground"); + var container = window.FindElementByAccessibilityId("PopupContainer"); + var screenshot = container.GetScreenshot(); + + window.Click(); + + var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray); + var topLeftColor = img[1, 1]; + var centerColor = img[img.Width / 2, img.Height / 2]; + + Assert.Equal(new Rgba32(0, 128, 0), topLeftColor); + Assert.Equal(new Rgba32(255, 0, 0), centerColor); + } + public static TheoryData StartupLocationData() { var sizes = new Size?[] { null, new Size(400, 300) }; From 6210018f3b5e98063252d2bf2933e1739ed17f85 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 3 Feb 2023 14:10:16 +0100 Subject: [PATCH 032/120] Propertly handle an InlineUIContainer's logical children --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 2 +- .../Utils/TreeHelper.cs | 2 +- .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- src/Avalonia.Controls/Control.cs | 4 ---- .../Documents/InlineUIContainer.cs | 18 ++++++++++++++++++ .../Platform/DefaultMenuInteractionHandler.cs | 2 +- .../Primitives/OverlayPopupHost.cs | 2 +- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- src/Avalonia.Controls/Primitives/PopupRoot.cs | 4 ++-- src/Avalonia.Controls/ProgressBar.cs | 2 +- src/Avalonia.Controls/TextBlock.cs | 4 ---- 11 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index f35124ee0a..91b65a1f72 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -3979,7 +3979,7 @@ namespace Avalonia.Controls { if (focusedObject is Control element) { - parent = element.Parent; + parent = element.VisualParent; if (parent != null) { dataGridWillReceiveRoutedEvent = false; diff --git a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs index f4ba644ae6..6aebf05d6b 100644 --- a/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs +++ b/src/Avalonia.Controls.DataGrid/Utils/TreeHelper.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.Utils { if (child is Control childElement) { - parent = childElement.Parent; + parent = childElement.VisualParent; } } child = parent; diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 98885e11ca..55649660f7 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -792,7 +792,7 @@ namespace Avalonia.Controls Control? element = focused as Control; if (element != null) { - parent = element.Parent; + parent = element.VisualParent; } } focused = parent; diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ed24c3c7c2..ab7c9948c4 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -2,14 +2,12 @@ using System; using System.Collections.Generic; using System.ComponentModel; using Avalonia.Automation.Peers; -using Avalonia.Controls.Documents; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; -using Avalonia.Media; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Threading; @@ -211,8 +209,6 @@ namespace Avalonia.Controls remove => RemoveHandler(SizeChangedEvent, value); } - public new Control? Parent => (Control?)base.Parent; - /// bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null; diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs index 58afb24b5c..f06c8515ee 100644 --- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs +++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs @@ -64,5 +64,23 @@ namespace Avalonia.Controls.Documents internal override void AppendText(StringBuilder stringBuilder) { } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ChildProperty) + { + if(change.OldValue is Control oldChild) + { + LogicalChildren.Remove(oldChild); + } + + if(change.NewValue is Control newChild) + { + LogicalChildren.Add(newChild); + } + } + } } } diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index de3aca76d9..4dd868253e 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -553,7 +553,7 @@ namespace Avalonia.Controls.Platform } } - protected static IMenuItem? GetMenuItem(Control? item) + protected static IMenuItem? GetMenuItem(StyledElement? item) { while (true) { diff --git a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs index e265f4eb6a..d466edeb33 100644 --- a/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs +++ b/src/Avalonia.Controls/Primitives/OverlayPopupHost.cs @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Primitives set { /* Not currently supported in overlay popups */ } } - protected internal override Interactive? InteractiveParent => Parent; + protected internal override Interactive? InteractiveParent => (Interactive?)VisualParent; public void Dispose() => Hide(); diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index c85199a665..3b68cd2ae8 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -723,7 +723,7 @@ namespace Avalonia.Controls.Primitives while (e is object && (!e.Focusable || !e.IsEffectivelyEnabled || !e.IsVisible)) { - e = e.Parent; + e = e.VisualParent as Control; } if (e is object) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 57ec864cad..b3436d4176 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -72,12 +72,12 @@ namespace Avalonia.Controls.Primitives /// /// Popup events are passed to their parent window. This facilitates this. /// - protected internal override Interactive? InteractiveParent => Parent; + protected internal override Interactive? InteractiveParent => (Interactive?)Parent; /// /// Gets the control that is hosting the popup root. /// - Visual? IHostedVisualTreeRoot.Host => Parent; + Visual? IHostedVisualTreeRoot.Host => VisualParent; /// /// Gets the styling parent of the popup root. diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 7e0d695264..ce3158b282 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls private void UpdateIndicator() { // Gets the size of the parent indicator container - var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size; + var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size; if (_indicator != null) { diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index df9a3eb8f3..ec31470126 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -673,8 +673,6 @@ namespace Avalonia.Controls controlRun.Control is Control control) { VisualChildren.Remove(control); - - LogicalChildren.Remove(control); } } } @@ -693,8 +691,6 @@ namespace Avalonia.Controls { VisualChildren.Add(control); - LogicalChildren.Add(control); - control.Measure(Size.Infinity); } } From 93396ea6d870aa7eb58e525aaf9b4f127d04507f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 3 Feb 2023 15:20:51 +0100 Subject: [PATCH 033/120] perf: Animatable small adjust - delegate caching - try to partially avoid changing the Transitions property during subscribe/unsubscribe operations --- src/Avalonia.Base/Animation/Animatable.cs | 32 ++++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index eddb89c3e8..50d9a48e9e 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -29,6 +29,12 @@ namespace Avalonia.Animation private bool _transitionsEnabled = true; private bool _isSubscribedToTransitionsCollection = false; private Dictionary? _transitionState; + readonly NotifyCollectionChangedEventHandler _collectionChanged; + + public Animatable() + { + _collectionChanged = TransitionsCollectionChanged; + } /// /// Gets or sets the clock which controls the animations on the control. @@ -61,14 +67,14 @@ namespace Avalonia.Animation { _transitionsEnabled = true; - if (Transitions is object) + if (Transitions is Transitions transitions) { if (!_isSubscribedToTransitionsCollection) { _isSubscribedToTransitionsCollection = true; - Transitions.CollectionChanged += TransitionsCollectionChanged; + transitions.CollectionChanged += _collectionChanged; } - AddTransitions(Transitions); + AddTransitions(transitions); } } } @@ -86,14 +92,14 @@ namespace Avalonia.Animation { _transitionsEnabled = false; - if (Transitions is object) + if (Transitions is Transitions transitions) { if (_isSubscribedToTransitionsCollection) { _isSubscribedToTransitionsCollection = false; - Transitions.CollectionChanged -= TransitionsCollectionChanged; + transitions.CollectionChanged -= _collectionChanged; } - RemoveTransitions(Transitions); + RemoveTransitions(transitions); } } } @@ -120,7 +126,7 @@ namespace Avalonia.Animation toAdd = newTransitions.Except(oldTransitions).ToList(); } - newTransitions.CollectionChanged += TransitionsCollectionChanged; + newTransitions.CollectionChanged += _collectionChanged; _isSubscribedToTransitionsCollection = true; AddTransitions(toAdd); } @@ -134,19 +140,19 @@ namespace Avalonia.Animation toRemove = oldTransitions.Except(newTransitions).ToList(); } - oldTransitions.CollectionChanged -= TransitionsCollectionChanged; + oldTransitions.CollectionChanged -= _collectionChanged; RemoveTransitions(toRemove); } } else if (_transitionsEnabled && - Transitions is object && + Transitions is Transitions transitions && _transitionState is object && !change.Property.IsDirect && change.Priority > BindingPriority.Animation) { - for (var i = Transitions.Count -1; i >= 0; --i) + for (var i = transitions.Count - 1; i >= 0; --i) { - var transition = Transitions[i]; + var transition = transitions[i]; if (transition.Property == change.Property && _transitionState.TryGetValue(transition, out var state)) @@ -166,11 +172,11 @@ namespace Avalonia.Animation { oldValue = animatedValue; } - + var clock = Clock ?? AvaloniaLocator.Current.GetRequiredService(); state.Instance?.Dispose(); state.Instance = transition.Apply( this, - Clock ?? AvaloniaLocator.Current.GetRequiredService(), + clock, oldValue, newValue); return; From eb1aa547a88a25813ec6dfa68cb336dff9d973b2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Feb 2023 15:22:57 +0100 Subject: [PATCH 034/120] Fix default transparency level. The macOS backend sets up a window with a transparency level of `None` but the managed code thinks it's `Transparent`, meaning that making the window transparent did nothing as it thinks it's already set. Fixes #8419 --- src/Avalonia.Native/WindowImplBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 1f290acd86..50bee0d395 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -501,7 +501,7 @@ namespace Avalonia.Native } } - public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent; + public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { From b1ca75a30b9cda3f91dbd9af4c2d89246d52b66f Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 3 Feb 2023 16:13:12 +0100 Subject: [PATCH 035/120] fix: Address review --- src/Avalonia.Base/Animation/Animatable.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index 50d9a48e9e..6a05dfbe6d 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -29,12 +29,9 @@ namespace Avalonia.Animation private bool _transitionsEnabled = true; private bool _isSubscribedToTransitionsCollection = false; private Dictionary? _transitionState; - readonly NotifyCollectionChangedEventHandler _collectionChanged; - - public Animatable() - { - _collectionChanged = TransitionsCollectionChanged; - } + private NotifyCollectionChangedEventHandler _collectionChanged; + private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler => + _collectionChanged ??= TransitionsCollectionChanged; /// /// Gets or sets the clock which controls the animations on the control. @@ -72,7 +69,7 @@ namespace Avalonia.Animation if (!_isSubscribedToTransitionsCollection) { _isSubscribedToTransitionsCollection = true; - transitions.CollectionChanged += _collectionChanged; + transitions.CollectionChanged += TransitionsCollectionChangedHandler; } AddTransitions(transitions); } @@ -97,7 +94,7 @@ namespace Avalonia.Animation if (_isSubscribedToTransitionsCollection) { _isSubscribedToTransitionsCollection = false; - transitions.CollectionChanged -= _collectionChanged; + transitions.CollectionChanged -= TransitionsCollectionChangedHandler; } RemoveTransitions(transitions); } @@ -126,7 +123,7 @@ namespace Avalonia.Animation toAdd = newTransitions.Except(oldTransitions).ToList(); } - newTransitions.CollectionChanged += _collectionChanged; + newTransitions.CollectionChanged += TransitionsCollectionChangedHandler; _isSubscribedToTransitionsCollection = true; AddTransitions(toAdd); } @@ -140,7 +137,7 @@ namespace Avalonia.Animation toRemove = oldTransitions.Except(newTransitions).ToList(); } - oldTransitions.CollectionChanged -= _collectionChanged; + oldTransitions.CollectionChanged -= TransitionsCollectionChangedHandler; RemoveTransitions(toRemove); } } From 41966d314fe8c0e7d8e82b513e285adfb3379c48 Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Fri, 3 Feb 2023 16:40:53 +0100 Subject: [PATCH 036/120] fix: Builld --- src/Avalonia.Base/Animation/Animatable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index 6a05dfbe6d..5208c8b218 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/src/Avalonia.Base/Animation/Animatable.cs @@ -29,7 +29,7 @@ namespace Avalonia.Animation private bool _transitionsEnabled = true; private bool _isSubscribedToTransitionsCollection = false; private Dictionary? _transitionState; - private NotifyCollectionChangedEventHandler _collectionChanged; + private NotifyCollectionChangedEventHandler? _collectionChanged; private NotifyCollectionChangedEventHandler TransitionsCollectionChangedHandler => _collectionChanged ??= TransitionsCollectionChanged; From 2bbaf74d8f05184b4d7873e526dc97935efd6096 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 00:13:25 +0100 Subject: [PATCH 037/120] Fix integration tests on macOS. Seems that the version of macOS on the test runner applies some shading to the top of the window. Check the color of a pixel lower down. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 3b74ed314b..7bb991aae6 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -202,7 +202,7 @@ namespace Avalonia.IntegrationTests.Appium window.Click(); var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray); - var topLeftColor = img[1, 1]; + var topLeftColor = img[10, 10]; var centerColor = img[img.Width / 2, img.Height / 2]; Assert.Equal(new Rgba32(0, 128, 0), topLeftColor); @@ -223,7 +223,7 @@ namespace Avalonia.IntegrationTests.Appium window.Click(); var img = SixLabors.ImageSharp.Image.Load(screenshot.AsByteArray); - var topLeftColor = img[1, 1]; + var topLeftColor = img[10, 10]; var centerColor = img[img.Width / 2, img.Height / 2]; Assert.Equal(new Rgba32(0, 128, 0), topLeftColor); From db78f7c8701c94f13794d6e9e5f7c872f89f4387 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Fri, 3 Feb 2023 21:18:55 -0500 Subject: [PATCH 038/120] Update angle to 2.1.0.2023020321 --- samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj | 1 - samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj | 1 - src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- .../Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj | 2 +- 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj index e4c83dca49..e465e9caf3 100644 --- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj +++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj @@ -31,7 +31,6 @@ - diff --git a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj index 1b83a3e567..a24e55de81 100644 --- a/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj +++ b/samples/MobileSandbox.Desktop/MobileSandbox.Desktop.csproj @@ -24,7 +24,6 @@ - diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index b7dca78845..a24fe31df8 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj index 0c0fe5b921..f3af312d1a 100644 --- a/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj +++ b/src/tools/Avalonia.Designer.HostApp/Avalonia.Designer.HostApp.csproj @@ -23,7 +23,7 @@ - + From 757f82c385c7f23b790b3d20e428c55b044c1317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 17:44:14 +0000 Subject: [PATCH 039/120] Updated browser packages to stable. --- .../ControlCatalog.Browser.Blazor.csproj | 4 ++-- .../Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj index d0fb614840..733a4b7194 100644 --- a/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj +++ b/samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj @@ -9,8 +9,8 @@ - - + + diff --git a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj index a9cad0538f..9017ce1546 100644 --- a/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj +++ b/src/Browser/Avalonia.Browser.Blazor/Avalonia.Browser.Blazor.csproj @@ -15,7 +15,7 @@ - + From d0fe9e0e14f3dbe72ece505ddb2d2ea88606d371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 18:06:42 +0000 Subject: [PATCH 040/120] Updated Skia and HarfBuzz to latest stable. --- build/HarfBuzzSharp.props | 6 +++--- build/SkiaSharp.props | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props index 620ec58ff3..75d317be1a 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,7 +1,7 @@  - - - + + + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 31619399f9..f45addaa2a 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,7 +1,7 @@  - - - + + + From 9fdf98dde6e4d466e9ff72bf9635f8fdad1455f6 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:18:35 -0500 Subject: [PATCH 041/120] Only use font fallback if match found --- .../Media/TextFormatting/TextCharacters.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index c9dafaced7..8480be3882 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -118,14 +118,18 @@ namespace Avalonia.Media.TextFormatting fontManager.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight, defaultTypeface.Stretch, defaultTypeface.FontFamily, defaultProperties.CultureInfo, out var fallbackTypeface); - - var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - - if (matchFound && TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) + + if (matchFound) { + var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); + //Fallback found - return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), - biDiLevel); + if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) + { + + return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), + biDiLevel); + } } // no fallback found From 1cfdae9448d9eac12bba5ac02ac859ab7e459134 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Sat, 4 Feb 2023 14:23:03 -0500 Subject: [PATCH 042/120] Cleanup --- src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index 8480be3882..b4734d702b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -121,12 +121,11 @@ namespace Avalonia.Media.TextFormatting if (matchFound) { + // Fallback found var fallbackGlyphTypeface = fontManager.GetOrAddGlyphTypeface(fallbackTypeface); - - //Fallback found + if (TryGetShapeableLength(textSpan, fallbackGlyphTypeface, defaultGlyphTypeface, out count)) - { - + { return new UnshapedTextRun(text.Slice(0, count), defaultProperties.WithTypeface(fallbackTypeface), biDiLevel); } From 571b7746eac6cc694aa586ec83d48ad5c9f4b9ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 22 Jan 2023 11:46:28 +0100 Subject: [PATCH 043/120] Added failing integration test. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + .../IntegrationTestApp/MainWindow.axaml.cs | 2 ++ .../WindowTests_MacOS.cs | 24 ++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b116e4c789..7b1f0eddce 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -139,6 +139,7 @@ Maximized FullScreen + Can Resize diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 3cd5350cce..087f25666b 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -66,11 +66,13 @@ namespace IntegrationTestApp var locationComboBox = this.GetControl("ShowWindowLocation"); var stateComboBox = this.GetControl("ShowWindowState"); var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; + var canResizeCheckBox = this.GetControl("ShowWindowCanResize"); var owner = (Window)this.GetVisualRoot()!; var window = new ShowWindowTest { WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, + CanResize = canResizeCheckBox.IsChecked.Value, }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index d9817ecdd1..06180be74a 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -323,11 +323,30 @@ namespace Avalonia.IntegrationTests.Appium secondaryWindow.GetChromeButtons().close.Click(); } - private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + [PlatformTheory(TestPlatforms.MacOS)] + [InlineData(ShowWindowMode.NonOwned)] + [InlineData(ShowWindowMode.Owned)] + [InlineData(ShowWindowMode.Modal)] + public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMode mode) + { + using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false)) + { + var secondaryWindow = GetWindow("SecondaryWindow"); + var (_, _, zoomButton) = secondaryWindow.GetChromeButtons(); + Assert.False(zoomButton.Enabled); + } + } + + private IDisposable OpenWindow( + PixelSize? size, + ShowWindowMode mode, + WindowStartupLocation location, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); if (size.HasValue) @@ -338,6 +357,9 @@ namespace Avalonia.IntegrationTests.Appium locationComboBox.Click(); _session.FindElementByName(location.ToString()).SendClick(); + + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); return showButton.OpenWindowWithClick(); } From 2951b80c39601a8368cc902f76ef4f01bc9307c5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 21:26:19 +0100 Subject: [PATCH 044/120] Add additional failing integration tests. For problems introduced in #10153. --- .../WindowTests.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 7bb991aae6..23381c2e58 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -30,9 +30,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(StartupLocationData))] - public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location) + public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location, bool canResize) { - using var window = OpenWindow(size, mode, location); + using var window = OpenWindow(size, mode, location, canResize: canResize); var info = GetWindowInfo(); if (size.HasValue) @@ -230,10 +230,10 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(new Rgba32(255, 0, 0), centerColor); } - public static TheoryData StartupLocationData() + public static TheoryData StartupLocationData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -243,7 +243,8 @@ namespace Avalonia.IntegrationTests.Appium { if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned)) { - data.Add(size, mode, location); + data.Add(size, mode, location, true); + data.Add(size, mode, location, false); } } } @@ -311,14 +312,16 @@ namespace Avalonia.IntegrationTests.Appium Size? size, ShowWindowMode mode, WindowStartupLocation location = WindowStartupLocation.Manual, - WindowState state = Controls.WindowState.Normal) + WindowState state = Controls.WindowState.Normal, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); - + if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); @@ -331,6 +334,9 @@ namespace Avalonia.IntegrationTests.Appium stateComboBox.Click(); _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); + return showButton.OpenWindowWithClick(); } From fe2233d25a53e654ff82705cba3069319538743c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20=C5=A0olt=C3=A9s?= Date: Sat, 4 Feb 2023 22:03:50 +0100 Subject: [PATCH 045/120] Update ItemsPresenter.cs --- src/Avalonia.Controls/Presenters/ItemsPresenter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 8594b584fa..e8eaac7d17 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -28,19 +28,19 @@ namespace Avalonia.Controls.Presenters /// Defines the property. /// public static readonly StyledProperty AreHorizontalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); + AvaloniaProperty.Register(nameof(AreHorizontalSnapPointsRegular)); /// /// Defines the property. /// public static readonly StyledProperty AreVerticalSnapPointsRegularProperty = - AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); + AvaloniaProperty.Register(nameof(AreVerticalSnapPointsRegular)); /// /// Defines the event. /// public static readonly RoutedEvent HorizontalSnapPointsChangedEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(HorizontalSnapPointsChanged), RoutingStrategies.Bubble); @@ -48,7 +48,7 @@ namespace Avalonia.Controls.Presenters /// Defines the event. /// public static readonly RoutedEvent VerticalSnapPointsChangedEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(VerticalSnapPointsChanged), RoutingStrategies.Bubble); @@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters Size IScrollable.Viewport => _logicalScrollable?.Viewport ?? default; /// - /// Gets or sets whether the horizontal snap points for the are equidistant from each other. + /// Gets or sets whether the horizontal snap points for the are equidistant from each other. /// public bool AreHorizontalSnapPointsRegular { @@ -148,7 +148,7 @@ namespace Avalonia.Controls.Presenters } /// - /// Gets or sets whether the vertical snap points for the are equidistant from each other. + /// Gets or sets whether the vertical snap points for the are equidistant from each other. /// public bool AreVerticalSnapPointsRegular { From f41749170277d8e79c7a5e16af1a1db083705008 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 22:44:24 +0100 Subject: [PATCH 046/120] Speed up integration tests. Don't change combo boxes that are already set to the correct value. --- .../WindowTests.cs | 21 +++++++++++++------ .../WindowTests_MacOS.cs | 14 +++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 23381c2e58..f3861ca267 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -325,14 +325,23 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } - stateComboBox.Click(); - _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (stateComboBox.GetComboBoxValue() != state.ToString()) + { + stateComboBox.Click(); + _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 06180be74a..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -352,11 +352,17 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); From 465d72f2903572c66a444f9f2b31392b7ad6cd69 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Sat, 4 Feb 2023 16:46:13 -0700 Subject: [PATCH 047/120] Add nullable ref annotation to Windows.Close --- src/Avalonia.Controls/Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a20b4eee58..b1110ece55 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -450,7 +450,7 @@ namespace Avalonia.Controls /// resulting task will produce the value when the window /// is closed. /// - public void Close(object dialogResult) + public void Close(object? dialogResult) { _dialogResult = dialogResult; CloseCore(WindowCloseReason.WindowClosing, true); From f8af158a38bd1718126d0b2e3fc35d8b89a18ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 4 Feb 2023 17:35:08 +0000 Subject: [PATCH 048/120] Updated packages used by tests. --- build/ImageSharp.props | 2 +- build/Moq.props | 2 +- build/XUnit.props | 15 +++++++-------- .../Rendering/SceneGraph/DrawOperationTests.cs | 4 ++-- .../Avalonia.Benchmarks.csproj | 2 +- .../Avalonia.IntegrationTests.Appium.csproj | 2 +- .../Avalonia.LeakTests/Avalonia.LeakTests.csproj | 2 +- 7 files changed, 14 insertions(+), 15 deletions(-) diff --git a/build/ImageSharp.props b/build/ImageSharp.props index 178c274ac9..66e6580070 100644 --- a/build/ImageSharp.props +++ b/build/ImageSharp.props @@ -1,5 +1,5 @@ - + diff --git a/build/Moq.props b/build/Moq.props index 9e2fd1db5d..357f0c9a5f 100644 --- a/build/Moq.props +++ b/build/Moq.props @@ -1,5 +1,5 @@  - + diff --git a/build/XUnit.props b/build/XUnit.props index 17ead91aa3..3c89c8b52b 100644 --- a/build/XUnit.props +++ b/build/XUnit.props @@ -1,13 +1,12 @@  - - - - - - - - + + + + + + + diff --git a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs index 07d2d672ae..c1468a28e4 100644 --- a/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs +++ b/tests/Avalonia.Base.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -29,7 +29,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph double height, double scaleX, double scaleY, - double? penThickness, + double penThickness, double expectedX, double expectedY, double expectedWidth, @@ -38,7 +38,7 @@ namespace Avalonia.Base.UnitTests.Rendering.SceneGraph var target = new TestRectangleDrawOperation( new Rect(x, y, width, height), Matrix.CreateScale(scaleX, scaleY), - penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null); + new Pen(Brushes.Black, penThickness)); Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); } diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj index 0ddee2ad7a..9ea0482abc 100644 --- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj +++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 3ff91139f1..5de2b85569 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -10,7 +10,7 @@ - + diff --git a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj index 15815c81b6..c3d9aa0622 100644 --- a/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj +++ b/tests/Avalonia.LeakTests/Avalonia.LeakTests.csproj @@ -1,6 +1,6 @@  - net461 + net462 From 8c07476a1a61ff0d70af12672697b301958394b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sun, 5 Feb 2023 09:01:44 +0000 Subject: [PATCH 049/120] Fixed NuGet package authors. --- build/SharedVersion.props | 1 + 1 file changed, 1 insertion(+) diff --git a/build/SharedVersion.props b/build/SharedVersion.props index eca3ba37b0..2849262591 100644 --- a/build/SharedVersion.props +++ b/build/SharedVersion.props @@ -3,6 +3,7 @@ Avalonia 11.0.999 + Avalonia Team Copyright 2022 © The AvaloniaUI Project https://avaloniaui.net https://github.com/AvaloniaUI/Avalonia/ From cc850694cdf17aaca4fc48571090620371fe4e1f Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 5 Feb 2023 12:00:38 +0100 Subject: [PATCH 050/120] editorconfig: don't apply s_ prefix to public fields --- .editorconfig | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.editorconfig b/.editorconfig index e6ae266849..a144ec8843 100644 --- a/.editorconfig +++ b/.editorconfig @@ -55,16 +55,17 @@ dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# static fields should have s_ prefix -dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion -dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields -dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style +# private static fields should have s_ prefix +dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion +dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style -dotnet_naming_symbols.static_fields.applicable_kinds = field -dotnet_naming_symbols.static_fields.required_modifiers = static +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.required_modifiers = static +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private -dotnet_naming_style.static_prefix_style.required_prefix = s_ -dotnet_naming_style.static_prefix_style.capitalization = camel_case +dotnet_naming_style.private_static_prefix_style.required_prefix = s_ +dotnet_naming_style.private_static_prefix_style.capitalization = camel_case # internal and private fields should be _camelCase dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion @@ -117,7 +118,7 @@ csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after -csharp_space_around_declaration_statements = do_not_ignore +csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false @@ -211,5 +212,5 @@ indent_size = 2 # Shell scripts [*.sh] end_of_line = lf -[*.{cmd, bat}] +[*.{cmd,bat}] end_of_line = crlf From 7eda49e0616dfed92badbfb1dfddec952e377612 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Sun, 5 Feb 2023 22:33:45 +0100 Subject: [PATCH 051/120] Ensure IRenderRoot.Renderer is never null --- .../Composition/CompositingRenderer.cs | 82 +++++++++++++------ src/Avalonia.Base/Rendering/IRenderRoot.cs | 2 - src/Avalonia.Base/Rendering/IRenderer.cs | 3 + src/Avalonia.Base/Visual.cs | 10 +-- src/Avalonia.Controls/TopLevel.cs | 40 +++------ src/Avalonia.Controls/Window.cs | 6 +- src/Avalonia.Controls/WindowBase.cs | 6 +- .../Diagnostics/Controls/Application.cs | 2 +- src/Avalonia.Native/WindowImpl.cs | 3 +- .../ButtonTests.cs | 20 ++--- .../TopLevelTests.cs | 39 +++++---- .../Utils/HotKeyManagerTests.cs | 12 ++- .../WindowBaseTests.cs | 14 +++- .../WindowTests.cs | 2 + .../WindowingPlatformMock.cs | 35 -------- tests/Avalonia.UnitTests/TestTemplatedRoot.cs | 54 ------------ 16 files changed, 138 insertions(+), 192 deletions(-) delete mode 100644 tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs delete mode 100644 tests/Avalonia.UnitTests/TestTemplatedRoot.cs diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs index 668d650ffd..7fa2d4955f 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs @@ -20,15 +20,17 @@ public class CompositingRenderer : IRendererWithCompositor { private readonly IRenderRoot _root; private readonly Compositor _compositor; - CompositionDrawingContext _recorder = new(); - DrawingContext _recordingContext; - private HashSet _dirty = new(); - private HashSet _recalculateChildren = new(); + private readonly CompositionDrawingContext _recorder = new(); + private readonly DrawingContext _recordingContext; + private readonly HashSet _dirty = new(); + private readonly HashSet _recalculateChildren = new(); + private readonly Action _update; + private bool _queuedUpdate; - private Action _update; private bool _updating; + private bool _isDisposed; - internal CompositionTarget CompositionTarget; + internal CompositionTarget CompositionTarget { get; } /// /// Asks the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered. @@ -38,6 +40,17 @@ public class CompositingRenderer : IRendererWithCompositor /// public RendererDiagnostics Diagnostics { get; } + /// + public Compositor Compositor => _compositor; + + /// + /// Initializes a new instance of + /// + /// The render root using this renderer. + /// The associated compositors. + /// + /// A function returning the list of native platform's surfaces that can be consumed by rendering subsystems. + /// public CompositingRenderer(IRenderRoot root, Compositor compositor, Func> surfaces) { _root = root; @@ -66,7 +79,7 @@ public class CompositingRenderer : IRendererWithCompositor /// public event EventHandler? SceneInvalidated; - void QueueUpdate() + private void QueueUpdate() { if(_queuedUpdate) return; @@ -77,9 +90,11 @@ public class CompositingRenderer : IRendererWithCompositor /// public void AddDirty(Visual visual) { + if (_isDisposed) + return; if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); - _dirty.Add((Visual)visual); + _dirty.Add(visual); QueueUpdate(); } @@ -126,9 +141,11 @@ public class CompositingRenderer : IRendererWithCompositor /// public void RecalculateChildren(Visual visual) { + if (_isDisposed) + return; if (_updating) throw new InvalidOperationException("Visual was invalidated during the render pass"); - _recalculateChildren.Add((Visual)visual); + _recalculateChildren.Add(visual); QueueUpdate(); } @@ -171,7 +188,7 @@ public class CompositingRenderer : IRendererWithCompositor if (sortedChildren != null) for (var c = 0; c < visualChildren.Count; c++) { - if (!ReferenceEquals(compositionChildren[c], ((Visual)sortedChildren[c].visual).CompositionVisual)) + if (!ReferenceEquals(compositionChildren[c], sortedChildren[c].visual.CompositionVisual)) { mismatch = true; break; @@ -179,7 +196,7 @@ public class CompositingRenderer : IRendererWithCompositor } else for (var c = 0; c < visualChildren.Count; c++) - if (!ReferenceEquals(compositionChildren[c], ((Visual)visualChildren[c]).CompositionVisual)) + if (!ReferenceEquals(compositionChildren[c], visualChildren[c].CompositionVisual)) { mismatch = true; break; @@ -201,7 +218,7 @@ public class CompositingRenderer : IRendererWithCompositor { foreach (var ch in sortedChildren) { - var compositionChild = ((Visual)ch.visual).CompositionVisual; + var compositionChild = ch.visual.CompositionVisual; if (compositionChild != null) compositionChildren.Add(compositionChild); } @@ -210,7 +227,7 @@ public class CompositingRenderer : IRendererWithCompositor else foreach (var ch in v.GetVisualChildren()) { - var compositionChild = ((Visual)ch).CompositionVisual; + var compositionChild = ch.CompositionVisual; if (compositionChild != null) compositionChildren.Add(compositionChild); } @@ -289,13 +306,18 @@ public class CompositingRenderer : IRendererWithCompositor _updating = false; } } - + + /// public void Resized(Size size) { } + /// public void Paint(Rect rect) { + if (_isDisposed) + return; + QueueUpdate(); CompositionTarget.RequestRedraw(); if(RenderOnlyOnRenderThread && Compositor.Loop.RunsInBackground) @@ -304,17 +326,34 @@ public class CompositingRenderer : IRendererWithCompositor CompositionTarget.ImmediateUIThreadRender(); } - public void Start() => CompositionTarget.IsEnabled = true; - - public void Stop() + /// + public void Start() { - CompositionTarget.IsEnabled = false; + if (_isDisposed) + return; + + CompositionTarget.IsEnabled = true; } - public ValueTask TryGetRenderInterfaceFeature(Type featureType) => Compositor.TryGetRenderInterfaceFeature(featureType); + /// + public void Stop() + => CompositionTarget.IsEnabled = false; + + /// + public ValueTask TryGetRenderInterfaceFeature(Type featureType) + => Compositor.TryGetRenderInterfaceFeature(featureType); + /// public void Dispose() { + if (_isDisposed) + return; + + _isDisposed = true; + _dirty.Clear(); + _recalculateChildren.Clear(); + SceneInvalidated = null; + Stop(); CompositionTarget.Dispose(); @@ -323,9 +362,4 @@ public class CompositingRenderer : IRendererWithCompositor if (Compositor.Loop.RunsInBackground) _compositor.Commit().Wait(); } - - /// - /// The associated object - /// - public Compositor Compositor => _compositor; } diff --git a/src/Avalonia.Base/Rendering/IRenderRoot.cs b/src/Avalonia.Base/Rendering/IRenderRoot.cs index fa3260ffb4..09df7b7830 100644 --- a/src/Avalonia.Base/Rendering/IRenderRoot.cs +++ b/src/Avalonia.Base/Rendering/IRenderRoot.cs @@ -1,6 +1,4 @@ using Avalonia.Metadata; -using Avalonia.Platform; -using Avalonia.VisualTree; namespace Avalonia.Rendering { diff --git a/src/Avalonia.Base/Rendering/IRenderer.cs b/src/Avalonia.Base/Rendering/IRenderer.cs index ba960ff5f3..7e32504e17 100644 --- a/src/Avalonia.Base/Rendering/IRenderer.cs +++ b/src/Avalonia.Base/Rendering/IRenderer.cs @@ -90,6 +90,9 @@ namespace Avalonia.Rendering public interface IRendererWithCompositor : IRenderer { + /// + /// The associated object + /// Compositor Compositor { get; } } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e6d7492c51..fc215fc890 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -348,7 +348,7 @@ namespace Avalonia /// public void InvalidateVisual() { - VisualRoot?.Renderer?.AddDirty(this); + VisualRoot?.Renderer.AddDirty(this); } /// @@ -449,7 +449,7 @@ namespace Avalonia protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.LogicalChildrenCollectionChanged(sender, e); - VisualRoot?.Renderer?.RecalculateChildren(this); + VisualRoot?.Renderer.RecalculateChildren(this); } /// @@ -477,7 +477,7 @@ namespace Avalonia OnAttachedToVisualTree(e); AttachedToVisualTree?.Invoke(this, e); InvalidateVisual(); - _visualRoot.Renderer?.RecalculateChildren(_visualParent!); + _visualRoot.Renderer.RecalculateChildren(_visualParent!); if (ZIndex != 0 && VisualParent is Visual parent) parent.HasNonUniformZIndexChildren = true; @@ -540,7 +540,7 @@ namespace Avalonia } DetachedFromVisualTree?.Invoke(this, e); - e.Root?.Renderer?.AddDirty(this); + e.Root.Renderer.AddDirty(this); var visualChildren = VisualChildren; @@ -659,7 +659,7 @@ namespace Avalonia parentVisual.HasNonUniformZIndexChildren = true; sender?.InvalidateVisual(); - parent?.VisualRoot?.Renderer?.RecalculateChildren(parent); + parent?.VisualRoot?.Renderer.RecalculateChildren(parent); } /// diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 676fa1519a..4db71abfa8 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -94,7 +94,6 @@ namespace Avalonia.Controls private readonly IInputManager? _inputManager; private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; - private readonly IPlatformRenderInterface? _renderInterface; private readonly IGlobalStyles? _globalStyles; private readonly IGlobalThemeVariantProvider? _applicationThemeHost; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; @@ -136,36 +135,21 @@ namespace Avalonia.Controls /// public TopLevel(ITopLevelImpl impl, IAvaloniaDependencyResolver? dependencyResolver) { - if (impl == null) - { - throw new InvalidOperationException( - "Could not create window implementation: maybe no windowing subsystem was initialized?"); - } - - PlatformImpl = impl; + PlatformImpl = impl ?? throw new InvalidOperationException( + "Could not create window implementation: maybe no windowing subsystem was initialized?"); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; - dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; + dependencyResolver ??= AvaloniaLocator.Current; _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); - _renderInterface = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); _applicationThemeHost = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); - - if (Renderer != null) - { - Renderer.SceneInvalidated += SceneInvalidated; - } - else - { - // Prevent nullable error. - Renderer = null!; - } + Renderer.SceneInvalidated += SceneInvalidated; impl.SetInputRoot(this); @@ -216,7 +200,7 @@ namespace Avalonia.Controls if(impl.TryGetFeature() is {} systemNavigationManager) { - systemNavigationManager.BackRequested += (s, e) => + systemNavigationManager.BackRequested += (_, e) => { e.RoutedEvent = BackRequestedEvent; RaiseEvent(e); @@ -337,7 +321,7 @@ namespace Avalonia.Controls { _layoutManager = CreateLayoutManager(); - if (_layoutManager is LayoutManager typedLayoutManager && Renderer is not null) + if (_layoutManager is LayoutManager typedLayoutManager) { _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager); _layoutDiagnosticBridge.SetupBridge(); @@ -356,7 +340,7 @@ namespace Avalonia.Controls /// /// Gets the renderer for the window. /// - public IRenderer Renderer { get; private set; } + public IRenderer Renderer { get; } internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; @@ -450,7 +434,7 @@ namespace Avalonia.Controls /// The dirty area. protected virtual void HandlePaint(Rect rect) { - Renderer?.Paint(rect); + Renderer.Paint(rect); } /// @@ -468,8 +452,8 @@ namespace Avalonia.Controls _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } - Renderer?.Dispose(); - Renderer = null!; + Renderer.SceneInvalidated -= SceneInvalidated; + Renderer.Dispose(); _layoutDiagnosticBridge?.Dispose(); _layoutDiagnosticBridge = null; @@ -488,7 +472,7 @@ namespace Avalonia.Controls OnClosed(EventArgs.Empty); - LayoutManager?.Dispose(); + LayoutManager.Dispose(); } /// @@ -503,7 +487,7 @@ namespace Avalonia.Controls Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + Renderer.Resized(clientSize); } /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index b1110ece55..ba1b599421 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -573,7 +573,7 @@ namespace Avalonia.Controls return; } - Renderer?.Stop(); + Renderer.Stop(); if (Owner is Window owner) { @@ -721,7 +721,7 @@ namespace Avalonia.Controls SetWindowStartupLocation(owner?.PlatformImpl); PlatformImpl?.Show(ShowActivated, false); - Renderer?.Start(); + Renderer.Start(); OnOpened(EventArgs.Empty); } } @@ -798,7 +798,7 @@ namespace Avalonia.Controls PlatformImpl?.Show(ShowActivated, true); - Renderer?.Start(); + Renderer.Start(); Observable.FromEventPattern( x => Closed += x, diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index aad0482b50..0c9a91148b 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -129,7 +129,7 @@ namespace Avalonia.Controls { using (FreezeVisibilityChangeHandling()) { - Renderer?.Stop(); + Renderer.Stop(); PlatformImpl?.Hide(); IsVisible = false; } @@ -153,7 +153,7 @@ namespace Avalonia.Controls } PlatformImpl?.Show(true, false); - Renderer?.Start(); + Renderer.Start(); OnOpened(EventArgs.Empty); } } @@ -219,7 +219,7 @@ namespace Avalonia.Controls { ClientSize = clientSize; LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + Renderer.Resized(clientSize); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 7426c4e2ed..a0ff3a714f 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.Controls RendererRoot = application.ApplicationLifetime switch { Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow?.Renderer, - Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as Visual)?.VisualRoot?.Renderer, + Lifetimes.ISingleViewApplicationLifetime single => single.MainView?.VisualRoot?.Renderer, _ => null }; diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index f27d94b61a..64c1d0da10 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -119,7 +119,8 @@ namespace Avalonia.Native { if(e.Type == RawPointerEventType.LeftButtonDown) { - var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x => + var window = _inputRoot as Window; + var visual = window?.Renderer.HitTestFirst(e.Position, window, x => { if (x is IInputElement ie && (!ie.IsHitTestVisible || !ie.IsEffectivelyVisible)) { diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index 4ff98bdedd..2679d4ce06 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -140,10 +140,9 @@ namespace Avalonia.Controls.UnitTests .Returns>((p, r, f) => r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { - Bounds = new Rect(0, 0, 100, 100), - Renderer = renderer.Object + Bounds = new Rect(0, 0, 100, 100) }; bool clicked = false; @@ -172,10 +171,9 @@ namespace Avalonia.Controls.UnitTests .Returns>((p, r, f) => r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { - Bounds = new Rect(0, 0, 100, 100), - Renderer = renderer.Object + Bounds = new Rect(0, 0, 100, 100) }; bool clicked = false; @@ -206,11 +204,10 @@ namespace Avalonia.Controls.UnitTests r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? new Visual[] { r } : new Visual[0]); - var target = new TestButton() + var target = new TestButton(renderer.Object) { Bounds = new Rect(0, 0, 100, 100), - RenderTransform = new TranslateTransform { X = 100, Y = 0 }, - Renderer = renderer.Object + RenderTransform = new TranslateTransform { X = 100, Y = 0 } }; //actual bounds of button should be 100,0,100,100 x -> translated 100 pixels @@ -386,9 +383,10 @@ namespace Avalonia.Controls.UnitTests private class TestButton : Button, IRenderRoot { - public TestButton() + public TestButton(IRenderer renderer) { IsVisible = true; + Renderer = renderer; } public new Rect Bounds @@ -399,7 +397,7 @@ namespace Avalonia.Controls.UnitTests public Size ClientSize => throw new NotImplementedException(); - public IRenderer Renderer { get; set; } + public IRenderer Renderer { get; } public double RenderScaling => throw new NotImplementedException(); diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index d63251c1f5..2644e7184a 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -6,6 +6,7 @@ using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.UnitTests; using Moq; @@ -20,7 +21,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); var target = new TestTopLevel(impl.Object); Assert.True(((ILogical)target).IsAttachedToLogicalTree); @@ -32,7 +33,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -46,7 +47,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -60,7 +61,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -76,7 +77,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(services)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); var target = new TestTopLevel(impl.Object, Mock.Of()); @@ -91,7 +92,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupProperty(x => x.Resized); impl.SetupGet(x => x.RenderScaling).Returns(1); @@ -117,7 +118,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); var target = new TestTopLevel(impl.Object); @@ -133,7 +134,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); impl.Setup(x => x.ClientSize).Returns(new Size(123, 456)); @@ -151,7 +152,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); bool raised = false; @@ -169,7 +170,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); @@ -200,7 +201,7 @@ namespace Avalonia.Controls.UnitTests using (UnitTestApplication.Start(services)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); @@ -222,7 +223,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); var child = new TestTopLevel(impl.Object); @@ -240,7 +241,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var target = new TestTopLevel(impl.Object); var raised = false; @@ -257,7 +258,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupAllProperties(); var layoutManager = new Mock(); @@ -274,7 +275,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockTopLevelImpl(); impl.SetupGet(x => x.RenderScaling).Returns(1); var child = new Border { Classes = { "foo" } }; @@ -317,6 +318,14 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope)); } + private static Mock CreateMockTopLevelImpl() + { + var renderer = new Mock(); + renderer.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); + return renderer; + } + private class TestTopLevel : TopLevel { private readonly ILayoutManager _layoutManager; diff --git a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs index f367112cc0..336aad79da 100644 --- a/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Utils/HotKeyManagerTests.cs @@ -4,9 +4,7 @@ using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Styling; using Avalonia.UnitTests; -using Moq; using Xunit; using Factory = System.Func, Avalonia.Controls.Window, Avalonia.AvaloniaObject>; @@ -20,7 +18,7 @@ namespace Avalonia.Controls.UnitTests.Utils using (AvaloniaLocator.EnterScope()) { AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture1 = new KeyGesture(Key.A, KeyModifiers.Control); var gesture2 = new KeyGesture(Key.B, KeyModifiers.Control); @@ -64,7 +62,7 @@ namespace Avalonia.Controls.UnitTests.Utils var commandResult = 0; var expectedParameter = 1; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -106,7 +104,7 @@ namespace Avalonia.Controls.UnitTests.Utils var target = new KeyboardDevice(); var isExecuted = false; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -146,7 +144,7 @@ namespace Avalonia.Controls.UnitTests.Utils var target = new KeyboardDevice(); var clickExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); @@ -199,7 +197,7 @@ namespace Avalonia.Controls.UnitTests.Utils var clickExecutedCount = 0; var commandExecutedCount = 0; AvaloniaLocator.CurrentMutable - .Bind().ToConstant(new WindowingPlatformMock()); + .Bind().ToConstant(new MockWindowingPlatform()); var gesture = new KeyGesture(Key.A, KeyModifiers.Control); diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs index a10b1324d6..d65fa06183 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); var target = new TestWindowBase(impl.Object); target.Activate(); @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); impl.SetupAllProperties(); bool raised = false; @@ -55,7 +55,7 @@ namespace Avalonia.Controls.UnitTests { using (UnitTestApplication.Start(TestServices.StyledWindow)) { - var impl = new Mock(); + var impl = CreateMockWindowBaseImpl(); impl.SetupAllProperties(); bool raised = false; @@ -241,6 +241,14 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope)); } + private static Mock CreateMockWindowBaseImpl() + { + var renderer = new Mock(); + renderer.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); + return renderer; + } + private class TestWindowBase : WindowBase { public bool IsClosed { get; private set; } diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 014174990e..cada2bfa6f 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests public void Setting_Title_Should_Set_Impl_Title() { var windowImpl = new Mock(); + windowImpl.Setup(r => r.CreateRenderer(It.IsAny())) + .Returns(RendererMocks.CreateRenderer().Object); var windowingPlatform = new MockWindowingPlatform(() => windowImpl.Object); using (UnitTestApplication.Start(new TestServices(windowingPlatform: windowingPlatform))) diff --git a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs b/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs deleted file mode 100644 index e8471d41fb..0000000000 --- a/tests/Avalonia.Controls.UnitTests/WindowingPlatformMock.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using Moq; -using Avalonia.Platform; - -namespace Avalonia.Controls.UnitTests -{ - public class WindowingPlatformMock : IWindowingPlatform - { - private readonly Func _windowImpl; - private readonly Func _popupImpl; - - public WindowingPlatformMock(Func windowImpl = null, Func popupImpl = null ) - { - _windowImpl = windowImpl; - _popupImpl = popupImpl; - } - - public IWindowImpl CreateWindow() - { - return _windowImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); - } - - public IWindowImpl CreateEmbeddableWindow() - { - throw new NotImplementedException(); - } - - public ITrayIconImpl CreateTrayIcon() - { - return null; - } - - public IPopupImpl CreatePopup() => _popupImpl?.Invoke() ?? Mock.Of(x => x.RenderScaling == 1); - } -} diff --git a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs b/tests/Avalonia.UnitTests/TestTemplatedRoot.cs deleted file mode 100644 index 38ab3c3c5d..0000000000 --- a/tests/Avalonia.UnitTests/TestTemplatedRoot.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Layout; -using Avalonia.LogicalTree; -using Avalonia.Platform; -using Avalonia.Rendering; -using Avalonia.Styling; - -namespace Avalonia.UnitTests -{ - public class TestTemplatedRoot : ContentControl, ILayoutRoot, IRenderRoot, ILogicalRoot - { - private readonly NameScope _nameScope = new NameScope(); - - public TestTemplatedRoot() - { - LayoutManager = new LayoutManager(this); - Template = new FuncControlTemplate((x, scope) => new ContentPresenter - { - Name = "PART_ContentPresenter", - }.RegisterInNameScope(scope)); - } - - public Size ClientSize => new Size(100, 100); - - public Size MaxClientSize => Size.Infinity; - - public double LayoutScaling => 1; - - public ILayoutManager LayoutManager { get; set; } - - public double RenderScaling => 1; - - public IRenderTarget RenderTarget => null; - - public IRenderer Renderer => null; - - public IRenderTarget CreateRenderTarget() - { - throw new NotImplementedException(); - } - - public void Invalidate(Rect rect) - { - throw new NotImplementedException(); - } - - public Point PointToClient(PixelPoint p) => p.ToPoint(1); - - public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1); - } -} From 1642129d0f7477ce0b02c38d8482f253525f2d41 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 6 Feb 2023 04:07:02 -0500 Subject: [PATCH 052/120] Implement simple PreloadDepsAssemblies --- .../DesignXamlLoader.cs | 71 +++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs index 181883656c..690926a193 100644 --- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs +++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs @@ -1,16 +1,79 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.XamlIl; -namespace Avalonia.Designer.HostApp +namespace Avalonia.Designer.HostApp; + +class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader { - class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader + public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + { + PreloadDepsAssemblies(configuration.LocalAssembly ?? Assembly.GetEntryAssembly()); + + return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); + } + + private void PreloadDepsAssemblies(Assembly targetAssembly) { - public object Load(RuntimeXamlLoaderDocument document, RuntimeXamlLoaderConfiguration configuration) + // Assemblies loaded in memory (e.g. single file) return empty string from Location. + // In these cases, don't try probing next to the assembly. + var assemblyLocation = targetAssembly.Location; + if (string.IsNullOrEmpty(assemblyLocation)) + { + return; + } + + var depsJsonFile = Path.ChangeExtension(assemblyLocation, ".deps.json"); + if (!File.Exists(depsJsonFile)) + { + return; + } + + using var stream = File.OpenRead(depsJsonFile); + + /* + We can't use any references in the Avalonia.Designer.HostApp. Including even json. + Ideally we would prefer Microsoft.Extensions.DependencyModel package, but can't use it here. + So, instead we need to fallback to some JSON parsing using pretty easy regex. + + Json part example: +"Avalonia.Xaml.Interactions/11.0.0-preview5": { + "dependencies": { + "Avalonia": "11.0.999", + "Avalonia.Xaml.Interactivity": "11.0.0-preview5" + }, + "runtime": { + "lib/net6.0/Avalonia.Xaml.Interactions.dll": { + "assemblyVersion": "11.0.0.0", + "fileVersion": "11.0.0.0" + } + } +}, + We want to extract "lib/net6.0/Avalonia.Xaml.Interactions.dll" from here. + No need to resolve real path of ref assemblies. + No need to handle special cases with .NET Framework and GAC. + */ + var text = new StreamReader(stream).ReadToEnd(); + var matches = Regex.Matches( text, """runtime"\s*:\s*{\s*"([^"]+)"""); + + foreach (Match match in matches) { - return AvaloniaXamlIlRuntimeCompiler.Load(document, configuration); + if (match.Groups[1] is { Success: true } g) + { + var assemblyName = Path.GetFileNameWithoutExtension(g.Value); + try + { + _ = Assembly.Load(new AssemblyName(assemblyName)); + } + catch + { + } + } } } } From e7a6d6fbc690b96af757199c98ff1f324f3d75ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 12:00:48 +0100 Subject: [PATCH 053/120] Implement windowWillUseStandardFrame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems that `NSWindow.isZoomed` returns the wrong result unless you implement this? ¯\_(ツ)_/¯ --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index d3b7b4ede6..784072221d 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -223,6 +223,19 @@ } } +// From chromium: +// +// > The delegate or the window class should implement this method so that +// > -[NSWindow isZoomed] can be then determined by whether or not the current +// > window frame is equal to the zoomed frame. +// +// If we don't implement this, then isZoomed always returns true for a non- +// resizable window ¯\_(ツ)_/¯ +- (NSRect)windowWillUseStandardFrame:(NSWindow*)window + defaultFrame:(NSRect)newFrame { + return newFrame; +} + -(BOOL)canBecomeKeyWindow { if(_canBecomeKeyWindow) From 044a499db331b0d13e33d6427643bc1868470d30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 14:27:09 +0100 Subject: [PATCH 054/120] Added additional failing integration tests. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index f3861ca267..ec24caa18c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -63,9 +63,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(WindowStateData))] - public void WindowState(Size? size, ShowWindowMode mode, WindowState state) + public void WindowState(Size? size, ShowWindowMode mode, WindowState state, bool canResize) { - using var window = OpenWindow(size, mode, state: state); + using var window = OpenWindow(size, mode, state: state, canResize: canResize); try { @@ -253,10 +253,10 @@ namespace Avalonia.IntegrationTests.Appium return data; } - public static TheoryData WindowStateData() + public static TheoryData WindowStateData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -274,7 +274,8 @@ namespace Avalonia.IntegrationTests.Appium mode != ShowWindowMode.NonOwned) continue; - data.Add(size, mode, state); + data.Add(size, mode, state, true); + data.Add(size, mode, state, false); } } } From 39fe9b17fb346ee5e63d6bfa508ed8d775eda355 Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Mon, 6 Feb 2023 19:26:11 +0100 Subject: [PATCH 055/120] Nullability fixes for Avalonia.Base --- src/Avalonia.Base/Animation/KeySpline.cs | 11 ++- src/Avalonia.Base/AvaloniaObject.cs | 38 ++++------- .../Diagnostics/AvaloniaObjectExtensions.cs | 3 - src/Avalonia.Base/Input/DragEventArgs.cs | 18 ++--- src/Avalonia.Base/Input/KeyGesture.cs | 2 +- .../Input/KeyboardNavigationHandler.cs | 44 ++++++------ .../Input/Navigation/TabNavigation.cs | 48 ++++--------- .../LogicalTree/LogicalExtensions.cs | 6 +- src/Avalonia.Base/Media/Color.cs | 9 +-- src/Avalonia.Base/Media/DrawingContext.cs | 2 +- src/Avalonia.Base/Media/DrawingGroup.cs | 26 +++---- src/Avalonia.Base/Media/DrawingImage.cs | 6 +- src/Avalonia.Base/Media/FontFamily.cs | 4 +- .../Media/Fonts/FontFamilyKey.cs | 5 +- src/Avalonia.Base/Media/FormattedText.cs | 6 +- src/Avalonia.Base/Media/GeometryDrawing.cs | 6 +- src/Avalonia.Base/Media/GlyphRunDrawing.cs | 12 ++-- src/Avalonia.Base/Media/HslColor.cs | 2 +- src/Avalonia.Base/Media/HsvColor.cs | 2 +- src/Avalonia.Base/Media/IVisualBrush.cs | 3 +- .../Media/Immutable/ImmutableDashStyle.cs | 24 ++----- .../Media/Immutable/ImmutableVisualBrush.cs | 7 +- src/Avalonia.Base/Media/TextDecoration.cs | 14 ++-- src/Avalonia.Base/Media/VisualBrush.cs | 7 +- .../Platform/IDrawingContextImpl.cs | 4 +- .../Platform/Internal/AssemblyDescriptor.cs | 28 ++++---- .../Animations/CompositionAnimation.cs | 2 +- .../Animations/CompositionAnimationGroup.cs | 2 +- .../Animations/ImplicitAnimationCollection.cs | 2 +- .../Composition/CompositionDrawingSurface.cs | 3 +- .../Composition/CompositionObject.cs | 4 +- .../Composition/CompositionPropertySet.cs | 2 +- .../Drawing/CompositionDrawingContext.cs | 14 +++- .../Composition/Expressions/Expression.cs | 2 - .../ExpressionEvaluationContext.cs | 1 - .../Composition/Server/DrawingContextProxy.cs | 4 +- .../Rendering/ImmediateRenderer.cs | 6 +- .../SceneGraph/ExperimentalAcrylicNode.cs | 7 +- src/Avalonia.Base/Utilities/TypeUtilities.cs | 11 +-- src/Avalonia.Base/Utilities/WeakEvent.cs | 68 ++----------------- .../Utilities/WeakEventHandlerManager.cs | 45 ++++++------ src/Avalonia.Base/Visual.cs | 24 +++---- .../VisualTree/VisualExtensions.cs | 19 +++--- .../HeadlessPlatformRenderInterface.cs | 4 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 12 ++++ .../Avalonia.Direct2D1/Media/GlyphRunImpl.cs | 7 +- tests/Avalonia.UnitTests/MockGlyphRun.cs | 8 +-- 47 files changed, 226 insertions(+), 358 deletions(-) diff --git a/src/Avalonia.Base/Animation/KeySpline.cs b/src/Avalonia.Base/Animation/KeySpline.cs index 6ca5b2e759..ed6adb79b8 100644 --- a/src/Avalonia.Base/Animation/KeySpline.cs +++ b/src/Avalonia.Base/Animation/KeySpline.cs @@ -79,15 +79,12 @@ namespace Avalonia.Animation /// culture of the string /// Thrown if the string does not have 4 values /// A with the appropriate values set - public static KeySpline Parse(string value, CultureInfo culture) + public static KeySpline Parse(string value, CultureInfo? culture) { - if (culture is null) - culture = CultureInfo.InvariantCulture; + culture ??= CultureInfo.InvariantCulture; - using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\".")) - { - return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); - } + using var tokenizer = new StringTokenizer(value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."); + return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble()); } /// diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1946d4ba5c..50a7a5c831 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -152,7 +152,7 @@ namespace Avalonia property = property ?? throw new ArgumentNullException(nameof(property)); VerifyAccess(); - _values?.ClearLocalValue(property); + _values.ClearLocalValue(property); } /// @@ -242,7 +242,14 @@ namespace Avalonia return registered.InvokeGetter(this); } - /// + /// + /// Gets an base value. + /// + /// The property. + /// + /// Gets the value of the property excluding animated values, otherwise . + /// Note that this method does not return property values that come from inherited or default values. + /// public Optional GetBaseValue(StyledProperty property) { _ = property ?? throw new ArgumentNullException(nameof(property)); @@ -261,7 +268,7 @@ namespace Avalonia VerifyAccess(); - return _values?.IsAnimating(property) ?? false; + return _values.IsAnimating(property); } /// @@ -279,7 +286,7 @@ namespace Avalonia VerifyAccess(); - return _values?.IsSet(property) ?? false; + return _values.IsSet(property); } /// @@ -515,14 +522,12 @@ namespace Avalonia /// The property. public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property); - /// internal void AddInheritanceChild(AvaloniaObject child) { _inheritanceChildren ??= new List(); _inheritanceChildren.Add(child); } - - /// + internal void RemoveInheritanceChild(AvaloniaObject child) { _inheritanceChildren?.Remove(child); @@ -541,24 +546,11 @@ namespace Avalonia return new AvaloniaPropertyValue( property, GetValue(property), - BindingPriority.Unset, - "Local Value"); - } - else if (_values != null) - { - var result = _values.GetDiagnostic(property); - - if (result != null) - { - return result; - } + BindingPriority.LocalValue, + null); } - return new AvaloniaPropertyValue( - property, - GetValue(property), - BindingPriority.Unset, - "Unset"); + return _values.GetDiagnostic(property); } internal ValueStore GetValueStore() => _values; diff --git a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs index d7b1f2e053..270cac95f2 100644 --- a/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/Diagnostics/AvaloniaObjectExtensions.cs @@ -1,6 +1,3 @@ -using System; -using Avalonia.Data; - namespace Avalonia.Diagnostics { /// diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 403dd6f23e..8d7cc2b9a1 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -1,36 +1,28 @@ using System; using Avalonia.Interactivity; using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Input { public class DragEventArgs : RoutedEventArgs { - private Interactive _target; - private Point _targetLocation; + private readonly Interactive _target; + private readonly Point _targetLocation; public DragDropEffects DragEffects { get; set; } - public IDataObject Data { get; private set; } + public IDataObject Data { get; } - public KeyModifiers KeyModifiers { get; private set; } + public KeyModifiers KeyModifiers { get; } public Point GetPosition(Visual relativeTo) { - var point = new Point(0, 0); - if (relativeTo == null) { throw new ArgumentNullException(nameof(relativeTo)); } - if (_target != null) - { - point = _target.TranslatePoint(_targetLocation, relativeTo) ?? point; - } - - return point; + return _target.TranslatePoint(_targetLocation, relativeTo) ?? new Point(0, 0); } [Unstable] diff --git a/src/Avalonia.Base/Input/KeyGesture.cs b/src/Avalonia.Base/Input/KeyGesture.cs index c6618fd550..9ee8ae9711 100644 --- a/src/Avalonia.Base/Input/KeyGesture.cs +++ b/src/Avalonia.Base/Input/KeyGesture.cs @@ -136,7 +136,7 @@ namespace Avalonia.Input return StringBuilderCache.GetStringAndRelease(s); } - public bool Matches(KeyEventArgs keyEvent) => + public bool Matches(KeyEventArgs? keyEvent) => keyEvent != null && keyEvent.KeyModifiers == KeyModifiers && ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(Key); diff --git a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs index b05d8f30bb..ba909de60f 100644 --- a/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs +++ b/src/Avalonia.Base/Input/KeyboardNavigationHandler.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Avalonia.Input.Navigation; using Avalonia.VisualTree; @@ -51,7 +50,7 @@ namespace Avalonia.Input // If there's a custom keyboard navigation handler as an ancestor, use that. var custom = (element as Visual)?.FindAncestorOfType(true); - if (custom is object && HandlePreCustomNavigation(custom, element, direction, out var ce)) + if (custom is not null && HandlePreCustomNavigation(custom, element, direction, out var ce)) return ce; var result = direction switch @@ -117,32 +116,27 @@ namespace Avalonia.Input NavigationDirection direction, [NotNullWhen(true)] out IInputElement? result) { - if (customHandler != null) + var (handled, next) = customHandler.GetNext(element, direction); + + if (handled) { - var (handled, next) = customHandler.GetNext(element, direction); + if (next is not null) + { + result = next; + return true; + } - if (handled) + var r = direction switch { - if (next != null) - { - result = next; - return true; - } - else if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous) - { - var r = direction switch - { - NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler), - NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler), - _ => throw new NotSupportedException(), - }; - - if (r is object) - { - result = r; - return true; - } - } + NavigationDirection.Next => TabNavigation.GetNextTabOutside(customHandler), + NavigationDirection.Previous => TabNavigation.GetPrevTabOutside(customHandler), + _ => null + }; + + if (r is not null) + { + result = r; + return true; } } diff --git a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs index d218867cf2..c460ecf3b3 100644 --- a/src/Avalonia.Base/Input/Navigation/TabNavigation.cs +++ b/src/Avalonia.Base/Input/Navigation/TabNavigation.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Avalonia.VisualTree; namespace Avalonia.Input.Navigation @@ -54,8 +52,7 @@ namespace Avalonia.Input.Navigation // Avoid the endless loop here for Cycle groups if (loopStartElement == nextTabElement) break; - if (loopStartElement == null) - loopStartElement = nextTabElement; + loopStartElement ??= nextTabElement; var firstTabElementInside = GetNextTab(null, nextTabElement, true); if (firstTabElementInside != null) @@ -80,12 +77,9 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetNextTabOutside(ICustomKeyboardNavigation e) { - if (e is IInputElement container) + if (e is IInputElement container && GetLastInTree(container) is { } last) { - var last = GetLastInTree(container); - - if (last is object) - return GetNextTab(last, false); + return GetNextTab(last, false); } return null; @@ -93,11 +87,8 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetPrevTab(IInputElement? e, IInputElement? container, bool goDownOnly) { - if (e is null && container is null) - throw new InvalidOperationException("Either 'e' or 'container' must be non-null."); - - if (container is null) - container = GetGroupParent(e!); + container ??= + GetGroupParent(e ?? throw new InvalidOperationException("Either 'e' or 'container' must be non-null.")); KeyboardNavigationMode tabbingType = GetKeyNavigationMode(container); @@ -163,8 +154,7 @@ namespace Avalonia.Input.Navigation // Avoid the endless loop here if (loopStartElement == nextTabElement) break; - if (loopStartElement == null) - loopStartElement = nextTabElement; + loopStartElement ??= nextTabElement; // At this point nextTabElement is TabGroup var lastTabElementInside = GetPrevTab(null, nextTabElement, true); @@ -189,22 +179,18 @@ namespace Avalonia.Input.Navigation public static IInputElement? GetPrevTabOutside(ICustomKeyboardNavigation e) { - if (e is IInputElement container) + if (e is IInputElement container && GetFirstChild(container) is { } first) { - var first = GetFirstChild(container); - - if (first is object) - return GetPrevTab(first, null, false); + return GetPrevTab(first, null, false); } return null; } - private static IInputElement? FocusedElement(IInputElement e) + private static IInputElement? FocusedElement(IInputElement? e) { - var iie = e; // Focus delegation is enabled only if keyboard focus is outside the container - if (iie != null && !iie.IsKeyboardFocusWithin) + if (e != null && !e.IsKeyboardFocusWithin) { var focusedElement = (FocusManager.Instance as FocusManager)?.GetFocusedElement(e); if (focusedElement != null) @@ -229,13 +215,11 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetFirstChild(IInputElement e) { // If the element has a FocusedElement it should be its first child - if (FocusedElement(e) is IInputElement focusedElement) + if (FocusedElement(e) is { } focusedElement) return focusedElement; // Return the first visible element. - var uiElement = e as InputElement; - - if (uiElement is null || IsVisibleAndEnabled(uiElement)) + if (e is not InputElement uiElement || IsVisibleAndEnabled(uiElement)) { if (e is Visual elementAsVisual) { @@ -265,7 +249,7 @@ namespace Avalonia.Input.Navigation private static IInputElement? GetLastChild(IInputElement e) { // If the element has a FocusedElement it should be its last child - if (FocusedElement(e) is IInputElement focusedElement) + if (FocusedElement(e) is { } focusedElement) return focusedElement; // Return the last visible element. @@ -273,9 +257,7 @@ namespace Avalonia.Input.Navigation if (uiElement == null || IsVisibleAndEnabled(uiElement)) { - var elementAsVisual = e as Visual; - - if (elementAsVisual != null) + if (e is Visual elementAsVisual) { var children = elementAsVisual.VisualChildren; var count = children.Count; @@ -322,7 +304,7 @@ namespace Avalonia.Input.Navigation return firstTabElement; } - private static IInputElement? GetLastInTree(IInputElement container) + private static IInputElement GetLastInTree(IInputElement container) { IInputElement? result; IInputElement? c = container; diff --git a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs index 74720c0a77..9ab7da3ff7 100644 --- a/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs +++ b/src/Avalonia.Base/LogicalTree/LogicalExtensions.cs @@ -48,7 +48,7 @@ namespace Avalonia.LogicalTree /// The logical. /// If given logical should be included in search. /// First ancestor of given type. - public static T? FindLogicalAncestorOfType(this ILogical logical, bool includeSelf = false) where T : class + public static T? FindLogicalAncestorOfType(this ILogical? logical, bool includeSelf = false) where T : class { if (logical is null) { @@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree /// The logical. /// If given logical should be included in search. /// First descendant of given type. - public static T? FindLogicalDescendantOfType(this ILogical logical, bool includeSelf = false) where T : class + public static T? FindLogicalDescendantOfType(this ILogical? logical, bool includeSelf = false) where T : class { if (logical is null) { @@ -185,7 +185,7 @@ namespace Avalonia.LogicalTree /// True if is an ancestor of ; /// otherwise false. /// - public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) + public static bool IsLogicalAncestorOf(this ILogical? logical, ILogical? target) { var current = target?.LogicalParent; diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 5470a735b3..ab89177295 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -147,16 +147,11 @@ namespace Avalonia.Media /// The color string. /// The parsed color /// The status of the operation. - public static bool TryParse(string s, out Color color) + public static bool TryParse(string? s, out Color color) { color = default; - if (s is null) - { - return false; - } - - if (s.Length == 0) + if (string.IsNullOrEmpty(s)) { return false; } diff --git a/src/Avalonia.Base/Media/DrawingContext.cs b/src/Avalonia.Base/Media/DrawingContext.cs index d295111d72..622181dba0 100644 --- a/src/Avalonia.Base/Media/DrawingContext.cs +++ b/src/Avalonia.Base/Media/DrawingContext.cs @@ -240,7 +240,7 @@ namespace Avalonia.Media /// /// The foreground brush. /// The glyph run. - public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) + public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun) { _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs index 481329c20c..b7abda2c61 100644 --- a/src/Avalonia.Base/Media/DrawingGroup.cs +++ b/src/Avalonia.Base/Media/DrawingGroup.cs @@ -13,14 +13,14 @@ namespace Avalonia.Media public static readonly StyledProperty OpacityProperty = AvaloniaProperty.Register(nameof(Opacity), 1); - public static readonly StyledProperty TransformProperty = - AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); - public static readonly StyledProperty ClipGeometryProperty = - AvaloniaProperty.Register(nameof(ClipGeometry)); + public static readonly StyledProperty ClipGeometryProperty = + AvaloniaProperty.Register(nameof(ClipGeometry)); - public static readonly StyledProperty OpacityMaskProperty = - AvaloniaProperty.Register(nameof(OpacityMask)); + public static readonly StyledProperty OpacityMaskProperty = + AvaloniaProperty.Register(nameof(OpacityMask)); public static readonly DirectProperty ChildrenProperty = AvaloniaProperty.RegisterDirect( @@ -36,19 +36,19 @@ namespace Avalonia.Media set => SetValue(OpacityProperty, value); } - public Transform Transform + public Transform? Transform { get => GetValue(TransformProperty); set => SetValue(TransformProperty, value); } - public Geometry ClipGeometry + public Geometry? ClipGeometry { get => GetValue(ClipGeometryProperty); set => SetValue(ClipGeometryProperty, value); } - public IBrush OpacityMask + public IBrush? OpacityMask { get => GetValue(OpacityMaskProperty); set => SetValue(OpacityMaskProperty, value); @@ -159,7 +159,7 @@ namespace Avalonia.Media public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) { - if (((brush == null) && (pen == null)) || (geometry == null)) + if ((brush == null) && (pen == null)) { return; } @@ -167,9 +167,9 @@ namespace Avalonia.Media AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry)); } - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { - if (foreground == null || glyphRun == null) + if (foreground == null) { return; } @@ -184,7 +184,7 @@ namespace Avalonia.Media AddDrawing(glyphRunDrawing); } - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { if (pen == null) { diff --git a/src/Avalonia.Base/Media/DrawingImage.cs b/src/Avalonia.Base/Media/DrawingImage.cs index 38ddbdfaed..1b22a1ee69 100644 --- a/src/Avalonia.Base/Media/DrawingImage.cs +++ b/src/Avalonia.Base/Media/DrawingImage.cs @@ -20,8 +20,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty DrawingProperty = - AvaloniaProperty.Register(nameof(Drawing)); + public static readonly StyledProperty DrawingProperty = + AvaloniaProperty.Register(nameof(Drawing)); /// public event EventHandler? Invalidated; @@ -30,7 +30,7 @@ namespace Avalonia.Media /// Gets or sets the drawing content. /// [Content] - public Drawing Drawing + public Drawing? Drawing { get => GetValue(DrawingProperty); set => SetValue(DrawingProperty, value); diff --git a/src/Avalonia.Base/Media/FontFamily.cs b/src/Avalonia.Base/Media/FontFamily.cs index da84861668..f4406bd010 100644 --- a/src/Avalonia.Base/Media/FontFamily.cs +++ b/src/Avalonia.Base/Media/FontFamily.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media case 2: { - var source = segments[0].StartsWith("/") + var source = segments[0].StartsWith("/", StringComparison.Ordinal) ? new Uri(segments[0], UriKind.Relative) : new Uri(segments[0], UriKind.RelativeOrAbsolute); @@ -188,7 +188,7 @@ namespace Avalonia.Media { unchecked { - return ((FamilyNames != null ? FamilyNames.GetHashCode() : 0) * 397) ^ (Key != null ? Key.GetHashCode() : 0); + return (FamilyNames.GetHashCode() * 397) ^ (Key is not null ? Key.GetHashCode() : 0); } } diff --git a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs index f607c67fed..12bb7e77e7 100644 --- a/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Base/Media/Fonts/FontFamilyKey.cs @@ -41,10 +41,7 @@ namespace Avalonia.Media.Fonts { var hash = (int)2166136261; - if (Source != null) - { - hash = (hash * 16777619) ^ Source.GetHashCode(); - } + hash = (hash * 16777619) ^ Source.GetHashCode(); if (BaseUri != null) { diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 28757b1a1d..3b63a98720 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -1354,7 +1354,7 @@ namespace Avalonia.Media { var highlightBounds = currentLine.GetTextBounds(x0,x1 - x0); - if (highlightBounds != null) + if (highlightBounds.Count > 0) { foreach (var bound in highlightBounds) { @@ -1365,7 +1365,7 @@ namespace Avalonia.Media // Convert logical units (which extend leftward from the right edge // of the paragraph) to physical units. // - // Note that since rect is in logical units, rect.Right corresponds to + // Note that since rect is in logical units, rect.Right corresponds to // the visual *left* edge of the rectangle in the RTL case. Specifically, // is the distance leftward from the right edge of the formatting rectangle // whose width is the paragraph width passed to FormatLine. @@ -1384,7 +1384,7 @@ namespace Avalonia.Media else { accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union); - } + } } } } diff --git a/src/Avalonia.Base/Media/GeometryDrawing.cs b/src/Avalonia.Base/Media/GeometryDrawing.cs index 26cc2c3cab..ac2dce1e42 100644 --- a/src/Avalonia.Base/Media/GeometryDrawing.cs +++ b/src/Avalonia.Base/Media/GeometryDrawing.cs @@ -15,8 +15,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty GeometryProperty = - AvaloniaProperty.Register(nameof(Geometry)); + public static readonly StyledProperty GeometryProperty = + AvaloniaProperty.Register(nameof(Geometry)); /// /// Defines the property. @@ -34,7 +34,7 @@ namespace Avalonia.Media /// Gets or sets the that describes the shape of this . /// [Content] - public Geometry Geometry + public Geometry? Geometry { get => GetValue(GeometryProperty); set => SetValue(GeometryProperty, value); diff --git a/src/Avalonia.Base/Media/GlyphRunDrawing.cs b/src/Avalonia.Base/Media/GlyphRunDrawing.cs index 242b9913fa..06d92fd81c 100644 --- a/src/Avalonia.Base/Media/GlyphRunDrawing.cs +++ b/src/Avalonia.Base/Media/GlyphRunDrawing.cs @@ -2,19 +2,19 @@ { public class GlyphRunDrawing : Drawing { - public static readonly StyledProperty ForegroundProperty = - AvaloniaProperty.Register(nameof(Foreground)); + public static readonly StyledProperty ForegroundProperty = + AvaloniaProperty.Register(nameof(Foreground)); - public static readonly StyledProperty GlyphRunProperty = - AvaloniaProperty.Register(nameof(GlyphRun)); + public static readonly StyledProperty GlyphRunProperty = + AvaloniaProperty.Register(nameof(GlyphRun)); - public IBrush Foreground + public IBrush? Foreground { get => GetValue(ForegroundProperty); set => SetValue(ForegroundProperty, value); } - public GlyphRun GlyphRun + public GlyphRun? GlyphRun { get => GetValue(GlyphRunProperty); set => SetValue(GlyphRunProperty, value); diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 425a3138c3..b4bf6fd217 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -254,7 +254,7 @@ namespace Avalonia.Media /// The HSL color string to parse. /// The parsed . /// True if parsing was successful; otherwise, false. - public static bool TryParse(string s, out HslColor hslColor) + public static bool TryParse(string? s, out HslColor hslColor) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index 9f95b31518..f97457c54d 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -254,7 +254,7 @@ namespace Avalonia.Media /// The HSV color string to parse. /// The parsed . /// True if parsing was successful; otherwise, false. - public static bool TryParse(string s, out HsvColor hsvColor) + public static bool TryParse(string? s, out HsvColor hsvColor) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Media/IVisualBrush.cs b/src/Avalonia.Base/Media/IVisualBrush.cs index 6662613ff4..a7d3e4da10 100644 --- a/src/Avalonia.Base/Media/IVisualBrush.cs +++ b/src/Avalonia.Base/Media/IVisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Metadata; -using Avalonia.VisualTree; namespace Avalonia.Media { @@ -12,6 +11,6 @@ namespace Avalonia.Media /// /// Gets the visual to draw. /// - Visual Visual { get; } + Visual? Visual { get; } } } diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs index 1f53f06955..6dff006045 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableDashStyle.cs @@ -39,17 +39,8 @@ namespace Avalonia.Media.Immutable { return true; } - else if (other is null) - { - return false; - } - if (Offset != other.Offset) - { - return false; - } - - return SequenceEqual(Dashes, other.Dashes); + return other is not null && Offset == other.Offset && SequenceEqual(_dashes, other.Dashes); } /// @@ -58,30 +49,27 @@ namespace Avalonia.Media.Immutable var hashCode = 717868523; hashCode = hashCode * -1521134295 + Offset.GetHashCode(); - if (_dashes != null) + foreach (var i in _dashes) { - foreach (var i in _dashes) - { - hashCode = hashCode * -1521134295 + i.GetHashCode(); - } + hashCode = hashCode * -1521134295 + i.GetHashCode(); } return hashCode; } - private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList? right) + private static bool SequenceEqual(double[] left, IReadOnlyList? right) { if (ReferenceEquals(left, right)) { return true; } - if (left == null || right == null || left.Count != right.Count) + if (right is null || left.Length != right.Count) { return false; } - for (var c = 0; c < left.Count; c++) + for (var c = 0; c < left.Length; c++) { if (left[c] != right[c]) { diff --git a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs index 9b443391c5..0b625080e3 100644 --- a/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs +++ b/src/Avalonia.Base/Media/Immutable/ImmutableVisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Media.Imaging; -using Avalonia.VisualTree; namespace Avalonia.Media.Immutable { @@ -31,11 +30,11 @@ namespace Avalonia.Media.Immutable RelativeRect? destinationRect = null, double opacity = 1, ImmutableTransform? transform = null, - RelativePoint transformOrigin = new RelativePoint(), + RelativePoint transformOrigin = default, RelativeRect? sourceRect = null, Stretch stretch = Stretch.Uniform, TileMode tileMode = TileMode.None, - Imaging.BitmapInterpolationMode bitmapInterpolationMode = Imaging.BitmapInterpolationMode.Default) + BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default) : base( alignmentX, alignmentY, @@ -62,6 +61,6 @@ namespace Avalonia.Media.Immutable } /// - public Visual Visual { get; } + public Visual? Visual { get; } } } diff --git a/src/Avalonia.Base/Media/TextDecoration.cs b/src/Avalonia.Base/Media/TextDecoration.cs index dc9e5cb907..b74b7df9c5 100644 --- a/src/Avalonia.Base/Media/TextDecoration.cs +++ b/src/Avalonia.Base/Media/TextDecoration.cs @@ -22,8 +22,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty StrokeProperty = - AvaloniaProperty.Register(nameof(Stroke)); + public static readonly StyledProperty StrokeProperty = + AvaloniaProperty.Register(nameof(Stroke)); /// /// Defines the property. @@ -34,8 +34,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty> StrokeDashArrayProperty = - AvaloniaProperty.Register>(nameof(StrokeDashArray)); + public static readonly StyledProperty?> StrokeDashArrayProperty = + AvaloniaProperty.Register?>(nameof(StrokeDashArray)); /// /// Defines the property. @@ -82,7 +82,7 @@ namespace Avalonia.Media /// /// Gets or sets the that specifies how the is painted. /// - public IBrush Stroke + public IBrush? Stroke { get { return GetValue(StrokeProperty); } set { SetValue(StrokeProperty, value); } @@ -101,7 +101,7 @@ namespace Avalonia.Media /// Gets or sets a collection of values that indicate the pattern of dashes and gaps /// that is used to draw the . /// - public AvaloniaList StrokeDashArray + public AvaloniaList? StrokeDashArray { get { return GetValue(StrokeDashArrayProperty); } set { SetValue(StrokeDashArrayProperty, value); } @@ -220,7 +220,7 @@ namespace Avalonia.Media var intersections = glyphRun.PlatformImpl.Item.GetIntersections((float)(thickness * 0.5d - offsetY), (float)(thickness * 1.5d - offsetY)); - if (intersections != null && intersections.Count > 0) + if (intersections.Count > 0) { var last = baselineOrigin.X; var finalPos = last + glyphRun.Size.Width; diff --git a/src/Avalonia.Base/Media/VisualBrush.cs b/src/Avalonia.Base/Media/VisualBrush.cs index 1261d233ac..2be3e9a94e 100644 --- a/src/Avalonia.Base/Media/VisualBrush.cs +++ b/src/Avalonia.Base/Media/VisualBrush.cs @@ -1,5 +1,4 @@ using Avalonia.Media.Immutable; -using Avalonia.VisualTree; namespace Avalonia.Media { @@ -11,8 +10,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty VisualProperty = - AvaloniaProperty.Register(nameof(Visual)); + public static readonly StyledProperty VisualProperty = + AvaloniaProperty.Register(nameof(Visual)); static VisualBrush() { @@ -38,7 +37,7 @@ namespace Avalonia.Media /// /// Gets or sets the visual to draw. /// - public Visual Visual + public Visual? Visual { get { return GetValue(VisualProperty); } set { SetValue(VisualProperty, value); } diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs index c05c04c22e..8509067cd0 100644 --- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs @@ -49,7 +49,7 @@ namespace Avalonia.Platform /// The stroke pen. /// The first point of the line. /// The second point of the line. - void DrawLine(IPen pen, Point p1, Point p2); + void DrawLine(IPen? pen, Point p1, Point p2); /// /// Draws a geometry. @@ -91,7 +91,7 @@ namespace Avalonia.Platform /// /// The foreground. /// The glyph run. - void DrawGlyphRun(IBrush foreground, IRef glyphRun); + void DrawGlyphRun(IBrush? foreground, IRef glyphRun); /// /// Creates a new that can be used as a render layer diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index 467cd530fc..d1a803fefb 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -19,25 +19,20 @@ internal class AssemblyDescriptor : IAssemblyDescriptor public AssemblyDescriptor(Assembly assembly) { Assembly = assembly; + Resources = assembly.GetManifestResourceNames() + .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); + Name = assembly.GetName().Name; - if (assembly != null) + using var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName); + if (resources != null) { - Resources = assembly.GetManifestResourceNames() - .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); - Name = assembly.GetName().Name; - using (var resources = assembly.GetManifestResourceStream(Constants.AvaloniaResourceName)) - { - if (resources != null) - { - Resources.Remove(Constants.AvaloniaResourceName); + Resources.Remove(Constants.AvaloniaResourceName); - var indexLength = new BinaryReader(resources).ReadInt32(); - var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); - var baseOffset = indexLength + 4; - AvaloniaResources = index.ToDictionary(r => GetPathRooted(r), r => (IAssetDescriptor) - new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); - } - } + var indexLength = new BinaryReader(resources).ReadInt32(); + var index = AvaloniaResourcesIndexReaderWriter.ReadIndex(new SlicedStream(resources, 4, indexLength)); + var baseOffset = indexLength + 4; + AvaloniaResources = index.ToDictionary(GetPathRooted, r => (IAssetDescriptor) + new AvaloniaResourceDescriptor(assembly, baseOffset + r.Offset, r.Size)); } } @@ -45,6 +40,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor public Dictionary? Resources { get; } public Dictionary? AvaloniaResources { get; } public string? Name { get; } + private static string GetPathRooted(AvaloniaResourcesIndexEntry r) => r.Path![0] == '/' ? r.Path : '/' + r.Path; } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs index a6db4330a3..455e9ebb5f 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations public abstract class CompositionAnimation : CompositionObject, ICompositionAnimationBase { private readonly CompositionPropertySet _propertySet; - internal CompositionAnimation(Compositor compositor) : base(compositor, null!) + internal CompositionAnimation(Compositor compositor) : base(compositor, null) { _propertySet = new CompositionPropertySet(compositor); } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs index bad3991f43..1500e88abe 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimationGroup.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.Composition.Animations public void Remove(CompositionAnimation value) => Animations.Remove(value); public void RemoveAll() => Animations.Clear(); - public CompositionAnimationGroup(Compositor compositor) : base(compositor, null!) + public CompositionAnimationGroup(Compositor compositor) : base(compositor, null) { } } diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs index 72be4edd07..d9adf261f8 100644 --- a/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs +++ b/src/Avalonia.Base/Rendering/Composition/Animations/ImplicitAnimationCollection.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition.Animations { private Dictionary _inner = new Dictionary(); private IDictionary _innerface; - internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null!) + internal ImplicitAnimationCollection(Compositor compositor) : base(compositor, null) { _innerface = _inner; } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs index ab4329df62..bfe70d593d 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawingSurface.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Avalonia.Rendering.Composition.Server; using Avalonia.Threading; @@ -7,7 +6,7 @@ namespace Avalonia.Rendering.Composition; public class CompositionDrawingSurface : CompositionSurface { - internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server; + internal new ServerCompositionDrawingSurface Server => (ServerCompositionDrawingSurface)base.Server!; internal CompositionDrawingSurface(Compositor compositor) : base(compositor, new ServerCompositionDrawingSurface(compositor.Server)) { } diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs index 50332926ad..8c21b534db 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionObject.cs @@ -22,7 +22,7 @@ namespace Avalonia.Rendering.Composition public ImplicitAnimationCollection? ImplicitAnimations { get; set; } private protected InlineDictionary PendingAnimations; - internal CompositionObject(Compositor compositor, ServerObject server) + internal CompositionObject(Compositor compositor, ServerObject? server) { Compositor = compositor; Server = server; @@ -32,7 +32,7 @@ namespace Avalonia.Rendering.Composition /// The associated Compositor /// public Compositor Compositor { get; } - internal ServerObject Server { get; } + internal ServerObject? Server { get; } public bool IsDisposed { get; private set; } private bool _registeredForSerialization; diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs index 7d794af9a2..efd89951bb 100644 --- a/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs +++ b/src/Avalonia.Base/Rendering/Composition/CompositionPropertySet.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.Composition private readonly Dictionary _variants = new Dictionary(); private readonly Dictionary _objects = new Dictionary(); - internal CompositionPropertySet(Compositor compositor) : base(compositor, null!) + internal CompositionPropertySet(Compositor compositor) : base(compositor, null) { } diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs index 05488a558f..b75d080cfd 100644 --- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs @@ -88,8 +88,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW } /// - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { + if (pen is null) + { + return; + } + var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, pen, p1, p2)) @@ -159,8 +164,13 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW public object? GetFeature(Type t) => null; /// - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { + if (foreground is null) + { + return; + } + var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs index 560ee05c10..b15da5d05d 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/Expression.cs @@ -165,8 +165,6 @@ namespace Avalonia.Rendering.Composition.Expressions public override ExpressionVariant Evaluate(ref ExpressionEvaluationContext context) { - if (context.ForeignFunctionInterface == null) - return default; var args = new List(); foreach (var expr in Parameters) args.Add(expr.Evaluate(ref context)); diff --git a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs index 9086c59aad..f268364b54 100644 --- a/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs +++ b/src/Avalonia.Base/Rendering/Composition/Expressions/ExpressionEvaluationContext.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Avalonia.Rendering.Composition.Server; // Special license applies License.md diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs index c58beebe7f..50df8bd32b 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs @@ -66,7 +66,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.DrawBitmap(source, opacityMask, opacityMaskRect, destRect); } - public void DrawLine(IPen pen, Point p1, Point p2) + public void DrawLine(IPen? pen, Point p1, Point p2) { _impl.DrawLine(pen, p1, p2); } @@ -86,7 +86,7 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont _impl.DrawEllipse(brush, pen, rect); } - public void DrawGlyphRun(IBrush foreground, IRef glyphRun) + public void DrawGlyphRun(IBrush? foreground, IRef glyphRun) { _impl.DrawGlyphRun(foreground, glyphRun); } diff --git a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs index c67ac7057d..8e5dc38317 100644 --- a/src/Avalonia.Base/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Base/Rendering/ImmediateRenderer.cs @@ -48,8 +48,10 @@ namespace Avalonia.Rendering /// void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush) { - var visual = brush.Visual; - Render(new DrawingContext(context), visual, visual.Bounds); + if (brush.Visual is { } visual) + { + Render(new DrawingContext(context), visual, visual.Bounds); + } } internal static void Render(Visual visual, DrawingContext context, bool updateTransformedBounds) diff --git a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 12b67105e9..82f8fc2d56 100644 --- a/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Base/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -80,11 +80,8 @@ namespace Avalonia.Rendering.SceneGraph { p *= Transform.Invert(); - if (Material != null) - { - var rect = Rect.Rect; - return rect.ContainsExclusive(p); - } + var rect = Rect.Rect; + return rect.ContainsExclusive(p); } return false; diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs index 3c44dd63ce..fafafabd82 100644 --- a/src/Avalonia.Base/Utilities/TypeUtilities.cs +++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs @@ -212,7 +212,7 @@ namespace Avalonia.Utilities var toTypeConverter = TypeDescriptor.GetConverter(toUnderl); - if (toTypeConverter.CanConvertFrom(from) == true) + if (toTypeConverter.CanConvertFrom(from)) { result = toTypeConverter.ConvertFrom(null, culture, value); return true; @@ -220,7 +220,7 @@ namespace Avalonia.Utilities var fromTypeConverter = TypeDescriptor.GetConverter(from); - if (fromTypeConverter.CanConvertTo(toUnderl) == true) + if (fromTypeConverter.CanConvertTo(toUnderl)) { result = fromTypeConverter.ConvertTo(null, culture, value, toUnderl); return true; @@ -329,7 +329,7 @@ namespace Avalonia.Utilities } [RequiresUnreferencedCode(TrimmingMessages.ImplicitTypeConvertionRequiresUnreferencedCodeMessage)] - public static T ConvertImplicit(object value) + public static T ConvertImplicit(object? value) { if (TryConvertImplicit(typeof(T), value, out var result)) { @@ -369,11 +369,6 @@ namespace Avalonia.Utilities /// public static bool IsNumeric(Type type) { - if (type == null) - { - return false; - } - var underlyingType = Nullable.GetUnderlyingType(type); if (underlyingType != null) diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs index e72606bf70..237a491615 100644 --- a/src/Avalonia.Base/Utilities/WeakEvent.cs +++ b/src/Avalonia.Base/Utilities/WeakEvent.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using Avalonia.Threading; @@ -15,7 +11,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event { private readonly Func, Action> _subscribe; - readonly ConditionalWeakTable _subscriptions = new(); + private readonly ConditionalWeakTable _subscriptions = new(); internal WeakEvent( Action> subscribe, @@ -51,56 +47,6 @@ public class WeakEvent : WeakEvent where TEventArgs : Event private readonly WeakEvent _ev; private readonly TSender _target; private readonly Action _compact; - - struct Entry - { - WeakReference>? _reference; - int _hashCode; - - public Entry(IWeakEventSubscriber r) - { - if (r == null) - { - _reference = null; - _hashCode = 0; - return; - } - - _hashCode = r.GetHashCode(); - _reference = new WeakReference>(r); - } - - public bool IsEmpty - { - get - { - if (_reference == null) - return true; - if (_reference.TryGetTarget(out _)) - return false; - _reference = null; - return true; - } - } - - public bool TryGetTarget([MaybeNullWhen(false)]out IWeakEventSubscriber target) - { - if (_reference == null) - { - target = null!; - return false; - } - return _reference.TryGetTarget(out target); - } - - public bool Equals(IWeakEventSubscriber r) - { - if (_reference == null || r.GetHashCode() != _hashCode) - return false; - return _reference.TryGetTarget(out var target) && target == r; - } - } - private readonly Action _unsubscribe; private readonly WeakHashList> _list = new(); private bool _compactScheduled; @@ -114,7 +60,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event _unsubscribe = ev._subscribe(target, OnEvent); } - void Destroy() + private void Destroy() { if(_destroyed) return; @@ -134,15 +80,15 @@ public class WeakEvent : WeakEvent where TEventArgs : Event ScheduleCompact(); } - void ScheduleCompact() + private void ScheduleCompact() { if(_compactScheduled || _destroyed) return; _compactScheduled = true; Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); } - - void Compact() + + private void Compact() { if(!_compactScheduled) return; @@ -152,7 +98,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event Destroy(); } - void OnEvent(object? sender, TEventArgs eventArgs) + private void OnEvent(object? sender, TEventArgs eventArgs) { var alive = _list.GetAlive(); if(alive == null) @@ -196,4 +142,4 @@ public class WeakEvent return () => unsubscribe(s, handler); }); } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs index 020ba7a6d9..ef143144e6 100644 --- a/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs +++ b/src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs @@ -60,8 +60,7 @@ namespace Avalonia.Utilities private static class SubscriptionTypeStorage where TArgs : EventArgs where TSubscriber : class { - public static readonly ConditionalWeakTable> Subscribers - = new ConditionalWeakTable>(); + public static readonly ConditionalWeakTable> Subscribers = new(); } private class SubscriptionDic : Dictionary> @@ -69,8 +68,7 @@ namespace Avalonia.Utilities { } - private static readonly Dictionary> Accessors - = new Dictionary>(); + private static readonly Dictionary> s_accessors = new(); private class Subscription where T : EventArgs where TSubscriber : class { @@ -81,18 +79,17 @@ namespace Avalonia.Utilities private readonly Delegate _delegate; private Descriptor[] _data = new Descriptor[2]; - private int _count = 0; + private int _count; - delegate void CallerDelegate(TSubscriber s, object sender, T args); - - struct Descriptor + private delegate void CallerDelegate(TSubscriber s, object? sender, T args); + + private struct Descriptor { - public WeakReference Subscriber; - public CallerDelegate Caller; + public WeakReference? Subscriber; + public CallerDelegate? Caller; } - private static Dictionary s_Callers = - new Dictionary(); + private static readonly Dictionary s_callers = new(); public Subscription(SubscriptionDic sdic, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicEvents | DynamicallyAccessedMemberTypes.NonPublicEvents)] Type targetType, @@ -101,8 +98,8 @@ namespace Avalonia.Utilities _sdic = sdic; _target = target; _eventName = eventName; - if (!Accessors.TryGetValue(targetType, out var evDic)) - Accessors[targetType] = evDic = new Dictionary(); + if (!s_accessors.TryGetValue(targetType, out var evDic)) + s_accessors[targetType] = evDic = new Dictionary(); if (evDic.TryGetValue(eventName, out var info)) { @@ -123,12 +120,12 @@ namespace Avalonia.Utilities var del = new Action(OnEvent); _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target); - _info.AddMethod!.Invoke(target, new[] { _delegate }); + _info.AddMethod!.Invoke(target, new object?[] { _delegate }); } - void Destroy() + private void Destroy() { - _info.RemoveMethod!.Invoke(_target, new[] { _delegate }); + _info.RemoveMethod!.Invoke(_target, new object?[] { _delegate }); _sdic.Remove(_eventName); } @@ -146,8 +143,8 @@ namespace Avalonia.Utilities MethodInfo method = s.Method; var subscriber = (TSubscriber)s.Target!; - if (!s_Callers.TryGetValue(method, out var caller)) - s_Callers[method] = caller = + if (!s_callers.TryGetValue(method, out var caller)) + s_callers[method] = caller = (CallerDelegate)Delegate.CreateDelegate(typeof(CallerDelegate), null, method); _data[_count] = new Descriptor { @@ -178,7 +175,7 @@ namespace Avalonia.Utilities } } - void Compact(bool preventDestroy = false) + private void Compact(bool preventDestroy = false) { int empty = -1; for (int c = 0; c < _count; c++) @@ -206,15 +203,15 @@ namespace Avalonia.Utilities Destroy(); } - void OnEvent(object sender, T eventArgs) + private void OnEvent(object? sender, T eventArgs) { var needCompact = false; - for(var c=0; c<_count; c++) + for (var c = 0; c < _count; c++) { - var r = _data[c].Subscriber; + var r = _data[c].Subscriber!; if (r.TryGetTarget(out var sub)) { - _data[c].Caller(sub, sender, eventArgs); + _data[c].Caller!(sub, sender, eventArgs); } else needCompact = true; diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index e6d7492c51..58e84f29d8 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/src/Avalonia.Base/Visual.cs @@ -483,17 +483,13 @@ namespace Avalonia parent.HasNonUniformZIndexChildren = true; var visualChildren = VisualChildren; + var visualChildrenCount = visualChildren.Count; - if (visualChildren != null) + for (var i = 0; i < visualChildrenCount; i++) { - var visualChildrenCount = visualChildren.Count; - - for (var i = 0; i < visualChildrenCount; i++) + if (visualChildren[i] is { } child) { - if (visualChildren[i] is Visual child) - { - child.OnAttachedToVisualTreeCore(e); - } + child.OnAttachedToVisualTreeCore(e); } } } @@ -543,17 +539,13 @@ namespace Avalonia e.Root?.Renderer?.AddDirty(this); var visualChildren = VisualChildren; + var visualChildrenCount = visualChildren.Count; - if (visualChildren != null) + for (var i = 0; i < visualChildrenCount; i++) { - var visualChildrenCount = visualChildren.Count; - - for (var i = 0; i < visualChildrenCount; i++) + if (visualChildren[i] is { } child) { - if (visualChildren[i] is Visual child) - { - child.OnDetachedFromVisualTreeCore(e); - } + child.OnDetachedFromVisualTreeCore(e); } } } diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs index b58db3b276..9e38c6e7f2 100644 --- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs @@ -46,7 +46,7 @@ namespace Avalonia.VisualTree Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; - v = v?.VisualParent; + v = v.VisualParent; while (v != null) { @@ -64,17 +64,13 @@ namespace Avalonia.VisualTree /// The first visual. /// The second visual. /// The common ancestor, or null if not found. - public static Visual? FindCommonVisualAncestor(this Visual visual, Visual target) + public static Visual? FindCommonVisualAncestor(this Visual? visual, Visual? target) { - Visual? v = visual ?? throw new ArgumentNullException(nameof(visual)); - - if (target is null) + if (visual is null || target is null) { return null; } - Visual? t = target; - void GoUpwards(ref Visual? node, int count) { for (int i = 0; i < count; ++i) @@ -83,6 +79,9 @@ namespace Avalonia.VisualTree } } + Visual? v = visual; + Visual? t = target; + // We want to find lowest node first, then make sure that both nodes are at the same height. // By doing that we can sometimes find out that other node is our lowest common ancestor. var firstHeight = CalculateDistanceFromRoot(v); @@ -144,7 +143,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First ancestor of given type. - public static T? FindAncestorOfType(this Visual visual, bool includeSelf = false) where T : class + public static T? FindAncestorOfType(this Visual? visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -173,7 +172,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First descendant of given type. - public static T? FindDescendantOfType(this Visual visual, bool includeSelf = false) where T : class + public static T? FindDescendantOfType(this Visual? visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -392,7 +391,7 @@ namespace Avalonia.VisualTree /// True if is an ancestor of ; /// otherwise false. /// - public static bool IsVisualAncestorOf(this Visual visual, Visual target) + public static bool IsVisualAncestorOf(this Visual? visual, Visual? target) { Visual? current = target?.VisualParent; diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 225e846390..68466fe381 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -141,9 +141,7 @@ namespace Avalonia.Headless } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } class HeadlessGeometryStub : IGeometryImpl diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index dcb20d2a44..ba646c64ee 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -208,6 +208,12 @@ namespace Avalonia.Skia public void DrawLine(IPen pen, Point p1, Point p2) { CheckLease(); + + if (pen is null) + { + return; + } + using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y)))) { if (paint.Paint is object) @@ -495,6 +501,12 @@ namespace Avalonia.Skia public void DrawGlyphRun(IBrush foreground, IRef glyphRun) { CheckLease(); + + if (foreground is null) + { + return; + } + using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Item.Size)) { var glyphRunImpl = (GlyphRunImpl)glyphRun.Item; diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs index 24b8fc04b3..446db47d92 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphRunImpl.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Platform; using SharpDX.DirectWrite; @@ -25,8 +26,6 @@ namespace Avalonia.Direct2D1.Media } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } } diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs index 477f34565f..0319803a5e 100644 --- a/tests/Avalonia.UnitTests/MockGlyphRun.cs +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Media.TextFormatting; using Avalonia.Platform; @@ -24,12 +25,9 @@ namespace Avalonia.UnitTests public void Dispose() { - } public IReadOnlyList GetIntersections(float lowerBound, float upperBound) - { - return null; - } + => Array.Empty(); } } From d99b795504cedec99428e2038bf1df2408215122 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 7 Feb 2023 11:54:32 +0800 Subject: [PATCH 056/120] fix: fix ListBoxItem generation. --- src/Avalonia.Controls/ListBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs index 8b1a307182..80d1677c2f 100644 --- a/src/Avalonia.Controls/ListBox.cs +++ b/src/Avalonia.Controls/ListBox.cs @@ -104,6 +104,7 @@ namespace Avalonia.Controls public void UnselectAll() => Selection.Clear(); protected internal override Control CreateContainerForItemOverride() => new ListBoxItem(); + protected internal override bool IsItemItsOwnContainerOverride(Control item) => item is ListBoxItem; /// protected override void OnGotFocus(GotFocusEventArgs e) From 38aaadf92db4e4957a3ccab2365937c215e3c89a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 15:16:59 +0100 Subject: [PATCH 057/120] Use custom zoom logic when !_canResize. `[NSWindow setIsZoomed]` requires that the window is resizable by the user in order to work; when `canResize == false` this is not that case. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ce82f7d83f..af4f92524b 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -225,16 +225,12 @@ bool WindowImpl::IsZoomed() { } void WindowImpl::DoZoom() { - switch (_decorations) { - case SystemDecorationsNone: - case SystemDecorationsBorderOnly: - [Window setFrame:[Window screen].visibleFrame display:true]; - break; - - - case SystemDecorationsFull: - [Window performZoom:Window]; - break; + if (_decorations == SystemDecorationsNone || + _decorations == SystemDecorationsBorderOnly || + _canResize == false) { + [Window setFrame:[Window screen].visibleFrame display:true]; + } else { + [Window performZoom:Window]; } } From 5346344d12fa314c7317f90f03c8e798ee4ef6cc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:32:55 +0100 Subject: [PATCH 058/120] Don't call virtual method from ctor. And remove unneeded checks for already-existing `Window` (this method is always called from ctor). --- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 038e9a048c..4d3768a4a8 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -580,21 +580,12 @@ void WindowBaseImpl::CleanNSWindow() { } } -void WindowBaseImpl::CreateNSWindow(bool isDialog) { - if (isDialog) { - if (![Window isKindOfClass:[AvnPanel class]]) { - CleanNSWindow(); - - Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - - [Window setHidesOnDeactivate:false]; - } +void WindowBaseImpl::CreateNSWindow(bool usePanel) { + if (usePanel) { + Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; + [Window setHidesOnDeactivate:false]; } else { - if (![Window isKindOfClass:[AvnWindow class]]) { - CleanNSWindow(); - - Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - } + Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; } } From b609ba58a8e445ff68ddbcd64d9ab3d9d4f6cb6c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:43:02 +0100 Subject: [PATCH 059/120] Disable zoom button when CanResize == false. To do this more easily, merged `HideOrShowTrafficLights` into a virtual `UpdateStyle`. They were always called together, and really _must_ be called together; this enforces that. --- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 37 +++++++++---------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 4c2758f6c6..afa3a2956f 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -106,8 +106,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle(); - - void UpdateStyle(); + virtual void UpdateStyle(); private: void CreateNSWindow (bool isDialog); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 3861aaf170..e7e9b7f1d8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP() WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl); - void HideOrShowTrafficLights (); - virtual HRESULT Show (bool activate, bool isDialog) override; virtual HRESULT SetEnabled (bool enable) override; @@ -101,6 +99,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle() override; + void UpdateStyle () override; private: void OnInitialiseNSWindow(); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index af4f92524b..ae3e1eaf1d 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase OnInitialiseNSWindow(); } -void WindowImpl::HideOrShowTrafficLights() { - if (Window == nil) { - return; - } - - bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; -} - void WindowImpl::OnInitialiseNSWindow(){ [GetWindowProtocol() setCanBecomeKeyWindow:true]; @@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { WindowBaseImpl::Show(activate, isDialog); GetWindowState(&_actualWindowState); - HideOrShowTrafficLights(); - return SetWindowState(_lastWindowState); } } @@ -257,8 +242,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) { UpdateStyle(); - HideOrShowTrafficLights(); - switch (_decorations) { case SystemDecorationsNone: [Window setHasShadow:NO]; @@ -415,9 +398,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) { } [GetWindowProtocol() setIsExtended:enable]; - - HideOrShowTrafficLights(); - UpdateStyle(); } @@ -608,3 +588,20 @@ NSWindowStyleMask WindowImpl::GetStyle() { } return s; } + +void WindowImpl::UpdateStyle() { + WindowBaseImpl::UpdateStyle(); + + if (Window == nil) { + return; + } + + bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; + + [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; +} + From d40041f02dd8ae5e53074e3b4c3e47b420ff9457 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 19:13:59 +0100 Subject: [PATCH 060/120] GetStyle => abstract CalculateStyleMask. - Make naming more clear - it's not getting the style mask, it's calculating what it should be in order to update the mask - Make it abstract to prevent it being called from the ctor in future --- native/Avalonia.Native/src/OSX/PopupImpl.mm | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 10 +--------- native/Avalonia.Native/src/OSX/WindowImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm index 9820a9f052..972d03d08c 100644 --- a/native/Avalonia.Native/src/OSX/PopupImpl.mm +++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm @@ -29,7 +29,7 @@ private: [Window setLevel:NSPopUpMenuWindowLevel]; } protected: - virtual NSWindowStyleMask GetStyle() override + virtual NSWindowStyleMask CalculateStyleMask() override { return NSWindowStyleMaskBorderless; } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index afa3a2956f..93decef136 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -105,7 +105,7 @@ BEGIN_INTERFACE_MAP() virtual void BringToFront (); protected: - virtual NSWindowStyleMask GetStyle(); + virtual NSWindowStyleMask CalculateStyleMask() = 0; virtual void UpdateStyle(); private: diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 4d3768a4a8..59102e15a6 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, lastSize = NSSize { 100, 100 }; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMinSize = NSSize { 0, 0 }; - lastMenu = nullptr; CreateNSWindow(usePanel); [Window setContentView:StandardContainer]; - [Window setStyleMask:NSWindowStyleMaskBorderless]; [Window setBackingType:NSBackingStoreBuffered]; - [Window setContentMinSize:lastMinSize]; [Window setContentMaxSize:lastMaxSize]; - [Window setOpaque:false]; } @@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() { return false; } -NSWindowStyleMask WindowBaseImpl::GetStyle() { - return NSWindowStyleMaskBorderless; -} - void WindowBaseImpl::UpdateStyle() { - [Window setStyleMask:GetStyle()]; + [Window setStyleMask:CalculateStyleMask()]; } void WindowBaseImpl::CleanNSWindow() { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index e7e9b7f1d8..9c684c77c4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -98,7 +98,7 @@ BEGIN_INTERFACE_MAP() bool CanBecomeKeyWindow (); protected: - virtual NSWindowStyleMask GetStyle() override; + virtual NSWindowStyleMask CalculateStyleMask() override; void UpdateStyle () override; private: diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ae3e1eaf1d..afd9c1a5ea 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -553,7 +553,7 @@ bool WindowImpl::IsOwned() { return _parent != nullptr; } -NSWindowStyleMask WindowImpl::GetStyle() { +NSWindowStyleMask WindowImpl::CalculateStyleMask() { unsigned long s = NSWindowStyleMaskBorderless; if(_actualWindowState == FullScreen) From 959b09c2434a285cead448fa8f22a6d282afb90d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 00:18:15 +0100 Subject: [PATCH 061/120] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 0839cbf183..1933d10919 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From 6a1910172c2a060f7ef67d875af03064db768d5f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 10:26:45 +0100 Subject: [PATCH 062/120] Move traffic lights logic into one place. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 4 ---- native/Avalonia.Native/src/OSX/WindowImpl.mm | 15 ++++++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 784072221d..23abf1d53f 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -274,10 +274,6 @@ -(void) setEnabled:(bool)enable { _isEnabled = enable; - - [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable]; } -(void)becomeKeyWindow diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index afd9c1a5ea..e4bbe24cb8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -599,9 +599,14 @@ void WindowImpl::UpdateStyle() { bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; -} + NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton]; + NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton]; + NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton]; + [closeButton setHidden:!hasTrafficLights]; + [closeButton setEnabled:_isEnabled]; + [miniaturizeButton setHidden:!hasTrafficLights]; + [miniaturizeButton setEnabled:_isEnabled]; + [zoomButton setHidden:!hasTrafficLights]; + [zoomButton setEnabled:_isEnabled && _canResize]; +} From a705f546bba1f3fb973aae2cdeb9ed8d08cf4f16 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 11:46:18 +0100 Subject: [PATCH 063/120] Enable resizing during fullscreen transition. macOS seems to tie resizing of the NSView inside the window to the resizable style mask of the window somehow. If we programmatically transition a non-resizable window to fullscreen, the inner NSView's size isn't changed, so we need to make the window resizable during the fullscreen transition. Makes the final two failing `WindowState` integration tests pass. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index e4bbe24cb8..47e83f8d56 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -146,11 +146,13 @@ bool WindowImpl::CanBecomeKeyWindow() void WindowImpl::StartStateTransition() { _transitioningWindowState = true; + UpdateStyle(); } void WindowImpl::EndStateTransition() { _transitioningWindowState = false; - + UpdateStyle(); + // Ensure correct order of child windows after fullscreen transition. BringToFront(); } @@ -573,7 +575,7 @@ NSWindowStyleMask WindowImpl::CalculateStyleMask() { case SystemDecorationsFull: s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; - if (_canResize && _isEnabled) { + if ((_canResize && _isEnabled) || _transitioningWindowState) { s = s | NSWindowStyleMaskResizable; } break; From 50a368eaa15e7a5756eeda2ec2fdf69d43e9f6da Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 13:05:05 +0100 Subject: [PATCH 064/120] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 1933d10919..47a471fafd 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); From 6dae36ead9858385e710a117c8a2bc4b002e960c Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Tue, 7 Feb 2023 14:01:39 +0000 Subject: [PATCH 065/120] Revert "Only set pointer events to handled in button if click is triggered" --- src/Avalonia.Controls/Button.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 9627f200df..1ec6f8dabc 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -394,10 +394,10 @@ namespace Avalonia.Controls if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { IsPressed = true; + e.Handled = true; if (ClickMode == ClickMode.Press) { - e.Handled = true; OnClick(); } } @@ -411,11 +411,11 @@ namespace Avalonia.Controls if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) { IsPressed = false; + e.Handled = true; if (ClickMode == ClickMode.Release && this.GetVisualsAt(e.GetPosition(this)).Any(c => this == c || this.IsVisualAncestorOf(c))) { - e.Handled = true; OnClick(); } } From 784c380c604b6fd6a139abd7233aaf668661bfd0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 16:10:31 +0100 Subject: [PATCH 066/120] Don't change z-order of window exiting fullscreen. When a window exits fullscreen, its child windows need to be ordered, but we shouldn't touch the z-order of the window itself as this sometimes seemed to result in the parent window being shown over the child windows. Fixes flaky integration tests (hopefully). --- native/Avalonia.Native/src/OSX/WindowImpl.h | 1 + native/Avalonia.Native/src/OSX/WindowImpl.mm | 21 ++++++++++++------- .../WindowTests_MacOS.cs | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 9c684c77c4..29bb659039 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -102,6 +102,7 @@ protected: void UpdateStyle () override; private: + void ZOrderChildWindows(); void OnInitialiseNSWindow(); NSString *_lastTitle; }; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 47e83f8d56..4510d135dc 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -119,14 +119,19 @@ void WindowImpl::BringToFront() } [Window invalidateShadow]; + ZOrderChildWindows(); + } +} + +void WindowImpl::ZOrderChildWindows() +{ + for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) + { + auto window = (*iterator)->Window; - for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) - { - auto window = (*iterator)->Window; - - // #9565: Only bring window to front if it's on the currently active space - if ([window isOnActiveSpace]) - (*iterator)->BringToFront(); + // #9565: Only bring window to front if it's on the currently active space + if ([window isOnActiveSpace]) { + (*iterator)->BringToFront(); } } } @@ -154,7 +159,7 @@ void WindowImpl::EndStateTransition() { UpdateStyle(); // Ensure correct order of child windows after fullscreen transition. - BringToFront(); + ZOrderChildWindows(); } SystemDecorations WindowImpl::Decorations() { diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 47a471fafd..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From e1138f2cb6a393802b235a073d28e85a64690ffe Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 7 Feb 2023 16:53:49 +0100 Subject: [PATCH 067/120] Nullability fixes for Avalonia.Controls --- .../Input/Platform/IClipboard.cs | 6 +- src/Avalonia.Base/Media/Color.cs | 2 +- .../Platform/Internal/AssemblyDescriptor.cs | 5 +- .../ClassicDesktopStyleApplicationLifetime.cs | 28 +- ...IClassicDesktopStyleApplicationLifetime.cs | 5 +- .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- .../Automation/AutomationProperties.cs | 282 +++++------------- src/Avalonia.Controls/Calendar/Calendar.cs | 18 +- .../CalendarBlackoutDatesCollection.cs | 14 +- .../Calendar/CalendarItem.cs | 112 +++---- .../Calendar/DateTimeHelper.cs | 33 +- .../CalendarDatePicker/CalendarDatePicker.cs | 20 +- src/Avalonia.Controls/Chrome/TitleBar.cs | 40 +-- .../MenuScrollingVisibilityConverter.cs | 1 - src/Avalonia.Controls/DefinitionBase.cs | 90 +++--- src/Avalonia.Controls/DockPanel.cs | 9 +- src/Avalonia.Controls/Documents/Inline.cs | 9 +- src/Avalonia.Controls/Documents/Span.cs | 14 +- src/Avalonia.Controls/Grid.cs | 230 +++++++------- src/Avalonia.Controls/Image.cs | 7 +- src/Avalonia.Controls/ItemsControl.cs | 12 +- .../LayoutTransformControl.cs | 20 +- src/Avalonia.Controls/MaskedTextBox.cs | 12 +- src/Avalonia.Controls/NativeControlHost.cs | 22 +- src/Avalonia.Controls/NativeMenu.Export.cs | 20 +- .../Primitives/AdornerLayer.cs | 12 +- src/Avalonia.Controls/Primitives/Popup.cs | 12 +- .../Primitives/SelectingItemsControl.cs | 31 +- .../Primitives/TemplatedControl.cs | 7 +- .../Primitives/TextSearch.cs | 8 +- src/Avalonia.Controls/Primitives/Track.cs | 19 +- .../Primitives/VisualLayerManager.cs | 16 +- src/Avalonia.Controls/RelativePanel.cs | 7 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 44 ++- src/Avalonia.Controls/Slider.cs | 13 +- src/Avalonia.Controls/StackPanel.cs | 9 +- src/Avalonia.Controls/TickBar.cs | 18 +- src/Avalonia.Controls/TrayIcon.cs | 9 +- src/Avalonia.Controls/TreeView.cs | 16 +- src/Avalonia.Controls/Viewbox.cs | 36 +-- src/Avalonia.Controls/WrapPanel.cs | 107 +++---- src/Browser/Avalonia.Browser/ClipboardImpl.cs | 10 +- 42 files changed, 580 insertions(+), 807 deletions(-) diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index bf2a5a8602..3de352fc4f 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform [NotClientImplementable] public interface IClipboard { - Task GetTextAsync(); + Task GetTextAsync(); - Task SetTextAsync(string text); + Task SetTextAsync(string? text); Task ClearAsync(); @@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform Task GetFormatsAsync(); - Task GetDataAsync(string format); + Task GetDataAsync(string format); } } diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index ab89177295..74e70b2a14 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -331,7 +331,7 @@ namespace Avalonia.Media /// /// Parses the given string representing a CSS color value into a new . /// - private static bool TryParseCssFormat(string s, out Color color) + private static bool TryParseCssFormat(string? s, out Color color) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index d1a803fefb..6a577c204c 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -18,7 +19,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor { public AssemblyDescriptor(Assembly assembly) { - Assembly = assembly; + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); Resources = assembly.GetManifestResourceNames() .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); Name = assembly.GetName().Name; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index fde401fb01..ada0b94124 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -17,32 +16,34 @@ namespace Avalonia.Controls.ApplicationLifetimes private int _exitCode; private CancellationTokenSource? _cts; private bool _isShuttingDown; - private HashSet _windows = new HashSet(); + private readonly HashSet _windows = new(); + + private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime; - private static ClassicDesktopStyleApplicationLifetime? _activeLifetime; static ClassicDesktopStyleApplicationLifetime() { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); - Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), OnWindowClosed); } - private static void WindowClosedEvent(object? sender, RoutedEventArgs e) + private static void OnWindowClosed(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Remove((Window)sender!); - _activeLifetime?.HandleWindowClosed((Window)sender!); + var window = (Window)sender!; + s_activeLifetime?._windows.Remove(window); + s_activeLifetime?.HandleWindowClosed(window); } private static void OnWindowOpened(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Add((Window)sender!); + s_activeLifetime?._windows.Add((Window)sender!); } public ClassicDesktopStyleApplicationLifetime() { - if (_activeLifetime != null) + if (s_activeLifetime != null) throw new InvalidOperationException( "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); - _activeLifetime = this; + s_activeLifetime = this; } /// @@ -65,9 +66,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public Window? MainWindow { get; set; } + /// public IReadOnlyList Windows => _windows.ToArray(); - private void HandleWindowClosed(Window window) + private void HandleWindowClosed(Window? window) { if (window == null) return; @@ -130,8 +132,8 @@ namespace Avalonia.Controls.ApplicationLifetimes public void Dispose() { - if (_activeLifetime == this) - _activeLifetime = null; + if (s_activeLifetime == this) + s_activeLifetime = null; } private bool DoShutdown( diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 22b5f8236d..b9a372f935 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -40,7 +40,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// The main window. /// Window? MainWindow { get; set; } - + + /// + /// Gets the list of all open windows in the application. + /// IReadOnlyList Windows { get; } /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 55649660f7..9a949e31d4 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -1711,7 +1711,7 @@ namespace Avalonia.Controls /// The predicate to use for the partial or /// exact match. /// Returns the object or null. - private object? TryGetMatch(string? searchText, AvaloniaList view, AutoCompleteFilterPredicate? predicate) + private object? TryGetMatch(string? searchText, AvaloniaList? view, AutoCompleteFilterPredicate? predicate) { if (predicate is null) return null; diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index 35f94722ce..3ea9c170ff 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -38,8 +38,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AcceleratorKey attached property. /// - public static readonly AttachedProperty AcceleratorKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AcceleratorKeyProperty = + AvaloniaProperty.RegisterAttached( "AcceleratorKey", typeof(AutomationProperties)); @@ -54,16 +54,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AccessKey attached property /// - public static readonly AttachedProperty AccessKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AccessKeyProperty = + AvaloniaProperty.RegisterAttached( "AccessKey", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.AutomationId attached property. /// - public static readonly AttachedProperty AutomationIdProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AutomationIdProperty = + AvaloniaProperty.RegisterAttached( "AutomationId", typeof(AutomationProperties)); @@ -78,8 +78,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.HelpText attached property. /// - public static readonly AttachedProperty HelpTextProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty HelpTextProperty = + AvaloniaProperty.RegisterAttached( "HelpText", typeof(AutomationProperties)); @@ -122,16 +122,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.ItemStatus attached property. /// - public static readonly AttachedProperty ItemStatusProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemStatusProperty = + AvaloniaProperty.RegisterAttached( "ItemStatus", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.ItemType attached property. /// - public static readonly AttachedProperty ItemTypeProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemTypeProperty = + AvaloniaProperty.RegisterAttached( "ItemType", typeof(AutomationProperties)); @@ -155,8 +155,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.Name attached attached property. /// - public static readonly AttachedProperty NameProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached( "Name", typeof(AutomationProperties)); @@ -193,25 +193,17 @@ namespace Avalonia.Automation /// public static void SetAcceleratorKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AcceleratorKeyProperty, value); } /// /// Helper for reading AcceleratorKey property from a StyledElement. /// - public static string GetAcceleratorKey(StyledElement element) + public static string? GetAcceleratorKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AcceleratorKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AcceleratorKeyProperty); } /// @@ -219,11 +211,7 @@ namespace Avalonia.Automation /// public static void SetAccessibilityView(StyledElement element, AccessibilityView value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessibilityViewProperty, value); } @@ -232,11 +220,7 @@ namespace Avalonia.Automation /// public static AccessibilityView GetAccessibilityView(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AccessibilityViewProperty); } @@ -245,50 +229,34 @@ namespace Avalonia.Automation /// public static void SetAccessKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessKeyProperty, value); } /// /// Helper for reading AccessKey property from a StyledElement. /// - public static string GetAccessKey(StyledElement element) + public static string? GetAccessKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AccessKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AccessKeyProperty); } /// /// Helper for setting AutomationId property on a StyledElement. /// - public static void SetAutomationId(StyledElement element, string value) + public static void SetAutomationId(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AutomationIdProperty, value); } /// /// Helper for reading AutomationId property from a StyledElement. /// - public static string GetAutomationId(StyledElement element) + public static string? GetAutomationId(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AutomationIdProperty); } @@ -297,11 +265,7 @@ namespace Avalonia.Automation /// public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ControlTypeOverrideProperty, value); } @@ -310,38 +274,26 @@ namespace Avalonia.Automation /// public static AutomationControlType? GetControlTypeOverride(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(ControlTypeOverrideProperty); } /// /// Helper for setting HelpText property on a StyledElement. /// - public static void SetHelpText(StyledElement element, string value) + public static void SetHelpText(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(HelpTextProperty, value); } /// /// Helper for reading HelpText property from a StyledElement. /// - public static string GetHelpText(StyledElement element) + public static string? GetHelpText(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(HelpTextProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(HelpTextProperty); } /// @@ -349,11 +301,7 @@ namespace Avalonia.Automation /// public static void SetIsColumnHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsColumnHeaderProperty, value); } @@ -362,12 +310,8 @@ namespace Avalonia.Automation /// public static bool GetIsColumnHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsColumnHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsColumnHeaderProperty); } /// @@ -375,11 +319,7 @@ namespace Avalonia.Automation /// public static void SetIsRequiredForForm(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRequiredForFormProperty, value); } @@ -388,12 +328,8 @@ namespace Avalonia.Automation /// public static bool GetIsRequiredForForm(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRequiredForFormProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRequiredForFormProperty); } /// @@ -401,12 +337,8 @@ namespace Avalonia.Automation /// public static bool GetIsRowHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRowHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRowHeaderProperty); } /// @@ -414,11 +346,7 @@ namespace Avalonia.Automation /// public static void SetIsRowHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRowHeaderProperty, value); } @@ -427,11 +355,7 @@ namespace Avalonia.Automation /// public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsOffscreenBehaviorProperty, value); } @@ -440,64 +364,44 @@ namespace Avalonia.Automation /// public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsOffscreenBehaviorProperty); } /// /// Helper for setting ItemStatus property on a StyledElement. /// - public static void SetItemStatus(StyledElement element, string value) + public static void SetItemStatus(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemStatusProperty, value); } /// /// Helper for reading ItemStatus property from a StyledElement. /// - public static string GetItemStatus(StyledElement element) + public static string? GetItemStatus(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemStatusProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemStatusProperty); } /// /// Helper for setting ItemType property on a StyledElement. /// - public static void SetItemType(StyledElement element, string value) + public static void SetItemType(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemTypeProperty, value); } /// /// Helper for reading ItemType property from a StyledElement. /// - public static string GetItemType(StyledElement element) + public static string? GetItemType(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemTypeProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemTypeProperty); } /// @@ -505,11 +409,7 @@ namespace Avalonia.Automation /// public static void SetLabeledBy(StyledElement element, Control value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LabeledByProperty, value); } @@ -518,11 +418,7 @@ namespace Avalonia.Automation /// public static Control GetLabeledBy(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(LabeledByProperty); } @@ -531,11 +427,7 @@ namespace Avalonia.Automation /// public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LiveSettingProperty, value); } @@ -544,38 +436,26 @@ namespace Avalonia.Automation /// public static AutomationLiveSetting GetLiveSetting(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(LiveSettingProperty); } /// /// Helper for setting Name property on a StyledElement. /// - public static void SetName(StyledElement element, string value) + public static void SetName(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(NameProperty, value); } /// /// Helper for reading Name property from a StyledElement. /// - public static string GetName(StyledElement element) + public static string? GetName(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(NameProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(NameProperty); } /// @@ -583,11 +463,7 @@ namespace Avalonia.Automation /// public static void SetPositionInSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(PositionInSetProperty, value); } @@ -596,12 +472,8 @@ namespace Avalonia.Automation /// public static int GetPositionInSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(PositionInSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(PositionInSetProperty); } /// @@ -609,11 +481,7 @@ namespace Avalonia.Automation /// public static void SetSizeOfSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(SizeOfSetProperty, value); } @@ -622,12 +490,8 @@ namespace Avalonia.Automation /// public static int GetSizeOfSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(SizeOfSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(SizeOfSetProperty); } } } diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 9c88bae5f6..3300292857 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -237,11 +237,11 @@ namespace Avalonia.Controls private DateTime _selectedYear; private DateTime _displayDate = DateTime.Today; - private DateTime? _displayDateStart = null; - private DateTime? _displayDateEnd = null; + private DateTime? _displayDateStart; + private DateTime? _displayDateEnd; private bool _isShiftPressed; - private bool _displayDateIsChanging = false; + private bool _displayDateIsChanging; internal CalendarDayButton? FocusButton { get; set; } internal CalendarButton? FocusCalendarButton { get; set; } @@ -291,7 +291,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid DayOfWeek"); } } @@ -346,10 +346,10 @@ namespace Avalonia.Controls } } - public static readonly StyledProperty HeaderBackgroundProperty = - AvaloniaProperty.Register(nameof(HeaderBackground)); + public static readonly StyledProperty HeaderBackgroundProperty = + AvaloniaProperty.Register(nameof(HeaderBackground)); - public IBrush HeaderBackground + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } @@ -478,7 +478,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid SelectionMode"); } } @@ -574,7 +574,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid."); + throw new ArgumentOutOfRangeException(nameof(e), "SelectedDate value is not valid."); } } else diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index fe8b616e02..8fb9b66f3d 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The Calendar whose dates this object represents. /// - private Calendar _owner; + private readonly Calendar _owner; /// /// Initializes a new instance of the @@ -79,13 +79,13 @@ namespace Avalonia.Controls.Primitives if (DateTime.Compare(end, start) > -1) { - rangeStart = DateTimeHelper.DiscardTime(start).Value; - rangeEnd = DateTimeHelper.DiscardTime(end).Value; + rangeStart = DateTimeHelper.DiscardTime(start); + rangeEnd = DateTimeHelper.DiscardTime(end); } else { - rangeStart = DateTimeHelper.DiscardTime(end).Value; - rangeEnd = DateTimeHelper.DiscardTime(start).Value; + rangeStart = DateTimeHelper.DiscardTime(end); + rangeEnd = DateTimeHelper.DiscardTime(start); } int count = Count; @@ -144,7 +144,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.InsertItem(index, item); @@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.SetItem(index, item); diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 032f452111..3d436b4485 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -44,30 +44,30 @@ namespace Avalonia.Controls.Primitives private ITemplate? _dayTitleTemplate; private DateTime _currentMonth; - private bool _isMouseLeftButtonDown = false; - private bool _isMouseLeftButtonDownYearView = false; - private bool _isControlPressed = false; + private bool _isMouseLeftButtonDown; + private bool _isMouseLeftButtonDownYearView; + private bool _isControlPressed; - private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar(); - - private PointerPressedEventArgs? _downEventArg; - private PointerPressedEventArgs? _downEventArgYearView; + private readonly System.Globalization.Calendar _calendar = new GregorianCalendar(); internal Calendar? Owner { get; set; } internal CalendarDayButton? CurrentButton { get; set; } - public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); - public IBrush HeaderBackground + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } } + public static readonly DirectProperty?> DayTitleTemplateProperty = AvaloniaProperty.RegisterDirect?>( nameof(DayTitleTemplate), o => o.DayTitleTemplate, (o,v) => o.DayTitleTemplate = v, defaultBindingMode: BindingMode.OneTime); + public ITemplate? DayTitleTemplate { get { return _dayTitleTemplate; } @@ -178,7 +178,7 @@ namespace Avalonia.Controls.Primitives { if (_dayTitleTemplate != null) { - var cell = (Control) _dayTitleTemplate.Build(); + var cell = _dayTitleTemplate.Build(); cell.DataContext = string.Empty; cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.ColumnProperty, i); @@ -308,16 +308,13 @@ namespace Avalonia.Controls.Primitives for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++) { var daytitle = MonthView!.Children[childIndex]; - if (daytitle != null) + if (Owner != null) { - if (Owner != null) - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; - } - else - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; - } + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; + } + else + { + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; } } } @@ -527,7 +524,7 @@ namespace Avalonia.Controls.Primitives childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat()); childButton.DataContext = dateToAdd; - if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) + if (DateTime.Compare(DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) { // Since we are sure DisplayDate is not equal to // DateTime.MaxValue, it is safe to use AddDays @@ -587,7 +584,7 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -676,7 +673,7 @@ namespace Avalonia.Controls.Primitives if (Owner != null) { selectedYear = Owner.SelectedYear; - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -696,9 +693,9 @@ namespace Avalonia.Controls.Primitives SetYearButtons(decade, decadeEnd); } } - internal void UpdateYearViewSelection(CalendarButton calendarButton) + internal void UpdateYearViewSelection(CalendarButton? calendarButton) { - if (Owner != null && calendarButton != null && calendarButton.DataContext != null) + if (Owner != null && calendarButton?.DataContext is DateTime selectedDate) { Owner.FocusCalendarButton!.IsCalendarButtonFocused = false; Owner.FocusCalendarButton = calendarButton; @@ -706,11 +703,11 @@ namespace Avalonia.Controls.Primitives if (Owner.DisplayMode == CalendarMode.Year) { - Owner.SelectedMonth = (DateTime)calendarButton.DataContext; + Owner.SelectedMonth = selectedDate; } else { - Owner.SelectedYear = (DateTime)calendarButton.DataContext; + Owner.SelectedYear = selectedDate; } } } @@ -719,7 +716,7 @@ namespace Avalonia.Controls.Primitives { int year; int count = -1; - foreach (object child in YearView!.Children) + foreach (var child in YearView!.Children) { CalendarButton childButton = (CalendarButton)child; year = decade + count; @@ -859,7 +856,8 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout) + if (_isMouseLeftButtonDown + && sender is CalendarDayButton { IsEnabled: true, IsBlackout: false, DataContext: DateTime selectedDate } b) { // Update the states of all buttons to be selected starting // from HoverStart to b @@ -867,7 +865,6 @@ namespace Avalonia.Controls.Primitives { case CalendarSelectionMode.SingleDate: { - DateTime selectedDate = (DateTime)b.DataContext!; Owner.CalendarDatePickerDisplayDateFlag = true; if (Owner.SelectedDates.Count == 0) { @@ -882,10 +879,9 @@ namespace Avalonia.Controls.Primitives case CalendarSelectionMode.SingleRange: case CalendarSelectionMode.MultipleRange: { - Debug.Assert(b.DataContext != null, "The DataContext should not be null!"); Owner.UnHighlightDays(); Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime?)b.DataContext; + Owner.HoverEnd = selectedDate; // Update the States of the buttons Owner.HighlightDays(); return; @@ -904,22 +900,14 @@ namespace Avalonia.Controls.Primitives Owner.Focus(); } - bool ctrl, shift; - CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift); - CalendarDayButton b = (CalendarDayButton)sender!; + CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift); - if (b != null) + if (sender is CalendarDayButton b) { _isControlPressed = ctrl; - if (b.IsEnabled && !b.IsBlackout) + if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate) { - DateTime selectedDate = (DateTime)b.DataContext!; _isMouseLeftButtonDown = true; - // null check is added for unit tests - if (e != null) - { - _downEventArg = e; - } switch (Owner.SelectionMode) { @@ -1010,12 +998,12 @@ namespace Avalonia.Controls.Primitives } } } - private void AddSelection(CalendarDayButton b) + private void AddSelection(CalendarDayButton b, DateTime selectedDate) { if (Owner != null) { Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime)b.DataContext!; + Owner.HoverEnd = selectedDate; if (Owner.HoverEnd != null && Owner.HoverStart != null) { @@ -1025,7 +1013,7 @@ namespace Avalonia.Controls.Primitives // SelectionMode Owner.IsMouseSelection = true; Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value); - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1039,11 +1027,11 @@ namespace Avalonia.Controls.Primitives Owner.OnDayButtonMouseUp(e); } _isMouseLeftButtonDown = false; - if (b != null && b.DataContext != null) + if (b != null && b.DataContext is DateTime selectedDate) { if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); return; } if (Owner.HoverStart.HasValue) @@ -1058,14 +1046,14 @@ namespace Avalonia.Controls.Primitives Owner.RemovedItems.Add(item); } Owner.SelectedDates.ClearInternal(); - AddSelection(b); + AddSelection(b, selectedDate); return; } case CalendarSelectionMode.MultipleRange: { // add the selection (either single day or // SingleRange day) - AddSelection(b); + AddSelection(b, selectedDate); return; } } @@ -1076,7 +1064,7 @@ namespace Avalonia.Controls.Primitives // be able to switch months if (b.IsInactive && b.IsBlackout) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1095,9 +1083,9 @@ namespace Avalonia.Controls.Primitives Owner.HoverStart = null; _isMouseLeftButtonDown = false; b.IsSelected = false; - if (b.DataContext != null) + if (b.DataContext is DateTime selectedDate) { - Owner.SelectedDates.Remove((DateTime)b.DataContext); + Owner.SelectedDates.Remove(selectedDate); } } } @@ -1107,35 +1095,26 @@ namespace Avalonia.Controls.Primitives private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e) { - CalendarButton b = (CalendarButton)sender!; - _isMouseLeftButtonDownYearView = true; - if (e != null) - { - _downEventArgYearView = e; - } - - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e) { _isMouseLeftButtonDownYearView = false; - if (Owner != null) + if (Owner != null && (sender as CalendarButton)?.DataContext is DateTime newMonth) { - DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!; - if (Owner.DisplayMode == CalendarMode.Year) { - Owner.DisplayDate = newmonth; + Owner.DisplayDate = newMonth; Owner.DisplayMode = CalendarMode.Month; } else { Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!"); - Owner.SelectedMonth = newmonth; + Owner.SelectedMonth = newMonth; Owner.DisplayMode = CalendarMode.Year; } } @@ -1145,8 +1124,7 @@ namespace Avalonia.Controls.Primitives { if (_isMouseLeftButtonDownYearView) { - CalendarButton b = (CalendarButton)sender!; - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } } diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs index bfff03a926..570f05cfe8 100644 --- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs +++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls public static int CompareDays(DateTime dt1, DateTime dt2) { - return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value); + return DateTime.Compare(DiscardTime(dt1), DiscardTime(dt2)); } public static int CompareYearMonth(DateTime dt1, DateTime dt2) @@ -71,14 +71,9 @@ namespace Avalonia.Controls return new DateTime(d.Year, d.Month, 1, 0, 0, 0); } - [return: NotNullIfNotNull("d")] - public static DateTime? DiscardTime(DateTime? d) + public static DateTime DiscardTime(DateTime d) { - if (d == null) - { - return null; - } - return d.Value.Date; + return d.Date; } public static int EndOfDecade(DateTime date) @@ -127,28 +122,14 @@ namespace Avalonia.Controls public static string ToYearMonthPatternString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.ToString(format.YearMonthPattern, format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.ToString(format.YearMonthPattern, format); } public static string ToYearString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.Year.ToString(format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.Year.ToString(format); } } } diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index b17648f5bb..869bdeabea 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -51,11 +51,11 @@ namespace Avalonia.Controls private bool _isDropDownOpen; private DateTime? _selectedDate; private string? _text; - private bool _suspendTextChangeHandler = false; - private bool _isPopupClosing = false; - private bool _ignoreButtonClick = false; - private bool _isFlyoutOpen = false; - private bool _isPressed = false; + private bool _suspendTextChangeHandler; + private bool _isPopupClosing; + private bool _ignoreButtonClick; + private bool _isFlyoutOpen; + private bool _isPressed; /// /// Occurs when the drop-down @@ -185,7 +185,7 @@ namespace Avalonia.Controls { _textBox.KeyDown += TextBox_KeyDown; _textBox.GotFocus += TextBox_GotFocus; - _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged()); + _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged()); if(SelectedDate.HasValue) { @@ -292,7 +292,7 @@ namespace Avalonia.Controls // Text else if (change.Property == TextProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); + var (_, newValue) = change.GetOldAndNewValue(); if (!_suspendTextChangeHandler) { @@ -595,9 +595,9 @@ namespace Avalonia.Controls private void Calendar_KeyDown(object? sender, KeyEventArgs e) { - Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender)); - - if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month) + if (!e.Handled + && sender is Calendar { DisplayMode: CalendarMode.Month } + && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape)) { Focus(); IsDropDownOpen = false; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 47b0bb6e2d..368c9d4c2f 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -17,28 +17,26 @@ namespace Avalonia.Controls.Chrome private void UpdateSize(Window window) { - if (window != null) + Margin = new Thickness( + window.OffScreenMargin.Left, + window.OffScreenMargin.Top, + window.OffScreenMargin.Right, + window.OffScreenMargin.Bottom); + + if (window.WindowState != WindowState.FullScreen) { - Margin = new Thickness( - window.OffScreenMargin.Left, - window.OffScreenMargin.Top, - window.OffScreenMargin.Right, - window.OffScreenMargin.Bottom); + Height = window.WindowDecorationMargin.Top; - if (window.WindowState != WindowState.FullScreen) + if (_captionButtons != null) { - Height = window.WindowDecorationMargin.Top; - - if (_captionButtons != null) - { - _captionButtons.Height = Height; - } + _captionButtons.Height = Height; } - - IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + + IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -55,6 +53,7 @@ namespace Avalonia.Controls.Chrome } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -64,13 +63,13 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable(6) { window.GetObservable(Window.WindowDecorationMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.WindowStateProperty) .Subscribe(x => { @@ -80,11 +79,12 @@ namespace Avalonia.Controls.Chrome PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); }), window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) - .Subscribe(x => UpdateSize(window)) + .Subscribe(_ => UpdateSize(window)) }; } } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs index 9d859a753a..18d668e9a4 100644 --- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -14,7 +14,6 @@ namespace Avalonia.Controls.Converters public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { if (parameter == null || - values == null || values.Count != 4 || !(values[0] is ScrollBarVisibility visibility) || !(values[1] is double offset) || diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 5c35a09f1c..eb587fb157 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -21,9 +21,9 @@ namespace Avalonia.Controls /// /// SharedSizeGroup property. /// - public string SharedSizeGroup + public string? SharedSizeGroup { - get { return (string)GetValue(SharedSizeGroupProperty); } + get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } @@ -32,20 +32,15 @@ namespace Avalonia.Controls /// internal void OnEnterParentTree() { - this.InheritanceParent = Parent; + InheritanceParent = Parent; if (_sharedState == null) { // start with getting SharedSizeGroup value. // this property is NOT inherited which should result in better overall perf. - string sharedSizeGroupId = SharedSizeGroup; - if (sharedSizeGroupId != null) + if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - _sharedState.AddMember(this); - } + _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + _sharedState.AddMember(this); } } @@ -321,13 +316,12 @@ namespace Avalonia.Controls return ((_flags & flags) == flags); } - private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - string sharedSizeGroupId = (string)e.NewValue!; + string? sharedSizeGroupId = e.NewValue.Value; if (definition._sharedState != null) { @@ -337,16 +331,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + if (definition._sharedState == null + && sharedSizeGroupId != null + && definition.PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = definition.PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); } } } @@ -357,17 +349,15 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static bool SharedSizeGroupPropertyValueValid(string value) + private static bool SharedSizeGroupPropertyValueValid(string? id) { // null is default value - if (value == null) + if (id == null) { return true; } - string id = (string)value; - - if (!string.IsNullOrEmpty(id)) + if (id.Length > 0) { int i = -1; while (++i < id.Length) @@ -397,14 +387,11 @@ namespace Avalonia.Controls /// existing scope just left. In both cases if the DefinitionBase object is already registered /// in SharedSizeState, it should un-register and register itself in a new one. /// - private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnPrivateSharedSizeScopePropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue!; - if (definition._sharedState != null) { // if definition is already registered And shared size scope is changing, @@ -413,16 +400,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (privateSharedSizeScope != null)) + if (definition._sharedState == null + && e.NewValue.Value is { } privateSharedSizeScope + && definition.SharedSizeGroup is { } sharedSizeGroup) { - string sharedSizeGroup = definition.SharedSizeGroup; - if (sharedSizeGroup != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroup); + definition._sharedState.AddMember(definition); } } } @@ -432,7 +417,7 @@ namespace Avalonia.Controls /// private SharedSizeScope? PrivateSharedSizeScope { - get { return (SharedSizeScope?)GetValue(PrivateSharedSizeScopeProperty); } + get { return GetValue(PrivateSharedSizeScopeProperty); } } /// @@ -465,7 +450,7 @@ namespace Avalonia.Controls private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with - [System.Flags] + [Flags] private enum Flags : byte { // @@ -520,11 +505,10 @@ namespace Avalonia.Controls /// internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) { - Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); _sharedSizeScope = sharedSizeScope; _sharedSizeGroupId = sharedSizeGroupId; _registry = new List(); - _layoutUpdated = new EventHandler(OnLayoutUpdated); + _layoutUpdated = OnLayoutUpdated; _broadcastInvalidation = true; } @@ -568,7 +552,7 @@ namespace Avalonia.Controls { for (int i = 0, count = _registry.Count; i < count; ++i) { - Grid parentGrid = (Grid)(_registry[i].Parent!); + Grid parentGrid = _registry[i].Parent!; parentGrid.Invalidate(); } _broadcastInvalidation = false; @@ -703,7 +687,7 @@ namespace Avalonia.Controls // measure is invalid - it used the old shared size, // which is larger than d's (possibly changed) minSize measureIsValid = (definitionBase.LayoutWasUpdated && - MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); + MathUtilities.GreaterThanOrClose(definitionBase._minSize, MinSize)); } if(!measureIsValid) @@ -786,8 +770,8 @@ namespace Avalonia.Controls /// /// /// - public static readonly AttachedProperty SharedSizeGroupProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty SharedSizeGroupProperty = + AvaloniaProperty.RegisterAttached( "SharedSizeGroup", validate: SharedSizeGroupPropertyValueValid); @@ -796,8 +780,8 @@ namespace Avalonia.Controls /// static DefinitionBase() { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } /// diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index 3e3ed509b5..1a0cf1644a 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -101,9 +101,6 @@ namespace Avalonia.Controls Size childConstraint; // Contains the suggested input constraint for this child. Size childDesiredSize; // Contains the return size from child measure. - if (child == null) - { continue; } - // Child constraint is the remaining size; this is total size minus size consumed by previous children. childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), Math.Max(0.0, constraint.Height - accumulatedHeight)); @@ -122,7 +119,7 @@ namespace Avalonia.Controls // will deal with computing our minimum size (parentSize) due to that accumulation. // Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does // not accumulate: Width for Top/Bottom, Height for Left/Right. - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: case Dock.Right: @@ -164,8 +161,6 @@ namespace Avalonia.Controls for (int i = 0; i < totalChildrenCount; ++i) { var child = children[i]; - if (child == null) - { continue; } Size childDesiredSize = child.DesiredSize; Rect rcChild = new Rect( @@ -176,7 +171,7 @@ namespace Avalonia.Controls if (i < nonFillChildrenCount) { - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: accumulatedLeft += childDesiredSize.Width; diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 47581e87f1..23b806583e 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -13,8 +13,8 @@ namespace Avalonia.Controls.Documents /// /// AvaloniaProperty for property. /// - public static readonly StyledProperty TextDecorationsProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty TextDecorationsProperty = + AvaloniaProperty.Register( nameof(TextDecorations)); /// @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Documents /// /// The TextDecorations property specifies decorations that are added to the text of an element. /// - public TextDecorationCollection TextDecorations + public TextDecorationCollection? TextDecorations { get { return GetValue(TextDecorationsProperty); } set { SetValue(TextDecorationsProperty, value); } @@ -83,7 +83,8 @@ namespace Avalonia.Controls.Documents return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize, textDecorations, Foreground, background, BaselineAlignment); } - + + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index a7a702ceae..d3565cbdd5 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; using Avalonia.Media.TextFormatting; @@ -51,6 +52,7 @@ namespace Avalonia.Controls.Documents } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -68,26 +70,26 @@ namespace Avalonia.Controls.Documents { base.OnInlineHostChanged(oldValue, newValue); - if (Inlines is not null) - { - Inlines.InlineHost = newValue; - } + Inlines.InlineHost = newValue; } private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) { + void OnInlinesInvalidated(object? sender, EventArgs e) + => InlineHost?.Invalidate(); + if (oldValue is not null) { oldValue.LogicalChildren = null; oldValue.InlineHost = null; - oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); + oldValue.Invalidated -= OnInlinesInvalidated; } if (newValue is not null) { newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = InlineHost; - newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); + newValue.Invalidated += OnInlinesInvalidated; } } } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 7737fdac2e..2501440ff2 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -164,20 +164,21 @@ namespace Avalonia.Controls /// /// Returns a ColumnDefinitions of column definitions. /// + [MemberNotNull(nameof(_extData))] public ColumnDefinitions ColumnDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.ColumnDefinitions == null) { _extData.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } - return (_data.ColumnDefinitions); + return (_extData.ColumnDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.ColumnDefinitions = value; - _data.ColumnDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.ColumnDefinitions = value; + _extData.ColumnDefinitions.Parent = this; InvalidateMeasure(); } } @@ -185,20 +186,21 @@ namespace Avalonia.Controls /// /// Returns a RowDefinitions of row definitions. /// + [MemberNotNull(nameof(_extData))] public RowDefinitions RowDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.RowDefinitions == null) { _extData.RowDefinitions = new RowDefinitions() { Parent = this }; } - return (_data.RowDefinitions); + return (_extData.RowDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.RowDefinitions = value; - _data.RowDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.RowDefinitions = value; + _extData.RowDefinitions.Parent = this; InvalidateMeasure(); } } @@ -211,7 +213,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size constraint) { Size gridDesiredSize; - ExtendedData extData = ExtData; + var extData = _extData; try { @@ -221,17 +223,14 @@ namespace Avalonia.Controls if (extData == null) { gridDesiredSize = new Size(); - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Measure(constraint); - gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), - Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); - } + child.Measure(constraint); + gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } else @@ -512,17 +511,14 @@ namespace Avalonia.Controls { ArrangeOverrideInProgress = true; - if (_data == null) + if (_extData is null) { - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Arrange(new Rect(arrangeSize)); - } + child.Arrange(new Rect(arrangeSize)); } } else @@ -532,15 +528,11 @@ namespace Avalonia.Controls SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - var children = this.Children; + var children = Children; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { var cell = children[currentCell]; - if (cell == null) - { - continue; - } int columnIndex = PrivateCells[currentCell].ColumnIndex; int rowIndex = PrivateCells[currentCell].RowIndex; @@ -599,7 +591,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) @@ -621,7 +613,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) @@ -654,18 +646,20 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool ColumnDefinitionsDirty { - get => ColumnDefinitions?.IsDirty ?? false; + get => ColumnDefinitions.IsDirty; set => ColumnDefinitions.IsDirty = value; } /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool RowDefinitionsDirty { - get => RowDefinitions?.IsDirty ?? false; + get => RowDefinitions.IsDirty; set => RowDefinitions.IsDirty = value; } @@ -686,8 +680,10 @@ namespace Avalonia.Controls /// private void ValidateCellsCore() { - var children = this.Children; - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var children = Children; + var extData = _extData!; extData.CellCachesCollection = new CellCache[children.Count]; extData.CellGroup1 = int.MaxValue; @@ -702,10 +698,6 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { var child = children[i]; - if (child == null) - { - continue; - } CellCache cell = new CellCache(); @@ -713,19 +705,19 @@ namespace Avalonia.Controls // Read indices from the corresponding properties: // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1); + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1); // Read span properties: // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); @@ -792,7 +784,7 @@ namespace Avalonia.Controls { if (ColumnDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.ColumnDefinitions == null) { @@ -818,7 +810,7 @@ namespace Avalonia.Controls ColumnDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); + Debug.Assert(_extData is { DefinitionsU.Count: > 0 }); } /// @@ -833,7 +825,7 @@ namespace Avalonia.Controls { if (RowDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.RowDefinitions == null) { @@ -859,7 +851,7 @@ namespace Avalonia.Controls RowDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); + Debug.Assert(_extData is { DefinitionsV.Count: > 0 }); } /// @@ -965,8 +957,7 @@ namespace Avalonia.Controls bool ignoreDesiredSizeU, bool forceInfinityV) { - bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged); + MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _); } /// @@ -994,7 +985,7 @@ namespace Avalonia.Controls return; } - var children = this.Children; + var children = Children; Hashtable? spanStore = null; bool ignoreDesiredSizeV = forceInfinityV; @@ -1101,8 +1092,6 @@ namespace Avalonia.Controls int cell, bool forceInfinityV) { - - double cellMeasureWidth; double cellMeasureHeight; @@ -1144,15 +1133,9 @@ namespace Avalonia.Controls } - var child = this.Children[cell]; - if (child != null) - { - Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); - child.Measure(childConstraint); - } - - - + var child = Children[cell]; + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); } /// @@ -1230,7 +1213,7 @@ namespace Avalonia.Controls // avoid processing when asked to distribute "0" if (!MathUtilities.IsZero(requestedSize)) { - DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + DefinitionBase?[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; int autoDefinitionsCount = 0; double rangeMinSize = 0; @@ -1288,20 +1271,24 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); // adjust sizeToDistribute value by subtracting auto definition min size - sizeToDistribute -= (tempDefinitions[i].MinSize); + sizeToDistribute -= (tempDefinition.MinSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); - if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinition.PreferredSize); + if (newMinSize > tempDefinition.MinSize) { tempDefinition.UpdateMinSize(newMinSize); } sizeToDistribute -= newMinSize; } @@ -1325,24 +1312,28 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].PreferredSize; + double preferredSize = tempDefinition.PreferredSize; double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].MinSize; + double preferredSize = tempDefinition.MinSize; double newMinSize = preferredSize + sizeToDistribute / (count - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } // sanity check: requested size must all be distributed @@ -1376,8 +1367,10 @@ namespace Avalonia.Controls for (int i = 0; i < count; ++i) { - double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; - tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + var tempDefinition = tempDefinitions[i]!; + + double deltaSize = (maxMaxSize - tempDefinition.SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinition.UpdateMinSize(tempDefinition.SizeCache + deltaSize); } } else @@ -1388,7 +1381,7 @@ namespace Avalonia.Controls // for (int i = 0; i < count; ++i) { - tempDefinitions[i].UpdateMinSize(equalSize); + tempDefinitions[i]!.UpdateMinSize(equalSize); } } } @@ -1429,7 +1422,7 @@ namespace Avalonia.Controls double availableSize) { int defCount = definitions.Count; - DefinitionBase[] tempDefinitions = TempDefinitions; + DefinitionBase?[] tempDefinitions = TempDefinitions; int minCount = 0, maxCount = 0; double takenSize = 0; double totalStarWeight = 0.0; @@ -1560,8 +1553,8 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1]!.MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1]!.SizeCache : -1.0; // choose the def with larger ratio to the current proportion ("max discrepancy") double proportion = remainingStarWeight / remainingAvailableSize; @@ -1579,13 +1572,13 @@ namespace Avalonia.Controls double resolvedSize; if (chooseMin == true) { - resolvedDef = tempDefinitions[minCount - 1]; + resolvedDef = tempDefinitions[minCount - 1]!; resolvedSize = resolvedDef.MinSize; --minCount; } else { - resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedDef = tempDefinitions[defCount + maxCount - 1]!; resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); --maxCount; } @@ -1603,12 +1596,12 @@ namespace Avalonia.Controls // advance to the next candidate defs, removing ones that have been resolved. // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + while (minCount > 0 && tempDefinitions[minCount - 1]!.MeasureSize < 0.0) { --minCount; tempDefinitions[minCount] = null!; } - while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1]!.MeasureSize < 0.0) { --maxCount; tempDefinitions[defCount + maxCount] = null!; @@ -1637,8 +1630,7 @@ namespace Avalonia.Controls // resolved as 'min'. Their allocation can be increased to make up the gap. for (int i = minCount; i < minCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[i]; - if (def != null) + if (tempDefinitions[i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1653,8 +1645,7 @@ namespace Avalonia.Controls // resolved as 'max'. Their allocation can be decreased to make up the gap. for (int i = maxCount; i < maxCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[defCount + i]; - if (def != null) + if (tempDefinitions[defCount + i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1695,7 +1686,7 @@ namespace Avalonia.Controls totalStarWeight = 0.0; for (int i = 0; i < starCount; ++i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; totalStarWeight += def.MeasureSize; def.SizeCache = totalStarWeight; } @@ -1703,7 +1694,7 @@ namespace Avalonia.Controls // resolve the defs, in decreasing order of weight for (int i = starCount - 1; i >= 0; --i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; // min and max should have no effect by now, but just in case... @@ -2095,7 +2086,7 @@ namespace Avalonia.Controls { // DpiScale dpiScale = GetDpi(); // double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; - var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -2302,8 +2293,7 @@ namespace Avalonia.Controls /// private void SetValid() { - ExtendedData extData = ExtData; - if (extData != null) + if (_extData is { } extData) { // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); @@ -2330,12 +2320,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); _gridLinesRenderer = null; } @@ -2364,7 +2354,7 @@ namespace Avalonia.Controls { Grid grid = (Grid)d; - if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + if (grid._extData != null // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { grid.InvalidateVisual(); @@ -2375,13 +2365,11 @@ namespace Avalonia.Controls private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { - Visual? child = d as Visual; - - if (child != null) + if (d is Visual child) { Grid? grid = child.GetVisualParent(); if (grid != null - && grid.ExtData != null + && grid._extData != null && grid.ListenToNotifications) { grid.CellsStructureDirty = true; @@ -2427,7 +2415,7 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsU { - get { return (ExtData.DefinitionsU!); } + get { return _extData!.DefinitionsU!; } } /// @@ -2435,17 +2423,19 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsV { - get { return (ExtData.DefinitionsV!); } + get { return _extData!.DefinitionsV!; } } /// /// Helper accessor to layout time array of definitions. /// - private DefinitionBase[] TempDefinitions + private DefinitionBase?[] TempDefinitions { get { - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var extData = _extData!; int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2; if (extData.TempDefinitions == null @@ -2516,7 +2506,7 @@ namespace Avalonia.Controls /// private CellCache[] PrivateCells { - get { return (ExtData.CellCachesCollection!); } + get { return _extData!.CellCachesCollection!; } } /// @@ -2582,18 +2572,10 @@ namespace Avalonia.Controls set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } } - /// - /// Returns reference to extended data bag. - /// - private ExtendedData ExtData - { - get { return (_data!); } - } - /// /// Returns *-weight, adjusted for scale computed during Phase 1 /// - static double StarWeight(DefinitionBase def, double scale) + private static double StarWeight(DefinitionBase def, double scale) { if (scale < 0.0) { @@ -2609,17 +2591,17 @@ namespace Avalonia.Controls } // Extended data instantiated on demand, for non-trivial case handling only - private ExtendedData? _data; + private ExtendedData? _extData; // Grid validity / property caches dirtiness flags private Flags _flags; private GridLinesRenderer? _gridLinesRenderer; // Keeps track of definition indices. - int[]? _definitionIndices; + private int[]? _definitionIndices; // Stores unrounded values and rounding errors during layout rounding. - double[]? _roundingErrors; + private double[]? _roundingErrors; // 5 is an arbitrary constant chosen to end the measure loop private const int c_layoutLoopMaxCount = 5; @@ -2645,14 +2627,14 @@ namespace Avalonia.Controls internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup3; // index of the first cell in third cell group internal int CellGroup4; // index of the first cell in forth cell group - internal DefinitionBase[]? TempDefinitions; // temporary array used during layout for various purposes + internal DefinitionBase?[]? TempDefinitions; // temporary array used during layout for various purposes // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) } /// /// Grid validity / property caches dirtiness flags /// - [System.Flags] + [Flags] private enum Flags { // @@ -2768,7 +2750,7 @@ namespace Avalonia.Controls /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// - [System.Flags] + [Flags] internal enum LayoutTimeSizeType : byte { None = 0x00, @@ -3317,7 +3299,7 @@ namespace Avalonia.Controls internal void UpdateRenderBounds(Size arrangeSize) { _lastArrangeSize = arrangeSize; - this.InvalidateVisual(); + InvalidateVisual(); } private static Size _lastArrangeSize; diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 7408bff902..2cf0fc3ec4 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -14,8 +14,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty SourceProperty = - AvaloniaProperty.Register(nameof(Source)); + public static readonly StyledProperty SourceProperty = + AvaloniaProperty.Register(nameof(Source)); /// /// Defines the property. @@ -42,7 +42,7 @@ namespace Avalonia.Controls /// Gets or sets the image that will be displayed. /// [Content] - public IImage Source + public IImage? Source { get { return GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } @@ -66,6 +66,7 @@ namespace Avalonia.Controls set { SetValue(StretchDirectionProperty, value); } } + /// protected override bool BypassFlowDirectionPolicies => true; /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 59b5bf48a5..42674cb481 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics.CodeAnalysis; using Avalonia.Automation.Peers; using Avalonia.Collections; using Avalonia.Controls.Generators; @@ -17,7 +16,6 @@ using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.Styling; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -106,7 +104,6 @@ namespace Avalonia.Controls private Tuple? _containerBeingPrepared; private ScrollViewer? _scrollViewer; private ItemsPresenter? _itemsPresenter; - private IScrollSnapPointsInfo? _scrolSnapPointInfo; /// /// Initializes a new instance of the class. @@ -221,6 +218,7 @@ namespace Avalonia.Controls } + /// public event EventHandler HorizontalSnapPointsChanged { add @@ -240,6 +238,7 @@ namespace Avalonia.Controls } } + /// public event EventHandler VerticalSnapPointsChanged { add @@ -424,13 +423,12 @@ namespace Avalonia.Controls /// true if the item is (or is eligible to be) its own container; otherwise, false. protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true; + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); _scrollViewer = e.NameScope.Find("PART_ScrollViewer"); _itemsPresenter = e.NameScope.Find("PART_ItemsPresenter"); - - _scrolSnapPointInfo = _itemsPresenter as IScrollSnapPointsInfo; } /// @@ -477,11 +475,13 @@ namespace Avalonia.Controls base.OnKeyDown(e); } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new ItemsControlAutomationPeer(this); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -748,11 +748,13 @@ namespace Avalonia.Controls return true; } + /// public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) { return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List(); } + /// public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) { offset = 0; diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index ce254684b7..387dc27562 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls .AddClassHandler((x, e) => x.OnLayoutTransformChanged(e)); ChildProperty.Changed - .AddClassHandler((x, e) => x.OnChildChanged(e)); + .AddClassHandler((x, _) => x.OnChildChanged()); UseRenderTransformProperty.Changed .AddClassHandler((x, e) => x.OnUseRenderTransformPropertyChanged(e)); @@ -146,7 +146,7 @@ namespace Avalonia.Controls return transformedDesiredSize; } - IDisposable? _renderTransformChangedEvent; + private IDisposable? _renderTransformChangedEvent; private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e) { @@ -167,8 +167,7 @@ namespace Avalonia.Controls .Subscribe( (x) => { - var target2 = x.Sender as LayoutTransformControl; - if (target2 != null) + if (x.Sender is LayoutTransformControl target2) { target2.LayoutTransform = target2.RenderTransform; } @@ -182,7 +181,7 @@ namespace Avalonia.Controls } } - private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) + private void OnChildChanged() { if (null != TransformRoot) { @@ -206,18 +205,18 @@ namespace Avalonia.Controls /// /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// - private Size _childActualSize = default; + private Size _childActualSize; /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// - private MatrixTransform _matrixTransform = new MatrixTransform(); + private readonly MatrixTransform _matrixTransform = new(); /// /// Transformation matrix corresponding to _matrixTransform. /// private Matrix _transformation; - private IDisposable? _transformChangedEvent = null; + private IDisposable? _transformChangedEvent; /// /// Returns true if Size a is smaller than Size b in either dimension. @@ -263,10 +262,7 @@ namespace Avalonia.Controls // Get the transform matrix and apply it _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); - if (null != _matrixTransform) - { - _matrixTransform.Matrix = _transformation; - } + _matrixTransform.Matrix = _transformation; // New transform means re-layout is necessary InvalidateMeasure(); diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 080326606e..5a3eb47ce4 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -178,12 +178,11 @@ namespace Avalonia.Controls } } - - } Type IStyleable.StyleKey => typeof(TextBox); + /// protected override void OnGotFocus(GotFocusEventArgs e) { if (HidePromptOnLeave == true && MaskProvider != null) @@ -193,6 +192,7 @@ namespace Avalonia.Controls base.OnGotFocus(e); } + /// protected override async void OnKeyDown(KeyEventArgs e) { if (MaskProvider == null) @@ -271,15 +271,17 @@ namespace Avalonia.Controls } } + /// protected override void OnLostFocus(RoutedEventArgs e) { - if (HidePromptOnLeave == true && MaskProvider != null) + if (HidePromptOnLeave && MaskProvider != null) { Text = MaskProvider.ToString(!HidePromptOnLeave, true); } base.OnLostFocus(e); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { void UpdateMaskProvider() @@ -357,6 +359,8 @@ namespace Avalonia.Controls } base.OnPropertyChanged(change); } + + /// protected override void OnTextInput(TextInputEventArgs e) { _ignoreTextChanges = true; @@ -423,7 +427,7 @@ namespace Avalonia.Controls return startPosition; } - private void RefreshText(MaskedTextProvider provider, int position) + private void RefreshText(MaskedTextProvider? provider, int position) { if (provider != null) { diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 6b9e378d3d..a94a1ee983 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -16,19 +16,17 @@ namespace Avalonia.Controls private IPlatformHandle? _nativeControlHandle; private bool _queuedForDestruction; private bool _queuedForMoveResize; - private readonly List _propertyChangedSubscriptions = new List(); + private readonly List _propertyChangedSubscriptions = new(); + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; var visual = (Visual)this; while (visual != null) { - if (visual is Visual v) - { - v.PropertyChanged += PropertyChangedHandler; - _propertyChangedSubscriptions.Add(v); - } + visual.PropertyChanged += PropertyChangedHandler; + _propertyChangedSubscriptions.Add(visual); visual = visual.GetVisualParent(); } @@ -42,15 +40,13 @@ namespace Avalonia.Controls EnqueueForMoveResize(); } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; - if (_propertyChangedSubscriptions != null) - { - foreach (var v in _propertyChangedSubscriptions) - v.PropertyChanged -= PropertyChangedHandler; - _propertyChangedSubscriptions.Clear(); - } + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= PropertyChangedHandler; + _propertyChangedSubscriptions.Clear(); UpdateHost(); } @@ -128,7 +124,7 @@ namespace Avalonia.Controls return new Rect(position.Value, bounds.Size); } - void EnqueueForMoveResize() + private void EnqueueForMoveResize() { if(_queuedForMoveResize) return; diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 9c1fb93a48..ab64416a2c 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -12,10 +12,10 @@ namespace Avalonia.Controls public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); - private static readonly AttachedProperty s_nativeMenuInfoProperty = - AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); - - class NativeMenuInfo + private static readonly AttachedProperty s_nativeMenuInfoProperty = + AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); + + private sealed class NativeMenuInfo { public bool ChangingIsExported { get; set; } public ITopLevelNativeMenuExporter? Exporter { get; } @@ -33,7 +33,7 @@ namespace Avalonia.Controls } } - static NativeMenuInfo GetInfo(TopLevel target) + private static NativeMenuInfo GetInfo(TopLevel target) { var rv = target.GetValue(s_nativeMenuInfoProperty); if (rv == null) @@ -45,18 +45,18 @@ namespace Avalonia.Controls return rv; } - static void SetIsNativeMenuExported(TopLevel tl, bool value) + private static void SetIsNativeMenuExported(TopLevel tl, bool value) { GetInfo(tl).ChangingIsExported = true; tl.SetValue(IsNativeMenuExportedProperty, value); } - public static readonly AttachedProperty MenuProperty - = AvaloniaProperty.RegisterAttached("Menu"); + public static readonly AttachedProperty MenuProperty + = AvaloniaProperty.RegisterAttached("Menu"); - public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); + public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu); - public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); + public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); static NativeMenu() { diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 3464857131..79719912ea 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -34,8 +34,8 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty AdornerProperty = AvaloniaProperty.RegisterAttached("Adorner"); - private static readonly AttachedProperty s_adornedElementInfoProperty = - AvaloniaProperty.RegisterAttached("AdornedElementInfo"); + private static readonly AttachedProperty s_adornedElementInfoProperty = + AvaloniaProperty.RegisterAttached("AdornedElementInfo"); private static readonly AttachedProperty s_savedAdornerLayerProperty = AvaloniaProperty.RegisterAttached("SavedAdornerLayer"); @@ -159,8 +159,8 @@ namespace Avalonia.Controls.Primitives return; } - AdornerLayer.SetAdornedElement(adorner, visual); - AdornerLayer.SetIsClipEnabled(adorner, false); + SetAdornedElement(adorner, visual); + SetIsClipEnabled(adorner, false); ((ISetLogicalParent) adorner).SetParent(visual); layer.Children.Add(adorner); @@ -177,6 +177,7 @@ namespace Avalonia.Controls.Primitives ((ISetLogicalParent) adorner).SetParent(null); } + /// protected override Size MeasureOverride(Size availableSize) { foreach (var child in Children) @@ -199,6 +200,7 @@ namespace Avalonia.Controls.Primitives return default; } + /// protected override Size ArrangeOverride(Size finalSize) { foreach (var child in Children) @@ -217,7 +219,7 @@ namespace Avalonia.Controls.Primitives } else { - ArrangeChild((Control) child, finalSize); + ArrangeChild(child, finalSize); } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3b68cd2ae8..d6cd71aedc 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); - private bool _isOpenRequested = false; + private bool _isOpenRequested; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -377,9 +377,9 @@ namespace Avalonia.Controls.Primitives popupHost.SetChild(Child); ((ISetLogicalParent)popupHost).SetParent(this); - if (InheritsTransform && placementTarget is Control c) + if (InheritsTransform) { - TransformTrackingHelper.Track(c, PlacementTargetTransformChanged) + TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged) .DisposeWith(handlerCleanup); } else @@ -518,6 +518,7 @@ namespace Avalonia.Controls.Primitives Close(); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -579,7 +580,7 @@ namespace Avalonia.Controls.Primitives var scaleX = 1.0; var scaleY = 1.0; - if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m) + if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is { } m) { scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); @@ -623,6 +624,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new PopupAutomationPeer(this); @@ -850,7 +852,7 @@ namespace Avalonia.Controls.Primitives var popupHost = _openState.PopupHost; - return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual); + return ((Visual)popupHost).IsVisualAncestorOf(visual); } public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..8d79d62dc4 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -3,16 +3,14 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Xml.Linq; -using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -255,6 +253,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the model that holds the current selection. /// + [AllowNull] protected ISelectionModel Selection { get @@ -399,6 +398,7 @@ namespace Avalonia.Controls.Primitives return null; } + /// protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.ItemsCollectionChanged(sender!, e); @@ -409,12 +409,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); AutoScrollToSelectedItemIfNecessary(); } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -431,6 +433,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { base.PrepareContainerForItemOverride(element, item, index); @@ -447,12 +450,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) { base.ContainerIndexChangedOverride(container, oldIndex, newIndex); MarkContainerSelected(container, Selection.IsSelected(newIndex)); } + /// protected internal override void ClearContainerForItemOverride(Control element) { base.ClearContainerForItemOverride(element); @@ -463,7 +468,7 @@ namespace Avalonia.Controls.Primitives KeyboardNavigation.SetTabOnceActiveElement(panel, null); } - if (element is ISelectable selectable) + if (element is ISelectable) MarkContainerSelected(element, false); } @@ -498,7 +503,8 @@ namespace Avalonia.Controls.Primitives DataValidationErrors.SetError(this, error); } } - + + /// protected override void OnInitialized() { base.OnInitialized(); @@ -509,6 +515,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnTextInput(TextInputEventArgs e) { if (!e.Handled) @@ -551,6 +558,7 @@ namespace Avalonia.Controls.Primitives base.OnTextInput(e); } + /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -582,6 +590,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -592,7 +601,7 @@ namespace Avalonia.Controls.Primitives } if (change.Property == ItemsProperty && _updateState is null && _selection is object) { - var newValue = change.GetNewValue(); + var newValue = change.GetNewValue(); _selection.Source = newValue; if (newValue is null) @@ -695,7 +704,7 @@ namespace Avalonia.Controls.Primitives { if (multi) { - if (Selection.IsSelected(index) == true) + if (Selection.IsSelected(index)) { Selection.Deselect(index); } @@ -716,12 +725,10 @@ namespace Avalonia.Controls.Primitives Selection.Select(index); } - if (Presenter?.Panel != null) + if (Presenter?.Panel is { } panel) { var container = ContainerFromIndex(index); - KeyboardNavigation.SetTabOnceActiveElement( - (InputElement)Presenter.Panel, - container); + KeyboardNavigation.SetTabOnceActiveElement(panel, container); } } @@ -940,7 +947,7 @@ namespace Avalonia.Controls.Primitives private void UpdateContainerSelection() { - if (Presenter?.Panel is Panel panel) + if (Presenter?.Panel is { } panel) { foreach (var container in panel.Children) { diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 9a684c4534..d8874832bd 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -290,12 +290,6 @@ namespace Avalonia.Controls.Primitives ApplyTemplatedParent(child, this); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); - - // Existing code kinda expect to see a NameScope even if it's empty - if (nameScope == null) - { - nameScope = new NameScope(); - } var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); @@ -320,6 +314,7 @@ namespace Avalonia.Controls.Primitives return this; } + /// protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { var count = VisualChildren.Count; diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs index 949532cb16..962fba361e 100644 --- a/src/Avalonia.Controls/Primitives/TextSearch.cs +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -11,15 +11,15 @@ namespace Avalonia.Controls.Primitives /// Defines the Text attached property. /// This text will be considered during text search in (such as ) /// - public static readonly AttachedProperty TextProperty - = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); + public static readonly AttachedProperty TextProperty + = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); /// /// Sets the for a control. /// /// The control /// The search text to set - public static void SetText(Control control, string text) + public static void SetText(Control control, string? text) { control.SetValue(TextProperty, text); } @@ -29,7 +29,7 @@ namespace Avalonia.Controls.Primitives /// /// The control /// The property value - public static string GetText(Control control) + public static string? GetText(Control control) { return control.GetValue(TextProperty); } diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 14ec7a2849..9e8d1478fa 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -5,7 +5,6 @@ using System; using Avalonia.Controls.Metadata; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Metadata; @@ -31,14 +30,14 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty OrientationProperty = ScrollBar.OrientationProperty.AddOwner(); - public static readonly StyledProperty ThumbProperty = - AvaloniaProperty.Register(nameof(Thumb)); + public static readonly StyledProperty ThumbProperty = + AvaloniaProperty.Register(nameof(Thumb)); - public static readonly StyledProperty