From da38b72a35149bc83fe4fb5acb1d828320baccfe Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 15:17:35 +0200 Subject: [PATCH 001/260] fixes(DevTools): Renamed Type as AssignedType --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 8 ++++---- .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 6 +++--- .../Diagnostics/ViewModels/PropertyViewModel.cs | 2 +- .../Diagnostics/Views/ControlDetailsView.xaml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index e4c4ca6115..6bd26e39d6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -3,7 +3,7 @@ namespace Avalonia.Diagnostics.ViewModels internal class AvaloniaPropertyViewModel : PropertyViewModel { private readonly AvaloniaObject _target; - private string _type; + private string _assignedType; private object? _value; private string _priority; private string _group; @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Priority => _priority; - public override string Type => _type; + public override string AssignedType => _assignedType; public override string Value { @@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); _group = "Properties"; @@ -66,7 +66,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); if (val != null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index 65626aeea5..b87d2b0b53 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -5,7 +5,7 @@ namespace Avalonia.Diagnostics.ViewModels internal class ClrPropertyViewModel : PropertyViewModel { private readonly object _target; - private string _type; + private string _assignedType; private object? _value; #nullable disable @@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Name { get; } public override string Group => "CLR Properties"; - public override string Type => _type; + public override string AssignedType => _assignedType; public override string Value { @@ -60,7 +60,7 @@ namespace Avalonia.Diagnostics.ViewModels { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index fdbd8c1aa3..080eab072c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels public abstract object Key { get; } public abstract string Name { get; } public abstract string Group { get; } - public abstract string Type { get; } + public abstract string AssignedType { get; } public abstract string Value { get; set; } public abstract string Priority { get; } public abstract bool? IsAttached { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 4b37438993..22ad3374ed 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -33,7 +33,7 @@ - + From 4c3666ba214a9926d6468eb912ec4ea5e424b1d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:18:36 +0200 Subject: [PATCH 002/260] feat(DevTools): Add PropertyType on ControlDettails --- .../ViewModels/AvaloniaPropertyViewModel.cs | 4 +++ .../ViewModels/ClrPropertyViewModel.cs | 4 +++ .../ViewModels/PropertyViewModel.cs | 26 ++++++++++++++++--- .../Diagnostics/Views/ControlDetailsView.xaml | 1 + 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 6bd26e39d6..68e12b545e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -7,6 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels private object? _value; private string _priority; private string _group; + private readonly string _propertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -20,6 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels $"[{property.OwnerType.Name}.{property.Name}]" : property.Name; + _propertyType = GetTypeName(property.PropertyType); Update(); } @@ -50,6 +52,8 @@ namespace Avalonia.Diagnostics.ViewModels public override string Group => _group; + public override string PropertyType => _propertyType; + // [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] public override void Update() { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index b87d2b0b53..cbbf3e2fbb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -7,6 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels private readonly object _target; private string _assignedType; private object? _value; + private readonly string _propertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -25,6 +26,8 @@ namespace Avalonia.Diagnostics.ViewModels Name = property.DeclaringType.Name + '.' + property.Name; } + _propertyType = GetTypeName(property.PropertyType); + Update(); } @@ -34,6 +37,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Group => "CLR Properties"; public override string AssignedType => _assignedType; + public override string PropertyType => _propertyType; public override string Value { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 080eab072c..067693de38 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Globalization; using System.Reflection; +using System.Linq; namespace Avalonia.Diagnostics.ViewModels { @@ -17,8 +18,27 @@ namespace Avalonia.Diagnostics.ViewModels public abstract string AssignedType { get; } public abstract string Value { get; set; } public abstract string Priority { get; } - public abstract bool? IsAttached { get; } - public abstract void Update(); + public abstract bool? IsAttached { get; } + public abstract void Update(); + public abstract string PropertyType { get; } + + + protected static string GetTypeName(Type type) + { + var name = type.Name; + if (Nullable.GetUnderlyingType(type) is Type nullable) + { + name = nullable.Name + "?"; + } + else if (type.IsGenericType) + { + var definition = type.GetGenericTypeDefinition(); + var arguments = type.GetGenericArguments(); + name = definition.Name.Substring(0, definition.Name.IndexOf('`')); + name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + } + return name; + } protected static string ConvertToString(object? value) { @@ -30,7 +50,7 @@ namespace Avalonia.Diagnostics.ViewModels var converter = TypeDescriptor.GetConverter(value); //CollectionConverter does not deliver any important information. It just displays "(Collection)". - if (!converter.CanConvertTo(typeof(string)) || + if (!converter.CanConvertTo(typeof(string)) || converter.GetType() == typeof(CollectionConverter)) { return value.ToString() ?? "(null)"; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 22ad3374ed..b92ea85227 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -34,6 +34,7 @@ + From 67f83633ce3e7615dd97707e38799c2b769451e5 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:26:19 +0200 Subject: [PATCH 003/260] feat(DevTools): Allowed to decode Nullable and Generic Type for AssignedType --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 4 ++-- .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 68e12b545e..29e049a5d8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -60,7 +60,7 @@ namespace Avalonia.Diagnostics.ViewModels if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); _group = "Properties"; @@ -70,7 +70,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); if (val != null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index cbbf3e2fbb..fe48179150 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); } } } From 3efcd9d259c43d756d5c1ee1ad548d15cac171f2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:35:18 +0200 Subject: [PATCH 004/260] feat(DevTools): Allow caching of GetTypeName --- .../ViewModels/PropertyViewModel.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 067693de38..1de1c34ea0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Globalization; using System.Reflection; using System.Linq; +using System.Runtime.CompilerServices; namespace Avalonia.Diagnostics.ViewModels { @@ -11,6 +12,8 @@ namespace Avalonia.Diagnostics.ViewModels private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private static readonly Type[] StringParameter = { typeof(string) }; private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) }; + private static readonly ConditionalWeakTable s_getTypeNameCache = + new ConditionalWeakTable(); public abstract object Key { get; } public abstract string Name { get; } @@ -25,17 +28,21 @@ namespace Avalonia.Diagnostics.ViewModels protected static string GetTypeName(Type type) { - var name = type.Name; - if (Nullable.GetUnderlyingType(type) is Type nullable) + if (!s_getTypeNameCache.TryGetValue(type, out var name)) { - name = nullable.Name + "?"; - } - else if (type.IsGenericType) - { - var definition = type.GetGenericTypeDefinition(); - var arguments = type.GetGenericArguments(); - name = definition.Name.Substring(0, definition.Name.IndexOf('`')); - name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + name = type.Name; + if (Nullable.GetUnderlyingType(type) is Type nullable) + { + name = nullable.Name + "?"; + } + else if (type.IsGenericType) + { + var definition = type.GetGenericTypeDefinition(); + var arguments = type.GetGenericArguments(); + name = definition.Name.Substring(0, definition.Name.IndexOf('`')); + name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + } + s_getTypeNameCache.Add(type, name); } return name; } From d0e810305192ab8d058095f759743bae06103628 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 21 Jul 2021 09:25:10 +0200 Subject: [PATCH 005/260] feat(DevTools): Allow toggle PropertyType column visibility --- .../Diagnostics/ViewModels/MainViewModel.cs | 16 ++++++++++++++-- .../Diagnostics/Views/ControlDetailsView.xaml | 5 ++++- .../Diagnostics/Views/MainView.xaml | 10 ++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 3f367165ac..90f127acd2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -22,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; + private bool _showPropertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -46,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } - + public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; @@ -151,7 +152,7 @@ namespace Avalonia.Diagnostics.ViewModels get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } } - + private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; @@ -220,5 +221,16 @@ namespace Avalonia.Diagnostics.ViewModels { StartupScreenIndex = options.StartupScreenIndex; } + + public bool ShowDettailsPropertyType + { + get => _showPropertyType; + private set => RaiseAndSetIfChanged(ref _showPropertyType , value); + } + + public void ToggleShowDettailsPropertyType(object paramter) + { + ShowDettailsPropertyType = !ShowDettailsPropertyType; + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index b92ea85227..5dfc75106a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -34,7 +34,10 @@ - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 8c4db33f91..6d01eb27a1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -15,6 +15,16 @@ IsEnabled="False"/> + + + + + + + + From 739d9de5b7c70d2d35d28063e19663745919ffb4 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 12:32:40 +0200 Subject: [PATCH 006/260] feat(DevTools): Shows the property type and the type assigned to the property in a single or split column. --- .../Diagnostics/ViewModels/PropertyViewModel.cs | 3 +++ .../Diagnostics/Views/ControlDetailsView.xaml | 9 ++++++++- src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 1de1c34ea0..d3cdff6e71 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -24,6 +24,9 @@ namespace Avalonia.Diagnostics.ViewModels public abstract bool? IsAttached { get; } public abstract void Update(); public abstract string PropertyType { get; } + public string Type => PropertyType == AssignedType + ? PropertyType + : $"{PropertyType} {{{AssignedType}}}"; protected static string GetTypeName(Type type) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 5dfc75106a..e6965884b1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -33,7 +33,14 @@ - + + - + Date: Wed, 1 Sep 2021 17:03:26 +0200 Subject: [PATCH 007/260] feat: Added Post to IDispatcher --- src/Avalonia.Base/Threading/IDispatcher.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Threading/IDispatcher.cs b/src/Avalonia.Base/Threading/IDispatcher.cs index 8f46f99283..538fa028b9 100644 --- a/src/Avalonia.Base/Threading/IDispatcher.cs +++ b/src/Avalonia.Base/Threading/IDispatcher.cs @@ -27,6 +27,15 @@ namespace Avalonia.Threading /// A task that can be used to track the method's execution. void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); + /// + /// Invokes a method on the dispatcher thread. + /// + /// type of argument + /// The method to call. + /// The argument of method to call. + /// The priority with which to invoke the method. + void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal); + /// /// Posts an action that will be invoked on the dispatcher thread. /// @@ -59,4 +68,4 @@ namespace Avalonia.Threading /// A task that represents a proxy for the task returned by . Task InvokeAsync(Func> function, DispatcherPriority priority = DispatcherPriority.Normal); } -} \ No newline at end of file +} From 75bd449a6901f2a405daeeebc79c9d92e4d68b8a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 17:18:44 +0200 Subject: [PATCH 008/260] feat: Implement Post method of IDispatcher. --- src/Avalonia.Base/ApiCompatBaseline.txt | 3 + src/Avalonia.Base/Threading/Dispatcher.cs | 11 ++- src/Avalonia.Base/Threading/JobRunner.cs | 84 ++++++++++++++++--- .../Avalonia.UnitTests/ImmediateDispatcher.cs | 6 ++ 4 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4701a83175 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Base: +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post(System.Action, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index fe2cec11f0..217f1b9c3f 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -81,7 +81,7 @@ namespace Avalonia.Threading Contract.Requires(action != null); return _jobRunner.InvokeAsync(action, priority); } - + /// public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal) { @@ -110,6 +110,13 @@ namespace Avalonia.Threading _jobRunner.Post(action, priority); } + /// + public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) + { + Contract.Requires(action != null); + _jobRunner.Post(action, arg, priority); + } + /// /// This is needed for platform backends that don't have internal priority system (e. g. win32) /// To ensure that there are no jobs with higher priority @@ -142,4 +149,4 @@ namespace Avalonia.Threading } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 58f623fb3f..7aeb1972ea 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -13,7 +13,7 @@ namespace Avalonia.Threading { private IPlatformThreadingInterface _platform; - private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) + private readonly Queue[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1) .Select(_ => new Queue()).ToArray(); public JobRunner(IPlatformThreadingInterface platform) @@ -59,7 +59,7 @@ namespace Avalonia.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Func function, DispatcherPriority priority) { - var job = new Job(function, priority); + var job = new JobWithResult(function, priority); AddJob(job); return job.Task; } @@ -75,6 +75,17 @@ namespace Avalonia.Threading AddJob(new Job(action, priority, true)); } + /// + /// Post action that will be invoked on main thread + /// + /// The method to call. + /// The parameter of method to call. + /// The priority with which to invoke the method. + internal void Post(Action action, T parameter, DispatcherPriority priority) + { + AddJob(new Job(action, parameter, priority, true)); + } + /// /// Allows unit tests to change the platform threading interface. /// @@ -86,7 +97,7 @@ namespace Avalonia.Threading private void AddJob(IJob job) { bool needWake; - var queue = _queues[(int) job.Priority]; + var queue = _queues[(int)job.Priority]; lock (queue) { needWake = queue.Count == 0; @@ -98,7 +109,7 @@ namespace Avalonia.Threading private IJob GetNextJob(DispatcherPriority minimumPriority) { - for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--) + for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--) { var q = _queues[c]; lock (q) @@ -109,14 +120,14 @@ namespace Avalonia.Threading } return null; } - + private interface IJob { /// /// Gets the job priority. /// DispatcherPriority Priority { get; } - + /// /// Runs the job. /// @@ -157,7 +168,7 @@ namespace Avalonia.Threading /// The task. /// public Task Task => _taskCompletionSource?.Task; - + /// void IJob.Run() { @@ -177,11 +188,60 @@ namespace Avalonia.Threading } } } - + /// - /// A job to run. + /// A tipizzed job to run. /// - private sealed class Job : IJob + private sealed class Job : IJob + { + private readonly Action _action; + private readonly T _parameter; + private readonly TaskCompletionSource _taskCompletionSource; + + /// + /// Initializes a new instance of the class. + /// + /// The method to call. + /// The parameter of method to call. + /// The job priority. + /// Do not wrap exception in TaskCompletionSource + + public Job(Action action, T parameter, DispatcherPriority priority, bool throwOnUiThread) + { + _action = action; + _parameter = parameter; + Priority = priority; + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + } + + /// + public DispatcherPriority Priority { get; } + + /// + void IJob.Run() + { + if (_taskCompletionSource == null) + { + _action(_parameter); + return; + } + try + { + _action(_parameter); + _taskCompletionSource.SetResult(null); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } + } + } + + + /// + /// A job to run thath return value. + /// + private sealed class JobWithResult : IJob { private readonly Func _function; private readonly TaskCompletionSource _taskCompletionSource; @@ -191,7 +251,7 @@ namespace Avalonia.Threading /// /// The method to call. /// The job priority. - public Job(Func function, DispatcherPriority priority) + public JobWithResult(Func function, DispatcherPriority priority) { _function = function; Priority = priority; @@ -200,7 +260,7 @@ namespace Avalonia.Threading /// public DispatcherPriority Priority { get; } - + /// /// The task. /// diff --git a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs index fac4ee64e7..5f0d41590f 100644 --- a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs +++ b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs @@ -21,6 +21,12 @@ namespace Avalonia.UnitTests action(); } + /// + public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) + { + action(arg); + } + /// public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { From aa5df186f049065a7d2167a54f1d3725a8b23738 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 2 Sep 2021 09:40:54 +0200 Subject: [PATCH 009/260] fixes: XML Code Comment --- src/Avalonia.Base/Threading/JobRunner.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 7aeb1972ea..357fbd9b59 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -190,8 +190,9 @@ namespace Avalonia.Threading } /// - /// A tipizzed job to run. + /// A typed job to run. /// + /// Type of job parameter private sealed class Job : IJob { private readonly Action _action; @@ -237,10 +238,10 @@ namespace Avalonia.Threading } } - /// /// A job to run thath return value. /// + /// Type of job result private sealed class JobWithResult : IJob { private readonly Func _function; From 02e30f80f5c2293e115ef8b0679e4ddd8537d166 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 8 Sep 2021 20:53:05 +0300 Subject: [PATCH 010/260] [OSX] Add Swipe, Rotate and Magnify PitchToZoom trackpad gestures as native mouse events, because they can be triggered from magic mouse AFAIK and creation of new TrackpadDevice is too big task for me now --- native/Avalonia.Native/src/OSX/window.mm | 31 ++++++++++++++++++++++++ src/Avalonia.Native/avn.idl | 5 +++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 14fe60ab0b..b1e923c434 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1625,6 +1625,19 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return; } } + else if (type == Magnify) + { + delta.X = delta.Y = [event magnification]; + } + else if (type == Rotate) + { + delta.X = delta.Y = [event rotation]; + } + else if (type == Swipe) + { + delta.X = [event deltaX]; + delta.Y = [event deltaY]; + } auto timestamp = [event timestamp] * 1000; auto modifiers = [self getModifiers:[event modifierFlags]]; @@ -1749,6 +1762,24 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent [super scrollWheel:event]; } +- (void)magnifyWithEvent:(NSEvent *)event +{ + [self mouseEvent:event withType:Magnify]; + [super magnifyWithEvent:event]; +} + +- (void)rotateWithEvent:(NSEvent *)event +{ + [self mouseEvent:event withType:Rotate]; + [super rotateWithEvent:event]; +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + [self mouseEvent:event withType:Swipe]; + [super swipeWithEvent:event]; +} + - (void)mouseEntered:(NSEvent *)event { _isMouseOver = true; diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 70d85dacdd..05fcdc71a5 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -295,7 +295,10 @@ enum AvnRawMouseEventType TouchBegin, TouchUpdate, TouchEnd, - TouchCancel + TouchCancel, + Magnify, + Rotate, + Swipe, } enum AvnRawKeyEventType From 33c22a952f13b7217cce887c1f4dfd739f98cce4 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 8 Sep 2021 21:51:50 +0300 Subject: [PATCH 011/260] [OSX] Add PointerMagnifyGesture, PointerRotateGesture, PointerSwipeGesture events --- src/Avalonia.Input/InputElement.cs | 83 ++++++++++++++++++- src/Avalonia.Input/MouseDevice.cs | 72 ++++++++++++++++ .../PointerMagnifyGestureEventArgs.cs | 19 +++++ .../PointerRotateGestureEventArgs.cs | 19 +++++ .../PointerSwipeGestureEventArgs.cs | 19 +++++ src/Avalonia.Input/Raw/RawPointerEventArgs.cs | 5 +- .../Raw/RawPointerGestureEventArgs.cs | 19 +++++ src/Avalonia.Native/WindowImplBase.cs | 21 ++++- 8 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs create mode 100644 src/Avalonia.Input/PointerRotateGestureEventArgs.cs create mode 100644 src/Avalonia.Input/PointerSwipeGestureEventArgs.cs create mode 100644 src/Avalonia.Input/Raw/RawPointerGestureEventArgs.cs diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 63080e74e4..93434af4a1 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -186,6 +186,30 @@ namespace Avalonia.Input RoutedEvent.Register( "PointerWheelChanged", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PointerMagnifyGestureEvent = + RoutedEvent.Register( + "PointerMagnifyGesture", + RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PointerRotateGestureEvent = + RoutedEvent.Register( + "PointerRotateGesture", + RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + + /// + /// Defines the event. + /// + public static readonly RoutedEvent PointerSwipeGestureEvent = + RoutedEvent.Register( + "PointerSwipeGesture", + RoutingStrategies.Tunnel | RoutingStrategies.Bubble); /// /// Defines the event. @@ -223,6 +247,9 @@ namespace Avalonia.Input PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); + PointerMagnifyGestureEvent.AddClassHandler((x, e) => x.OnPointerMagnifyGesture(e)); + PointerRotateGestureEvent.AddClassHandler((x, e) => x.OnPointerRotateGesture(e)); + PointerSwipeGestureEvent.AddClassHandler((x, e) => x.OnPointerSwipeGesture(e)); } public InputElement() @@ -349,13 +376,43 @@ namespace Avalonia.Input } /// - /// Occurs when the mouse wheen is scrolled over the control. + /// Occurs when the mouse is scrolled over the control. /// public event EventHandler PointerWheelChanged { add { AddHandler(PointerWheelChangedEvent, value); } remove { RemoveHandler(PointerWheelChangedEvent, value); } } + + /// + /// Occurs when the user uses magnify (Pitch to Zoom) gesture on a trackpad and pointer is over the control. + /// Works only on macOS. + /// + public event EventHandler PointerMagnifyGesture + { + add { AddHandler(PointerMagnifyGestureEvent, value); } + remove { RemoveHandler(PointerMagnifyGestureEvent, value); } + } + + /// + /// Occurs when the user uses rotate gesture on a trackpad and pointer is over the control. + /// Works only on macOS. + /// + public event EventHandler PointerRotateGesture + { + add { AddHandler(PointerRotateGestureEvent, value); } + remove { RemoveHandler(PointerRotateGestureEvent, value); } + } + + /// + /// Occurs when the user uses swipe gesture on a trackpad and pointer is over the control. + /// Works only on macOS. + /// + public event EventHandler PointerSwipeGesture + { + add { AddHandler(PointerSwipeGestureEvent, value); } + remove { RemoveHandler(PointerSwipeGestureEvent, value); } + } /// /// Occurs when a tap gesture occurs on the control. @@ -617,6 +674,30 @@ namespace Avalonia.Input protected virtual void OnPointerWheelChanged(PointerWheelEventArgs e) { } + + /// + /// Called before the trackpad event occurs. + /// + /// The event args. + protected virtual void OnPointerMagnifyGesture(PointerMagnifyGestureEventArgs e) + { + } + + /// + /// Called before the trackpad event occurs. + /// + /// The event args. + protected virtual void OnPointerRotateGesture(PointerRotateGestureEventArgs e) + { + } + + /// + /// Called before the trackpad event occurs. + /// + /// The event args. + protected virtual void OnPointerSwipeGesture(PointerSwipeGestureEventArgs e) + { + } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index cfa3690daf..745c0caafd 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -178,6 +178,15 @@ namespace Avalonia.Input case RawPointerEventType.Wheel: e.Handled = MouseWheel(mouse, e.Timestamp, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, keyModifiers); break; + case RawPointerEventType.Magnify: + e.Handled = GestureMagnify(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers); + break; + case RawPointerEventType.Rotate: + e.Handled = GestureRotate(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers); + break; + case RawPointerEventType.Swipe: + e.Handled = GestureSwipe(mouse, e.Timestamp, e.Root, e.Position, props, ((RawPointerGestureEventArgs)e).Delta, keyModifiers); + break; } } @@ -331,6 +340,69 @@ namespace Avalonia.Input return false; } + + private bool GestureMagnify(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, + PointerPointProperties props, + Vector delta, KeyModifiers inputModifiers) + { + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); + + var hit = HitTest(root, p); + + if (hit != null) + { + var source = GetSource(hit); + var e = new PointerMagnifyGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); + + source?.RaiseEvent(e); + return e.Handled; + } + + return false; + } + + private bool GestureRotate(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, + PointerPointProperties props, + Vector delta, KeyModifiers inputModifiers) + { + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); + + var hit = HitTest(root, p); + + if (hit != null) + { + var source = GetSource(hit); + var e = new PointerRotateGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); + + source?.RaiseEvent(e); + return e.Handled; + } + + return false; + } + + private bool GestureSwipe(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, + PointerPointProperties props, + Vector delta, KeyModifiers inputModifiers) + { + device = device ?? throw new ArgumentNullException(nameof(device)); + root = root ?? throw new ArgumentNullException(nameof(root)); + + var hit = HitTest(root, p); + + if (hit != null) + { + var source = GetSource(hit); + var e = new PointerSwipeGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); + + source?.RaiseEvent(e); + return e.Handled; + } + + return false; + } private IInteractive GetSource(IVisual hit) { diff --git a/src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs b/src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs new file mode 100644 index 0000000000..828307c5a7 --- /dev/null +++ b/src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs @@ -0,0 +1,19 @@ +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.Input +{ + public class PointerMagnifyGestureEventArgs : PointerEventArgs + { + public double Delta { get; set; } + + public PointerMagnifyGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + Point rootVisualPosition, ulong timestamp, + PointerPointProperties properties, KeyModifiers modifiers, double delta) + : base(InputElement.PointerMagnifyGestureEvent, source, pointer, rootVisual, rootVisualPosition, + timestamp, properties, modifiers) + { + Delta = delta; + } + } +} diff --git a/src/Avalonia.Input/PointerRotateGestureEventArgs.cs b/src/Avalonia.Input/PointerRotateGestureEventArgs.cs new file mode 100644 index 0000000000..4bf1ffaaaa --- /dev/null +++ b/src/Avalonia.Input/PointerRotateGestureEventArgs.cs @@ -0,0 +1,19 @@ +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.Input +{ + public class PointerRotateGestureEventArgs : PointerEventArgs + { + public double Delta { get; set; } + + public PointerRotateGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + Point rootVisualPosition, ulong timestamp, + PointerPointProperties properties, KeyModifiers modifiers, double delta) + : base(InputElement.PointerRotateGestureEvent, source, pointer, rootVisual, rootVisualPosition, + timestamp, properties, modifiers) + { + Delta = delta; + } + } +} diff --git a/src/Avalonia.Input/PointerSwipeGestureEventArgs.cs b/src/Avalonia.Input/PointerSwipeGestureEventArgs.cs new file mode 100644 index 0000000000..c4994c8504 --- /dev/null +++ b/src/Avalonia.Input/PointerSwipeGestureEventArgs.cs @@ -0,0 +1,19 @@ +using Avalonia.Interactivity; +using Avalonia.VisualTree; + +namespace Avalonia.Input +{ + public class PointerSwipeGestureEventArgs : PointerEventArgs + { + public Vector Delta { get; set; } + + public PointerSwipeGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + Point rootVisualPosition, ulong timestamp, + PointerPointProperties properties, KeyModifiers modifiers, Vector delta) + : base(InputElement.PointerSwipeGestureEvent, source, pointer, rootVisual, rootVisualPosition, + timestamp, properties, modifiers) + { + Delta = delta; + } + } +} diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs index 62a1dd5d84..d6406121c7 100644 --- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -21,7 +21,10 @@ namespace Avalonia.Input.Raw TouchBegin, TouchUpdate, TouchEnd, - TouchCancel + TouchCancel, + Magnify, + Rotate, + Swipe } /// diff --git a/src/Avalonia.Input/Raw/RawPointerGestureEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerGestureEventArgs.cs new file mode 100644 index 0000000000..5a6dbda9a7 --- /dev/null +++ b/src/Avalonia.Input/Raw/RawPointerGestureEventArgs.cs @@ -0,0 +1,19 @@ +namespace Avalonia.Input.Raw +{ + public class RawPointerGestureEventArgs : RawPointerEventArgs + { + public RawPointerGestureEventArgs( + IInputDevice device, + ulong timestamp, + IInputRoot root, + RawPointerEventType gestureType, + Point position, + Vector delta, RawInputModifiers inputModifiers) + : base(device, timestamp, root, gestureType, position, inputModifiers) + { + Delta = delta; + } + + public Vector Delta { get; private set; } + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 4a3baa2788..f1c42efb7f 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -306,11 +306,28 @@ namespace Avalonia.Native switch (type) { case AvnRawMouseEventType.Wheel: - Input?.Invoke(new RawMouseWheelEventArgs(_mouse, timeStamp, _inputRoot, point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers)); + Input?.Invoke(new RawMouseWheelEventArgs(_mouse, timeStamp, _inputRoot, + point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers)); + break; + + case AvnRawMouseEventType.Magnify: + Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Magnify, + point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers)); + break; + + case AvnRawMouseEventType.Rotate: + Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Rotate, + point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers)); + break; + + case AvnRawMouseEventType.Swipe: + Input?.Invoke(new RawPointerGestureEventArgs(_mouse, timeStamp, _inputRoot, RawPointerEventType.Swipe, + point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (RawInputModifiers)modifiers)); break; default: - var e = new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (RawInputModifiers)modifiers); + var e = new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, + point.ToAvaloniaPoint(), (RawInputModifiers)modifiers); if(!ChromeHitTest(e)) { From 7c414bb3b10fc6199f280fff8be02854724504f8 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 20 Oct 2021 11:14:33 +0200 Subject: [PATCH 012/260] feat(DevTools): Allow to Show/Hide implemented interfaces on Control Details --- .../Diagnostics/DevToolsOptions.cs | 5 ++ .../ViewModels/ControlDetailsViewModel.cs | 46 +++++++++++-------- .../Diagnostics/ViewModels/MainViewModel.cs | 21 ++++++++- .../ViewModels/TreePageViewModel.cs | 6 +++ .../Diagnostics/Views/MainView.xaml | 10 ++++ 5 files changed, 67 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 5336dca65b..6b5e7b1ec1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -27,5 +27,10 @@ namespace Avalonia.Diagnostics /// Get or set the startup screen index where the DevTools window will be displayed. /// public int? StartupScreenIndex { get; set; } + + /// + /// Gets or sets a value indicating whether DevTools should be displayed implemented interfaces on Control details. The default value is true. + /// + public bool ShowImplementedInterfaces { get; set; } = true; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 3790951b0c..45e578dc10 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -17,11 +17,12 @@ namespace Avalonia.Diagnostics.ViewModels internal class ControlDetailsViewModel : ViewModelBase, IDisposable { private readonly IVisual _control; - private readonly IDictionary> _propertyIndex; + private IDictionary>? _propertyIndex; private PropertyViewModel? _selectedProperty; private bool _snapshotStyles; private bool _showInactiveStyles; private string? _styleStatus; + private DataGridCollectionView _propertiesView; public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) { @@ -29,19 +30,6 @@ namespace Avalonia.Diagnostics.ViewModels TreePage = treePage; - var properties = GetAvaloniaProperties(control) - .Concat(GetClrProperties(control)) - .OrderBy(x => x, PropertyComparer.Instance) - .ThenBy(x => x.Name) - .ToList(); - - _propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList()); - - var view = new DataGridCollectionView(properties); - view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); - view.Filter = FilterProperty; - PropertiesView = view; - Layout = new ControlLayoutViewModel(control); if (control is INotifyPropertyChanged inpc) @@ -133,7 +121,7 @@ namespace Avalonia.Diagnostics.ViewModels public TreePageViewModel TreePage { get; } - public DataGridCollectionView PropertiesView { get; } + public DataGridCollectionView PropertiesView { get => _propertiesView; private set => RaiseAndSetIfChanged( ref _propertiesView , value); } public ObservableCollection AppliedStyles { get; } @@ -227,18 +215,21 @@ namespace Avalonia.Diagnostics.ViewModels } } - private IEnumerable GetClrProperties(object o) + private IEnumerable GetClrProperties(object o, bool showImplementedInterfaces) { foreach (var p in GetClrProperties(o, o.GetType())) { yield return p; } - foreach (var i in o.GetType().GetInterfaces()) + if (showImplementedInterfaces) { - foreach (var p in GetClrProperties(o, i)) + foreach (var i in o.GetType().GetInterfaces()) { - yield return p; + foreach (var p in GetClrProperties(o, i)) + { + yield return p; + } } } } @@ -378,5 +369,22 @@ namespace Avalonia.Diagnostics.ViewModels } } } + + public void UpdatePropertiesView(bool showImplementedInterfaces) + { + + var properties = GetAvaloniaProperties(_control) + .Concat(GetClrProperties(_control, showImplementedInterfaces)) + .OrderBy(x => x, PropertyComparer.Instance) + .ThenBy(x => x.Name) + .ToList(); + + _propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToList()); + + var view = new DataGridCollectionView(properties); + view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group))); + view.Filter = FilterProperty; + PropertiesView = view; + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index d0a4ad38c5..bb94ba16bb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -22,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; private bool _freezePopups; + private bool _showImplementedInterfaces; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -52,7 +53,7 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } - + public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; @@ -157,7 +158,7 @@ namespace Avalonia.Diagnostics.ViewModels get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } } - + private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; @@ -225,6 +226,22 @@ namespace Avalonia.Diagnostics.ViewModels public void SetOptions(DevToolsOptions options) { StartupScreenIndex = options.StartupScreenIndex; + ShowImplementedInterfaces = options.ShowImplementedInterfaces; + } + + public bool ShowImplementedInterfaces + { + get => _showImplementedInterfaces; + private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value); + } + + public void ToggleShowImplementedInterfaces(object parametr) + { + ShowImplementedInterfaces = !ShowImplementedInterfaces; + if (Content is TreePageViewModel viewModel) + { + viewModel.UpdatePropertiesView(); + } } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 4b18cf414a..f43896f810 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -39,6 +39,7 @@ namespace Avalonia.Diagnostics.ViewModels Details = value != null ? new ControlDetailsViewModel(this, value.Visual) : null; + Details?.UpdatePropertiesView(MainView.ShowImplementedInterfaces); Details?.UpdateStyleFilters(); } } @@ -134,5 +135,10 @@ namespace Avalonia.Diagnostics.ViewModels return null; } + + internal void UpdatePropertiesView() + { + Details?.UpdatePropertiesView(MainView?.ShowImplementedInterfaces ?? true); + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 6f2ac96a66..f45a65dbf3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -38,6 +38,16 @@ IsEnabled="False" /> + + + + + + + + From ae48a9a13b22df8bd2cecfff923e9a5374f31cdd Mon Sep 17 00:00:00 2001 From: Foaltin Dorin Date: Fri, 12 Nov 2021 15:37:59 +0200 Subject: [PATCH 013/260] Fix DataGrid selection broken after 3 right clicks --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 10c7c16488..95ee73be4e 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -5751,6 +5751,7 @@ namespace Avalonia.Controls return true; } // Unselect everything except the row that was clicked on + _noSelectionChangeCount++; try { UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); From 4870fc0d4d86c8e9c3c2aaa58453042f91eae7b2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 22 Nov 2021 12:59:33 +0100 Subject: [PATCH 014/260] feat(Style): Allow to using attached properties in attribute selector --- .../Markup/Parsers/SelectorGrammar.cs | 68 ++++++++++++++++++- .../Markup/Parsers/SelectorParser.cs | 55 +++++++++++++-- 2 files changed, 116 insertions(+), 7 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 953a7e9a15..15197d39c5 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -25,6 +25,7 @@ namespace Avalonia.Markup.Parsers Traversal, TypeName, Property, + AttachedProperty, Template, End, } @@ -74,6 +75,9 @@ namespace Avalonia.Markup.Parsers case State.Name: (state, syntax) = ParseName(ref r); break; + case State.AttachedProperty: + (state, syntax) = ParseAttachedProperty(ref r); + break; } if (syntax != null) { @@ -270,11 +274,15 @@ namespace Avalonia.Markup.Parsers return (State.CanHaveType, ParseType(ref r, new OfTypeSyntax())); } - private static (State, ISyntax) ParseProperty(ref CharacterReader r) + private static (State, ISyntax?) ParseProperty(ref CharacterReader r) { var property = r.ParseIdentifier(); - if (!r.TakeIf('=')) + if (r.TakeIf('(')) + { + return (State.AttachedProperty, default); + } + else if (!r.TakeIf('=')) { throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'"); } @@ -286,6 +294,42 @@ namespace Avalonia.Markup.Parsers return (State.CanHaveType, new PropertySyntax { Property = property.ToString(), Value = value.ToString() }); } + private static (State, ISyntax) ParseAttachedProperty(ref CharacterReader r) + { + var syntax = ParseType(ref r, new AttachedPropertySyntax()); + if (!r.TakeIf('.')) + { + throw new ExpressionParseException(r.Position, $"Expected '.', got '{r.Peek}'"); + } + var property = r.ParseIdentifier(); + if (property.IsEmpty) + { + throw new ExpressionParseException(r.Position, $"Expected Attached Property Name, got '{r.Peek}'"); + } + syntax.Property = property.ToString(); + + if (!r.TakeIf(')')) + { + throw new ExpressionParseException(r.Position, $"Expected ')', got '{r.Peek}'"); + } + + if (!r.TakeIf('=')) + { + throw new ExpressionParseException(r.Position, $"Expected '=', got '{r.Peek}'"); + } + + var value = r.TakeUntil(']'); + + syntax.Value = value.ToString(); + + r.Take(); + + var state = r.End + ? State.End + : State.Middle; + return (state, syntax); + } + private static TSyntax ParseType(ref CharacterReader r, TSyntax syntax) where TSyntax : ITypeSyntax { @@ -461,6 +505,26 @@ namespace Avalonia.Markup.Parsers } } + public class AttachedPropertySyntax : ISyntax, ITypeSyntax + { + public string Xmlns { get; set; } = string.Empty; + + public string TypeName { get; set; } = string.Empty; + + public string Property { get; set; } = string.Empty; + + public string Value { get; set; } = string.Empty; + + public override bool Equals(object obj) + { + return obj is AttachedPropertySyntax syntax + && syntax.Xmlns == Xmlns + && syntax.TypeName == syntax.TypeName + && syntax.Property == syntax.Property + && syntax.Value == syntax.Value; + } + } + public class IsSyntax : ISyntax, ITypeSyntax { public string TypeName { get; set; } = string.Empty; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 11fb287d46..23bbdb762d 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using Avalonia.Styling; using Avalonia.Utilities; +using System.Linq; namespace Avalonia.Markup.Parsers { @@ -75,23 +76,67 @@ namespace Avalonia.Markup.Parsers throw new InvalidOperationException($"Cannot find '{property.Property}' on '{type}"); } + { + object typedValue; + + if (TypeUtilities.TryConvert( + targetProperty.PropertyType, + property.Value, + CultureInfo.InvariantCulture, + out typedValue)) + { + result = result.PropertyEquals(targetProperty, typedValue); + } + else + { + throw new InvalidOperationException( + $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}"); + } + } + break; + } + case SelectorGrammar.AttachedPropertySyntax attachedProperty: + var targetType = result?.TargetType; + + if (targetType == null) + { + throw new InvalidOperationException("Attached Property selectors must be applied to a type."); + } + + var attachedPropertyOwnerType = Resolve(attachedProperty.Xmlns, attachedProperty.TypeName); + + if (attachedPropertyOwnerType is null) + { + throw new InvalidOperationException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}"); + } + + var targetAttachedProperty = AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(targetType) + .FirstOrDefault(ap => ap.OwnerType == attachedPropertyOwnerType && ap.Name == attachedProperty.Property); + + if (targetAttachedProperty == null) + { + throw new InvalidOperationException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType}"); + } + + { object typedValue; if (TypeUtilities.TryConvert( - targetProperty.PropertyType, - property.Value, + targetAttachedProperty.PropertyType, + attachedProperty.Value, CultureInfo.InvariantCulture, out typedValue)) { - result = result.PropertyEquals(targetProperty, typedValue); + result = result.PropertyEquals(targetAttachedProperty, typedValue); } else { throw new InvalidOperationException( - $"Could not convert '{property.Value}' to '{targetProperty.PropertyType}"); + $"Could not convert '{attachedProperty.Value}' to '{targetAttachedProperty.PropertyType}"); } - break; } + + break; case SelectorGrammar.ChildSyntax child: result = result.Child(); break; From 3e9c86054b71efafe67c146d59334e099b2792e7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 22 Nov 2021 13:00:15 +0100 Subject: [PATCH 015/260] feat(Style): Add test to check using attached properties in attribute selector --- .../Parsers/SelectorGrammarTests.cs | 36 ++++++++++ .../Parsers/SelectorParserTests.cs | 49 ++++++++++++++ .../SelectorTests_PropertyEquals.cs | 67 +++++++++++++++++++ 3 files changed, 152 insertions(+) diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs index 568f6deaf2..6fbf024ff1 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorGrammarTests.cs @@ -197,6 +197,42 @@ namespace Avalonia.Markup.UnitTests.Parsers result); } + [Fact] + public void OfType_AttachedProperty() + { + var result = SelectorGrammar.Parse("Button[(Grid.Column)=1]"); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.AttachedPropertySyntax { + Xmlns = string.Empty, + TypeName="Grid", + Property = "Column", + Value = "1" }, + }, + result); + } + + [Fact] + public void OfType_AttachedProperty_WithNamespace() + { + var result = SelectorGrammar.Parse("Button[(x|Grid.Column)=1]"); + + Assert.Equal( + new SelectorGrammar.ISyntax[] + { + new SelectorGrammar.OfTypeSyntax { TypeName = "Button" }, + new SelectorGrammar.AttachedPropertySyntax { + Xmlns = "x", + TypeName="Grid", + Property = "Column", + Value = "1" }, + }, + result); + } + [Fact] public void Not_OfType() { diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs index 1c0cba56c9..338bdb3ae1 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/SelectorParserTests.cs @@ -7,6 +7,25 @@ namespace Avalonia.Markup.UnitTests.Parsers { public class SelectorParserTests { + static SelectorParserTests() + { + //Ensure the attached properties are registered before run tests + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Grid).TypeHandle); + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Auth).TypeHandle); + } + + class Auth + { + public readonly static AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached("Name"); + + public static string GetName(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(NameProperty); + + public static void SetName(AvaloniaObject avaloniaObject, string value) => + avaloniaObject.SetValue(NameProperty, value); + } + [Fact] public void Parses_Boolean_Property_Selector() { @@ -14,6 +33,36 @@ namespace Avalonia.Markup.UnitTests.Parsers var result = target.Parse("TextBlock[IsPointerOver=True]"); } + [Fact] + public void Parses_AttacchedProperty_Selector_With_Namespace() + { + var target = new SelectorParser((ns, type) => + { + return (ns, type) switch + { + ("", nameof(TextBlock)) => typeof(TextBlock), + ("l",nameof(Auth)) => typeof(Auth), + _ => null + }; + }); + var result = target.Parse("TextBlock[(l|Auth.Name)=Admin]"); + } + + [Fact] + public void Parses_AttacchedProperty_Selector() + { + var target = new SelectorParser((ns, type) => + { + return (ns, type) switch + { + ("", nameof(TextBlock)) => typeof(TextBlock), + ("", nameof(Grid)) => typeof(Grid), + _ => null + }; + }); + var result = target.Parse("TextBlock[(Grid.Column)=1]"); + } + [Fact] public void Parses_Comma_Separated_Selectors() { diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs index 7689a458ae..729b42196b 100644 --- a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs +++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs @@ -8,6 +8,73 @@ namespace Avalonia.Styling.UnitTests { public class SelectorTests_PropertyEquals { + static SelectorTests_PropertyEquals() + { + //Ensure the attached properties are registered before run tests + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Grid).TypeHandle); + System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor(typeof(Auth).TypeHandle); + } + + class Auth + { + public readonly static AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached("Name"); + + public static string GetName(AvaloniaObject avaloniaObject) => + avaloniaObject.GetValue(NameProperty); + + public static void SetName(AvaloniaObject avaloniaObject, string value) => + avaloniaObject.SetValue(NameProperty, value); + } + + [Fact] + public async Task PropertyEquals_Attached_Property_Matching_Value() + { + var target = new Markup.Parsers.SelectorParser((ns, type) => + { + return (ns, type) switch + { + ("", nameof(TextBlock)) => typeof(TextBlock), + ("", nameof(Grid)) => typeof(Grid), + _ => null + }; + }).Parse("TextBlock[(Grid.Column)=1]"); + + + var control = new TextBlock(); + var activator = target.Match(control).Activator.ToObservable(); + + Assert.False(await activator.Take(1)); + Grid.SetColumn(control, 1); + Assert.True(await activator.Take(1)); + Grid.SetColumn(control, 0); + Assert.False(await activator.Take(1)); + } + + [Fact] + public async Task PropertyEquals_Attached_Property_With_Namespace_Matching_Value() + { + var target = new Markup.Parsers.SelectorParser((ns, type) => + { + return (ns, type) switch + { + ("", nameof(TextBlock)) => typeof(TextBlock), + ("l", nameof(Auth)) => typeof(Auth), + _ => null + }; + }).Parse("TextBlock[(l|Auth.Name)=Admin]"); + + + var control = new TextBlock(); + var activator = target.Match(control).Activator.ToObservable(); + + Assert.False(await activator.Take(1)); + Auth.SetName(control, "Admin"); + Assert.True(await activator.Take(1)); + Auth.SetName(control, null); + Assert.False(await activator.Take(1)); + } + [Fact] public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value() { From f59486ebc2a06bf586e84034efca4579c23999ca Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 22 Nov 2021 16:46:57 +0100 Subject: [PATCH 016/260] fixes(SelectorGrammar): AttachedPropertySyntax method Equal --- .../Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 15197d39c5..620477b6cd 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -519,9 +519,9 @@ namespace Avalonia.Markup.Parsers { return obj is AttachedPropertySyntax syntax && syntax.Xmlns == Xmlns - && syntax.TypeName == syntax.TypeName - && syntax.Property == syntax.Property - && syntax.Value == syntax.Value; + && syntax.TypeName == TypeName + && syntax.Property == Property + && syntax.Value == Value; } } From 0f18e319b0f60473e273c854ff9afae4604b9718 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 23 Nov 2021 14:50:22 +0100 Subject: [PATCH 017/260] fixes(Style): Missing XamlIl Compilation for SelectorGrammar.AttachedPropertySyntax --- .../AvaloniaXamlIlSelectorTransformer.cs | 73 +++++++++++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 2 + 2 files changed, 75 insertions(+) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index dfabd66d17..bc89513b22 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -40,6 +40,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var selectorType = pn.Property.GetClrProperty().Getter.ReturnType; var initialNode = new XamlIlSelectorInitialNode(node, selectorType); + var avaloniaAttachedPropertyT = context.GetAvaloniaTypes().AvaloniaAttachedPropertyT; XamlIlSelectorNode Create(IEnumerable syntax, Func typeResolver) { @@ -85,6 +86,50 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue); break; } + case SelectorGrammar.AttachedPropertySyntax: + { + var targetType = result?.TargetType; + if (targetType == null) + { + throw new XamlParseException("Attached Property selectors must be applied to a type.",node); + } + + var attachedProperty= (SelectorGrammar.AttachedPropertySyntax)i; + var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type; + + if (attachedPropertyOwnerType is null) + { + throw new XamlParseException($"Cannot find '{attachedProperty.Xmlns}:{attachedProperty.TypeName}",node); + } + + var attachedPropertyName = attachedProperty.Property + "Property"; + + var targetPropertyField = attachedPropertyOwnerType.GetAllFields() + .FirstOrDefault(f => f.IsStatic + && f.IsPublic + && f.Name == attachedPropertyName + && f.FieldType!.GenericTypeDefinition == avaloniaAttachedPropertyT + ); + + if (targetPropertyField is null) + { + throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType}", node); + } + + var targetPropertyType = XamlIlAvaloniaPropertyHelper + .GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node); + + if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, + new XamlAstTextNode(node, attachedProperty.Value, context.Configuration.WellKnownTypes.String), + targetPropertyType, out var typedValue)) + throw new XamlParseException( + $"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}", + node); + + result = new XamlIlAttacchedPropertyEqualsSelector(result, targetPropertyField, typedValue); + + break; + } case SelectorGrammar.ChildSyntax child: result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child); break; @@ -338,6 +383,34 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } } + + class XamlIlAttacchedPropertyEqualsSelector : XamlIlSelectorNode + { + public XamlIlAttacchedPropertyEqualsSelector(XamlIlSelectorNode previous, + IXamlField propertyFiled, + IXamlAstValueNode value) + : base(previous) + { + PropertyFiled = propertyFiled; + Value = value; + } + + public IXamlField PropertyFiled { get; set; } + public IXamlAstValueNode Value { get; set; } + + public override IXamlType TargetType => Previous?.TargetType; + protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen) + { + codeGen.Ldsfld(PropertyFiled); + context.Emit(Value, codeGen, context.Configuration.WellKnownTypes.Object); + EmitCall(context, codeGen, + m => m.Name == "PropertyEquals" + && m.Parameters.Count == 3 + && m.Parameters[1].FullName == "Avalonia.AvaloniaProperty" + && m.Parameters[2].Equals(context.Configuration.WellKnownTypes.Object)); + } + } + class XamlIlOrSelectorNode : XamlIlSelectorNode { List _selectors = new List(); 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 6dd3521183..89b88790dc 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType AvaloniaObjectExtensions { get; } public IXamlType AvaloniaProperty { get; } public IXamlType AvaloniaPropertyT { get; } + public IXamlType AvaloniaAttachedPropertyT { get; } public IXamlType IBinding { get; } public IXamlMethod AvaloniaObjectBindMethod { get; } public IXamlMethod AvaloniaObjectSetValueMethod { get; } @@ -93,6 +94,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers AvaloniaObjectExtensions = cfg.TypeSystem.GetType("Avalonia.AvaloniaObjectExtensions"); AvaloniaProperty = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty"); AvaloniaPropertyT = cfg.TypeSystem.GetType("Avalonia.AvaloniaProperty`1"); + AvaloniaAttachedPropertyT = cfg.TypeSystem.GetType("Avalonia.AttachedProperty`1"); BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority"); IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding"); IDisposable = cfg.TypeSystem.GetType("System.IDisposable"); From cadd264be0bd1e5ed710f5bd81b8ea602a5eac61 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 23 Nov 2021 14:51:01 +0100 Subject: [PATCH 018/260] fixes: PropertyEqualsSelector.ToString --- src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index 5d9c3fe56b..7681673ab6 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -53,11 +53,16 @@ namespace Avalonia.Styling if (_property.IsAttached) { + builder.Append('('); builder.Append(_property.OwnerType.Name); builder.Append('.'); } builder.Append(_property.Name); + if (_property.IsAttached) + { + builder.Append(')'); + } builder.Append('='); builder.Append(_value ?? string.Empty); builder.Append(']'); From 9f2390ae3c13eb4d00e3242d6ed00a6504299bbe Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 23 Nov 2021 18:23:21 +0100 Subject: [PATCH 019/260] fixes: minor fixes --- .../Transformers/AvaloniaXamlIlSelectorTransformer.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index bc89513b22..79589a5a4f 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -86,15 +86,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers result = new XamlIlPropertyEqualsSelector(result, targetProperty, typedValue); break; } - case SelectorGrammar.AttachedPropertySyntax: + case SelectorGrammar.AttachedPropertySyntax attachedProperty: { var targetType = result?.TargetType; if (targetType == null) { throw new XamlParseException("Attached Property selectors must be applied to a type.",node); } - - var attachedProperty= (SelectorGrammar.AttachedPropertySyntax)i; var attachedPropertyOwnerType = typeResolver(attachedProperty.Xmlns, attachedProperty.TypeName).Type; if (attachedPropertyOwnerType is null) @@ -108,12 +106,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers .FirstOrDefault(f => f.IsStatic && f.IsPublic && f.Name == attachedPropertyName - && f.FieldType!.GenericTypeDefinition == avaloniaAttachedPropertyT + && f.FieldType.GenericTypeDefinition == avaloniaAttachedPropertyT ); if (targetPropertyField is null) { - throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType}", node); + throw new XamlParseException($"Cannot find '{attachedProperty.Property}' on '{attachedPropertyOwnerType.GetFqn()}", node); } var targetPropertyType = XamlIlAvaloniaPropertyHelper @@ -127,7 +125,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers node); result = new XamlIlAttacchedPropertyEqualsSelector(result, targetPropertyField, typedValue); - break; } case SelectorGrammar.ChildSyntax child: From 17ec020239ca4b737c60195b0e039ea98f7b7ca1 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 27 Nov 2021 16:51:04 +0300 Subject: [PATCH 020/260] [OSX] Trackpad Gestures - code refactoring --- src/Avalonia.Input/Gestures.cs | 12 +++ src/Avalonia.Input/InputElement.cs | 85 ++++++------------- src/Avalonia.Input/MouseDevice.cs | 6 +- ...PointerTouchPadGestureMagnifyEventArgs.cs} | 6 +- ... PointerTouchPadGestureRotateEventArgs.cs} | 6 +- ...> PointerTouchPadGestureSwipeEventArgs.cs} | 6 +- 6 files changed, 50 insertions(+), 71 deletions(-) rename src/Avalonia.Input/{PointerRotateGestureEventArgs.cs => PointerTouchPadGestureMagnifyEventArgs.cs} (55%) rename src/Avalonia.Input/{PointerMagnifyGestureEventArgs.cs => PointerTouchPadGestureRotateEventArgs.cs} (55%) rename src/Avalonia.Input/{PointerSwipeGestureEventArgs.cs => PointerTouchPadGestureSwipeEventArgs.cs} (55%) diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 639b4ef117..f218bcb9ce 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -28,6 +28,18 @@ namespace Avalonia.Input public static readonly RoutedEvent ScrollGestureEndedEvent = RoutedEvent.Register( "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PointerTouchPadGestureMagnifyEvent = + RoutedEvent.Register( + "PointerMagnifyGesture", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PointerTouchPadGestureRotateEvent = + RoutedEvent.Register( + "PointerRotateGesture", RoutingStrategies.Bubble, typeof(Gestures)); + + public static readonly RoutedEvent PointerTouchPadGestureSwipeEvent = + RoutedEvent.Register( + "PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures)); #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. private static readonly WeakReference s_lastPress = new WeakReference(null); diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 88a5b0e971..af135e3e9f 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -186,40 +186,34 @@ namespace Avalonia.Input RoutedEvent.Register( "PointerWheelChanged", RoutingStrategies.Tunnel | RoutingStrategies.Bubble); - + /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerMagnifyGestureEvent = - RoutedEvent.Register( - "PointerMagnifyGesture", - RoutingStrategies.Tunnel | RoutingStrategies.Bubble); - + public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; + /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerRotateGestureEvent = - RoutedEvent.Register( - "PointerRotateGesture", - RoutingStrategies.Tunnel | RoutingStrategies.Bubble); + public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent PointerSwipeGestureEvent = - RoutedEvent.Register( - "PointerSwipeGesture", - RoutingStrategies.Tunnel | RoutingStrategies.Bubble); - + public static readonly RoutedEvent PointerTouchPadGestureMagnifyEvent + = Gestures.PointerTouchPadGestureMagnifyEvent; + /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent TappedEvent = Gestures.TappedEvent; - + public static readonly RoutedEvent PointerTouchPadGestureRotateEvent + = Gestures.PointerTouchPadGestureRotateEvent; + /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent DoubleTappedEvent = Gestures.DoubleTappedEvent; + public static readonly RoutedEvent PointerTouchPadGestureSwipeEvent + = Gestures.PointerTouchPadGestureSwipeEvent; private bool _isEffectivelyEnabled = true; private bool _isFocused; @@ -247,9 +241,6 @@ namespace Avalonia.Input PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); - PointerMagnifyGestureEvent.AddClassHandler((x, e) => x.OnPointerMagnifyGesture(e)); - PointerRotateGestureEvent.AddClassHandler((x, e) => x.OnPointerRotateGesture(e)); - PointerSwipeGestureEvent.AddClassHandler((x, e) => x.OnPointerSwipeGesture(e)); } public InputElement() @@ -388,30 +379,30 @@ namespace Avalonia.Input /// Occurs when the user uses magnify (Pitch to Zoom) gesture on a trackpad and pointer is over the control. /// Works only on macOS. /// - public event EventHandler PointerMagnifyGesture + public event EventHandler PointerTouchPadGestureMagnify { - add { AddHandler(PointerMagnifyGestureEvent, value); } - remove { RemoveHandler(PointerMagnifyGestureEvent, value); } + add { AddHandler(PointerTouchPadGestureMagnifyEvent, value); } + remove { RemoveHandler(PointerTouchPadGestureMagnifyEvent, value); } } /// /// Occurs when the user uses rotate gesture on a trackpad and pointer is over the control. /// Works only on macOS. /// - public event EventHandler PointerRotateGesture + public event EventHandler PointerTouchPadGestureRotate { - add { AddHandler(PointerRotateGestureEvent, value); } - remove { RemoveHandler(PointerRotateGestureEvent, value); } + add { AddHandler(PointerTouchPadGestureRotateEvent, value); } + remove { RemoveHandler(PointerTouchPadGestureRotateEvent, value); } } /// /// Occurs when the user uses swipe gesture on a trackpad and pointer is over the control. /// Works only on macOS. /// - public event EventHandler PointerSwipeGesture + public event EventHandler PointerTouchPadGestureSwipe { - add { AddHandler(PointerSwipeGestureEvent, value); } - remove { RemoveHandler(PointerSwipeGestureEvent, value); } + add { AddHandler(PointerTouchPadGestureSwipeEvent, value); } + remove { RemoveHandler(PointerTouchPadGestureSwipeEvent, value); } } /// @@ -674,30 +665,6 @@ namespace Avalonia.Input protected virtual void OnPointerWheelChanged(PointerWheelEventArgs e) { } - - /// - /// Called before the trackpad event occurs. - /// - /// The event args. - protected virtual void OnPointerMagnifyGesture(PointerMagnifyGestureEventArgs e) - { - } - - /// - /// Called before the trackpad event occurs. - /// - /// The event args. - protected virtual void OnPointerRotateGesture(PointerRotateGestureEventArgs e) - { - } - - /// - /// Called before the trackpad event occurs. - /// - /// The event args. - protected virtual void OnPointerSwipeGesture(PointerSwipeGestureEventArgs e) - { - } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index c919242c78..7197397e99 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -355,7 +355,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerMagnifyGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); + var e = new PointerTouchPadGestureMagnifyEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); source?.RaiseEvent(e); return e.Handled; @@ -376,7 +376,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerRotateGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); + var e = new PointerTouchPadGestureRotateEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta.X); source?.RaiseEvent(e); return e.Handled; @@ -397,7 +397,7 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerSwipeGestureEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); + var e = new PointerTouchPadGestureSwipeEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, delta); source?.RaiseEvent(e); return e.Handled; diff --git a/src/Avalonia.Input/PointerRotateGestureEventArgs.cs b/src/Avalonia.Input/PointerTouchPadGestureMagnifyEventArgs.cs similarity index 55% rename from src/Avalonia.Input/PointerRotateGestureEventArgs.cs rename to src/Avalonia.Input/PointerTouchPadGestureMagnifyEventArgs.cs index 4bf1ffaaaa..d55d23d2c8 100644 --- a/src/Avalonia.Input/PointerRotateGestureEventArgs.cs +++ b/src/Avalonia.Input/PointerTouchPadGestureMagnifyEventArgs.cs @@ -3,14 +3,14 @@ using Avalonia.VisualTree; namespace Avalonia.Input { - public class PointerRotateGestureEventArgs : PointerEventArgs + public class PointerTouchPadGestureMagnifyEventArgs : PointerEventArgs { public double Delta { get; set; } - public PointerRotateGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + public PointerTouchPadGestureMagnifyEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, double delta) - : base(InputElement.PointerRotateGestureEvent, source, pointer, rootVisual, rootVisualPosition, + : base(InputElement.PointerTouchPadGestureMagnifyEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) { Delta = delta; diff --git a/src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs b/src/Avalonia.Input/PointerTouchPadGestureRotateEventArgs.cs similarity index 55% rename from src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs rename to src/Avalonia.Input/PointerTouchPadGestureRotateEventArgs.cs index 828307c5a7..8563cf6609 100644 --- a/src/Avalonia.Input/PointerMagnifyGestureEventArgs.cs +++ b/src/Avalonia.Input/PointerTouchPadGestureRotateEventArgs.cs @@ -3,14 +3,14 @@ using Avalonia.VisualTree; namespace Avalonia.Input { - public class PointerMagnifyGestureEventArgs : PointerEventArgs + public class PointerTouchPadGestureRotateEventArgs : PointerEventArgs { public double Delta { get; set; } - public PointerMagnifyGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + public PointerTouchPadGestureRotateEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, double delta) - : base(InputElement.PointerMagnifyGestureEvent, source, pointer, rootVisual, rootVisualPosition, + : base(InputElement.PointerTouchPadGestureRotateEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) { Delta = delta; diff --git a/src/Avalonia.Input/PointerSwipeGestureEventArgs.cs b/src/Avalonia.Input/PointerTouchPadGestureSwipeEventArgs.cs similarity index 55% rename from src/Avalonia.Input/PointerSwipeGestureEventArgs.cs rename to src/Avalonia.Input/PointerTouchPadGestureSwipeEventArgs.cs index c4994c8504..1d9c0d6189 100644 --- a/src/Avalonia.Input/PointerSwipeGestureEventArgs.cs +++ b/src/Avalonia.Input/PointerTouchPadGestureSwipeEventArgs.cs @@ -3,14 +3,14 @@ using Avalonia.VisualTree; namespace Avalonia.Input { - public class PointerSwipeGestureEventArgs : PointerEventArgs + public class PointerTouchPadGestureSwipeEventArgs : PointerEventArgs { public Vector Delta { get; set; } - public PointerSwipeGestureEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + public PointerTouchPadGestureSwipeEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) - : base(InputElement.PointerSwipeGestureEvent, source, pointer, rootVisual, rootVisualPosition, + : base(InputElement.PointerTouchPadGestureSwipeEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) { Delta = delta; From b0317f46a5bf2aa524b028b0aac011392a0dd9d0 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 28 Nov 2021 20:38:49 +0100 Subject: [PATCH 021/260] Add support for drawing ellipses directly via DrawingContext --- .../HeadlessPlatformRenderInterface.cs | 4 + src/Avalonia.Visuals/Media/DrawingContext.cs | 28 ++++- .../Platform/IDrawingContextImpl.cs | 12 ++ .../SceneGraph/DeferredDrawingContextImpl.cs | 14 +++ .../Rendering/SceneGraph/EllipseNode.cs | 118 ++++++++++++++++++ .../Rendering/SceneGraph/RectangleNode.cs | 3 +- src/Skia/Avalonia.Skia/DrawingContextImpl.cs | 28 +++++ .../Media/DrawingContextImpl.cs | 39 ++++++ .../NullDrawingContextImpl.cs | 4 + 9 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 63cbfb2dbe..48d0ef9da9 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -447,6 +447,10 @@ namespace Avalonia.Headless } + public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + { + } + public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 4e3dc8699c..8e8b116a04 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.Threading; @@ -190,6 +189,33 @@ namespace Avalonia.Media DrawRectangle(null, pen, rect, cornerRadius, cornerRadius); } + /// + /// Draws an ellipse with the specified Brush and Pen. + /// + /// The brush used to fill the ellipse, or null for no fill. + /// The pen used to stroke the ellipse, or null for no stroke. + /// The location of the center of the ellipse. + /// The horizontal radius of the ellipse. + /// The vertical radius of the ellipse. + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + public void DrawEllipse(IBrush brush, IPen pen, Point center, double radiusX, double radiusY) + { + if (brush == null && !PenIsVisible(pen)) + { + return; + } + + var originX = center.X - radiusX; + var originY = center.Y - radiusY; + var width = radiusX * 2; + var height = radiusY * 2; + + PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height)); + } + /// /// Draws a custom drawing operation /// diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 39d4066e55..ac2c5c9f08 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -71,6 +71,18 @@ namespace Avalonia.Platform void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default); + /// + /// Draws an ellipse with the specified Brush and Pen. + /// + /// The brush used to fill the ellipse, or null for no fill. + /// The pen used to stroke the ellipse, or null for no stroke. + /// The ellipse bounds. + /// + /// The brush and the pen can both be null. If the brush is null, then no fill is performed. + /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. + /// + void DrawEllipse(IBrush brush, IPen pen, Rect rect); + /// /// Draws text. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index f4039dc0bc..da1a00504a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -179,6 +179,20 @@ namespace Avalonia.Rendering.SceneGraph } } + public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + { + var next = NextDrawAs(); + + if (next == null || !next.Item.Equals(Transform, brush, pen, rect)) + { + Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush))); + } + else + { + ++_drawOperationindex; + } + } + public void Custom(ICustomDrawOperation custom) { var next = NextDrawAs(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs new file mode 100644 index 0000000000..c817303d51 --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.Platform; +using Avalonia.VisualTree; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// A node in the scene graph which represents an ellipse draw. + /// + internal class EllipseNode : BrushDrawOperation + { + public EllipseNode( + Matrix transform, + IBrush brush, + IPen pen, + Rect rect, + IDictionary childScenes = null) + : base(rect.Inflate(pen?.Thickness ?? 0), transform) + { + Transform = transform; + Brush = brush?.ToImmutable(); + Pen = pen?.ToImmutable(); + Rect = rect; + ChildScenes = childScenes; + } + + /// + /// Gets the fill brush. + /// + public IBrush Brush { get; } + + /// + /// Gets the stroke pen. + /// + public ImmutablePen Pen { get; } + + /// + /// Gets the transform with which the node will be drawn. + /// + public Matrix Transform { get; } + + /// + /// Gets the rect of the ellipse to draw. + /// + public Rect Rect { get; } + + public override IDictionary ChildScenes { get; } + + public bool Equals(Matrix transform, IBrush brush, IPen pen, Rect rect) + { + return transform == Transform && + Equals(brush, Brush) && + Equals(Pen, pen) && + rect.Equals(Rect); + } + + public override void Render(IDrawingContextImpl context) + { + context.DrawEllipse(Brush, Pen, Rect); + } + + public override bool HitTest(Point p) + { + if (!Transform.TryInvert(out Matrix inverted)) + { + return false; + } + + p *= inverted; + + var center = Rect.Center; + + var strokeThickness = Pen?.Thickness ?? 0; + + var rx = Rect.Width / 2 + strokeThickness / 2; + var ry = Rect.Height / 2 + strokeThickness / 2; + + var dx = p.X - center.X; + var dy = p.Y - center.Y; + + if (Math.Abs(dx) > rx || Math.Abs(dy) > ry) + { + return false; + } + + if (Brush != null) + { + return Contains(rx, ry); + } + else if (strokeThickness > 0) + { + bool inStroke = Contains(rx, ry); + + rx = Rect.Width / 2 - strokeThickness / 2; + ry = Rect.Height / 2 - strokeThickness / 2; + + bool inInner = Contains(rx, ry); + + return inStroke && !inInner; + } + + bool Contains(double radiusX, double radiusY) + { + var rx2 = radiusX * radiusX; + var ry2 = radiusY * radiusY; + + var distance = ry2 * dx * dx + rx2 * dy * dy; + + return distance < rx2 * ry2; + } + + return false; + } + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 187c1da0a9..285fbce605 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index 7026e6d9ce..4dedd27445 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -414,6 +414,34 @@ namespace Avalonia.Skia } } + /// + public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + { + if (rect.Height <= 0 || rect.Width <= 0) + return; + + var rc = rect.ToSKRect(); + + if (brush != null) + { + using (var paint = CreatePaint(_fillPaint, brush, rect.Size)) + { + Canvas.DrawOval(rc, paint.Paint); + } + } + + if (pen?.Brush != null) + { + using (var paint = CreatePaint(_strokePaint, pen, rect.Size)) + { + if (paint.Paint is object) + { + Canvas.DrawOval(rc, paint.Paint); + } + } + } + } + /// public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs index 622f47f953..470157110c 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs @@ -337,6 +337,45 @@ namespace Avalonia.Direct2D1.Media } } + /// + public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + { + var rc = rect.ToDirect2D(); + + if (brush != null) + { + using (var b = CreateBrush(brush, rect.Size)) + { + if (b.PlatformBrush != null) + { + _deviceContext.FillEllipse(new Ellipse + { + Point = rect.Center.ToSharpDX(), + RadiusX = (float)(rect.Width / 2), + RadiusY = (float)(rect.Height / 2) + }, b.PlatformBrush); + } + } + } + + if (pen?.Brush != null) + { + using (var wrapper = CreateBrush(pen.Brush, rect.Size)) + using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext)) + { + if (wrapper.PlatformBrush != null) + { + _deviceContext.DrawEllipse(new Ellipse + { + Point = rect.Center.ToSharpDX(), + RadiusX = (float)(rect.Width / 2), + RadiusY = (float)(rect.Height / 2) + }, wrapper.PlatformBrush, (float)pen.Thickness, d2dStroke); + } + } + } + } + /// /// Draws text. /// diff --git a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs index 7626be7760..549f450ece 100644 --- a/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs +++ b/tests/Avalonia.Benchmarks/NullDrawingContextImpl.cs @@ -39,6 +39,10 @@ namespace Avalonia.Benchmarks { } + public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + { + } + public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text) { } From 7172b921a22b59f8b009ee66a23ee6b7d83991bd Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 12 Dec 2021 17:45:35 +0100 Subject: [PATCH 022/260] Update API baseline. --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index e3f9f9a070..ee4f70e074 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -60,6 +60,7 @@ CannotAddAbstractMembers : Member 'public Avalonia.Media.FlowDirection Avalonia. CannotAddAbstractMembers : Member 'public System.Double Avalonia.Media.TextFormatting.TextParagraphProperties.Indent.get()' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalonia.Media.TextFormatting.TextRunProperties.BaselineAlignment.get()' is abstract in the implementation but is missing in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawEllipse(Avalonia.Media.IBrush, Avalonia.Media.IPen, Avalonia.Rect)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PopBitmapBlendMode()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.PushBitmapBlendMode(Avalonia.Visuals.Media.Imaging.BitmapBlendingMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Double Avalonia.Platform.IGeometryImpl.ContourLength' is present in the implementation but not in the contract. @@ -76,4 +77,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 77 +Total Issues: 78 From 80041d8f30e217871185782fa8365561d50f3bf7 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 12 Dec 2021 17:50:21 +0100 Subject: [PATCH 023/260] Change pointers page to use new ellipse drawing APIs. --- samples/ControlCatalog/Pages/PointersPage.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs index 60e946dfbe..fddc503a90 100644 --- a/samples/ControlCatalog/Pages/PointersPage.cs +++ b/samples/ControlCatalog/Pages/PointersPage.cs @@ -99,10 +99,9 @@ namespace ControlCatalog.Pages foreach (var pt in _pointers.Values) { var brush = new ImmutableSolidColorBrush(pt.Color); - context.DrawGeometry(brush, null, new EllipseGeometry(new Rect(pt.Point.X - 75, pt.Point.Y - 75, - 150, 150))); + + context.DrawEllipse(brush, null, pt.Point, 75, 75); } - } } } From 500fa65b723273e0ea1d1ffd5c4ef91c405cf65b Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 12 Dec 2021 18:59:53 +0100 Subject: [PATCH 024/260] Add tests for ellipse hit testing. --- .../Rendering/SceneGraph/EllipseNode.cs | 2 +- .../Rendering/SceneGraph/EllipseNodeTests.cs | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/EllipseNodeTests.cs diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs index c817303d51..a8c5579a4b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs @@ -109,7 +109,7 @@ namespace Avalonia.Rendering.SceneGraph var distance = ry2 * dx * dx + rx2 * dy * dy; - return distance < rx2 * ry2; + return distance <= rx2 * ry2; } return false; diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/EllipseNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/EllipseNodeTests.cs new file mode 100644 index 0000000000..565b217180 --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/EllipseNodeTests.cs @@ -0,0 +1,47 @@ +using Avalonia.Media; +using Avalonia.Media.Immutable; +using Avalonia.Rendering.SceneGraph; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class EllipseNodeTests + { + [Theory] + [InlineData(50, 50, true)] + [InlineData(50, 0, true)] + [InlineData(100, 50, true)] + [InlineData(50, 100, true)] + [InlineData(-1, 0, false)] + [InlineData(101, 0, false)] + [InlineData(101, 101, false)] + [InlineData(0, 101, false)] + public void FillOnly_HitTest(double x, double y, bool inside) + { + var ellipseNode = new EllipseNode(Matrix.Identity, Brushes.Black, null, new Rect(0,0, 100, 100), null); + + var point = new Point(x, y); + + Assert.True(ellipseNode.HitTest(point) == inside); + } + + [Theory] + [InlineData(50, 0, true)] + [InlineData(51, 0, true)] + [InlineData(100, 50, true)] + [InlineData(50, 100, true)] + [InlineData(-1, 50, true)] + [InlineData(53, 50, false)] + [InlineData(101, 0, false)] + [InlineData(101, 101, false)] + [InlineData(0, 101, false)] + public void StrokeOnly_HitTest(double x, double y, bool inside) + { + var ellipseNode = new EllipseNode(Matrix.Identity, null, new ImmutablePen(Brushes.Black, 2), new Rect(0, 0, 100, 100), null); + + var point = new Point(x, y); + + Assert.Equal(inside, ellipseNode.HitTest(point)); + } + } +} From 4b3b37c961945978b1abdd07dc34f752c310d9de Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 13 Dec 2021 15:16:13 +0100 Subject: [PATCH 025/260] fixes(DevTools): The Type column does not update when the property value changes. --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 2 ++ .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 46f8614273..a09e17ea68 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -45,6 +45,7 @@ namespace Avalonia.Diagnostics.ViewModels { var convertedValue = ConvertFromString(value, Property.PropertyType); _target.SetValue(Property, convertedValue); + Update(); } catch { } } @@ -84,6 +85,7 @@ namespace Avalonia.Diagnostics.ViewModels RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group)); } } + RaisePropertyChanged(nameof(Type)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index b6bdb00170..e87be9fa36 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -48,6 +48,7 @@ namespace Avalonia.Diagnostics.ViewModels { var convertedValue = ConvertFromString(value, Property.PropertyType); Property.SetValue(_target, convertedValue); + Update(); } catch { } } @@ -67,6 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); RaiseAndSetIfChanged(ref _assignedType, _value?.GetType() ?? Property.PropertyType, nameof(AssignedType)); + RaisePropertyChanged(nameof(Type)); } } } From 048cf388bcdb570f3fb176b5ea77e15919057948 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Dec 2021 17:59:08 +0000 Subject: [PATCH 026/260] allow disabling of osx menu export. --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 7 +++++++ src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index b9d8fd3711..bda1c91750 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -142,6 +142,13 @@ namespace Avalonia.Native private void DoLayoutReset(bool forceUpdate = false) { + var macOpts = AvaloniaLocator.Current.GetService(); + + if (macOpts != null && macOpts.DisableNativeMenus) + { + return; + } + if (_resetQueued || forceUpdate) { _resetQueued = false; diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index eef765e7ec..809c063b47 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -73,5 +73,10 @@ namespace Avalonia /// You can prevent Avalonia from adding those items to the OSX Application Menu with this property. The default value is false. /// public bool DisableDefaultApplicationMenuItems { get; set; } + + /// + /// + /// + public bool DisableNativeMenus { get; set; } } } From 18115363f8445e3ea9894a098497ddd48ab0f745 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 17 Dec 2021 10:45:35 +0800 Subject: [PATCH 027/260] Update src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs Co-authored-by: Max Katz --- src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 809c063b47..10619d675b 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -75,7 +75,7 @@ namespace Avalonia public bool DisableDefaultApplicationMenuItems { get; set; } /// - /// + /// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application. /// public bool DisableNativeMenus { get; set; } } From cce425b8dae0474528d93e7379939c69ca21eab3 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 16 Dec 2021 12:17:13 +0000 Subject: [PATCH 028/260] fix red shown during resizing catalina. --- native/Avalonia.Native/src/OSX/window.mm | 1 - 1 file changed, 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 16f49b8e26..f5253fbef2 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -328,7 +328,6 @@ public: BaseEvents->Resized(AvnSize{x,y}, reason); } - [StandardContainer setFrameSize:NSSize{x,y}]; [Window setContentSize:NSSize{x, y}]; } @finally From 06807324d022da8a5031991e5d6082440e22d2c5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 17 Dec 2021 13:09:27 +0000 Subject: [PATCH 029/260] [OSX] fix resize calls before window shown. --- native/Avalonia.Native/src/OSX/window.mm | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index f5253fbef2..5d697cc0ec 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -27,6 +27,7 @@ public: AvnPoint lastPositionSet; NSString* _lastTitle; IAvnMenu* _mainMenu; + NSSize _lastSize; bool _shown; bool _inResize; @@ -35,6 +36,7 @@ public: { _shown = false; _inResize = false; + _lastSize = NSSize { 0, 0 }; _mainMenu = nullptr; BaseEvents = events; _glContext = gl; @@ -226,9 +228,17 @@ public: if(ret == nullptr) return E_POINTER; - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; + if(!_shown) + { + ret->Width = _lastSize.width; + ret->Height = _lastSize.height; + } + else + { + auto frame = [View frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + } return S_OK; } @@ -323,6 +333,8 @@ public: @try { + _lastSize = NSSize{x,y}; + if(!_shown) { BaseEvents->Resized(AvnSize{x,y}, reason); From 29d164952d3696c1d8f52b7675ca57df5fa7afe7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 17 Dec 2021 13:37:44 +0000 Subject: [PATCH 030/260] Update skiasharp --- 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 1346a1dafc..1d84d5289a 100644 --- a/build/HarfBuzzSharp.props +++ b/build/HarfBuzzSharp.props @@ -1,7 +1,7 @@  - - - + + + diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props index 4a75a18290..bb370256f9 100644 --- a/build/SkiaSharp.props +++ b/build/SkiaSharp.props @@ -1,7 +1,7 @@  - - - + + + From 6405558dd0063e2a92a1f178c91db222b5ebeea6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 17 Dec 2021 13:38:45 +0000 Subject: [PATCH 031/260] remove skiasharp from nuget.config --- NuGet.Config | 1 - 1 file changed, 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index e430390d39..7a1f28bea7 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -5,6 +5,5 @@ - From 0a65e0a50d433588f77d24edea90d20c10149bff Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 17 Dec 2021 16:25:14 +0000 Subject: [PATCH 032/260] forcefully invalidate shadow. --- native/Avalonia.Native/src/OSX/window.mm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 5d697cc0ec..3ee9e2214c 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -45,6 +45,7 @@ public: StandardContainer = [[AutoFitContentView new] initWithContent:View]; Window = [[AvnWindow alloc] initWithParent:this]; + [Window setContentView: StandardContainer]; lastPositionSet.X = 100; lastPositionSet.Y = 100; @@ -126,8 +127,6 @@ public: SetPosition(lastPositionSet); UpdateStyle(); - [Window setContentView: StandardContainer]; - [Window setTitle:_lastTitle]; if(ShouldTakeFocusOnShow() && activate) @@ -340,7 +339,8 @@ public: BaseEvents->Resized(AvnSize{x,y}, reason); } - [Window setContentSize:NSSize{x, y}]; + [Window setContentSize:NSSize{x,y}]; + [Window invalidateShadow]; } @finally { From 54e4f13ea6fd90d99416bcd039d0e522fbfcbe3c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 17 Dec 2021 16:30:47 +0000 Subject: [PATCH 033/260] Revert "[OSX] fix resize calls before window shown." This reverts commit 06807324d022da8a5031991e5d6082440e22d2c5. --- native/Avalonia.Native/src/OSX/window.mm | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 3ee9e2214c..0b88908252 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -27,7 +27,6 @@ public: AvnPoint lastPositionSet; NSString* _lastTitle; IAvnMenu* _mainMenu; - NSSize _lastSize; bool _shown; bool _inResize; @@ -36,7 +35,6 @@ public: { _shown = false; _inResize = false; - _lastSize = NSSize { 0, 0 }; _mainMenu = nullptr; BaseEvents = events; _glContext = gl; @@ -227,17 +225,9 @@ public: if(ret == nullptr) return E_POINTER; - if(!_shown) - { - ret->Width = _lastSize.width; - ret->Height = _lastSize.height; - } - else - { - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; - } + auto frame = [View frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; return S_OK; } @@ -332,8 +322,6 @@ public: @try { - _lastSize = NSSize{x,y}; - if(!_shown) { BaseEvents->Resized(AvnSize{x,y}, reason); From 7624e14bcdd7c4ee7dea52aa12ac38f36727a1b6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 17 Dec 2021 20:57:30 -0800 Subject: [PATCH 034/260] Support RelativeSource-based bindings without requiring an x:DataType property. Only require an x:DataType property when we actually use it. --- .../AvaloniaXamlIlBindingPathTransformer.cs | 8 +- ...valoniaXamlIlDataContextTypeTransformer.cs | 22 ++-- .../XamlIlBindingPathHelper.cs | 101 ++++++++++-------- .../CompiledBindingExtensionTests.cs | 24 ++++- 4 files changed, 93 insertions(+), 62 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs index 4d8c940bbc..4d21a29eb9 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs @@ -99,7 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } } - if (startType == null) + Func startTypeResolver = startType is not null ? () => startType : () => { var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); if (parentDataContextNode is null) @@ -107,10 +107,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", binding); } - startType = parentDataContextNode.DataContextType; - } + return parentDataContextNode.DataContextType; + }; - XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startType); + XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, binding, startTypeResolver, context.ParentNodes().OfType().First().Type.GetClrType()); } return node; 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 349143253e..24dd3f7771 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using XamlX; @@ -11,7 +12,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { class AvaloniaXamlIlDataContextTypeTransformer : IXamlAstTransformer { - private const string AvaloniaNs = "https://github.com/avaloniaui"; public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) { if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlDataContextTypeMetadataNode) @@ -120,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var parentItemsDataContext = context.ParentNodes().SkipWhile(n => n != parentObject).OfType().FirstOrDefault(); if (parentItemsDataContext != null) { - itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, parentItemsDataContext.DataContextType); + itemsCollectionType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, parentItemsBinding, () => parentItemsDataContext.DataContextType, parentObject.Type.GetClrType()); } } } @@ -153,16 +153,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } else if (obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().CompiledBindingExtension)) { - IXamlType startType; - var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); - if (parentDataContextNode is null) + Func startTypeResolver = () => { - throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj); - } + var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); + if (parentDataContextNode is null) + { + throw new XamlX.XamlParseException("Cannot parse a compiled binding without an explicit x:DataType directive to give a starting data type for bindings.", obj); + } - startType = parentDataContextNode.DataContextType; + return parentDataContextNode.DataContextType; + }; - var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startType); + var bindingResultType = XamlIlBindingPathHelper.UpdateCompiledBindingExtension(context, obj, startTypeResolver, on.Type.GetClrType()); return new AvaloniaXamlIlDataContextTypeMetadataNode(on, bindingResultType); } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs index 1974dfe3bc..7f7f60ed94 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs @@ -20,7 +20,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions { static class XamlIlBindingPathHelper { - public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, IXamlType startType) + public static IXamlType UpdateCompiledBindingExtension(AstTransformationContext context, XamlAstConstructableObjectNode binding, Func startTypeResolver, IXamlType selfType) { IXamlType bindingResultType = null; if (binding.Arguments.Count > 0 && binding.Arguments[0] is ParsedBindingPathNode bindingPath) @@ -28,7 +28,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var transformed = TransformBindingPath( context, bindingPath, - startType, + startTypeResolver, + selfType, bindingPath.Path); bindingResultType = transformed.BindingResultType; @@ -41,7 +42,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions if (bindingPathAssignment is null) { - return startType; + return startTypeResolver(); } if (bindingPathAssignment.Values[0] is ParsedBindingPathNode bindingPathNode) @@ -49,7 +50,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions var transformed = TransformBindingPath( context, bindingPathNode, - startType, + startTypeResolver, + selfType, bindingPathNode.Path); bindingResultType = transformed.BindingResultType; @@ -64,13 +66,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions return bindingResultType; } - private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, IXamlType startType, IEnumerable bindingExpression) + private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func startTypeResolver, IXamlType selfType, IEnumerable bindingExpression) { List transformNodes = new List(); List nodes = new List(); foreach (var astNode in bindingExpression) { - var targetType = nodes.Count == 0 ? startType : nodes[nodes.Count - 1].Type; + var targetTypeResolver = nodes.Count == 0 ? startTypeResolver : () => nodes[nodes.Count - 1].Type; switch (astNode) { case BindingExpressionGrammar.EmptyExpressionNode _: @@ -79,59 +81,66 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions transformNodes.Add(new XamlIlNotPathElementNode(context.Configuration.WellKnownTypes.Boolean)); break; case BindingExpressionGrammar.StreamNode _: - IXamlType observableType; - if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true) { - observableType = targetType; - } - else - { - observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false); - } + IXamlType targetType = targetTypeResolver(); + IXamlType observableType; + if (targetType.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) == true) + { + observableType = targetType; + } + else + { + observableType = targetType.GetAllInterfaces().FirstOrDefault(i => i.GenericTypeDefinition?.Equals(context.Configuration.TypeSystem.FindType("System.IObservable`1")) ?? false); + } - if (observableType != null) - { - nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0])); - break; - } - bool foundTask = false; - for (var currentType = targetType; currentType != null; currentType = currentType.BaseType) - { - if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1"))) + if (observableType != null) { - foundTask = true; - nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0])); + nodes.Add(new XamlIlStreamObservablePathElementNode(observableType.GenericArguments[0])); break; } + bool foundTask = false; + for (var currentType = targetType; currentType != null; currentType = currentType.BaseType) + { + if (currentType.GenericTypeDefinition.Equals(context.Configuration.TypeSystem.GetType("System.Threading.Tasks.Task`1"))) + { + foundTask = true; + nodes.Add(new XamlIlStreamTaskPathElementNode(currentType.GenericArguments[0])); + break; + } + } + if (foundTask) + { + break; + } + throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo); } - if (foundTask) - { - break; - } - throw new XamlX.XamlParseException($"Compiled bindings do not support stream bindings for objects of type {targetType.FullName}.", lineInfo); case BindingExpressionGrammar.PropertyNameNode propName: - var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property"; - var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f => - f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe); - - if (avaloniaPropertyFieldMaybe != null) { - nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, - XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo))); - } - else - { - var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName); + IXamlType targetType = targetTypeResolver(); + var avaloniaPropertyFieldNameMaybe = propName.PropertyName + "Property"; + var avaloniaPropertyFieldMaybe = targetType.GetAllFields().FirstOrDefault(f => + f.IsStatic && f.IsPublic && f.Name == avaloniaPropertyFieldNameMaybe); - if (clrProperty is null) + if (avaloniaPropertyFieldMaybe != null) + { + nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe, + XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo))); + } + else { - throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); + var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName); + + if (clrProperty is null) + { + throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo); + } + nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty)); } - nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty)); + break; } - break; case BindingExpressionGrammar.IndexerNode indexer: { + IXamlType targetType = targetTypeResolver(); if (targetType.IsArray) { nodes.Add(new XamlIlArrayIndexerPathElementNode(targetType, indexer.Arguments, lineInfo)); @@ -183,7 +192,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyField, context.GetAvaloniaTypes(), lineInfo))); break; case BindingExpressionGrammar.SelfNode _: - nodes.Add(new SelfPathElementNode(targetType)); + nodes.Add(new SelfPathElementNode(selfType)); break; case VisualAncestorBindingExpressionNode visualAncestor: nodes.Add(new FindVisualAncestorPathElementNode(visualAncestor.Type, visualAncestor.Level)); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 8a61458030..15c6f5877f 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs @@ -731,8 +731,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions + Title='foo'> "; var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); @@ -1042,6 +1041,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions } } + [Fact] + public void Binds_To_Self_Without_DataType() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + + +"; + var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml); + var textBlock = window.FindControl("textBlock"); + + window.ApplyTemplate(); + window.Presenter.ApplyTemplate(); + + Assert.Equal(textBlock.Name, textBlock.Text); + } + } + void Throws(string type, Action cb) { try From 22a55fed2758cbf325b71b5a4a54500fb925915b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 Dec 2021 14:45:25 +0100 Subject: [PATCH 035/260] Added a lot of nullable annotations to Avalonia.Visuals. Except types related to text and one method in `DrawingContext` which I'm not sure what to do with. --- .../Resources/Resource.Designer.cs | 2 +- .../Animation/Animators/BaseBrushAnimator.cs | 2 +- .../Animation/Animators/TransformAnimator.cs | 4 +- .../Animation/CompositePageTransition.cs | 2 +- src/Avalonia.Visuals/Animation/CrossFade.cs | 6 +- .../Animation/IPageTransition.cs | 2 +- src/Avalonia.Visuals/Animation/PageSlide.cs | 10 +- .../Animation/RenderLoopClock.cs | 4 +- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 1 + src/Avalonia.Visuals/CornerRadius.cs | 2 +- src/Avalonia.Visuals/Matrix.cs | 2 +- src/Avalonia.Visuals/Media/BoxShadow.cs | 11 +- src/Avalonia.Visuals/Media/BoxShadows.cs | 6 +- src/Avalonia.Visuals/Media/Brush.cs | 22 ++-- src/Avalonia.Visuals/Media/BrushConverter.cs | 8 +- src/Avalonia.Visuals/Media/BrushExtensions.cs | 6 +- src/Avalonia.Visuals/Media/CharacterHit.cs | 2 +- src/Avalonia.Visuals/Media/Color.cs | 2 +- src/Avalonia.Visuals/Media/DashStyle.cs | 2 +- src/Avalonia.Visuals/Media/DrawingContext.cs | 35 ++++-- src/Avalonia.Visuals/Media/DrawingImage.cs | 2 +- src/Avalonia.Visuals/Media/EllipseGeometry.cs | 6 +- .../Media/ExperimentalAcrylicMaterial.cs | 6 +- src/Avalonia.Visuals/Media/FontFamily.cs | 16 +-- src/Avalonia.Visuals/Media/FontManager.cs | 2 +- .../Media/Fonts/FamilyNameCollection.cs | 6 +- .../Media/Fonts/FontFamilyKey.cs | 10 +- .../Media/Fonts/FontFamilyLoader.cs | 6 +- src/Avalonia.Visuals/Media/FormattedText.cs | 16 +-- .../Media/FormattedTextStyleSpan.cs | 4 +- src/Avalonia.Visuals/Media/Geometry.cs | 24 ++-- src/Avalonia.Visuals/Media/GlyphRun.cs | 9 +- src/Avalonia.Visuals/Media/GradientBrush.cs | 12 +- src/Avalonia.Visuals/Media/IPen.cs | 4 +- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 29 +++-- .../Media/Imaging/CroppedBitmap.cs | 16 +-- .../Media/Imaging/RenderTargetBitmap.cs | 5 +- .../Media/Imaging/WriteableBitmap.cs | 14 ++- .../Media/Immutable/ImmutableDashStyle.cs | 4 +- .../Media/Immutable/ImmutablePen.cs | 14 +-- .../Immutable/ImmutableSolidColorBrush.cs | 4 +- .../ImmutableExperimentalAcrylicMaterial.cs | 2 +- src/Avalonia.Visuals/Media/KnownColors.cs | 8 +- src/Avalonia.Visuals/Media/LineGeometry.cs | 6 +- .../Media/MaterialExtensions.cs | 2 +- src/Avalonia.Visuals/Media/PathGeometry.cs | 35 ++++-- .../Media/PathGeometryCollections.cs | 4 +- .../Media/PathMarkupParser.cs | 34 +++++- src/Avalonia.Visuals/Media/Pen.cs | 22 ++-- src/Avalonia.Visuals/Media/PixelPoint.cs | 2 +- src/Avalonia.Visuals/Media/PixelRect.cs | 2 +- src/Avalonia.Visuals/Media/PixelSize.cs | 2 +- src/Avalonia.Visuals/Media/PixelVector.cs | 2 +- .../Media/PolylineGeometry.cs | 11 +- .../Media/RectangleGeometry.cs | 6 +- src/Avalonia.Visuals/Media/StreamGeometry.cs | 12 +- .../GenericTextRunProperties.cs | 14 +-- .../Media/TextFormatting/ITextSource.cs | 2 +- .../TextFormatting/ShapedTextCharacters.cs | 4 +- .../Media/TextFormatting/TextFormatter.cs | 2 +- .../Media/TextFormatting/TextFormatterImpl.cs | 18 +-- .../Media/TextFormatting/TextLayout.cs | 22 ++-- .../Media/TextFormatting/TextLine.cs | 2 +- .../Media/TextFormatting/TextLineBreak.cs | 4 +- .../Media/TextFormatting/TextLineImpl.cs | 4 +- .../TextFormatting/TextParagraphProperties.cs | 2 +- .../Media/TextFormatting/TextRun.cs | 4 +- .../Media/TextFormatting/TextRunProperties.cs | 12 +- .../Media/TextFormatting/TextShaper.cs | 2 +- .../TextFormatting/Unicode/UnicodeData.cs | 4 +- src/Avalonia.Visuals/Media/Transform.cs | 2 +- .../Media/TransformConverter.cs | 6 +- src/Avalonia.Visuals/Media/TransformGroup.cs | 2 +- .../Transformation/TransformOperation.cs | 4 +- src/Avalonia.Visuals/Media/Typeface.cs | 6 +- src/Avalonia.Visuals/Media/UnicodeRange.cs | 2 +- .../ExportRenderingSubsystemAttribute.cs | 6 +- .../Platform/IDrawingContextImpl.cs | 4 +- .../Platform/IFontManagerImpl.cs | 2 +- .../Platform/IGeometryImpl.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 2 +- .../Platform/IRenderTarget.cs | 2 +- .../Platform/ITextShaperImpl.cs | 2 +- .../Platform/PathGeometryContext.cs | 35 +++++- src/Avalonia.Visuals/Point.cs | 2 +- src/Avalonia.Visuals/Rect.cs | 2 +- src/Avalonia.Visuals/RelativePoint.cs | 2 +- src/Avalonia.Visuals/RelativeRect.cs | 2 +- .../Rendering/DefaultRenderTimer.cs | 16 ++- .../Rendering/DeferredRenderer.cs | 72 ++++++----- .../Rendering/DirtyVisuals.cs | 5 + .../Rendering/IDeferredRendererLock.cs | 2 +- src/Avalonia.Visuals/Rendering/IRenderer.cs | 4 +- .../Rendering/ImmediateRenderer.cs | 16 ++- .../Rendering/RenderLayers.cs | 6 +- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 14 +-- .../SceneGraph/BrushDrawOperation.cs | 2 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 48 ++++---- .../SceneGraph/ExperimentalAcrylicNode.cs | 2 +- .../Rendering/SceneGraph/GeometryClipNode.cs | 2 +- .../Rendering/SceneGraph/GeometryNode.cs | 14 +-- .../Rendering/SceneGraph/GlyphRunNode.cs | 6 +- .../Rendering/SceneGraph/IVisualNode.cs | 4 +- .../Rendering/SceneGraph/LineNode.cs | 6 +- .../Rendering/SceneGraph/OpacityMaskNode.cs | 12 +- .../Rendering/SceneGraph/RectangleNode.cs | 14 +-- .../Rendering/SceneGraph/Scene.cs | 33 +++-- .../Rendering/SceneGraph/SceneBuilder.cs | 53 ++++---- .../Rendering/SceneGraph/SceneLayer.cs | 4 +- .../Rendering/SceneGraph/SceneLayers.cs | 23 ++-- .../Rendering/SceneGraph/TextNode.cs | 6 +- .../Rendering/SceneGraph/VisualNode.cs | 28 ++--- .../Rendering/SleepLoopRenderTimer.cs | 2 +- .../Rendering/ZIndexComparer.cs | 2 +- src/Avalonia.Visuals/RoundedRect.cs | 2 +- src/Avalonia.Visuals/Size.cs | 2 +- src/Avalonia.Visuals/Thickness.cs | 2 +- src/Avalonia.Visuals/Visual.cs | 21 ++-- src/Avalonia.Visuals/VisualExtensions.cs | 27 +++-- .../VisualTree/TransformedBounds.cs | 2 +- .../VisualTree/VisualExtensions.cs | 113 ++++++++++-------- .../VisualTree/VisualLocator.cs | 14 +-- .../VisualTreeAttachmentEventArgs.cs | 7 +- 123 files changed, 683 insertions(+), 574 deletions(-) diff --git a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs index 83db67fcee..87fd47df25 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs +++ b/src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs @@ -14,7 +14,7 @@ namespace Avalonia.AndroidTestApplication { - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "12.1.99.62")] public partial class Resource { diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs index ba7a3868c7..a2c4b0313b 100644 --- a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs @@ -143,7 +143,7 @@ namespace Avalonia.Animation.Animators if (!match(firstKeyType)) continue; - animator = (IAnimator)Activator.CreateInstance(animatorType); + animator = (IAnimator?)Activator.CreateInstance(animatorType); if (animator != null) { animator.Property = Property; diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 1b2142f6c9..1d7bfd3748 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -11,10 +11,10 @@ namespace Avalonia.Animation.Animators /// public class TransformAnimator : Animator { - DoubleAnimator _doubleAnimator; + DoubleAnimator? _doubleAnimator; /// - public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) + public override IDisposable? Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) { var ctrl = (Visual)control; diff --git a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs index 2deebd7792..2a6eae3e38 100644 --- a/src/Avalonia.Visuals/Animation/CompositePageTransition.cs +++ b/src/Avalonia.Visuals/Animation/CompositePageTransition.cs @@ -37,7 +37,7 @@ namespace Avalonia.Animation public List PageTransitions { get; set; } = new List(); /// - public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + public Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) { var transitionTasks = PageTransitions .Select(transition => transition.Start(from, to, forward, cancellationToken)) diff --git a/src/Avalonia.Visuals/Animation/CrossFade.cs b/src/Avalonia.Visuals/Animation/CrossFade.cs index 5eaa920b32..608a0880ec 100644 --- a/src/Avalonia.Visuals/Animation/CrossFade.cs +++ b/src/Avalonia.Visuals/Animation/CrossFade.cs @@ -100,7 +100,7 @@ namespace Avalonia.Animation } /// - public async Task Start(Visual from, Visual to, CancellationToken cancellationToken) + public async Task Start(Visual? from, Visual? to, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -112,7 +112,7 @@ namespace Avalonia.Animation { if (to != null) { - disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation)); + disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation)!); } if (from != null) @@ -151,7 +151,7 @@ namespace Avalonia.Animation /// /// A that tracks the progress of the animation. /// - Task IPageTransition.Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + Task IPageTransition.Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) { return Start(from, to, cancellationToken); } diff --git a/src/Avalonia.Visuals/Animation/IPageTransition.cs b/src/Avalonia.Visuals/Animation/IPageTransition.cs index 2d19ddbb5b..5abfdbbd0d 100644 --- a/src/Avalonia.Visuals/Animation/IPageTransition.cs +++ b/src/Avalonia.Visuals/Animation/IPageTransition.cs @@ -26,6 +26,6 @@ namespace Avalonia.Animation /// /// A that tracks the progress of the animation. /// - Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken); + Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken); } } diff --git a/src/Avalonia.Visuals/Animation/PageSlide.cs b/src/Avalonia.Visuals/Animation/PageSlide.cs index 7d033ccf61..b5a6062593 100644 --- a/src/Avalonia.Visuals/Animation/PageSlide.cs +++ b/src/Avalonia.Visuals/Animation/PageSlide.cs @@ -62,7 +62,7 @@ namespace Avalonia.Animation public Easing SlideOutEasing { get; set; } = new LinearEasing(); /// - public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) + public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { @@ -155,17 +155,17 @@ namespace Avalonia.Animation /// /// Any one of the parameters may be null, but not both. /// - private static IVisual GetVisualParent(IVisual from, IVisual to) + private static IVisual GetVisualParent(IVisual? from, IVisual? to) { - var p1 = (from ?? to).VisualParent; - var p2 = (to ?? from).VisualParent; + var p1 = (from ?? to)!.VisualParent; + var p2 = (to ?? from)!.VisualParent; if (p1 != null && p2 != null && p1 != p2) { throw new ArgumentException("Controls for PageSlide must have same parent."); } - return p1; + return p1 ?? throw new InvalidOperationException("Cannot determine visual parent."); } } } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index 504caef461..942e7eb7c6 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -9,7 +9,9 @@ namespace Avalonia.Animation { protected override void Stop() { - AvaloniaLocator.Current.GetService().Remove(this); + var loop = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IRenderLoop."); + loop.Remove(this); } bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj index 51ebfcb11f..486f08913c 100644 --- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj +++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj @@ -15,4 +15,5 @@ + diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index 037bb16e31..893f7c4565 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -91,7 +91,7 @@ namespace Avalonia /// /// The Object to compare against. /// True if the Object is equal to this corner radius; False otherwise. - public override bool Equals(object obj) => obj is CornerRadius other && Equals(other); + public override bool Equals(object? obj) => obj is CornerRadius other && Equals(other); public override int GetHashCode() { diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 243dafe817..b08a0eb98a 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -272,7 +272,7 @@ namespace Avalonia /// /// The Object to compare against. /// True if the Object is equal to this matrix; False otherwise. - public override bool Equals(object obj) => obj is Matrix other && Equals(other); + public override bool Equals(object? obj) => obj is Matrix other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/Media/BoxShadow.cs b/src/Avalonia.Visuals/Media/BoxShadow.cs index 50f75365b0..b01f59f5f8 100644 --- a/src/Avalonia.Visuals/Media/BoxShadow.cs +++ b/src/Avalonia.Visuals/Media/BoxShadow.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Text; using Avalonia.Animation.Animators; @@ -26,7 +27,7 @@ namespace Avalonia.Media return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is BoxShadow other && Equals(other); } @@ -59,7 +60,7 @@ namespace Avalonia.Media _index = 0; } - public bool TryReadString(out string s) + public bool TryReadString([MaybeNullWhen(false)] out string s) { s = null; if (_index >= _arr.Length) @@ -152,11 +153,11 @@ namespace Avalonia.Media tokenizer.TryReadString(out var token5); if (token4 != null) - blur = double.Parse(token3, CultureInfo.InvariantCulture); + blur = double.Parse(token3!, CultureInfo.InvariantCulture); if (token5 != null) - spread = double.Parse(token4, CultureInfo.InvariantCulture); + spread = double.Parse(token4!, CultureInfo.InvariantCulture); - var color = Color.Parse(token5 ?? token4 ?? token3); + var color = Color.Parse(token5 ?? token4 ?? token3!); return new BoxShadow { IsInset = inset, diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs index 810ac70b99..4614ea4e3c 100644 --- a/src/Avalonia.Visuals/Media/BoxShadows.cs +++ b/src/Avalonia.Visuals/Media/BoxShadows.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media public struct BoxShadows { private readonly BoxShadow _first; - private readonly BoxShadow[] _list; + private readonly BoxShadow[]? _list; public int Count { get; } static BoxShadows() @@ -39,7 +39,7 @@ namespace Avalonia.Media throw new IndexOutOfRangeException(); if (c == 0) return _first; - return _list[c - 1]; + return _list![c - 1]; } } @@ -134,7 +134,7 @@ namespace Avalonia.Media return true; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is BoxShadows other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/Brush.cs b/src/Avalonia.Visuals/Media/Brush.cs index cf7f5f531c..11fbe9393a 100644 --- a/src/Avalonia.Visuals/Media/Brush.cs +++ b/src/Avalonia.Visuals/Media/Brush.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(Opacity), 1.0); /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; static Brush() { @@ -43,18 +43,20 @@ namespace Avalonia.Media /// The . public static IBrush Parse(string s) { - Contract.Requires(s != null); - Contract.Requires(s.Length > 0); + _ = s ?? throw new ArgumentNullException(nameof(s)); - if (s[0] == '#') + if (s.Length > 0) { - return new ImmutableSolidColorBrush(Color.Parse(s)); - } + if (s[0] == '#') + { + return new ImmutableSolidColorBrush(Color.Parse(s)); + } - var brush = KnownColors.GetKnownBrush(s); - if (brush != null) - { - return brush; + var brush = KnownColors.GetKnownBrush(s); + if (brush != null) + { + return brush; + } } throw new FormatException($"Invalid brush string: '{s}'."); diff --git a/src/Avalonia.Visuals/Media/BrushConverter.cs b/src/Avalonia.Visuals/Media/BrushConverter.cs index b3c105ffc7..c501835c15 100644 --- a/src/Avalonia.Visuals/Media/BrushConverter.cs +++ b/src/Avalonia.Visuals/Media/BrushConverter.cs @@ -9,14 +9,14 @@ namespace Avalonia.Media /// public class BrushConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value) { - return Brush.Parse((string)value); + return value is string s ? Brush.Parse(s) : null; } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/BrushExtensions.cs b/src/Avalonia.Visuals/Media/BrushExtensions.cs index 87e698e705..2fc8778a5e 100644 --- a/src/Avalonia.Visuals/Media/BrushExtensions.cs +++ b/src/Avalonia.Visuals/Media/BrushExtensions.cs @@ -18,7 +18,7 @@ namespace Avalonia.Media /// public static IBrush ToImmutable(this IBrush brush) { - Contract.Requires(brush != null); + _ = brush ?? throw new ArgumentNullException(nameof(brush)); return (brush as IMutableBrush)?.ToImmutable() ?? brush; } @@ -33,7 +33,7 @@ namespace Avalonia.Media /// public static ImmutableDashStyle ToImmutable(this IDashStyle style) { - Contract.Requires(style != null); + _ = style ?? throw new ArgumentNullException(nameof(style)); return style as ImmutableDashStyle ?? ((DashStyle)style).ToImmutable(); } @@ -48,7 +48,7 @@ namespace Avalonia.Media /// public static ImmutablePen ToImmutable(this IPen pen) { - Contract.Requires(pen != null); + _ = pen ?? throw new ArgumentNullException(nameof(pen)); return pen as ImmutablePen ?? ((Pen)pen).ToImmutable(); } diff --git a/src/Avalonia.Visuals/Media/CharacterHit.cs b/src/Avalonia.Visuals/Media/CharacterHit.cs index f018b2d8a9..6bbbff4f5b 100644 --- a/src/Avalonia.Visuals/Media/CharacterHit.cs +++ b/src/Avalonia.Visuals/Media/CharacterHit.cs @@ -41,7 +41,7 @@ namespace Avalonia.Media return FirstCharacterIndex == other.FirstCharacterIndex && TrailingLength == other.TrailingLength; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is CharacterHit other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs index a57a962db4..083c15cd13 100644 --- a/src/Avalonia.Visuals/Media/Color.cs +++ b/src/Avalonia.Visuals/Media/Color.cs @@ -280,7 +280,7 @@ namespace Avalonia.Media return A == other.A && R == other.R && G == other.G && B == other.B; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Color other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/DashStyle.cs b/src/Avalonia.Visuals/Media/DashStyle.cs index acba2d3615..2ec9436ede 100644 --- a/src/Avalonia.Visuals/Media/DashStyle.cs +++ b/src/Avalonia.Visuals/Media/DashStyle.cs @@ -133,7 +133,7 @@ namespace Avalonia.Media } } - private void DashesChanged(object sender, NotifyCollectionChangedEventArgs e) + private void DashesChanged(object? sender, NotifyCollectionChangedEventArgs e) { Invalidated?.Invoke(this, e); } diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 8e8b116a04..0ff143782d 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -20,9 +20,9 @@ namespace Avalonia.Media private static ThreadSafeObjectPool> TransformStackPool { get; } = ThreadSafeObjectPool>.Default; - private Stack _states = StateStackPool.Get(); + private Stack? _states = StateStackPool.Get(); - private Stack _transformContainers = TransformStackPool.Get(); + private Stack? _transformContainers = TransformStackPool.Get(); readonly struct TransformContainer { @@ -80,7 +80,7 @@ namespace Avalonia.Media /// The rect in the output to draw to. public void DrawImage(IImage source, Rect rect) { - Contract.Requires(source != null); + _ = source ?? throw new ArgumentNullException(nameof(source)); DrawImage(source, new Rect(source.Size), rect); } @@ -94,7 +94,7 @@ namespace Avalonia.Media /// The bitmap interpolation mode. public void DrawImage(IImage source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) { - Contract.Requires(source != null); + _ = source ?? throw new ArgumentNullException(nameof(source)); source.Draw(this, sourceRect, destRect, bitmapInterpolationMode); } @@ -121,7 +121,8 @@ namespace Avalonia.Media /// The geometry. public void DrawGeometry(IBrush brush, IPen pen, Geometry geometry) { - DrawGeometry(brush, pen, geometry.PlatformImpl); + if (geometry.PlatformImpl is not null) + DrawGeometry(brush, pen, geometry.PlatformImpl); } /// @@ -132,7 +133,7 @@ namespace Avalonia.Media /// The geometry. public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) { - Contract.Requires(geometry != null); + _ = geometry ?? throw new ArgumentNullException(nameof(geometry)); if (brush != null || PenIsVisible(pen)) { @@ -157,7 +158,7 @@ namespace Avalonia.Media /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// - public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0, double radiusY = 0, + public void DrawRectangle(IBrush? brush, IPen? pen, Rect rect, double radiusX = 0, double radiusY = 0, BoxShadows boxShadows = default) { if (brush == null && !PenIsVisible(pen)) @@ -230,7 +231,7 @@ namespace Avalonia.Media /// The text. public void DrawText(IBrush foreground, Point origin, FormattedText text) { - Contract.Requires(text != null); + _ = text ?? throw new ArgumentNullException(nameof(text)); if (foreground != null) { @@ -245,7 +246,7 @@ namespace Avalonia.Media /// The glyph run. public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun) { - Contract.Requires(glyphRun != null); + _ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); if (foreground != null) { @@ -279,11 +280,14 @@ namespace Avalonia.Media Clip, MatrixContainer, GeometryClip, - OpacityMask + OpacityMask, } public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) { + if (context._states is null) + throw new ObjectDisposedException(nameof(DrawingContext)); + _context = context; _type = type; _matrix = matrix; @@ -293,6 +297,8 @@ namespace Avalonia.Media public void Dispose() { + if (_context._states is null || _context._transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); if (_type == PushedStateType.None) return; if (_context._currentLevel != _level) @@ -343,7 +349,8 @@ namespace Avalonia.Media /// A disposable used to undo the clip geometry. public PushedState PushGeometryClip(Geometry clip) { - Contract.Requires(clip != null); + _ = clip ?? throw new ArgumentNullException(nameof(clip)); + PlatformImpl.PushGeometryClip(clip.PlatformImpl); return new PushedState(this, PushedState.PushedStateType.GeometryClip); } @@ -407,6 +414,8 @@ namespace Avalonia.Media /// A disposable used to undo the transformation. public PushedState PushTransformContainer() { + if (_transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); _transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); _currentContainerTransform = CurrentTransform * _currentContainerTransform; _currentTransform = Matrix.Identity; @@ -418,6 +427,8 @@ namespace Avalonia.Media /// public void Dispose() { + if (_states is null || _transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); while (_states.Count != 0) _states.Peek().Dispose(); StateStackPool.Return(_states); @@ -430,7 +441,7 @@ namespace Avalonia.Media PlatformImpl.Dispose(); } - private static bool PenIsVisible(IPen pen) + private static bool PenIsVisible(IPen? pen) { return pen?.Brush != null && pen.Thickness > 0; } diff --git a/src/Avalonia.Visuals/Media/DrawingImage.cs b/src/Avalonia.Visuals/Media/DrawingImage.cs index 6fa8d397a5..488822b693 100644 --- a/src/Avalonia.Visuals/Media/DrawingImage.cs +++ b/src/Avalonia.Visuals/Media/DrawingImage.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(Drawing)); /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; /// /// Gets or sets the drawing content. diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index bae6dc3d8c..7f9abaf531 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Platform; namespace Avalonia.Media @@ -95,9 +96,10 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); if (Rect != default) return factory.CreateEllipseGeometry(Rect); diff --git a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs index 6b699acd2e..cdea5f5fa3 100644 --- a/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ExperimentalAcrylicMaterial.cs @@ -78,7 +78,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(FallbackColor)); /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; /// /// Gets or Sets the BackgroundSource . @@ -299,14 +299,14 @@ namespace Avalonia.Media var lightness = (max + min) / 2.0; - lightness = 1 - ((1 - lightness) * luminosityOpacity.Value); + lightness = 1 - ((1 - lightness) * (luminosityOpacity ?? 1)); lightness = 0.13 + (lightness * 0.74); var luminosityColor = new Color(255, Trim(lightness), Trim(lightness), Trim(lightness)); var compensationMultiplier = 1 - PlatformTransparencyCompensationLevel; - return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + (luminosityOpacity.Value * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); + return new Color((byte)(255 * Math.Max(Math.Min(PlatformTransparencyCompensationLevel + ((luminosityOpacity ?? 1) * compensationMultiplier), 1.0), 0.0)), luminosityColor.R, luminosityColor.G, luminosityColor.B); } /// diff --git a/src/Avalonia.Visuals/Media/FontFamily.cs b/src/Avalonia.Visuals/Media/FontFamily.cs index f018733235..c4ebc1e0d4 100644 --- a/src/Avalonia.Visuals/Media/FontFamily.cs +++ b/src/Avalonia.Visuals/Media/FontFamily.cs @@ -27,7 +27,7 @@ namespace Avalonia.Media /// Specifies the base uri that is used to resolve font family assets. /// The name of the . /// Base uri must be an absolute uri. - public FontFamily(Uri baseUri, string name) + public FontFamily(Uri? baseUri, string name) { if (string.IsNullOrEmpty(name)) { @@ -77,7 +77,7 @@ namespace Avalonia.Media /// The family key. /// /// Key is only used for custom fonts. - public FontFamilyKey Key { get; } + public FontFamilyKey? Key { get; } /// /// Returns True if this instance is the system's default. @@ -95,7 +95,7 @@ namespace Avalonia.Media private struct FontFamilyIdentifier { - public FontFamilyIdentifier(string name, Uri source) + public FontFamilyIdentifier(string name, Uri? source) { Name = name; Source = source; @@ -103,7 +103,7 @@ namespace Avalonia.Media public string Name { get; } - public Uri Source { get; } + public Uri? Source { get; } } private static FontFamilyIdentifier GetFontFamilyIdentifier(string name) @@ -152,7 +152,7 @@ namespace Avalonia.Media /// /// Specified family is not supported. /// - public static FontFamily Parse(string s, Uri baseUri) + public static FontFamily Parse(string s, Uri? baseUri) { if (string.IsNullOrEmpty(s)) { @@ -192,12 +192,12 @@ namespace Avalonia.Media } } - public static bool operator !=(FontFamily a, FontFamily b) + public static bool operator !=(FontFamily? a, FontFamily? b) { return !(a == b); } - public static bool operator ==(FontFamily a, FontFamily b) + public static bool operator ==(FontFamily? a, FontFamily? b) { if (ReferenceEquals(a, b)) { @@ -207,7 +207,7 @@ namespace Avalonia.Media return !(a is null) && a.Equals(b); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (ReferenceEquals(this, obj)) { diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs index 33e7bf0200..463f020792 100644 --- a/src/Avalonia.Visuals/Media/FontManager.cs +++ b/src/Avalonia.Visuals/Media/FontManager.cs @@ -87,7 +87,7 @@ namespace Avalonia.Media /// /// The . /// - public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) + public GlyphTypeface? GetOrAddGlyphTypeface(Typeface typeface) { while (true) { diff --git a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs index 96312a5466..99daaf2143 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FamilyNameCollection.cs @@ -122,12 +122,12 @@ namespace Avalonia.Media.Fonts } } - public static bool operator !=(FamilyNameCollection a, FamilyNameCollection b) + public static bool operator !=(FamilyNameCollection? a, FamilyNameCollection? b) { return !(a == b); } - public static bool operator ==(FamilyNameCollection a, FamilyNameCollection b) + public static bool operator ==(FamilyNameCollection? a, FamilyNameCollection? b) { if (ReferenceEquals(a, b)) { @@ -144,7 +144,7 @@ namespace Avalonia.Media.Fonts /// /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (!(obj is FamilyNameCollection other)) { diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs index eee24e856d..f607c67fed 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyKey.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media.Fonts /// /// /// - public FontFamilyKey(Uri source, Uri baseUri = null) + public FontFamilyKey(Uri source, Uri? baseUri = null) { Source = source ?? throw new ArgumentNullException(nameof(source)); @@ -27,7 +27,7 @@ namespace Avalonia.Media.Fonts /// /// A base URI to use if is relative /// - public Uri BaseUri { get; } + public Uri? BaseUri { get; } /// /// Returns a hash code for this instance. @@ -55,12 +55,12 @@ namespace Avalonia.Media.Fonts } } - public static bool operator !=(FontFamilyKey a, FontFamilyKey b) + public static bool operator !=(FontFamilyKey? a, FontFamilyKey? b) { return !(a == b); } - public static bool operator ==(FontFamilyKey a, FontFamilyKey b) + public static bool operator ==(FontFamilyKey? a, FontFamilyKey? b) { if (ReferenceEquals(a, b)) { @@ -77,7 +77,7 @@ namespace Avalonia.Media.Fonts /// /// true if the specified is equal to this instance; otherwise, false. /// - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (!(obj is FontFamilyKey other)) { diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 02298417a4..52e46110a3 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -32,7 +32,8 @@ namespace Avalonia.Media.Fonts /// private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) { - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IAssetLoader."); var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); @@ -50,7 +51,8 @@ namespace Avalonia.Media.Fonts /// private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) { - var assetLoader = AvaloniaLocator.Current.GetService(); + var assetLoader = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IAssetLoader."); var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index ddffbe7500..499761d96c 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Avalonia.Platform; @@ -10,11 +11,11 @@ namespace Avalonia.Media { private readonly IPlatformRenderInterface _platform; private Size _constraint = Size.Infinity; - private IFormattedTextImpl _platformImpl; - private IReadOnlyList _spans; + private IFormattedTextImpl? _platformImpl; + private IReadOnlyList? _spans; private Typeface _typeface; private double _fontSize; - private string _text; + private string? _text; private TextAlignment _textAlignment; private TextWrapping _textWrapping; @@ -23,7 +24,8 @@ namespace Avalonia.Media /// public FormattedText() { - _platform = AvaloniaLocator.Current.GetService(); + _platform = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); } /// @@ -98,7 +100,7 @@ namespace Avalonia.Media /// Gets or sets a collection of spans that describe the formatting of subsections of the /// text. /// - public IReadOnlyList Spans + public IReadOnlyList? Spans { get => _spans; set => Set(ref _spans, value); @@ -107,7 +109,7 @@ namespace Avalonia.Media /// /// Gets or sets the text. /// - public string Text + public string? Text { get => _text; set => Set(ref _text, value); @@ -141,7 +143,7 @@ namespace Avalonia.Media if (_platformImpl == null) { _platformImpl = _platform.CreateFormattedText( - _text, + _text ?? string.Empty, _typeface, _fontSize, _textAlignment, diff --git a/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs b/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs index 92fdd651c3..fcb631d1eb 100644 --- a/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs +++ b/src/Avalonia.Visuals/Media/FormattedTextStyleSpan.cs @@ -14,7 +14,7 @@ public FormattedTextStyleSpan( int startIndex, int length, - IBrush foregroundBrush = null) + IBrush? foregroundBrush = null) { StartIndex = startIndex; Length = length; @@ -34,6 +34,6 @@ /// /// Gets the span's foreground brush. /// - public IBrush ForegroundBrush { get; } + public IBrush? ForegroundBrush { get; } } } diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs index ccef6665a9..76c67a5cf4 100644 --- a/src/Avalonia.Visuals/Media/Geometry.cs +++ b/src/Avalonia.Visuals/Media/Geometry.cs @@ -11,11 +11,11 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty TransformProperty = - AvaloniaProperty.Register(nameof(Transform)); + public static readonly StyledProperty TransformProperty = + AvaloniaProperty.Register(nameof(Transform)); private bool _isDirty = true; - private IGeometryImpl _platformImpl; + private IGeometryImpl? _platformImpl; static Geometry() { @@ -25,7 +25,7 @@ namespace Avalonia.Media /// /// Raised when the geometry changes. /// - public event EventHandler Changed; + public event EventHandler? Changed; /// /// Gets the geometry's bounding rectangle. @@ -35,7 +35,7 @@ namespace Avalonia.Media /// /// Gets the platform-specific implementation of the geometry. /// - public IGeometryImpl PlatformImpl + public IGeometryImpl? PlatformImpl { get { @@ -60,7 +60,7 @@ namespace Avalonia.Media /// /// Gets or sets a transform to apply to the geometry. /// - public Transform Transform + public Transform? Transform { get { return GetValue(TransformProperty); } set { SetValue(TransformProperty, value); } @@ -127,7 +127,7 @@ namespace Avalonia.Media /// Creates the platform implementation of the geometry, without the transform applied. /// /// - protected abstract IGeometryImpl CreateDefiningGeometry(); + protected abstract IGeometryImpl? CreateDefiningGeometry(); /// /// Invalidates the platform implementation of the geometry. @@ -141,8 +141,8 @@ namespace Avalonia.Media private void TransformChanged(AvaloniaPropertyChangedEventArgs e) { - var oldValue = (Transform)e.OldValue; - var newValue = (Transform)e.NewValue; + var oldValue = (Transform?)e.OldValue; + var newValue = (Transform?)e.NewValue; if (oldValue != null) { @@ -157,9 +157,9 @@ namespace Avalonia.Media TransformChanged(newValue, EventArgs.Empty); } - private void TransformChanged(object sender, EventArgs e) + private void TransformChanged(object? sender, EventArgs e) { - var transform = ((Transform)sender)?.Value; + var transform = ((Transform?)sender)?.Value; if (_platformImpl is ITransformedGeometryImpl t) { @@ -174,7 +174,7 @@ namespace Avalonia.Media } else if (_platformImpl != null && transform != null && transform != Matrix.Identity) { - _platformImpl = PlatformImpl.WithTransform(transform.Value); + _platformImpl = _platformImpl.WithTransform(transform.Value); } Changed?.Invoke(this, EventArgs.Empty); diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index 234122f6f5..fa9241c2ed 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -14,7 +14,7 @@ namespace Avalonia.Media private static readonly IComparer s_ascendingComparer = Comparer.Default; private static readonly IComparer s_descendingComparer = new ReverseComparer(); - private IGlyphRunImpl _glyphRunImpl; + private IGlyphRunImpl? _glyphRunImpl; private GlyphTypeface _glyphTypeface; private double _fontRenderingEmSize; private int _biDiLevel; @@ -199,7 +199,7 @@ namespace Avalonia.Media Initialize(); } - return _glyphRunImpl; + return _glyphRunImpl!; } } @@ -639,7 +639,8 @@ namespace Avalonia.Media throw new InvalidOperationException(); } - var platformRenderInterface = AvaloniaLocator.Current.GetService(); + var platformRenderInterface = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface"); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); } @@ -651,7 +652,7 @@ namespace Avalonia.Media private class ReverseComparer : IComparer { - public int Compare(T x, T y) + public int Compare(T? x, T? y) { return Comparer.Default.Compare(y, x); } diff --git a/src/Avalonia.Visuals/Media/GradientBrush.cs b/src/Avalonia.Visuals/Media/GradientBrush.cs index 5f795d4644..c84413ecbb 100644 --- a/src/Avalonia.Visuals/Media/GradientBrush.cs +++ b/src/Avalonia.Visuals/Media/GradientBrush.cs @@ -26,7 +26,7 @@ namespace Avalonia.Media public static readonly StyledProperty GradientStopsProperty = AvaloniaProperty.Register(nameof(GradientStops)); - private IDisposable _gradientStopsSubscription; + private IDisposable? _gradientStopsSubscription; static GradientBrush() { @@ -64,13 +64,13 @@ namespace Avalonia.Media { if (e.Sender is GradientBrush brush) { - var oldValue = (GradientStops)e.OldValue; - var newValue = (GradientStops)e.NewValue; + var oldValue = (GradientStops?)e.OldValue; + var newValue = (GradientStops?)e.NewValue; if (oldValue != null) { oldValue.CollectionChanged -= brush.GradientStopsChanged; - brush._gradientStopsSubscription.Dispose(); + brush._gradientStopsSubscription?.Dispose(); } if (newValue != null) @@ -83,12 +83,12 @@ namespace Avalonia.Media } } - private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void GradientStopsChanged(object? sender, NotifyCollectionChangedEventArgs e) { RaiseInvalidated(EventArgs.Empty); } - private void GradientStopChanged(Tuple e) + private void GradientStopChanged(Tuple e) { RaiseInvalidated(EventArgs.Empty); } diff --git a/src/Avalonia.Visuals/Media/IPen.cs b/src/Avalonia.Visuals/Media/IPen.cs index 0cdac312cc..1cad9948b4 100644 --- a/src/Avalonia.Visuals/Media/IPen.cs +++ b/src/Avalonia.Visuals/Media/IPen.cs @@ -8,12 +8,12 @@ /// /// Gets the brush used to draw the stroke. /// - IBrush Brush { get; } + IBrush? Brush { get; } /// /// Gets the style of dashed lines drawn with a object. /// - IDashStyle DashStyle { get; } + IDashStyle? DashStyle { get; } /// /// Gets the type of shape to use on both ends of a line. diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 5a0c57b333..020940d9c6 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -21,8 +21,7 @@ namespace Avalonia.Media.Imaging /// An instance of the class. public static Bitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return new Bitmap(factory.LoadBitmapToWidth(stream, width, interpolationMode)); + return new Bitmap(GetFactory().LoadBitmapToWidth(stream, width, interpolationMode)); } /// @@ -35,8 +34,7 @@ namespace Avalonia.Media.Imaging /// An instance of the class. public static Bitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return new Bitmap(factory.LoadBitmapToHeight(stream, height, interpolationMode)); + return new Bitmap(GetFactory().LoadBitmapToHeight(stream, height, interpolationMode)); } /// @@ -47,8 +45,7 @@ namespace Avalonia.Media.Imaging /// An instance of the class. public Bitmap CreateScaledBitmap(PixelSize destinationSize, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - return new Bitmap(factory.ResizeBitmap(PlatformImpl.Item, destinationSize, interpolationMode)); + return new Bitmap(GetFactory().ResizeBitmap(PlatformImpl.Item, destinationSize, interpolationMode)); } /// @@ -57,8 +54,7 @@ namespace Avalonia.Media.Imaging /// The filename of the bitmap. public Bitmap(string fileName) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - PlatformImpl = RefCountable.Create(factory.LoadBitmap(fileName)); + PlatformImpl = RefCountable.Create(GetFactory().LoadBitmap(fileName)); } /// @@ -67,8 +63,7 @@ namespace Avalonia.Media.Imaging /// The stream to read the bitmap from. public Bitmap(Stream stream) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); - PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream)); + PlatformImpl = RefCountable.Create(GetFactory().LoadBitmap(stream)); } /// @@ -106,9 +101,8 @@ namespace Avalonia.Media.Imaging [Obsolete("Use overload taking an AlphaFormat.")] public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride) { - var ri = AvaloniaLocator.Current.GetService(); - - PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService() + var ri = GetFactory(); + PlatformImpl = RefCountable.Create(ri .LoadBitmap(format, ri.DefaultAlphaFormat, data, size, dpi, stride)); } @@ -123,8 +117,7 @@ namespace Avalonia.Media.Imaging /// The number of bytes per row. public Bitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride) { - PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService() - .LoadBitmap(format, alphaFormat, data, size, dpi, stride)); + PlatformImpl = RefCountable.Create(GetFactory().LoadBitmap(format, alphaFormat, data, size, dpi, stride)); } /// @@ -173,5 +166,11 @@ namespace Avalonia.Media.Imaging destRect, bitmapInterpolationMode); } + + private static IPlatformRenderInterface GetFactory() + { + return AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + } } } diff --git a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs index 70823e114c..be1f6db561 100644 --- a/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/CroppedBitmap.cs @@ -11,8 +11,8 @@ namespace Avalonia.Media.Imaging /// /// Defines the property. /// - public static readonly StyledProperty SourceProperty = - AvaloniaProperty.Register(nameof(Source)); + public static readonly StyledProperty SourceProperty = + AvaloniaProperty.Register(nameof(Source)); /// /// Defines the property. @@ -20,7 +20,7 @@ namespace Avalonia.Media.Imaging public static readonly StyledProperty SourceRectProperty = AvaloniaProperty.Register(nameof(SourceRect)); - public event EventHandler Invalidated; + public event EventHandler? Invalidated; static CroppedBitmap() { @@ -31,7 +31,7 @@ namespace Avalonia.Media.Imaging /// /// Gets or sets the source for the bitmap. /// - public IImage Source + public IImage? Source { get => GetValue(SourceProperty); set => SetValue(SourceProperty, value); @@ -77,19 +77,19 @@ namespace Avalonia.Media.Imaging public Size Size { get { - if (Source == null) + if (Source is not IBitmap bmp) return Size.Empty; if (SourceRect.IsEmpty) return Source.Size; - return SourceRect.Size.ToSizeWithDpi((Source as IBitmap).Dpi); + return SourceRect.Size.ToSizeWithDpi(bmp.Dpi); } } public void Draw(DrawingContext context, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode) { - if (Source == null) + if (Source is not IBitmap bmp) return; - var topLeft = SourceRect.TopLeft.ToPointWithDpi((Source as IBitmap).Dpi); + var topLeft = SourceRect.TopLeft.ToPointWithDpi(bmp.Dpi); Source.Draw(context, sourceRect.Translate(new Vector(topLeft.X, topLeft.Y)), destRect, bitmapInterpolationMode); } } diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index 928b188f8c..fe8890b3ba 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs @@ -54,11 +54,12 @@ namespace Avalonia.Media.Imaging /// The platform-specific implementation. private static IRenderTargetBitmapImpl CreateImpl(PixelSize size, Vector dpi) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService(); + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); return factory.CreateRenderTargetBitmap(size, dpi); } /// - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.Item.CreateDrawingContext(vbr); + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? vbr) => PlatformImpl.Item.CreateDrawingContext(vbr); } } diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index b0e1a954e6..4dfb22440d 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -46,7 +46,7 @@ namespace Avalonia.Media.Imaging public static WriteableBitmap Decode(Stream stream) { - var ri = AvaloniaLocator.Current.GetService(); + var ri = GetFactory(); return new WriteableBitmap(ri.LoadWriteableBitmap(stream)); } @@ -61,7 +61,7 @@ namespace Avalonia.Media.Imaging /// An instance of the class. public new static WriteableBitmap DecodeToWidth(Stream stream, int width, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - var ri = AvaloniaLocator.Current.GetService(); + var ri = GetFactory(); return new WriteableBitmap(ri.LoadWriteableBitmapToWidth(stream, width, interpolationMode)); } @@ -76,19 +76,25 @@ namespace Avalonia.Media.Imaging /// An instance of the class. public new static WriteableBitmap DecodeToHeight(Stream stream, int height, BitmapInterpolationMode interpolationMode = BitmapInterpolationMode.HighQuality) { - var ri = AvaloniaLocator.Current.GetService(); + var ri = GetFactory(); return new WriteableBitmap(ri.LoadWriteableBitmapToHeight(stream, height, interpolationMode)); } private static IBitmapImpl CreatePlatformImpl(PixelSize size, in Vector dpi, PixelFormat? format, AlphaFormat? alphaFormat) { - var ri = AvaloniaLocator.Current.GetService(); + var ri = GetFactory(); PixelFormat finalFormat = format ?? ri.DefaultPixelFormat; AlphaFormat finalAlphaFormat = alphaFormat ?? ri.DefaultAlphaFormat; return ri.CreateWriteableBitmap(size, dpi, finalFormat, finalAlphaFormat); } + + private static IPlatformRenderInterface GetFactory() + { + return AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + } } } diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs index 2dd188e0a9..82485c13b0 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableDashStyle.cs @@ -30,10 +30,10 @@ namespace Avalonia.Media.Immutable public double Offset { get; } /// - public override bool Equals(object obj) => Equals(obj as IDashStyle); + public override bool Equals(object? obj) => Equals(obj as IDashStyle); /// - public bool Equals(IDashStyle other) + public bool Equals(IDashStyle? other) { if (ReferenceEquals(this, other)) { diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs index 3256f4b11a..8a53eaf7b8 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutablePen.cs @@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable public ImmutablePen( uint color, double thickness = 1.0, - ImmutableDashStyle dashStyle = null, + ImmutableDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) : this(new ImmutableSolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) @@ -38,9 +38,9 @@ namespace Avalonia.Media.Immutable /// The line join. /// The miter limit. public ImmutablePen( - IBrush brush, + IBrush? brush, double thickness = 1.0, - ImmutableDashStyle dashStyle = null, + ImmutableDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) @@ -58,7 +58,7 @@ namespace Avalonia.Media.Immutable /// /// Gets the brush used to draw the stroke. /// - public IBrush Brush { get; } + public IBrush? Brush { get; } /// /// Gets the stroke thickness. @@ -68,7 +68,7 @@ namespace Avalonia.Media.Immutable /// /// Specifies the style of dashed lines drawn with a object. /// - public IDashStyle DashStyle { get; } + public IDashStyle? DashStyle { get; } /// /// Specifies the type of graphic shape to use on both ends of a line. @@ -87,10 +87,10 @@ namespace Avalonia.Media.Immutable public double MiterLimit { get; } /// - public override bool Equals(object obj) => Equals(obj as IPen); + public override bool Equals(object? obj) => Equals(obj as IPen); /// - public bool Equals(IPen other) + public bool Equals(IPen? other) { if (ReferenceEquals(this, other)) { diff --git a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs index 8e93ac580e..46e1df4054 100644 --- a/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs +++ b/src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs @@ -46,14 +46,14 @@ namespace Avalonia.Media.Immutable /// public double Opacity { get; } - public bool Equals(ImmutableSolidColorBrush other) + public bool Equals(ImmutableSolidColorBrush? other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Color.Equals(other.Color) && Opacity.Equals(other.Opacity); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ImmutableSolidColorBrush other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs index f46d76cf3f..f8487243de 100644 --- a/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs +++ b/src/Avalonia.Visuals/Media/ImmutableExperimentalAcrylicMaterial.cs @@ -34,7 +34,7 @@ namespace Avalonia.Media } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is ImmutableExperimentalAcrylicMaterial other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/KnownColors.cs b/src/Avalonia.Visuals/Media/KnownColors.cs index fe09f5f538..8f07b5f8ac 100644 --- a/src/Avalonia.Visuals/Media/KnownColors.cs +++ b/src/Avalonia.Visuals/Media/KnownColors.cs @@ -20,7 +20,7 @@ namespace Avalonia.Media foreach (var field in typeof(KnownColor).GetRuntimeFields()) { if (field.FieldType != typeof(KnownColor)) continue; - var knownColor = (KnownColor)field.GetValue(null); + var knownColor = (KnownColor)field.GetValue(null)!; if (knownColor == KnownColor.None) continue; knownColorNames.Add(field.Name, knownColor); @@ -41,7 +41,7 @@ namespace Avalonia.Media } #if !BUILDTASK - public static ISolidColorBrush GetKnownBrush(string s) + public static ISolidColorBrush? GetKnownBrush(string s) { var color = GetKnownColor(s); return color != KnownColor.None ? color.ToBrush() : null; @@ -58,7 +58,7 @@ namespace Avalonia.Media return KnownColor.None; } - public static string GetKnownColorName(uint rgb) + public static string? GetKnownColorName(uint rgb) { return _knownColors.TryGetValue(rgb, out var name) ? name : null; } @@ -230,4 +230,4 @@ namespace Avalonia.Media Yellow = 0xffffff00, YellowGreen = 0xff9acd32 } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs index 94b638114b..b01a223aca 100644 --- a/src/Avalonia.Visuals/Media/LineGeometry.cs +++ b/src/Avalonia.Visuals/Media/LineGeometry.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Platform; namespace Avalonia.Media @@ -67,9 +68,10 @@ namespace Avalonia.Media } /// - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); return factory.CreateLineGeometry(StartPoint, EndPoint); } diff --git a/src/Avalonia.Visuals/Media/MaterialExtensions.cs b/src/Avalonia.Visuals/Media/MaterialExtensions.cs index c0b445c357..0b3d86d0a4 100644 --- a/src/Avalonia.Visuals/Media/MaterialExtensions.cs +++ b/src/Avalonia.Visuals/Media/MaterialExtensions.cs @@ -14,7 +14,7 @@ namespace Avalonia.Media /// public static IExperimentalAcrylicMaterial ToImmutable(this IExperimentalAcrylicMaterial material) { - Contract.Requires(material != null); + _ = material ?? throw new ArgumentNullException(nameof(material)); return (material as IMutableExperimentalAcrylicMaterial)?.ToImmutable() ?? material; } diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index 3d11c19b7d..0b6a157280 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -11,8 +11,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly DirectProperty FiguresProperty = - AvaloniaProperty.RegisterDirect(nameof(Figures), g => g.Figures, (g, f) => g.Figures = f); + public static readonly DirectProperty FiguresProperty = + AvaloniaProperty.RegisterDirect(nameof(Figures), g => g.Figures, (g, f) => g.Figures = f); /// /// Defines the property. @@ -20,9 +20,9 @@ namespace Avalonia.Media public static readonly StyledProperty FillRuleProperty = AvaloniaProperty.Register(nameof(FillRule)); - private PathFigures _figures; - private IDisposable _figuresObserver; - private IDisposable _figuresPropertiesObserver; + private PathFigures? _figures; + private IDisposable? _figuresObserver; + private IDisposable? _figuresPropertiesObserver; static PathGeometry() { @@ -35,7 +35,7 @@ namespace Avalonia.Media /// public PathGeometry() { - Figures = new PathFigures(); + _figures = new PathFigures(); } /// @@ -63,7 +63,7 @@ namespace Avalonia.Media /// The figures. /// [Content] - public PathFigures Figures + public PathFigures? Figures { get { return _figures; } set { SetAndRaise(FiguresProperty, ref _figures, value); } @@ -81,15 +81,21 @@ namespace Avalonia.Media set { SetValue(FillRuleProperty, value); } } - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService(); + var figures = Figures; + + if (figures is null) + return null; + + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); var geometry = factory.CreateStreamGeometry(); using (var ctx = new StreamGeometryContext(geometry.Open())) { ctx.SetFillRule(FillRule); - foreach (var f in Figures) + foreach (var f in figures) { f.ApplyTo(ctx); } @@ -98,7 +104,7 @@ namespace Avalonia.Media return geometry; } - private void OnFiguresChanged(PathFigures figures) + private void OnFiguresChanged(PathFigures? figures) { _figuresObserver?.Dispose(); _figuresPropertiesObserver?.Dispose(); @@ -120,12 +126,15 @@ namespace Avalonia.Media } - private void InvalidateGeometryFromSegments(object _, EventArgs __) + private void InvalidateGeometryFromSegments(object? _, EventArgs __) { InvalidateGeometry(); } public override string ToString() - => $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{(string.Join(" ", Figures))}"; + { + var figuresString = _figures is not null ? string.Join(" ", _figures) : string.Empty; + return $"{(FillRule != FillRule.EvenOdd ? "F1 " : "")}{figuresString}"; + } } } diff --git a/src/Avalonia.Visuals/Media/PathGeometryCollections.cs b/src/Avalonia.Visuals/Media/PathGeometryCollections.cs index 1589d2f5be..1165b192a7 100644 --- a/src/Avalonia.Visuals/Media/PathGeometryCollections.cs +++ b/src/Avalonia.Visuals/Media/PathGeometryCollections.cs @@ -20,11 +20,11 @@ namespace Avalonia.Media parser.Parse(pathData); } - return pathGeometry.Figures; + return pathGeometry.Figures!; } } public sealed class PathSegments : AvaloniaList { } -} \ No newline at end of file +} diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs index 8b9d0833db..30c5206125 100644 --- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs +++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using Avalonia.Platform; @@ -27,7 +28,7 @@ namespace Avalonia.Media { 'Z', Command.Close }, }; - private IGeometryContext _geometryContext; + private IGeometryContext? _geometryContext; private Point _currentPoint; private Point? _beginFigurePoint; private Point? _previousControlPoint; @@ -98,6 +99,8 @@ namespace Avalonia.Media /// The path data. public void Parse(string pathData) { + ThrowIfDisposed(); + var span = pathData.AsSpan(); _currentPoint = new Point(); @@ -171,6 +174,8 @@ namespace Avalonia.Media private void CreateFigure() { + ThrowIfDisposed(); + if (_isOpen) { _geometryContext.EndFigure(false); @@ -185,6 +190,8 @@ namespace Avalonia.Media private void SetFillRule(ref ReadOnlySpan span) { + ThrowIfDisposed(); + if (!ReadArgument(ref span, out var fillRule) || fillRule.Length != 1) { throw new InvalidDataException("Invalid fill rule."); @@ -209,6 +216,8 @@ namespace Avalonia.Media private void CloseFigure() { + ThrowIfDisposed(); + if (_isOpen) { _geometryContext.EndFigure(true); @@ -244,6 +253,8 @@ namespace Avalonia.Media private void AddLine(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + _currentPoint = relative ? ReadRelativePoint(ref span, _currentPoint) : ReadPoint(ref span); @@ -258,6 +269,8 @@ namespace Avalonia.Media private void AddHorizontalLine(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + _currentPoint = relative ? new Point(_currentPoint.X + ReadDouble(ref span), _currentPoint.Y) : _currentPoint.WithX(ReadDouble(ref span)); @@ -272,6 +285,8 @@ namespace Avalonia.Media private void AddVerticalLine(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + _currentPoint = relative ? new Point(_currentPoint.X, _currentPoint.Y + ReadDouble(ref span)) : _currentPoint.WithY(ReadDouble(ref span)); @@ -286,6 +301,8 @@ namespace Avalonia.Media private void AddCubicBezierCurve(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + var point1 = relative ? ReadRelativePoint(ref span, _currentPoint) : ReadPoint(ref span); @@ -316,6 +333,8 @@ namespace Avalonia.Media private void AddQuadraticBezierCurve(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + var start = relative ? ReadRelativePoint(ref span, _currentPoint) : ReadPoint(ref span); @@ -340,6 +359,8 @@ namespace Avalonia.Media private void AddSmoothCubicBezierCurve(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + var point2 = relative ? ReadRelativePoint(ref span, _currentPoint) : ReadPoint(ref span); @@ -369,6 +390,8 @@ namespace Avalonia.Media private void AddSmoothQuadraticBezierCurve(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + var end = relative ? ReadRelativePoint(ref span, _currentPoint) : ReadPoint(ref span); @@ -390,6 +413,8 @@ namespace Avalonia.Media private void AddArc(ref ReadOnlySpan span, bool relative) { + ThrowIfDisposed(); + var size = ReadSize(ref span); span = ReadSeparator(span); @@ -570,5 +595,12 @@ namespace Avalonia.Media span = span.Slice(1); return true; } + + [MemberNotNull(nameof(_geometryContext))] + private void ThrowIfDisposed() + { + if (_isDisposed || _geometryContext is null) + throw new ObjectDisposedException(nameof(PathMarkupParser)); + } } } diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index eb44b3f21e..7c966a35cf 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -12,8 +12,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty BrushProperty = - AvaloniaProperty.Register(nameof(Brush)); + public static readonly StyledProperty BrushProperty = + AvaloniaProperty.Register(nameof(Brush)); /// /// Defines the property. @@ -24,8 +24,8 @@ namespace Avalonia.Media /// /// Defines the property. /// - public static readonly StyledProperty DashStyleProperty = - AvaloniaProperty.Register(nameof(DashStyle)); + public static readonly StyledProperty DashStyleProperty = + AvaloniaProperty.Register(nameof(DashStyle)); /// /// Defines the property. @@ -64,7 +64,7 @@ namespace Avalonia.Media public Pen( uint color, double thickness = 1.0, - IDashStyle dashStyle = null, + IDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit) @@ -81,9 +81,9 @@ namespace Avalonia.Media /// The line join. /// The miter limit. public Pen( - IBrush brush, + IBrush? brush, double thickness = 1.0, - IDashStyle dashStyle = null, + IDashStyle? dashStyle = null, PenLineCap lineCap = PenLineCap.Flat, PenLineJoin lineJoin = PenLineJoin.Miter, double miterLimit = 10.0) @@ -110,7 +110,7 @@ namespace Avalonia.Media /// /// Gets or sets the brush used to draw the stroke. /// - public IBrush Brush + public IBrush? Brush { get => GetValue(BrushProperty); set => SetValue(BrushProperty, value); @@ -128,7 +128,7 @@ namespace Avalonia.Media /// /// Gets or sets the style of dashed lines drawn with a object. /// - public IDashStyle DashStyle + public IDashStyle? DashStyle { get => GetValue(DashStyleProperty); set => SetValue(DashStyleProperty, value); @@ -165,7 +165,7 @@ namespace Avalonia.Media /// /// Raised when the pen changes. /// - public event EventHandler Invalidated; + public event EventHandler? Invalidated; /// /// Creates an immutable clone of the brush. @@ -244,6 +244,6 @@ namespace Avalonia.Media /// The event args. protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); - private void AffectsRenderInvalidated(object sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); + private void AffectsRenderInvalidated(object? sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); } } diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs index 7d62d7bc23..502eb205a6 100644 --- a/src/Avalonia.Visuals/Media/PixelPoint.cs +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -144,7 +144,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - public override bool Equals(object obj) => obj is PixelPoint other && Equals(other); + public override bool Equals(object? obj) => obj is PixelPoint other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs index 32a728475f..0059a4b483 100644 --- a/src/Avalonia.Visuals/Media/PixelRect.cs +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -208,7 +208,7 @@ namespace Avalonia /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - public override bool Equals(object obj) => obj is PixelRect other && Equals(other); + public override bool Equals(object? obj) => obj is PixelRect other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index 530b01ed90..5a34c6f6b5 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -94,7 +94,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) => obj is PixelSize other && Equals(other); + public override bool Equals(object? obj) => obj is PixelSize other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Media/PixelVector.cs b/src/Avalonia.Visuals/Media/PixelVector.cs index f32e9c0178..79e7b94c21 100644 --- a/src/Avalonia.Visuals/Media/PixelVector.cs +++ b/src/Avalonia.Visuals/Media/PixelVector.cs @@ -143,7 +143,7 @@ namespace Avalonia return Math.Abs(_x - other._x) < tolerance && Math.Abs(_y - other._y) < tolerance; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (ReferenceEquals(null, obj)) return false; diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs index 94c809d432..8e040a6043 100644 --- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs +++ b/src/Avalonia.Visuals/Media/PolylineGeometry.cs @@ -24,7 +24,7 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(IsFilled)); private Points _points; - private IDisposable _pointsObserver; + private IDisposable? _pointsObserver; static PolylineGeometry() { @@ -37,7 +37,7 @@ namespace Avalonia.Media /// public PolylineGeometry() { - Points = new Points(); + _points = new Points(); } /// @@ -74,9 +74,10 @@ namespace Avalonia.Media return new PolylineGeometry(Points, IsFilled); } - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); var geometry = factory.CreateStreamGeometry(); using (var context = geometry.Open()) @@ -97,7 +98,7 @@ namespace Avalonia.Media return geometry; } - private void OnPointsChanged(Points newValue) + private void OnPointsChanged(Points? newValue) { _pointsObserver?.Dispose(); _pointsObserver = newValue?.ForEachItem( diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs index 743e103a88..97b4869d71 100644 --- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs +++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Platform; namespace Avalonia.Media @@ -46,9 +47,10 @@ namespace Avalonia.Media /// public override Geometry Clone() => new RectangleGeometry(Rect); - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); return factory.CreateRectangleGeometry(Rect); } diff --git a/src/Avalonia.Visuals/Media/StreamGeometry.cs b/src/Avalonia.Visuals/Media/StreamGeometry.cs index be914cc7b2..b034ff7823 100644 --- a/src/Avalonia.Visuals/Media/StreamGeometry.cs +++ b/src/Avalonia.Visuals/Media/StreamGeometry.cs @@ -1,3 +1,4 @@ +using System; using Avalonia.Platform; namespace Avalonia.Media @@ -7,7 +8,7 @@ namespace Avalonia.Media /// public class StreamGeometry : Geometry { - IStreamGeometryImpl _impl; + IStreamGeometryImpl? _impl; /// /// Initializes a new instance of the class. @@ -46,7 +47,7 @@ namespace Avalonia.Media /// public override Geometry Clone() { - return new StreamGeometry(((IStreamGeometryImpl)PlatformImpl).Clone()); + return new StreamGeometry(((IStreamGeometryImpl)PlatformImpl!).Clone()); } /// @@ -57,15 +58,16 @@ namespace Avalonia.Media /// public StreamGeometryContext Open() { - return new StreamGeometryContext(((IStreamGeometryImpl)PlatformImpl).Open()); + return new StreamGeometryContext(((IStreamGeometryImpl)PlatformImpl!).Open()); } /// - protected override IGeometryImpl CreateDefiningGeometry() + protected override IGeometryImpl? CreateDefiningGeometry() { if (_impl == null) { - var factory = AvaloniaLocator.Current.GetService(); + var factory = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); _impl = factory.CreateStreamGeometry(); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs index 7756ca5c8f..13d7b11645 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/GenericTextRunProperties.cs @@ -10,9 +10,9 @@ namespace Avalonia.Media.TextFormatting private const double DefaultFontRenderingEmSize = 12; public GenericTextRunProperties(Typeface typeface, double fontRenderingEmSize = DefaultFontRenderingEmSize, - TextDecorationCollection textDecorations = null, IBrush foregroundBrush = null, - IBrush backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline, - CultureInfo cultureInfo = null) + TextDecorationCollection? textDecorations = null, IBrush? foregroundBrush = null, + IBrush? backgroundBrush = null, BaselineAlignment baselineAlignment = BaselineAlignment.Baseline, + CultureInfo? cultureInfo = null) { Typeface = typeface; FontRenderingEmSize = fontRenderingEmSize; @@ -30,18 +30,18 @@ namespace Avalonia.Media.TextFormatting public override double FontRenderingEmSize { get; } /// - public override TextDecorationCollection TextDecorations { get; } + public override TextDecorationCollection? TextDecorations { get; } /// - public override IBrush ForegroundBrush { get; } + public override IBrush? ForegroundBrush { get; } /// - public override IBrush BackgroundBrush { get; } + public override IBrush? BackgroundBrush { get; } /// public override BaselineAlignment BaselineAlignment { get; } /// - public override CultureInfo CultureInfo { get; } + public override CultureInfo? CultureInfo { get; } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ITextSource.cs b/src/Avalonia.Visuals/Media/TextFormatting/ITextSource.cs index 0f9994bc65..32012ab8e9 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ITextSource.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ITextSource.cs @@ -10,6 +10,6 @@ /// /// The text source index. /// The text run. - TextRun GetTextRun(int textSourceIndex); + TextRun? GetTextRun(int textSourceIndex); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs index 33d1b30e3c..72bb7431d7 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs @@ -166,7 +166,7 @@ namespace Avalonia.Media.TextFormatting public readonly struct SplitTextCharactersResult { - public SplitTextCharactersResult(ShapedTextCharacters first, ShapedTextCharacters second) + public SplitTextCharactersResult(ShapedTextCharacters first, ShapedTextCharacters? second) { First = first; @@ -187,7 +187,7 @@ namespace Avalonia.Media.TextFormatting /// /// The second text run. /// - public ShapedTextCharacters Second { get; } + public ShapedTextCharacters? Second { get; } } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs index e4c898e2b8..d521077a43 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatter.cs @@ -41,6 +41,6 @@ namespace Avalonia.Media.TextFormatting /// 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, - TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null); + TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null); } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index df63b00c25..3d3e93dd4d 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -8,7 +8,7 @@ namespace Avalonia.Media.TextFormatting { /// public override TextLine FormatLine(ITextSource textSource, int firstTextSourceIndex, double paragraphWidth, - TextParagraphProperties paragraphProperties, TextLineBreak previousLineBreak = null) + TextParagraphProperties paragraphProperties, TextLineBreak? previousLineBreak = null) { var textWrapping = paragraphProperties.TextWrapping; @@ -241,7 +241,7 @@ namespace Avalonia.Media.TextFormatting first.Add(split.First); - second.Add(split.Second); + second.Add(split.Second!); if (secondCount > 0) { @@ -269,7 +269,7 @@ namespace Avalonia.Media.TextFormatting /// The formatted text runs. /// private static List FetchTextRuns(ITextSource textSource, - int firstTextSourceIndex, TextLineBreak previousLineBreak, out TextLineBreak nextLineBreak) + int firstTextSourceIndex, TextLineBreak? previousLineBreak, out TextLineBreak? nextLineBreak) { nextLineBreak = default; @@ -298,11 +298,11 @@ namespace Avalonia.Media.TextFormatting { for (; index < previousLineBreak.RemainingCharacters.Count; index++) { - splitResult.Second.Add(previousLineBreak.RemainingCharacters[index]); + splitResult.Second!.Add(previousLineBreak.RemainingCharacters[index]); } } - nextLineBreak = new TextLineBreak(splitResult.Second); + nextLineBreak = new TextLineBreak(splitResult.Second!); return splitResult.First; } @@ -346,7 +346,7 @@ namespace Avalonia.Media.TextFormatting { var splitResult = SplitTextRuns(textRuns, currentLength + runLineBreak.PositionWrap); - nextLineBreak = new TextLineBreak(splitResult.Second); + nextLineBreak = new TextLineBreak(splitResult.Second!); return splitResult.First; } @@ -553,7 +553,7 @@ namespace Avalonia.Media.TextFormatting internal readonly struct SplitTextRunsResult { - public SplitTextRunsResult(List first, List second) + public SplitTextRunsResult(List first, List? second) { First = first; @@ -574,7 +574,7 @@ namespace Avalonia.Media.TextFormatting /// /// The second text runs. /// - public List Second { get; } + public List? Second { get; } } private struct TextRunEnumerator @@ -586,7 +586,7 @@ namespace Avalonia.Media.TextFormatting { _textSource = textSource; _pos = firstTextSourceIndex; - Current = null; + Current = null!; } // ReSharper disable once MemberHidesStaticFromOuterClass diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs index a4c9392fa0..40e12c8e99 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Avalonia.Utilities; @@ -14,7 +15,7 @@ namespace Avalonia.Media.TextFormatting private readonly ReadOnlySlice _text; private readonly TextParagraphProperties _paragraphProperties; - private readonly IReadOnlyList> _textStyleOverrides; + private readonly IReadOnlyList>? _textStyleOverrides; private readonly TextTrimming _textTrimming; /// @@ -41,12 +42,12 @@ namespace Avalonia.Media.TextFormatting TextAlignment textAlignment = TextAlignment.Left, TextWrapping textWrapping = TextWrapping.NoWrap, TextTrimming textTrimming = TextTrimming.None, - TextDecorationCollection textDecorations = null, + TextDecorationCollection? textDecorations = null, double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, double lineHeight = double.NaN, int maxLines = 0, - IReadOnlyList> textStyleOverrides = null) + IReadOnlyList>? textStyleOverrides = null) { _text = string.IsNullOrEmpty(text) ? new ReadOnlySlice() : @@ -228,7 +229,7 @@ namespace Avalonia.Media.TextFormatting var currentY = 0d; var lineIndex = 0; - TextLine currentLine = null; + TextLine? currentLine = null; CharacterHit characterHit; for (; lineIndex < TextLines.Count; lineIndex++) @@ -289,7 +290,7 @@ namespace Avalonia.Media.TextFormatting /// private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, IBrush foreground, TextAlignment textAlignment, TextWrapping textWrapping, - TextDecorationCollection textDecorations, double lineHeight) + TextDecorationCollection? textDecorations, double lineHeight) { var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); @@ -339,6 +340,7 @@ namespace Avalonia.Media.TextFormatting /// /// Updates the layout and applies specified text style overrides. /// + [MemberNotNull(nameof(TextLines))] private void UpdateLayout() { if (_text.IsEmpty || MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) @@ -360,7 +362,7 @@ namespace Avalonia.Media.TextFormatting var textSource = new FormattedTextSource(_text, _paragraphProperties.DefaultTextRunProperties, _textStyleOverrides); - TextLine previousLine = null; + TextLine? previousLine = null; while (currentPosition < _text.Length) { @@ -558,17 +560,17 @@ namespace Avalonia.Media.TextFormatting { private readonly ReadOnlySlice _text; private readonly TextRunProperties _defaultProperties; - private readonly IReadOnlyList> _textModifier; + private readonly IReadOnlyList>? _textModifier; public FormattedTextSource(ReadOnlySlice text, TextRunProperties defaultProperties, - IReadOnlyList> textModifier) + IReadOnlyList>? textModifier) { _text = text; _defaultProperties = defaultProperties; _textModifier = textModifier; } - public TextRun GetTextRun(int textSourceIndex) + public TextRun? GetTextRun(int textSourceIndex) { if (textSourceIndex > _text.Length) { @@ -597,7 +599,7 @@ namespace Avalonia.Media.TextFormatting /// The created text style run. /// private static ValueSpan CreateTextStyleRun(ReadOnlySlice text, - TextRunProperties defaultProperties, IReadOnlyList> textModifier) + TextRunProperties defaultProperties, IReadOnlyList>? textModifier) { if (textModifier == null || textModifier.Count == 0) { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs index e343279a43..aea4227002 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs @@ -29,7 +29,7 @@ namespace Avalonia.Media.TextFormatting /// /// A value that represents the line break. /// - public abstract TextLineBreak TextLineBreak { get; } + public abstract TextLineBreak? TextLineBreak { get; } /// /// Gets the distance from the top to the baseline of the current TextLine object. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs index 4ee0a9a28f..d2bd58682a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineBreak.cs @@ -17,11 +17,11 @@ namespace Avalonia.Media.TextFormatting /// /// Get the /// - public TextEndOfLine TextEndOfLine { get; } + public TextEndOfLine? TextEndOfLine { get; } /// /// Get the remaining shaped characters that were split up by the during the formatting process. /// - public IReadOnlyList RemainingCharacters { get; } + public IReadOnlyList? RemainingCharacters { get; } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index b68e50c54e..a53b43935c 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -13,7 +13,7 @@ namespace Avalonia.Media.TextFormatting private readonly TextLineMetrics _textLineMetrics; public TextLineImpl(List textRuns, TextRange textRange, double paragraphWidth, - TextParagraphProperties paragraphProperties, TextLineBreak lineBreak = null, bool hasCollapsed = false) + TextParagraphProperties paragraphProperties, TextLineBreak? lineBreak = null, bool hasCollapsed = false) { TextRange = textRange; TextLineBreak = lineBreak; @@ -33,7 +33,7 @@ namespace Avalonia.Media.TextFormatting public override TextRange TextRange { get; } /// - public override TextLineBreak TextLineBreak { get; } + public override TextLineBreak? TextLineBreak { get; } /// public override bool HasCollapsed { get; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs index 4eff3fbd83..bbff09ad79 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextParagraphProperties.cs @@ -44,7 +44,7 @@ /// If not null, text decorations to apply to all runs in the line. This is in addition /// to any text decorations specified by the TextRunProperties for individual text runs. /// - public virtual TextDecorationCollection TextDecorations => null; + public virtual TextDecorationCollection? TextDecorations => null; /// /// Gets the text wrapping. diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs index 42cb5a7c46..4bfbb89006 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRun.cs @@ -24,7 +24,7 @@ namespace Avalonia.Media.TextFormatting /// /// A set of properties shared by every characters in the run /// - public virtual TextRunProperties Properties => null; + public virtual TextRunProperties? Properties => null; private class TextRunDebuggerProxy { @@ -49,7 +49,7 @@ namespace Avalonia.Media.TextFormatting } } - public TextRunProperties Properties => _textRun.Properties; + public TextRunProperties? Properties => _textRun.Properties; } } } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs index 468df33ab1..513778b596 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextRunProperties.cs @@ -25,29 +25,29 @@ namespace Avalonia.Media.TextFormatting /// /// Run TextDecorations. /// - public abstract TextDecorationCollection TextDecorations { get; } + public abstract TextDecorationCollection? TextDecorations { get; } /// /// Brush used to fill text. /// - public abstract IBrush ForegroundBrush { get; } + public abstract IBrush? ForegroundBrush { get; } /// /// Brush used to paint background of run. /// - public abstract IBrush BackgroundBrush { get; } + public abstract IBrush? BackgroundBrush { get; } /// /// Run text culture. /// - public abstract CultureInfo CultureInfo { get; } + public abstract CultureInfo? CultureInfo { get; } /// /// Run vertical box alignment /// public abstract BaselineAlignment BaselineAlignment { get; } - public bool Equals(TextRunProperties other) + public bool Equals(TextRunProperties? other) { if (ReferenceEquals(null, other)) return false; @@ -62,7 +62,7 @@ namespace Avalonia.Media.TextFormatting Equals(CultureInfo, other.CultureInfo); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return ReferenceEquals(this, obj) || obj is TextRunProperties other && Equals(other); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs index a02ace408f..2892e608ab 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextShaper.cs @@ -46,7 +46,7 @@ namespace Avalonia.Media.TextFormatting /// public GlyphRun ShapeText(ReadOnlySlice text, Typeface typeface, double fontRenderingEmSize, - CultureInfo culture) + CultureInfo? culture) { return _platformImpl.ShapeText(text, typeface, fontRenderingEmSize, culture); } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/UnicodeData.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/UnicodeData.cs index 3c00c49707..4189b24af6 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/UnicodeData.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/UnicodeData.cs @@ -24,8 +24,8 @@ static UnicodeData() { - s_unicodeDataTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.UnicodeData.trie")); - s_graphemeBreakTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.GraphemeBreak.trie")); + s_unicodeDataTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.UnicodeData.trie")!); + s_graphemeBreakTrie = new UnicodeTrie(typeof(UnicodeData).Assembly.GetManifestResourceStream("Avalonia.Assets.GraphemeBreak.trie")!); } /// diff --git a/src/Avalonia.Visuals/Media/Transform.cs b/src/Avalonia.Visuals/Media/Transform.cs index 7cf1b35ada..60701ecfaf 100644 --- a/src/Avalonia.Visuals/Media/Transform.cs +++ b/src/Avalonia.Visuals/Media/Transform.cs @@ -19,7 +19,7 @@ namespace Avalonia.Media /// /// Raised when the transform changes. /// - public event EventHandler Changed; + public event EventHandler? Changed; /// /// Gets the transform's . diff --git a/src/Avalonia.Visuals/Media/TransformConverter.cs b/src/Avalonia.Visuals/Media/TransformConverter.cs index e79c0b8b7b..33c6cd10fb 100644 --- a/src/Avalonia.Visuals/Media/TransformConverter.cs +++ b/src/Avalonia.Visuals/Media/TransformConverter.cs @@ -10,14 +10,14 @@ namespace Avalonia.Media /// public class TransformConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value) { - return TransformOperations.Parse((string)value); + return value is string s ? TransformOperations.Parse(s) : null; } } } diff --git a/src/Avalonia.Visuals/Media/TransformGroup.cs b/src/Avalonia.Visuals/Media/TransformGroup.cs index 8e3cfec274..0465efd5a5 100644 --- a/src/Avalonia.Visuals/Media/TransformGroup.cs +++ b/src/Avalonia.Visuals/Media/TransformGroup.cs @@ -24,7 +24,7 @@ namespace Avalonia.Media }; } - private void ChildTransform_Changed(object sender, System.EventArgs e) + private void ChildTransform_Changed(object? sender, System.EventArgs e) { this.RaiseChanged(); } diff --git a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs index 13a24cd523..a8c0fe9b12 100644 --- a/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs +++ b/src/Avalonia.Visuals/Media/Transformation/TransformOperation.cs @@ -92,8 +92,8 @@ namespace Avalonia.Media.Transformation } // ReSharper disable PossibleInvalidOperationException - TransformOperation fromValue = fromIdentity ? Identity : from.Value; - TransformOperation toValue = toIdentity ? Identity : to.Value; + TransformOperation fromValue = fromIdentity ? Identity : from!.Value; + TransformOperation toValue = toIdentity ? Identity : to!.Value; // ReSharper restore PossibleInvalidOperationException var interpolationType = toIdentity ? fromValue.Type : toValue.Type; diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 17824c3c5e..859cbafafc 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -48,7 +48,7 @@ namespace Avalonia.Media /// /// Gets the font family. /// - public FontFamily FontFamily { get; } + public FontFamily? FontFamily { get; } /// /// Gets the font style. @@ -66,7 +66,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public GlyphTypeface? GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { @@ -78,7 +78,7 @@ namespace Avalonia.Media return a.Equals(b); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is Typeface typeface && Equals(typeface); } diff --git a/src/Avalonia.Visuals/Media/UnicodeRange.cs b/src/Avalonia.Visuals/Media/UnicodeRange.cs index 6179b57ece..743b9fcc07 100644 --- a/src/Avalonia.Visuals/Media/UnicodeRange.cs +++ b/src/Avalonia.Visuals/Media/UnicodeRange.cs @@ -12,7 +12,7 @@ namespace Avalonia.Media public static UnicodeRange Default = Parse("0-10FFFD"); private readonly UnicodeRangeSegment _single; - private readonly IReadOnlyList _segments = null; + private readonly IReadOnlyList? _segments = null; public UnicodeRange(int start, int end) { diff --git a/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs b/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs index 57f9c68456..db157304f4 100644 --- a/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs +++ b/src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs @@ -6,7 +6,7 @@ namespace Avalonia.Platform public class ExportRenderingSubsystemAttribute : Attribute { public ExportRenderingSubsystemAttribute(OperatingSystemType requiredOS, int priority, string name, Type initializationType, string initializationMethod, - Type environmentChecker = null) + Type? environmentChecker = null) { Name = name; InitializationType = initializationType; @@ -17,11 +17,11 @@ namespace Avalonia.Platform } public string InitializationMethod { get; private set; } - public Type EnvironmentChecker { get; } + public Type? EnvironmentChecker { get; } public Type InitializationType { get; private set; } public string Name { get; private set; } public int Priority { get; private set; } public OperatingSystemType RequiredOS { get; private set; } - public string RequiresWindowingSubsystem { get; set; } + public string? RequiresWindowingSubsystem { get; set; } } } diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index ac2c5c9f08..7e81924a90 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -55,7 +55,7 @@ namespace Avalonia.Platform /// The fill brush. /// The stroke pen. /// The geometry. - void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry); + void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry); /// /// Draws a rectangle with the specified Brush and Pen. @@ -68,7 +68,7 @@ namespace Avalonia.Platform /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// - void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, + void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default); /// diff --git a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs index e562b45ca8..113c72d373 100644 --- a/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs +++ b/src/Avalonia.Visuals/Platform/IFontManagerImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Platform /// bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontFamily fontFamily, CultureInfo culture, out Typeface typeface); + FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface); /// /// Creates a glyph typeface. diff --git a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs index 79e125c5cb..ed6de1b5c7 100644 --- a/src/Avalonia.Visuals/Platform/IGeometryImpl.cs +++ b/src/Avalonia.Visuals/Platform/IGeometryImpl.cs @@ -23,7 +23,7 @@ namespace Avalonia.Platform /// /// The pen to use. May be null. /// The bounding rectangle. - Rect GetRenderBounds(IPen pen); + Rect GetRenderBounds(IPen? pen); /// /// Indicates whether the geometry's fill contains the specified point. diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 772f1ac9f3..60ae0b5ef8 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -29,7 +29,7 @@ namespace Avalonia.Platform TextAlignment textAlignment, TextWrapping wrapping, Size constraint, - IReadOnlyList spans); + IReadOnlyList? spans); /// /// Creates an ellipse geometry implementation. diff --git a/src/Avalonia.Visuals/Platform/IRenderTarget.cs b/src/Avalonia.Visuals/Platform/IRenderTarget.cs index 68b3b4c17d..7023f2ca51 100644 --- a/src/Avalonia.Visuals/Platform/IRenderTarget.cs +++ b/src/Avalonia.Visuals/Platform/IRenderTarget.cs @@ -18,7 +18,7 @@ namespace Avalonia.Platform /// A render to be used to render visual brushes. May be null if no visual brushes are /// to be drawn. /// - IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer); + IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer? visualBrushRenderer); } public interface IRenderTargetWithCorruptionInfo : IRenderTarget diff --git a/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs b/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs index d915da2603..73d198d7ef 100644 --- a/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs +++ b/src/Avalonia.Visuals/Platform/ITextShaperImpl.cs @@ -17,6 +17,6 @@ namespace Avalonia.Platform /// The font rendering em size. /// The culture. /// A shaped glyph run. - GlyphRun ShapeText(ReadOnlySlice text, Typeface typeface, double fontRenderingEmSize, CultureInfo culture); + GlyphRun ShapeText(ReadOnlySlice text, Typeface typeface, double fontRenderingEmSize, CultureInfo? culture); } } diff --git a/src/Avalonia.Visuals/Platform/PathGeometryContext.cs b/src/Avalonia.Visuals/Platform/PathGeometryContext.cs index 391a43d1cf..694e9f8d80 100644 --- a/src/Avalonia.Visuals/Platform/PathGeometryContext.cs +++ b/src/Avalonia.Visuals/Platform/PathGeometryContext.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Media; using Avalonia.Platform; @@ -6,8 +7,8 @@ namespace Avalonia.Visuals.Platform { public class PathGeometryContext : IGeometryContext { - private PathFigure _currentFigure; - private PathGeometry _pathGeometry; + private PathFigure? _currentFigure; + private PathGeometry? _pathGeometry; public PathGeometryContext(PathGeometry pathGeometry) { @@ -30,13 +31,16 @@ namespace Avalonia.Visuals.Platform Point = point }; - _currentFigure.Segments.Add(arcSegment); + CurrentFigureSegments().Add(arcSegment); } public void BeginFigure(Point startPoint, bool isFilled) { + ThrowIfDisposed(); + _currentFigure = new PathFigure { StartPoint = startPoint, IsClosed = false, IsFilled = isFilled }; + _pathGeometry.Figures ??= new(); _pathGeometry.Figures.Add(_currentFigure); } @@ -44,14 +48,14 @@ namespace Avalonia.Visuals.Platform { var bezierSegment = new BezierSegment { Point1 = point1, Point2 = point2, Point3 = point3 }; - _currentFigure.Segments.Add(bezierSegment); + CurrentFigureSegments().Add(bezierSegment); } public void QuadraticBezierTo(Point control, Point endPoint) { var quadraticBezierSegment = new QuadraticBezierSegment { Point1 = control, Point2 = endPoint }; - _currentFigure.Segments.Add(quadraticBezierSegment); + CurrentFigureSegments().Add(quadraticBezierSegment); } public void LineTo(Point point) @@ -61,7 +65,7 @@ namespace Avalonia.Visuals.Platform Point = point }; - _currentFigure.Segments.Add(lineSegment); + CurrentFigureSegments().Add(lineSegment); } public void EndFigure(bool isClosed) @@ -76,7 +80,26 @@ namespace Avalonia.Visuals.Platform public void SetFillRule(FillRule fillRule) { + ThrowIfDisposed(); _pathGeometry.FillRule = fillRule; } + + [MemberNotNull(nameof(_pathGeometry))] + private void ThrowIfDisposed() + { + if (_pathGeometry is null) + throw new ObjectDisposedException(nameof(PathGeometryContext)); + } + + private PathSegments CurrentFigureSegments() + { + ThrowIfDisposed(); + + if (_currentFigure is null) + throw new InvalidOperationException("No figure in progress."); + if (_currentFigure.Segments is null) + throw new InvalidOperationException("Current figure's segments cannot be null."); + return _currentFigure.Segments; + } } } diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 6febe9c802..67e7d71fbc 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -211,7 +211,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - public override bool Equals(object obj) => obj is Point other && Equals(other); + public override bool Equals(object? obj) => obj is Point other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index d2a72db6ae..6d7d6c2e54 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -341,7 +341,7 @@ namespace Avalonia /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - public override bool Equals(object obj) => obj is Rect other && Equals(other); + public override bool Equals(object? obj) => obj is Rect other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index 497820ec65..4550dbd54b 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -111,7 +111,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - public override bool Equals(object obj) => obj is RelativePoint other && Equals(other); + public override bool Equals(object? obj) => obj is RelativePoint other && Equals(other); /// /// Checks if the equals another point. diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index 1d1f53e299..f6eb257c85 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -113,7 +113,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - public override bool Equals(object obj) => obj is RelativeRect other && Equals(other); + public override bool Equals(object? obj) => obj is RelativeRect other && Equals(other); /// /// Checks if the equals another rectangle. diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index 8d6c3e67c1..43e00f3215 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -14,10 +14,10 @@ namespace Avalonia.Rendering /// public class DefaultRenderTimer : IRenderTimer { - private IRuntimePlatform _runtime; + private IRuntimePlatform? _runtime; private int _subscriberCount; - private Action _tick; - private IDisposable _subscription; + private Action? _tick; + private IDisposable? _subscription; /// /// Initializes a new instance of the class. @@ -77,10 +77,8 @@ namespace Avalonia.Rendering /// protected virtual IDisposable StartCore(Action tick) { - if (_runtime == null) - { - _runtime = AvaloniaLocator.Current.GetService(); - } + _runtime ??= AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IRuntimePlatform."); return _runtime.StartSystemTimer( TimeSpan.FromSeconds(1.0 / FramesPerSecond), @@ -92,13 +90,13 @@ namespace Avalonia.Rendering /// protected void Stop() { - _subscription.Dispose(); + _subscription?.Dispose(); _subscription = null; } private void InternalTick(TimeSpan tickCount) { - _tick(tickCount); + _tick?.Invoke(tickCount); } } } diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index fe63fdec46..7b9c515b97 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Avalonia.Logging; @@ -19,20 +20,20 @@ namespace Avalonia.Rendering /// public class DeferredRenderer : RendererBase, IRenderer, IRenderLoopTask, IVisualBrushRenderer { - private readonly IDispatcher _dispatcher; - private readonly IRenderLoop _renderLoop; + private readonly IDispatcher? _dispatcher; + private readonly IRenderLoop? _renderLoop; private readonly IVisual _root; private readonly ISceneBuilder _sceneBuilder; private bool _running; private bool _disposed; - private volatile IRef _scene; - private DirtyVisuals _dirty; - private HashSet _recalculateChildren; - private IRef _overlay; + private volatile IRef? _scene; + private DirtyVisuals? _dirty; + private HashSet? _recalculateChildren; + private IRef? _overlay; private int _lastSceneId = -1; private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects(); - private IRef _currentDraw; + private IRef? _currentDraw; private readonly IDeferredRendererLock _lock; private readonly object _sceneLock = new object(); private readonly object _startStopLock = new object(); @@ -50,14 +51,12 @@ namespace Avalonia.Rendering public DeferredRenderer( IRenderRoot root, IRenderLoop renderLoop, - ISceneBuilder sceneBuilder = null, - IDispatcher dispatcher = null, - IDeferredRendererLock rendererLock = null) : base(true) + ISceneBuilder? sceneBuilder = null, + IDispatcher? dispatcher = null, + IDeferredRendererLock? rendererLock = null) : base(true) { - Contract.Requires(root != null); - _dispatcher = dispatcher ?? Dispatcher.UIThread; - _root = root; + _root = root ?? throw new ArgumentNullException(nameof(root)); _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _renderLoop = renderLoop; @@ -77,13 +76,10 @@ namespace Avalonia.Rendering public DeferredRenderer( IVisual root, IRenderTarget renderTarget, - ISceneBuilder sceneBuilder = null) : base(true) + ISceneBuilder? sceneBuilder = null) : base(true) { - Contract.Requires(root != null); - Contract.Requires(renderTarget != null); - - _root = root; - RenderTarget = renderTarget; + _root = root ?? throw new ArgumentNullException(nameof(root)); + RenderTarget = renderTarget ?? throw new ArgumentNullException(nameof(renderTarget)); _sceneBuilder = sceneBuilder ?? new SceneBuilder(); Layers = new RenderLayers(); _lock = new ManagedDeferredRendererLock(); @@ -99,7 +95,7 @@ namespace Avalonia.Rendering /// /// Gets or sets a path to which rendered frame should be rendered for debugging. /// - public string DebugFramesPath { get; set; } + public string? DebugFramesPath { get; set; } /// /// Forces the renderer to only draw frames on the render thread. Makes Paint to wait until frame is rendered @@ -107,7 +103,7 @@ namespace Avalonia.Rendering public bool RenderOnlyOnRenderThread { get; set; } /// - public event EventHandler SceneInvalidated; + public event EventHandler? SceneInvalidated; /// /// Gets the render layers. @@ -117,7 +113,7 @@ namespace Avalonia.Rendering /// /// Gets the current render target. /// - internal IRenderTarget RenderTarget { get; private set; } + internal IRenderTarget? RenderTarget { get; private set; } /// public void AddDirty(IVisual visual) @@ -167,7 +163,7 @@ namespace Avalonia.Rendering } /// - public IEnumerable HitTest(Point p, IVisual root, Func filter) + public IEnumerable HitTest(Point p, IVisual root, Func? filter) { EnsureCanHitTest(); @@ -177,7 +173,7 @@ namespace Avalonia.Rendering } /// - public IVisual HitTestFirst(Point p, IVisual root, Func filter) + public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) { EnsureCanHitTest(); @@ -199,7 +195,7 @@ namespace Avalonia.Rendering while (true) { - Scene scene; + Scene? scene; bool? updated; lock (_sceneLock) { @@ -297,7 +293,7 @@ namespace Avalonia.Rendering internal void UnitTestRender() => Render(false); - internal Scene UnitTestScene() => _scene.Item; + internal Scene? UnitTestScene() => _scene?.Item; private void EnsureCanHitTest() { @@ -315,7 +311,7 @@ namespace Avalonia.Rendering if (l == null) return; - IDrawingContextImpl context = null; + IDrawingContextImpl? context = null; try { try @@ -358,10 +354,10 @@ namespace Avalonia.Rendering } } - private (IRef scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl context, + private (IRef? scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl? context, bool recursiveCall = false) { - IRef sceneRef; + IRef? sceneRef; lock (_sceneLock) sceneRef = _scene?.Clone(); if (sceneRef == null) @@ -416,7 +412,7 @@ namespace Avalonia.Rendering } - private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds) + private void Render(IDrawingContextImpl context, VisualNode node, IVisual? layer, Rect clipBounds) { if (layer == null || node.LayerRoot == layer) { @@ -459,7 +455,7 @@ namespace Avalonia.Rendering if (layer.Dirty.IsEmpty && !renderLayer.IsEmpty) continue; var renderTarget = renderLayer.Bitmap; - var node = (VisualNode)scene.FindNode(layer.LayerRoot); + var node = (VisualNode?)scene.FindNode(layer.LayerRoot); if (node != null) { @@ -515,7 +511,7 @@ namespace Avalonia.Rendering Math.Ceiling(rect.Bottom * scale) / scale)); } - private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent) + private void RenderOverlay(Scene scene, ref IDrawingContextImpl? parentContent) { EnsureDrawingContext(ref parentContent); @@ -545,7 +541,7 @@ namespace Avalonia.Rendering } } - private void RenderComposite(Scene scene, ref IDrawingContextImpl context) + private void RenderComposite(Scene scene, ref IDrawingContextImpl? context) { EnsureDrawingContext(ref context); @@ -596,7 +592,7 @@ namespace Avalonia.Rendering } } - private void EnsureDrawingContext(ref IDrawingContextImpl context) + private void EnsureDrawingContext([NotNull] ref IDrawingContextImpl? context) { if (context != null) { @@ -605,7 +601,7 @@ namespace Avalonia.Rendering if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true) { - RenderTarget.Dispose(); + RenderTarget!.Dispose(); RenderTarget = null; } @@ -647,10 +643,10 @@ namespace Avalonia.Rendering } else { - foreach (var visual in _recalculateChildren) + foreach (var visual in _recalculateChildren!) { var node = scene.FindNode(visual); - ((VisualNode)node)?.SortChildren(scene); + ((VisualNode?)node)?.SortChildren(scene); } _recalculateChildren.Clear(); @@ -724,7 +720,7 @@ namespace Avalonia.Rendering foreach (var layer in Layers) { - var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png"); + var fileName = Path.Combine(DebugFramesPath ?? string.Empty, $"frame-{id}-layer-{index++}.png"); layer.Bitmap.Item.Save(fileName); } } diff --git a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs index 00bc236b9c..a49cdd89bc 100644 --- a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs +++ b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs @@ -31,6 +31,11 @@ namespace Avalonia.Rendering /// The dirty visual. public void Add(IVisual visual) { + if (visual.VisualRoot is null) + { + return; + } + if (_deferring > 0) { _deferredChanges.Add(visual); diff --git a/src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs b/src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs index b2ed3afe6a..eab3dca58e 100644 --- a/src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs +++ b/src/Avalonia.Visuals/Rendering/IDeferredRendererLock.cs @@ -4,6 +4,6 @@ namespace Avalonia.Rendering { public interface IDeferredRendererLock { - IDisposable TryLock(); + IDisposable? TryLock(); } } diff --git a/src/Avalonia.Visuals/Rendering/IRenderer.cs b/src/Avalonia.Visuals/Rendering/IRenderer.cs index 0c7440d159..e998f78d5c 100644 --- a/src/Avalonia.Visuals/Rendering/IRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/IRenderer.cs @@ -27,7 +27,7 @@ namespace Avalonia.Rendering /// Indicates that the underlying low-level scene information has been updated. Used to /// signal that an update to the current pointer-over state may be required. /// - event EventHandler SceneInvalidated; + event EventHandler? SceneInvalidated; /// /// Mark a visual as dirty and needing re-rendering. @@ -57,7 +57,7 @@ namespace Avalonia.Rendering /// children will be excluded from the results. /// /// The visual at the specified point, topmost first. - IVisual HitTestFirst(Point p, IVisual root, Func filter); + IVisual? HitTestFirst(Point p, IVisual root, Func filter); /// /// Informs the renderer that the z-ordering of a visual's children has changed. diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index 88fbd290e6..f6642102f7 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -19,9 +19,9 @@ namespace Avalonia.Rendering public class ImmediateRenderer : RendererBase, IRenderer, IVisualBrushRenderer { private readonly IVisual _root; - private readonly IRenderRoot _renderRoot; + private readonly IRenderRoot? _renderRoot; private bool _updateTransformedBounds = true; - private IRenderTarget _renderTarget; + private IRenderTarget? _renderTarget; /// /// Initializes a new instance of the class. @@ -29,9 +29,7 @@ namespace Avalonia.Rendering /// The control to render. public ImmediateRenderer(IVisual root) { - Contract.Requires(root != null); - - _root = root; + _root = root ?? throw new ArgumentNullException(nameof(root)); _renderRoot = root as IRenderRoot; } @@ -49,7 +47,7 @@ namespace Avalonia.Rendering public bool DrawDirtyRects { get; set; } /// - public event EventHandler SceneInvalidated; + public event EventHandler? SceneInvalidated; /// public void Paint(Rect rect) @@ -169,7 +167,7 @@ namespace Avalonia.Rendering return HitTest(root, p, filter); } - public IVisual HitTestFirst(Point p, IVisual root, Func filter) + public IVisual? HitTestFirst(Point p, IVisual root, Func filter) { return HitTest(root, p, filter).FirstOrDefault(); } @@ -233,9 +231,9 @@ namespace Avalonia.Rendering private static IEnumerable HitTest( IVisual visual, Point p, - Func filter) + Func? filter) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); if (filter?.Invoke(visual) != false) { diff --git a/src/Avalonia.Visuals/Rendering/RenderLayers.cs b/src/Avalonia.Visuals/Rendering/RenderLayers.cs index e82934fbad..7583886835 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLayers.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLayers.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Avalonia.VisualTree; @@ -19,9 +20,8 @@ namespace Avalonia.Rendering for (var i = scene.Layers.Count - 1; i >= 0; --i) { var src = scene.Layers[i]; - RenderLayer layer; - if (!_index.TryGetValue(src.LayerRoot, out layer)) + if (!_index.TryGetValue(src.LayerRoot, out var layer)) { layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot); _inner.Add(layer); @@ -59,7 +59,7 @@ namespace Avalonia.Rendering _inner.Clear(); } - public bool TryGetValue(IVisual layerRoot, out RenderLayer value) + public bool TryGetValue(IVisual layerRoot, [NotNullWhen(true)] out RenderLayer? value) { return _index.TryGetValue(layerRoot, out value); } diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index b2a6b0da19..a5d7e15f93 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering private readonly IDispatcher _dispatcher; private List _items = new List(); private List _itemsCopy = new List(); - private IRenderTimer _timer; + private IRenderTimer? _timer; private int _inTick; private int _inUpdate; @@ -49,19 +49,15 @@ namespace Avalonia.Rendering { get { - if (_timer == null) - { - _timer = AvaloniaLocator.Current.GetService(); - } - - return _timer; + return _timer ??= AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Cannot locate IRenderTimer."); } } /// public void Add(IRenderLoopTask i) { - Contract.Requires(i != null); + _ = i ?? throw new ArgumentNullException(nameof(i)); Dispatcher.UIThread.VerifyAccess(); lock (_items) @@ -78,7 +74,7 @@ namespace Avalonia.Rendering /// public void Remove(IRenderLoopTask i) { - Contract.Requires(i != null); + _ = i ?? throw new ArgumentNullException(nameof(i)); Dispatcher.UIThread.VerifyAccess(); lock (_items) { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs index c559f05d70..cd3dac699a 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs @@ -17,6 +17,6 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets a collection of child scenes that are needed to draw visual brushes. /// - public abstract IDictionary ChildScenes { get; } + public abstract IDictionary? ChildScenes { get; } } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index da1a00504a..965754c0a6 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -14,7 +14,7 @@ namespace Avalonia.Rendering.SceneGraph internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport { private readonly ISceneBuilder _sceneBuilder; - private VisualNode _node; + private VisualNode? _node; private int _childIndex; private int _drawOperationindex; @@ -48,22 +48,19 @@ namespace Avalonia.Rendering.SceneGraph /// public UpdateState BeginUpdate(VisualNode node) { - Contract.Requires(node != null); + _ = _node ?? throw new ArgumentNullException(nameof(node)); - if (_node != null) + if (_childIndex < _node.Children.Count) { - if (_childIndex < _node.Children.Count) - { - _node.ReplaceChild(_childIndex, node); - } - else - { - _node.AddChild(node); - } - - ++_childIndex; + _node.ReplaceChild(_childIndex, node); + } + else + { + _node.AddChild(node); } + ++_childIndex; + var state = new UpdateState(this, _node, _childIndex, _drawOperationindex); _node = node; _childIndex = _drawOperationindex = 0; @@ -93,11 +90,11 @@ namespace Avalonia.Rendering.SceneGraph /// public void TrimChildren() { - _node.TrimChildren(_childIndex); + _node!.TrimChildren(_childIndex); } /// - public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry) + public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) { var next = NextDrawAs(); @@ -149,7 +146,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, + public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default) { var next = NextDrawAs(); @@ -259,7 +256,7 @@ namespace Avalonia.Rendering.SceneGraph if (next == null || !next.Item.Equals(null)) { - Add((new GeometryClipNode())); + Add(new GeometryClipNode()); } else { @@ -343,8 +340,11 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void PushGeometryClip(IGeometryImpl clip) + public void PushGeometryClip(IGeometryImpl? clip) { + if (clip is null) + return; + var next = NextDrawAs(); if (next == null || !next.Item.Equals(Transform, clip)) @@ -418,9 +418,9 @@ namespace Avalonia.Rendering.SceneGraph public void Dispose() { - Owner._node.TrimDrawOperations(Owner._drawOperationindex); + Owner._node!.TrimDrawOperations(Owner._drawOperationindex); - var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot).Dirty; + var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot!).Dirty; var drawOperations = Owner._node.DrawOperations; var drawOperationsCount = drawOperations.Count; @@ -451,7 +451,7 @@ namespace Avalonia.Rendering.SceneGraph private void Add(IRef node) { - if (_drawOperationindex < _node.DrawOperations.Count) + if (_drawOperationindex < _node!.DrawOperations.Count) { _node.ReplaceDrawOperation(_drawOperationindex, node); } @@ -463,12 +463,12 @@ namespace Avalonia.Rendering.SceneGraph ++_drawOperationindex; } - private IRef NextDrawAs() where T : class, IDrawOperation + private IRef? NextDrawAs() where T : class, IDrawOperation { - return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null; + return _drawOperationindex < _node!.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null; } - private IDictionary CreateChildScene(IBrush brush) + private IDictionary? CreateChildScene(IBrush? brush) { var visualBrush = brush as VisualBrush; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs index 8bd079d070..59ebcf5109 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ExperimentalAcrylicNode.cs @@ -23,7 +23,7 @@ namespace Avalonia.Rendering.SceneGraph : base(rect.Rect, transform) { Transform = transform; - Material = material?.ToImmutable(); + Material = material.ToImmutable(); Rect = rect; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs index 16092d4cbb..667b66420b 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryClipNode.cs @@ -33,7 +33,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the clip to be pushed or null if the operation represents a pop. /// - public IGeometryImpl Clip { get; } + public IGeometryImpl? Clip { get; } /// /// Gets the transform with which the node will be drawn. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index 508ca0ad18..7de1035441 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -20,10 +20,10 @@ namespace Avalonia.Rendering.SceneGraph /// The geometry. /// Child scenes for drawing visual brushes. public GeometryNode(Matrix transform, - IBrush brush, - IPen pen, + IBrush? brush, + IPen? pen, IGeometryImpl geometry, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(geometry.GetRenderBounds(pen), transform) { Transform = transform; @@ -41,12 +41,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the fill brush. /// - public IBrush Brush { get; } + public IBrush? Brush { get; } /// /// Gets the stroke pen. /// - public ImmutablePen Pen { get; } + public ImmutablePen? Pen { get; } /// /// Gets the geometry to draw. @@ -54,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph public IGeometryImpl Geometry { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// /// Determines if this draw operation equals another. @@ -68,7 +68,7 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IBrush brush, IPen pen, IGeometryImpl geometry) + public bool Equals(Matrix transform, IBrush? brush, IPen? pen, IGeometryImpl geometry) { return transform == Transform && Equals(brush, Brush) && diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs index a6dba1bd32..d6da087120 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs @@ -23,11 +23,11 @@ namespace Avalonia.Rendering.SceneGraph Matrix transform, IBrush foreground, GlyphRun glyphRun, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(new Rect(glyphRun.Size), transform) { Transform = transform; - Foreground = foreground?.ToImmutable(); + Foreground = foreground.ToImmutable(); GlyphRun = glyphRun; ChildScenes = childScenes; } @@ -48,7 +48,7 @@ namespace Avalonia.Rendering.SceneGraph public GlyphRun GlyphRun { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// public override void Render(IDrawingContextImpl context) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs index 6d12b5bca4..e042236346 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs @@ -19,7 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the parent scene graph node. /// - IVisualNode Parent { get; } + IVisualNode? Parent { get; } /// /// Gets the transform for the node from global to control coordinates. @@ -54,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the node's clip geometry, if any. /// - IGeometryImpl GeometryClip { get; set; } + IGeometryImpl? GeometryClip { get; set; } /// /// Gets a value indicating whether one of the node's ancestors has a geometry clip. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index 3dc6d5f50e..a9e1ce8ed7 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -25,11 +25,11 @@ namespace Avalonia.Rendering.SceneGraph IPen pen, Point p1, Point p2, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(LineBoundsHelper.CalculateBounds(p1, p2, pen), transform) { Transform = transform; - Pen = pen?.ToImmutable(); + Pen = pen.ToImmutable(); P1 = p1; P2 = p2; ChildScenes = childScenes; @@ -56,7 +56,7 @@ namespace Avalonia.Rendering.SceneGraph public Point P2 { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// /// Determines if this draw operation equals another. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs index b8e7b150ac..4b6e7d2254 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs @@ -17,10 +17,10 @@ namespace Avalonia.Rendering.SceneGraph /// The opacity mask to push. /// The bounds of the mask. /// Child scenes for drawing visual brushes. - public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary childScenes = null) + public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary? childScenes = null) : base(Rect.Empty, Matrix.Identity) { - Mask = mask?.ToImmutable(); + Mask = mask.ToImmutable(); MaskBounds = bounds; ChildScenes = childScenes; } @@ -37,7 +37,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the mask to be pushed or null if the operation represents a pop. /// - public IBrush Mask { get; } + public IBrush? Mask { get; } /// /// Gets the bounds of the opacity mask or null if the operation represents a pop. @@ -45,7 +45,7 @@ namespace Avalonia.Rendering.SceneGraph public Rect? MaskBounds { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// public override bool HitTest(Point p) => false; @@ -60,14 +60,14 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(IBrush mask, Rect? bounds) => Mask == mask && MaskBounds == bounds; + public bool Equals(IBrush? mask, Rect? bounds) => Mask == mask && MaskBounds == bounds; /// public override void Render(IDrawingContextImpl context) { if (Mask != null) { - context.PushOpacityMask(Mask, MaskBounds.Value); + context.PushOpacityMask(Mask, MaskBounds!.Value); } else { diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 285fbce605..3279c3a549 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -22,11 +22,11 @@ namespace Avalonia.Rendering.SceneGraph /// Child scenes for drawing visual brushes. public RectangleNode( Matrix transform, - IBrush brush, - IPen pen, + IBrush? brush, + IPen? pen, RoundedRect rect, BoxShadows boxShadows, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(boxShadows.TransformBounds(rect.Rect).Inflate((pen?.Thickness ?? 0) / 2), transform) { Transform = transform; @@ -45,12 +45,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the fill brush. /// - public IBrush Brush { get; } + public IBrush? Brush { get; } /// /// Gets the stroke pen. /// - public ImmutablePen Pen { get; } + public ImmutablePen? Pen { get; } /// /// Gets the rectangle to draw. @@ -63,7 +63,7 @@ namespace Avalonia.Rendering.SceneGraph public BoxShadows BoxShadows { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// /// Determines if this draw operation equals another. @@ -78,7 +78,7 @@ namespace Avalonia.Rendering.SceneGraph /// The properties of the other draw operation are passed in as arguments to prevent /// allocation of a not-yet-constructed draw operation object. /// - public bool Equals(Matrix transform, IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows) + public bool Equals(Matrix transform, IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows) { return transform == Transform && Equals(brush, Brush) && diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs index d8e5baac97..20c23d7bee 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs @@ -32,7 +32,7 @@ namespace Avalonia.Rendering.SceneGraph private Scene(VisualNode root, Dictionary index, SceneLayers layers, int generation) { - Contract.Requires(root != null); + _ = root ?? throw new ArgumentNullException(nameof(root)); var renderRoot = root.Visual as IRenderRoot; @@ -76,7 +76,7 @@ namespace Avalonia.Rendering.SceneGraph /// The node. public void Add(IVisualNode node) { - Contract.Requires(node != null); + _ = node ?? throw new ArgumentNullException(nameof(node)); _index.Add(node.Visual, node); } @@ -115,10 +115,9 @@ namespace Avalonia.Rendering.SceneGraph /// /// The node representing the visual or null if it could not be found. /// - public IVisualNode FindNode(IVisual visual) + public IVisualNode? FindNode(IVisual visual) { - IVisualNode node; - _index.TryGetValue(visual, out node); + _index.TryGetValue(visual, out var node); return node; } @@ -129,7 +128,7 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visuals at the specified point. - public IEnumerable HitTest(Point p, IVisual root, Func filter) + public IEnumerable HitTest(Point p, IVisual root, Func? filter) { var node = FindNode(root); return (node != null) ? new HitTestEnumerable(node, filter, p, Root) : Enumerable.Empty(); @@ -142,7 +141,7 @@ namespace Avalonia.Rendering.SceneGraph /// The root of the subtree to search. /// A filter. May be null. /// The visual at the specified point. - public IVisual HitTestFirst(Point p, IVisual root, Func filter) + public IVisual? HitTestFirst(Point p, IVisual root, Func? filter) { var node = FindNode(root); return (node != null) ? HitTestFirst(node, p, filter) : null; @@ -154,14 +153,14 @@ namespace Avalonia.Rendering.SceneGraph /// The node. public void Remove(IVisualNode node) { - Contract.Requires(node != null); + _ = node ?? throw new ArgumentNullException(nameof(node)); _index.Remove(node.Visual); node.Dispose(); } - private VisualNode Clone(VisualNode source, IVisualNode parent, Dictionary index) + private VisualNode Clone(VisualNode source, IVisualNode? parent, Dictionary index) { var result = source.Clone(parent); @@ -185,7 +184,7 @@ namespace Avalonia.Rendering.SceneGraph return result; } - private IVisual HitTestFirst(IVisualNode root, Point p, Func filter) + private IVisual HitTestFirst(IVisualNode root, Point p, Func? filter) { using var enumerator = new HitTestEnumerator(root, filter, p, Root); @@ -197,11 +196,11 @@ namespace Avalonia.Rendering.SceneGraph private class HitTestEnumerable : IEnumerable { private readonly IVisualNode _root; - private readonly Func _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; private readonly Point _point; - public HitTestEnumerable(IVisualNode root, Func filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerable(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _root = root; _filter = filter; @@ -223,12 +222,12 @@ namespace Avalonia.Rendering.SceneGraph private struct HitTestEnumerator : IEnumerator { private readonly PooledStack _nodeStack; - private readonly Func _filter; + private readonly Func? _filter; private readonly IVisualNode _sceneRoot; - private IVisual _current; + private IVisual? _current; private readonly Point _point; - public HitTestEnumerator(IVisualNode root, Func filter, Point point, IVisualNode sceneRoot) + public HitTestEnumerator(IVisualNode root, Func? filter, Point point, IVisualNode sceneRoot) { _nodeStack = new PooledStack(); _nodeStack.Push(new Entry(root, false, null, true)); @@ -283,7 +282,7 @@ namespace Avalonia.Rendering.SceneGraph throw new NotSupportedException(); } - public IVisual Current => _current; + public IVisual Current => _current!; object IEnumerator.Current => Current; @@ -307,7 +306,7 @@ namespace Avalonia.Rendering.SceneGraph if (node.GeometryClip != null) { var controlPoint = _sceneRoot.Visual.TranslatePoint(_point, node.Visual); - clipped = !node.GeometryClip.FillContains(controlPoint.Value); + clipped = !node.GeometryClip.FillContains(controlPoint!.Value); } if (!clipped && node.Visual is ICustomHitTest custom) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index b9131c26f4..cb916293ac 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -15,7 +15,7 @@ namespace Avalonia.Rendering.SceneGraph /// public void UpdateAll(Scene scene) { - Contract.Requires(scene != null); + _ = scene ?? throw new ArgumentNullException(nameof(scene)); Dispatcher.UIThread.VerifyAccess(); UpdateSize(scene); @@ -32,8 +32,8 @@ namespace Avalonia.Rendering.SceneGraph /// public bool Update(Scene scene, IVisual visual) { - Contract.Requires(scene != null); - Contract.Requires(visual != null); + _ = scene ?? throw new ArgumentNullException(nameof(scene)); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); Dispatcher.UIThread.VerifyAccess(); @@ -42,7 +42,7 @@ namespace Avalonia.Rendering.SceneGraph throw new AvaloniaInternalException("Cannot update the scene for an invisible root visual."); } - var node = (VisualNode)scene.FindNode(visual); + var node = (VisualNode?)scene.FindNode(visual); if (visual == scene.Root.Visual) { @@ -58,7 +58,7 @@ namespace Avalonia.Rendering.SceneGraph // The control has changed parents. Remove the node and recurse into the new parent node. ((VisualNode)node.Parent).RemoveChild(node); Deindex(scene, node); - node = (VisualNode)scene.FindNode(visual.VisualParent); + node = (VisualNode?)scene.FindNode(visual.VisualParent); } if (visual.IsVisible) @@ -101,7 +101,7 @@ namespace Avalonia.Rendering.SceneGraph { // The control has been hidden so remove it from its parent and deindex the // node and its descendents. - ((VisualNode)node.Parent)?.RemoveChild(node); + ((VisualNode?)node.Parent)?.RemoveChild(node); Deindex(scene, node); return true; } @@ -112,7 +112,7 @@ namespace Avalonia.Rendering.SceneGraph // The control has been removed so remove it from its parent and deindex the // node and its descendents. var trim = FindFirstDeadAncestor(scene, node); - ((VisualNode)trim.Parent).RemoveChild(trim); + ((VisualNode)trim.Parent!).RemoveChild(trim); Deindex(scene, trim); return true; } @@ -120,24 +120,24 @@ namespace Avalonia.Rendering.SceneGraph return false; } - private static VisualNode FindExistingAncestor(Scene scene, IVisual visual) + private static VisualNode? FindExistingAncestor(Scene scene, IVisual visual) { var node = scene.FindNode(visual); while (node == null && visual.IsVisible) { - visual = visual.VisualParent; + visual = visual.VisualParent!; node = scene.FindNode(visual); } - return visual.IsVisible ? (VisualNode)node : null; + return visual.IsVisible ? (VisualNode?)node : null; } private static VisualNode FindFirstDeadAncestor(Scene scene, IVisualNode node) { var parent = node.Parent; - while (parent.Visual.VisualRoot == null) + while (parent!.Visual.VisualRoot == null) { node = parent; parent = node.Parent; @@ -148,7 +148,7 @@ namespace Avalonia.Rendering.SceneGraph private static object GetOrCreateChildNode(Scene scene, IVisual child, VisualNode parent) { - var result = (VisualNode)scene.FindNode(child); + var result = (VisualNode?)scene.FindNode(child); if (result != null && result.Parent != parent) { @@ -173,7 +173,7 @@ namespace Avalonia.Rendering.SceneGraph var bounds = new Rect(visual.Bounds.Size); var contextImpl = (DeferredDrawingContextImpl)context.PlatformImpl; - contextImpl.Layers.Find(node.LayerRoot)?.Dirty.Add(node.Bounds); + contextImpl.Layers.Find(node.LayerRoot!)?.Dirty.Add(node.Bounds); if (visual.IsVisible) { @@ -353,7 +353,7 @@ namespace Avalonia.Rendering.SceneGraph node.SubTreeUpdated = true; - scene.Layers[node.LayerRoot].Dirty.Add(node.Bounds); + scene.Layers[node.LayerRoot!].Dirty.Add(node.Bounds); node.Visual.TransformedBounds = null; @@ -365,10 +365,10 @@ namespace Avalonia.Rendering.SceneGraph private static void ClearLayer(Scene scene, VisualNode node) { - var parent = (VisualNode)node.Parent; + var parent = (VisualNode)node.Parent!; var oldLayerRoot = node.LayerRoot; - var newLayerRoot = parent.LayerRoot; - var existingDirtyRects = scene.Layers[node.LayerRoot].Dirty; + var newLayerRoot = parent.LayerRoot!; + var existingDirtyRects = scene.Layers[node.LayerRoot!].Dirty; var newDirtyRects = scene.Layers[newLayerRoot].Dirty; existingDirtyRects.Coalesce(); @@ -378,16 +378,16 @@ namespace Avalonia.Rendering.SceneGraph newDirtyRects.Add(r); } - var oldLayer = scene.Layers[oldLayerRoot]; + var oldLayer = scene.Layers[oldLayerRoot!]; PropagateLayer(node, scene.Layers[newLayerRoot], oldLayer); scene.Layers.Remove(oldLayer); } private static void MakeLayer(Scene scene, VisualNode node) { - var oldLayerRoot = node.LayerRoot; + var oldLayerRoot = node.LayerRoot!; var layer = scene.Layers.Add(node.Visual); - var oldLayer = scene.Layers[oldLayerRoot]; + var oldLayer = scene.Layers[oldLayerRoot!]; UpdateLayer(node, layer); PropagateLayer(node, layer, scene.Layers[oldLayerRoot]); @@ -433,22 +433,23 @@ namespace Avalonia.Rendering.SceneGraph // HACK: Disabled layers because they're broken in current renderer. See #2244. private static bool ShouldStartLayer(IVisual visual) => false; - private static IGeometryImpl CreateLayerGeometryClip(VisualNode node) + private static IGeometryImpl? CreateLayerGeometryClip(VisualNode node) { - IGeometryImpl result = null; + IGeometryImpl? result = null; + VisualNode? n = node; for (;;) { - node = (VisualNode)node.Parent; + n = (VisualNode?)n!.Parent; - if (node == null || (node.GeometryClip == null && !node.HasAncestorGeometryClip)) + if (n == null || (n.GeometryClip == null && !n.HasAncestorGeometryClip)) { break; } - if (node?.GeometryClip != null) + if (n?.GeometryClip != null) { - var transformed = node.GeometryClip.WithTransform(node.Transform); + var transformed = n.GeometryClip.WithTransform(n.Transform); result = result == null ? transformed : result.Intersect(transformed); } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs index 02fae562ef..e9474f6e98 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayer.cs @@ -54,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets or sets the opacity mask for the layer. /// - public IBrush OpacityMask { get; set; } + public IBrush? OpacityMask { get; set; } /// /// Gets or sets the target rectangle for the layer opacity mask. @@ -64,7 +64,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the layer's geometry clip. /// - public IGeometryImpl GeometryClip { get; set; } + public IGeometryImpl? GeometryClip { get; set; } /// /// Gets the dirty rectangles for the layer. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs index 25f7383a1a..16d704e5f6 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneLayers.cs @@ -80,7 +80,7 @@ namespace Avalonia.Rendering.SceneGraph /// The created layer. public SceneLayer Add(IVisual layerRoot) { - Contract.Requires(layerRoot != null); + _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); var distance = layerRoot.CalculateDistanceFromAncestor(_root); var layer = new SceneLayer(layerRoot, distance); @@ -117,7 +117,7 @@ namespace Avalonia.Rendering.SceneGraph /// public bool Exists(IVisual layerRoot) { - Contract.Requires(layerRoot != null); + _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); return _index.ContainsKey(layerRoot); } @@ -127,10 +127,9 @@ namespace Avalonia.Rendering.SceneGraph /// /// The root visual. /// The layer if found, otherwise null. - public SceneLayer Find(IVisual layerRoot) + public SceneLayer? Find(IVisual layerRoot) { - SceneLayer result; - _index.TryGetValue(layerRoot, out result); + _index.TryGetValue(layerRoot, out var result); return result; } @@ -141,11 +140,9 @@ namespace Avalonia.Rendering.SceneGraph /// The layer. public SceneLayer GetOrAdd(IVisual layerRoot) { - Contract.Requires(layerRoot != null); + _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - SceneLayer result; - - if (!_index.TryGetValue(layerRoot, out result)) + if (!_index.TryGetValue(layerRoot, out var result)) { result = Add(layerRoot); } @@ -160,11 +157,9 @@ namespace Avalonia.Rendering.SceneGraph /// True if a matching layer was removed, otherwise false. public bool Remove(IVisual layerRoot) { - Contract.Requires(layerRoot != null); - - SceneLayer layer; + _ = layerRoot ?? throw new ArgumentNullException(nameof(layerRoot)); - if (_index.TryGetValue(layerRoot, out layer)) + if (_index.TryGetValue(layerRoot, out var layer)) { Remove(layer); } @@ -179,7 +174,7 @@ namespace Avalonia.Rendering.SceneGraph /// True if the layer was part of the scene, otherwise false. public bool Remove(SceneLayer layer) { - Contract.Requires(layer != null); + _ = layer ?? throw new ArgumentNullException(nameof(layer)); _index.Remove(layer.LayerRoot); return _inner.Remove(layer); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index 4b6c331023..4a1587fb90 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -23,11 +23,11 @@ namespace Avalonia.Rendering.SceneGraph IBrush foreground, Point origin, IFormattedTextImpl text, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(text.Bounds.Translate(origin), transform) { Transform = transform; - Foreground = foreground?.ToImmutable(); + Foreground = foreground.ToImmutable(); Origin = origin; Text = text; ChildScenes = childScenes; @@ -54,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph public IFormattedTextImpl Text { get; } /// - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } /// public override void Render(IDrawingContextImpl context) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs index db6b606b41..8a2f1f5073 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reactive.Disposables; -using Avalonia.Collections; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; @@ -19,9 +19,9 @@ namespace Avalonia.Rendering.SceneGraph private Rect? _bounds; private double _opacity; - private List _children; - private List> _drawOperations; - private IRef _drawOperationsRefCounter; + private List? _children; + private List>? _drawOperations; + private IRef? _drawOperationsRefCounter; private bool _drawOperationsCloned; private Matrix transformRestore; @@ -30,11 +30,9 @@ namespace Avalonia.Rendering.SceneGraph /// /// The visual that this node represents. /// The parent scene graph node, if any. - public VisualNode(IVisual visual, IVisualNode parent) + public VisualNode(IVisual visual, IVisualNode? parent) { - Contract.Requires(visual != null); - - Visual = visual; + Visual = visual ?? throw new ArgumentNullException(nameof(visual)); Parent = parent; HasAncestorGeometryClip = parent != null && (parent.HasAncestorGeometryClip || parent.GeometryClip != null); @@ -44,7 +42,7 @@ namespace Avalonia.Rendering.SceneGraph public IVisual Visual { get; } /// - public IVisualNode Parent { get; } + public IVisualNode? Parent { get; } /// public CornerRadius ClipToBoundsRadius { get; set; } @@ -65,7 +63,7 @@ namespace Avalonia.Rendering.SceneGraph public bool ClipToBounds { get; set; } /// - public IGeometryImpl GeometryClip { get; set; } + public IGeometryImpl? GeometryClip { get; set; } /// public bool HasAncestorGeometryClip { get; } @@ -87,7 +85,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets or sets the opacity mask for the scene graph node. /// - public IBrush OpacityMask { get; set; } + public IBrush? OpacityMask { get; set; } /// /// Gets a value indicating whether this node in the scene graph has already @@ -100,7 +98,7 @@ namespace Avalonia.Rendering.SceneGraph /// public bool OpacityChanged { get; private set; } - public IVisual LayerRoot { get; set; } + public IVisual? LayerRoot { get; set; } /// public IReadOnlyList Children => _children ?? EmptyChildren; @@ -259,7 +257,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// The new parent node. /// A cloned node. - public VisualNode Clone(IVisualNode parent) + public VisualNode Clone(IVisualNode? parent) { return new VisualNode(Visual, parent) { @@ -382,6 +380,7 @@ namespace Avalonia.Rendering.SceneGraph return result; } + [MemberNotNull(nameof(_children))] private void EnsureChildrenCreated(int capacity = 0) { if (_children == null) @@ -393,6 +392,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations). /// + [MemberNotNull(nameof(_drawOperations))] private void EnsureDrawOperationsCreated() { if (_drawOperations == null) @@ -412,7 +412,7 @@ namespace Avalonia.Rendering.SceneGraph _drawOperations.Add(drawOperation.Clone()); } - _drawOperationsRefCounter.Dispose(); + _drawOperationsRefCounter?.Dispose(); _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations)); _drawOperationsCloned = false; } diff --git a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs index 9cc94ffac3..86595754e9 100644 --- a/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/SleepLoopRenderTimer.cs @@ -6,7 +6,7 @@ namespace Avalonia.Rendering { public class SleepLoopRenderTimer : IRenderTimer { - private Action _tick; + private Action? _tick; private int _count; private readonly object _lock = new object(); private bool _running; diff --git a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs index e9700c70a5..4566b905c4 100644 --- a/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs +++ b/src/Avalonia.Visuals/Rendering/ZIndexComparer.cs @@ -9,6 +9,6 @@ namespace Avalonia.Rendering public static readonly ZIndexComparer Instance = new ZIndexComparer(); public static readonly Comparison ComparisonInstance = Instance.Compare; - public int Compare(IVisual x, IVisual y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); + public int Compare(IVisual? x, IVisual? y) => (x?.ZIndex ?? 0).CompareTo(y?.ZIndex ?? 0); } } diff --git a/src/Avalonia.Visuals/RoundedRect.cs b/src/Avalonia.Visuals/RoundedRect.cs index 3452bc1ff8..7d17d681ef 100644 --- a/src/Avalonia.Visuals/RoundedRect.cs +++ b/src/Avalonia.Visuals/RoundedRect.cs @@ -9,7 +9,7 @@ namespace Avalonia return Rect.Equals(other.Rect) && RadiiTopLeft.Equals(other.RadiiTopLeft) && RadiiTopRight.Equals(other.RadiiTopRight) && RadiiBottomLeft.Equals(other.RadiiBottomLeft) && RadiiBottomRight.Equals(other.RadiiBottomRight); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is RoundedRect other && Equals(other); } diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 8a805dc6c5..69c3ae7319 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -226,7 +226,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) => obj is Size other && Equals(other); + public override bool Equals(object? obj) => obj is Size other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index 06ebc9bfe7..da3a98088f 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -252,7 +252,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) => obj is Thickness other && Equals(other); + public override bool Equals(object? obj) => obj is Thickness other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 322b630e83..78c6d9c057 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -572,7 +572,7 @@ namespace Avalonia /// /// The sender. /// The event args. - private void RenderTransformChanged(object sender, EventArgs e) + private void RenderTransformChanged(object? sender, EventArgs e) { InvalidateVisual(); } @@ -593,13 +593,14 @@ namespace Avalonia if (_visualRoot != null) { - var e = new VisualTreeAttachmentEventArgs(old, VisualRoot); + var e = new VisualTreeAttachmentEventArgs(old!, _visualRoot); OnDetachedFromVisualTreeCore(e); } if (_visualParent is IRenderRoot || _visualParent?.IsAttachedToVisualTree == true) { - var root = this.FindAncestorOfType(); + var root = this.FindAncestorOfType() ?? + throw new AvaloniaInternalException("Visual is atached to visual tree but root could not be found."); var e = new VisualTreeAttachmentEventArgs(_visualParent, root); OnAttachedToVisualTreeCore(e); } @@ -607,28 +608,28 @@ namespace Avalonia OnVisualParentChanged(old, value); } - private void AffectsRenderInvalidated(object sender, EventArgs e) => InvalidateVisual(); + private void AffectsRenderInvalidated(object? sender, EventArgs e) => InvalidateVisual(); /// /// Called when the collection changes. /// /// The sender. /// The event args. - private void VisualChildrenChanged(object sender, NotifyCollectionChangedEventArgs e) + private void VisualChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: - SetVisualParent(e.NewItems, this); + SetVisualParent(e.NewItems!, this); break; case NotifyCollectionChangedAction.Remove: - SetVisualParent(e.OldItems, null); + SetVisualParent(e.OldItems!, null); break; case NotifyCollectionChangedAction.Replace: - SetVisualParent(e.OldItems, null); - SetVisualParent(e.NewItems, this); + SetVisualParent(e.OldItems!, null); + SetVisualParent(e.NewItems!, this); break; } } @@ -639,7 +640,7 @@ namespace Avalonia for (var i = 0; i < count; i++) { - var visual = (Visual) children[i]; + var visual = (Visual) children[i]!; visual.SetVisualParent(parent); } diff --git a/src/Avalonia.Visuals/VisualExtensions.cs b/src/Avalonia.Visuals/VisualExtensions.cs index e6523a1469..ff8a515db3 100644 --- a/src/Avalonia.Visuals/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualExtensions.cs @@ -16,8 +16,10 @@ namespace Avalonia /// The point in client coordinates. public static Point PointToClient(this IVisual visual, PixelPoint point) { - var rootPoint = visual.VisualRoot.PointToClient(point); - return visual.VisualRoot.TranslatePoint(rootPoint, visual).Value; + var root = visual.VisualRoot ?? + throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); + var rootPoint = root.PointToClient(point); + return root.TranslatePoint(rootPoint, visual)!.Value; } /// @@ -28,8 +30,10 @@ namespace Avalonia /// The point in screen coordinates. public static PixelPoint PointToScreen(this IVisual visual, Point point) { - var p = visual.TranslatePoint(point, visual.VisualRoot); - return visual.VisualRoot.PointToScreen(p.Value); + var root = visual.VisualRoot ?? + throw new ArgumentException("Control does not belong to a visual tree.", nameof(visual)); + var p = visual.TranslatePoint(point, root); + return visual.VisualRoot.PointToScreen(p!.Value); } /// @@ -93,28 +97,29 @@ namespace Avalonia private static Matrix GetOffsetFrom(IVisual ancestor, IVisual visual) { var result = Matrix.Identity; + IVisual? v = visual; - while (visual != ancestor) + while (v != ancestor) { - if (visual.RenderTransform?.Value != null) + if (v.RenderTransform?.Value != null) { - var origin = visual.RenderTransformOrigin.ToPixels(visual.Bounds.Size); + var origin = v.RenderTransformOrigin.ToPixels(v.Bounds.Size); var offset = Matrix.CreateTranslation(origin); - var renderTransform = (-offset) * visual.RenderTransform.Value * (offset); + var renderTransform = (-offset) * v.RenderTransform.Value * (offset); result *= renderTransform; } - var topLeft = visual.Bounds.TopLeft; + var topLeft = v.Bounds.TopLeft; if (topLeft != default) { result *= Matrix.CreateTranslation(topLeft); } - visual = visual.VisualParent; + v = v.VisualParent; - if (visual == null) + if (v == null) { throw new ArgumentException("'visual' is not a descendant of 'ancestor'."); } diff --git a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs index 3aa0392496..42f93d8edb 100644 --- a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs @@ -54,7 +54,7 @@ namespace Avalonia.VisualTree return Bounds == other.Bounds && Clip == other.Clip && Transform == other.Transform; } - public override bool Equals(object obj) => obj is TransformedBounds other && Equals(other); + public override bool Equals(object? obj) => obj is TransformedBounds other && Equals(other); public override int GetHashCode() { diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 8c4004efdc..79b250ba05 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -21,18 +21,17 @@ namespace Avalonia.VisualTree /// public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual ancestor) { - Contract.Requires(visual != null); - + IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; - while (visual != null && visual != ancestor) + while (v != null && v != ancestor) { - visual = visual.VisualParent; + v = v.VisualParent; result++; } - return visual != null ? result : -1; + return v != null ? result : -1; } /// @@ -44,15 +43,14 @@ namespace Avalonia.VisualTree /// public static int CalculateDistanceFromRoot(IVisual visual) { - Contract.Requires(visual != null); - + IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; - visual = visual?.VisualParent; + v = v?.VisualParent; - while (visual != null) + while (v != null) { - visual = visual.VisualParent; + v = v.VisualParent; result++; } @@ -66,54 +64,56 @@ namespace Avalonia.VisualTree /// The first visual. /// The second visual. /// The common ancestor, or null if not found. - public static IVisual FindCommonVisualAncestor(this IVisual visual, IVisual target) + public static IVisual? FindCommonVisualAncestor(this IVisual visual, IVisual target) { - Contract.Requires(visual != null); + IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); if (target is null) { return null; } - void GoUpwards(ref IVisual node, int count) + IVisual? t = target; + + void GoUpwards(ref IVisual? node, int count) { for (int i = 0; i < count; ++i) { - node = node.VisualParent; + node = node?.VisualParent; } } // 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(visual); - var secondHeight = CalculateDistanceFromRoot(target); + var firstHeight = CalculateDistanceFromRoot(v); + var secondHeight = CalculateDistanceFromRoot(t); if (firstHeight > secondHeight) { - GoUpwards(ref visual, firstHeight - secondHeight); + GoUpwards(ref v, firstHeight - secondHeight); } else { - GoUpwards(ref target, secondHeight - firstHeight); + GoUpwards(ref t, secondHeight - firstHeight); } - if (visual == target) + if (v == t) { - return visual; + return v; } - while (visual != null && target != null) + while (v != null && t != null) { - IVisual firstParent = visual.VisualParent; - IVisual secondParent = target.VisualParent; + IVisual? firstParent = v.VisualParent; + IVisual? secondParent = t.VisualParent; if (firstParent == secondParent) { return firstParent; } - visual = visual.VisualParent; - target = target.VisualParent; + v = v.VisualParent; + t = t.VisualParent; } return null; @@ -126,14 +126,14 @@ namespace Avalonia.VisualTree /// The visual's ancestors. public static IEnumerable GetVisualAncestors(this IVisual visual) { - Contract.Requires(visual != null); + IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); - visual = visual.VisualParent; + v = v.VisualParent; - while (visual != null) + while (v != null) { - yield return visual; - visual = visual.VisualParent; + yield return v; + v = v.VisualParent; } } @@ -144,14 +144,14 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First ancestor of given type. - public static T FindAncestorOfType(this IVisual visual, bool includeSelf = false) where T : class + public static T? FindAncestorOfType(this IVisual visual, bool includeSelf = false) where T : class { if (visual is null) { return null; } - IVisual parent = includeSelf ? visual : visual.VisualParent; + IVisual? parent = includeSelf ? visual : visual.VisualParent; while (parent != null) { @@ -173,7 +173,7 @@ namespace Avalonia.VisualTree /// The visual. /// If given visual should be included in search. /// First descendant of given type. - public static T FindDescendantOfType(this IVisual visual, bool includeSelf = false) where T : class + public static T? FindDescendantOfType(this IVisual visual, bool includeSelf = false) where T : class { if (visual is null) { @@ -195,7 +195,7 @@ namespace Avalonia.VisualTree /// The visual and its ancestors. public static IEnumerable GetSelfAndVisualAncestors(this IVisual visual) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); yield return visual; @@ -211,9 +211,9 @@ namespace Avalonia.VisualTree /// The root visual to test. /// The point. /// The visual at the requested point. - public static IVisual GetVisualAt(this IVisual visual, Point p) + public static IVisual? GetVisualAt(this IVisual visual, Point p) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); return visual.GetVisualAt(p, x => x.IsVisible); } @@ -228,11 +228,17 @@ namespace Avalonia.VisualTree /// children will be excluded from the results. /// /// The visual at the requested point. - public static IVisual GetVisualAt(this IVisual visual, Point p, Func filter) + public static IVisual? GetVisualAt(this IVisual visual, Point p, Func filter) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); var root = visual.GetVisualRoot(); + + if (root is null) + { + return null; + } + var rootPoint = visual.TranslatePoint(p, root); if (rootPoint.HasValue) @@ -253,7 +259,7 @@ namespace Avalonia.VisualTree this IVisual visual, Point p) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); return visual.GetVisualsAt(p, x => x.IsVisible); } @@ -273,9 +279,15 @@ namespace Avalonia.VisualTree Point p, Func filter) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); var root = visual.GetVisualRoot(); + + if (root is null) + { + return Array.Empty(); + } + var rootPoint = visual.TranslatePoint(p, root); if (rootPoint.HasValue) @@ -334,7 +346,7 @@ namespace Avalonia.VisualTree /// /// The visual. /// The parent, or null if the visual is unparented. - public static IVisual GetVisualParent(this IVisual visual) + public static IVisual? GetVisualParent(this IVisual visual) { return visual.VisualParent; } @@ -347,7 +359,7 @@ namespace Avalonia.VisualTree /// /// The parent, or null if the visual is unparented or its parent is not of type . /// - public static T GetVisualParent(this IVisual visual) where T : class + public static T? GetVisualParent(this IVisual visual) where T : class { return visual.VisualParent as T; } @@ -359,9 +371,9 @@ namespace Avalonia.VisualTree /// /// The root visual or null if the visual is not rooted. /// - public static IRenderRoot GetVisualRoot(this IVisual visual) + public static IRenderRoot? GetVisualRoot(this IVisual visual) { - Contract.Requires(visual != null); + _ = visual ?? throw new ArgumentNullException(nameof(visual)); return visual as IRenderRoot ?? visual.VisualRoot; } @@ -377,7 +389,7 @@ namespace Avalonia.VisualTree /// public static bool IsVisualAncestorOf(this IVisual visual, IVisual target) { - IVisual current = target?.VisualParent; + IVisual? current = target?.VisualParent; while (current != null) { @@ -402,10 +414,10 @@ namespace Avalonia.VisualTree ZIndex = element.ZIndex, }) .OrderBy(x => x, null) - .Select(x => x.Element); + .Select(x => x.Element!); } - private static T FindDescendantOfTypeCore(IVisual visual) where T : class + private static T? FindDescendantOfTypeCore(IVisual visual) where T : class { var visualChildren = visual.VisualChildren; var visualChildrenCount = visualChildren.Count; @@ -432,12 +444,15 @@ namespace Avalonia.VisualTree private class ZOrderElement : IComparable { - public IVisual Element { get; set; } + public IVisual? Element { get; set; } public int Index { get; set; } public int ZIndex { get; set; } - public int CompareTo(ZOrderElement other) + public int CompareTo(ZOrderElement? other) { + if (other is null) + return 1; + var z = other.ZIndex - ZIndex; if (z != 0) diff --git a/src/Avalonia.Visuals/VisualTree/VisualLocator.cs b/src/Avalonia.Visuals/VisualTree/VisualLocator.cs index 1e0662c17a..940c87bafa 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualLocator.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualLocator.cs @@ -6,18 +6,18 @@ namespace Avalonia.VisualTree { public class VisualLocator { - public static IObservable Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null) + public static IObservable Track(IVisual relativeTo, int ancestorLevel, Type? ancestorType = null) { return new VisualTracker(relativeTo, ancestorLevel, ancestorType); } - private class VisualTracker : LightweightObservableBase + private class VisualTracker : LightweightObservableBase { private readonly IVisual _relativeTo; private readonly int _ancestorLevel; - private readonly Type _ancestorType; + private readonly Type? _ancestorType; - public VisualTracker(IVisual relativeTo, int ancestorLevel, Type ancestorType) + public VisualTracker(IVisual relativeTo, int ancestorLevel, Type? ancestorType) { _relativeTo = relativeTo; _ancestorLevel = ancestorLevel; @@ -36,14 +36,14 @@ namespace Avalonia.VisualTree _relativeTo.DetachedFromVisualTree -= AttachedDetached; } - protected override void Subscribed(IObserver observer, bool first) + protected override void Subscribed(IObserver observer, bool first) { observer.OnNext(GetResult()); } - private void AttachedDetached(object sender, VisualTreeAttachmentEventArgs e) => PublishNext(GetResult()); + private void AttachedDetached(object? sender, VisualTreeAttachmentEventArgs e) => PublishNext(GetResult()); - private IVisual GetResult() + private IVisual? GetResult() { if (_relativeTo.IsAttachedToVisualTree) { diff --git a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs index d8f6ea8296..57a9340be8 100644 --- a/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs +++ b/src/Avalonia.Visuals/VisualTreeAttachmentEventArgs.cs @@ -17,11 +17,8 @@ namespace Avalonia /// The root visual. public VisualTreeAttachmentEventArgs(IVisual parent, IRenderRoot root) { - Contract.Requires(parent != null); - Contract.Requires(root != null); - - Parent = parent; - Root = root; + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + Root = root ?? throw new ArgumentNullException(nameof(root)); } /// From 882e3b79b5de96f9b40e7cdd6a805b222d80f3bf Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 Dec 2021 14:49:37 +0100 Subject: [PATCH 036/260] Disable CS8632 in Avalonia.Build.Tasks. `CS8632 The annotation for nullable reference types should only be used in code within a '#nullable' annotations context`: need to supress this as the project includes files from Avalonia.Visuals which now has nullable annotations enabled. --- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index 7884f13ddb..e864ea2007 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -6,7 +6,7 @@ tools $(DefineConstants);BUILDTASK;XAMLX_CECIL_INTERNAL;XAMLX_INTERNAL true - NU1605 + NU1605;CS8632 From 42f552d8035d2954f8cc97d0d12b63382905352f Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 17 Dec 2021 18:59:07 +0100 Subject: [PATCH 037/260] Nullability for text related types --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 4 +++- src/Avalonia.Visuals/Media/FontFallback.cs | 2 +- src/Avalonia.Visuals/Media/FontManager.cs | 8 ++++---- .../Media/FontManagerOptions.cs | 4 ++-- .../Media/Fonts/FontFamilyLoader.cs | 2 +- src/Avalonia.Visuals/Media/GlyphRun.cs | 18 +++--------------- src/Avalonia.Visuals/Media/GlyphTypeface.cs | 4 ++-- .../Media/TextFormatting/TextFormatterImpl.cs | 12 ++++++------ .../Media/TextFormatting/TextLineImpl.cs | 12 +++++++----- src/Avalonia.Visuals/Media/Typeface.cs | 2 +- src/Avalonia.Visuals/Media/UnicodeRange.cs | 2 +- 11 files changed, 31 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index ee4f70e074..ad9a5fd71a 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -5,6 +5,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. @@ -77,4 +79,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 78 +Total Issues: 79 diff --git a/src/Avalonia.Visuals/Media/FontFallback.cs b/src/Avalonia.Visuals/Media/FontFallback.cs index 240604c5c1..8943b57b31 100644 --- a/src/Avalonia.Visuals/Media/FontFallback.cs +++ b/src/Avalonia.Visuals/Media/FontFallback.cs @@ -8,7 +8,7 @@ /// /// Get or set the fallback /// - public FontFamily FontFamily { get; set; } + public FontFamily FontFamily { get; set; } = FontFamily.Default; /// /// Get or set the that is covered by the fallback. diff --git a/src/Avalonia.Visuals/Media/FontManager.cs b/src/Avalonia.Visuals/Media/FontManager.cs index 463f020792..72c1c8dcac 100644 --- a/src/Avalonia.Visuals/Media/FontManager.cs +++ b/src/Avalonia.Visuals/Media/FontManager.cs @@ -16,7 +16,7 @@ namespace Avalonia.Media private readonly ConcurrentDictionary _glyphTypefaceCache = new ConcurrentDictionary(); private readonly FontFamily _defaultFontFamily; - private readonly IReadOnlyList _fontFallbacks; + private readonly IReadOnlyList? _fontFallbacks; public FontManager(IFontManagerImpl platformImpl) { @@ -87,7 +87,7 @@ namespace Avalonia.Media /// /// The . /// - public GlyphTypeface? GetOrAddGlyphTypeface(Typeface typeface) + public GlyphTypeface GetOrAddGlyphTypeface(Typeface typeface) { while (true) { @@ -105,7 +105,7 @@ namespace Avalonia.Media if (typeface.FontFamily == _defaultFontFamily) { - return null; + throw new InvalidOperationException($"Could not create glyph typeface for: {typeface.FontFamily.Name}."); } typeface = new Typeface(_defaultFontFamily, typeface.Style, typeface.Weight); @@ -126,7 +126,7 @@ namespace Avalonia.Media /// public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, - FontFamily fontFamily, CultureInfo culture, out Typeface typeface) + FontFamily? fontFamily, CultureInfo? culture, out Typeface typeface) { if(_fontFallbacks != null) { diff --git a/src/Avalonia.Visuals/Media/FontManagerOptions.cs b/src/Avalonia.Visuals/Media/FontManagerOptions.cs index 983fd6eb32..54227dce0f 100644 --- a/src/Avalonia.Visuals/Media/FontManagerOptions.cs +++ b/src/Avalonia.Visuals/Media/FontManagerOptions.cs @@ -4,8 +4,8 @@ namespace Avalonia.Media { public class FontManagerOptions { - public string DefaultFamilyName { get; set; } + public string? DefaultFamilyName { get; set; } - public IReadOnlyList FontFallbacks { get; set; } + public IReadOnlyList? FontFallbacks { get; set; } } } diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 52e46110a3..203c8dc221 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -89,7 +89,7 @@ namespace Avalonia.Media.Fonts { fileExtension = "." + fontFamilyKey.Source.AbsolutePath.Split('.').LastOrDefault(); - var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').LastOrDefault(); + var fileName = fontFamilyKey.Source.LocalPath.Replace(fileExtension, string.Empty).Split('.').Last(); location = new Uri(fontFamilyKey.Source.AbsoluteUri.Replace("." + fileName + fileExtension, string.Empty), UriKind.RelativeOrAbsolute); diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index fa9241c2ed..e15a19306a 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -27,14 +27,6 @@ namespace Avalonia.Media private ReadOnlySlice _glyphClusters; private ReadOnlySlice _characters; - /// - /// Initializes a new instance of the class. - /// - public GlyphRun() - { - - } - /// /// Initializes a new instance of the class by specifying properties of the class. /// @@ -56,7 +48,7 @@ namespace Avalonia.Media ReadOnlySlice glyphClusters = default, int biDiLevel = 0) { - GlyphTypeface = glyphTypeface; + _glyphTypeface = glyphTypeface; FontRenderingEmSize = fontRenderingEmSize; @@ -74,13 +66,9 @@ namespace Avalonia.Media } /// - /// Gets or sets the for the . + /// Gets the for the . /// - public GlyphTypeface GlyphTypeface - { - get => _glyphTypeface; - set => Set(ref _glyphTypeface, value); - } + public GlyphTypeface GlyphTypeface => _glyphTypeface; /// /// Gets or sets the em size used for rendering the . diff --git a/src/Avalonia.Visuals/Media/GlyphTypeface.cs b/src/Avalonia.Visuals/Media/GlyphTypeface.cs index 2be505b9c0..67dfbb84b6 100644 --- a/src/Avalonia.Visuals/Media/GlyphTypeface.cs +++ b/src/Avalonia.Visuals/Media/GlyphTypeface.cs @@ -8,8 +8,8 @@ namespace Avalonia.Media public const int InvisibleGlyph = 3; public GlyphTypeface(Typeface typeface) - : this(FontManager.Current?.PlatformImpl.CreateGlyphTypeface(typeface)) - { + : this(FontManager.Current.PlatformImpl.CreateGlyphTypeface(typeface)) + { } public GlyphTypeface(IGlyphTypefaceImpl platformImpl) diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs index 3d3e93dd4d..7c6af4eaa7 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs @@ -277,7 +277,7 @@ namespace Avalonia.Media.TextFormatting var textRuns = new List(); - if (previousLineBreak != null) + if (previousLineBreak?.RemainingCharacters != null) { for (var index = 0; index < previousLineBreak.RemainingCharacters.Count; index++) { @@ -317,7 +317,7 @@ namespace Avalonia.Media.TextFormatting while (textRunEnumerator.MoveNext()) { - var textRun = textRunEnumerator.Current; + var textRun = textRunEnumerator.Current!; switch (textRun) { @@ -398,7 +398,7 @@ namespace Avalonia.Media.TextFormatting /// The current line break if the line was explicitly broken. /// The wrapped text line. private static TextLine PerformTextWrapping(List textRuns, TextRange textRange, - double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak currentLineBreak) + double paragraphWidth, TextParagraphProperties paragraphProperties, TextLineBreak? currentLineBreak) { var availableWidth = paragraphWidth; var currentWidth = 0.0; @@ -517,7 +517,7 @@ namespace Avalonia.Media.TextFormatting var lineBreak = remainingCharacters?.Count > 0 ? new TextLineBreak(remainingCharacters) : null; - if (lineBreak is null && currentLineBreak.TextEndOfLine != null) + if (lineBreak is null && currentLineBreak?.TextEndOfLine != null) { lineBreak = new TextLineBreak(currentLineBreak.TextEndOfLine); } @@ -586,11 +586,11 @@ namespace Avalonia.Media.TextFormatting { _textSource = textSource; _pos = firstTextSourceIndex; - Current = null!; + Current = null; } // ReSharper disable once MemberHidesStaticFromOuterClass - public TextRun Current { get; private set; } + public TextRun? Current { get; private set; } public bool MoveNext() { diff --git a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs index a53b43935c..b1397518e4 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Platform; using Avalonia.Utilities; @@ -479,12 +480,13 @@ namespace Avalonia.Media.TextFormatting /// internal static ShapedTextCharacters CreateShapedSymbol(TextRun textRun) { - var formatterImpl = AvaloniaLocator.Current.GetService(); + var properties = textRun.Properties; - var glyphRun = formatterImpl.ShapeText(textRun.Text, textRun.Properties.Typeface, textRun.Properties.FontRenderingEmSize, - textRun.Properties.CultureInfo); + _ = properties ?? throw new InvalidOperationException($"{nameof(TextRun.Properties)} should not be null."); - return new ShapedTextCharacters(glyphRun, textRun.Properties); + var glyphRun = TextShaper.Current.ShapeText(textRun.Text, properties.Typeface, properties.FontRenderingEmSize, properties.CultureInfo); + + return new ShapedTextCharacters(glyphRun, properties); } } } diff --git a/src/Avalonia.Visuals/Media/Typeface.cs b/src/Avalonia.Visuals/Media/Typeface.cs index 859cbafafc..8245b63440 100644 --- a/src/Avalonia.Visuals/Media/Typeface.cs +++ b/src/Avalonia.Visuals/Media/Typeface.cs @@ -66,7 +66,7 @@ namespace Avalonia.Media /// /// The glyph typeface. /// - public GlyphTypeface? GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); + public GlyphTypeface GlyphTypeface => FontManager.Current.GetOrAddGlyphTypeface(this); public static bool operator !=(Typeface a, Typeface b) { diff --git a/src/Avalonia.Visuals/Media/UnicodeRange.cs b/src/Avalonia.Visuals/Media/UnicodeRange.cs index 743b9fcc07..0077dd64ac 100644 --- a/src/Avalonia.Visuals/Media/UnicodeRange.cs +++ b/src/Avalonia.Visuals/Media/UnicodeRange.cs @@ -37,7 +37,7 @@ namespace Avalonia.Media internal UnicodeRangeSegment Single => _single; - internal IReadOnlyList Segments => _segments; + internal IReadOnlyList? Segments => _segments; /// /// Determines if given value is inside the range. From 4582b43118a6105170a08cf50b4b09a24705a651 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 Dec 2021 23:39:54 +0100 Subject: [PATCH 038/260] More nullability fixes. --- .../Diagnostics/ViewModels/TreePageViewModel.cs | 7 ++++--- src/Avalonia.Input/AccessKeyHandler.cs | 2 +- src/Avalonia.Input/InputElement.cs | 2 +- src/Avalonia.Input/Pointer.cs | 2 +- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 27b28f35fc..4416437479 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -86,14 +86,15 @@ namespace Avalonia.Diagnostics.ViewModels public void SelectControl(IControl control) { var node = default(TreeNode); + IControl? c = control; - while (node == null && control != null) + while (node == null && c != null) { - node = FindNode(control); + node = FindNode(c); if (node == null) { - control = control.GetVisualParent(); + c = c.GetVisualParent(); } } diff --git a/src/Avalonia.Input/AccessKeyHandler.cs b/src/Avalonia.Input/AccessKeyHandler.cs index a3330b65ae..60175ae588 100644 --- a/src/Avalonia.Input/AccessKeyHandler.cs +++ b/src/Avalonia.Input/AccessKeyHandler.cs @@ -188,7 +188,7 @@ namespace Avalonia.Input // If the menu is open, only match controls in the menu's visual tree. if (menuIsOpen) { - matches = matches.Where(x => MainMenu.IsVisualAncestorOf(x)); + matches = matches.Where(x => x is not null && MainMenu!.IsVisualAncestorOf(x)); } var match = matches.FirstOrDefault(); diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index c2197ee64d..def0a26b27 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -675,7 +675,7 @@ namespace Avalonia.Input /// . /// /// The parent control. - private void UpdateIsEffectivelyEnabled(InputElement parent) + private void UpdateIsEffectivelyEnabled(InputElement? parent) { IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true); diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs index 433b275ce4..3012f07f6a 100644 --- a/src/Avalonia.Input/Pointer.cs +++ b/src/Avalonia.Input/Pointer.cs @@ -54,7 +54,7 @@ namespace Avalonia.Input Captured.DetachedFromVisualTree += OnCaptureDetached; } - IInputElement GetNextCapture(IVisual parent) + IInputElement? GetNextCapture(IVisual parent) { return parent as IInputElement ?? parent.FindAncestorOfType(); } diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index 8cd4d7b111..a971c8e997 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -106,8 +106,8 @@ namespace Avalonia.Web.Blazor public IRenderer CreateRenderer(IRenderRoot root) { - var loop = AvaloniaLocator.Current.GetService(); - + var loop = AvaloniaLocator.Current.GetService() ?? + throw new InvalidOperationException("Unable to locate IRenderLoop."); return new DeferredRenderer(root, loop); } From 1524274a3b88fbd48d64e0bcb17bad9176f68831 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 17 Dec 2021 23:41:10 +0100 Subject: [PATCH 039/260] Add a hacky exception for existing potential problem. This isn't good, but I don't want to change that as part of adding nullable annotations. --- src/Avalonia.Visuals/Media/DrawingContext.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index 0ff143782d..b55b56817b 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -351,6 +351,12 @@ namespace Avalonia.Media { _ = clip ?? throw new ArgumentNullException(nameof(clip)); + // HACK: This check was added when nullable annotations pointed out that we're potentially + // pushing a null value for the clip here. Ideally we'd return an empty PushedState here but + // I don't want to make that change as part of adding nullable annotations. + if (clip.PlatformImpl is null) + throw new InvalidOperationException("Cannot push empty geometry clip."); + PlatformImpl.PushGeometryClip(clip.PlatformImpl); return new PushedState(this, PushedState.PushedStateType.GeometryClip); } From 1bacdc4b6e54b2810b952bb0ccb34f3caa768d80 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 18 Dec 2021 16:33:01 +0100 Subject: [PATCH 040/260] Added nullable annotations for DrawEllipse. --- src/Avalonia.Visuals/Media/DrawingContext.cs | 2 +- .../Platform/IDrawingContextImpl.cs | 2 +- .../SceneGraph/DeferredDrawingContextImpl.cs | 2 +- .../Rendering/SceneGraph/EllipseNode.cs | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index b55b56817b..a2e3bdc6aa 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -202,7 +202,7 @@ namespace Avalonia.Media /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// - public void DrawEllipse(IBrush brush, IPen pen, Point center, double radiusX, double radiusY) + public void DrawEllipse(IBrush? brush, IPen? pen, Point center, double radiusX, double radiusY) { if (brush == null && !PenIsVisible(pen)) { diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs index 7e81924a90..a1f42c171c 100644 --- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs @@ -81,7 +81,7 @@ namespace Avalonia.Platform /// The brush and the pen can both be null. If the brush is null, then no fill is performed. /// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible. /// - void DrawEllipse(IBrush brush, IPen pen, Rect rect); + void DrawEllipse(IBrush? brush, IPen? pen, Rect rect); /// /// Draws text. diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 965754c0a6..934d2666b4 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -176,7 +176,7 @@ namespace Avalonia.Rendering.SceneGraph } } - public void DrawEllipse(IBrush brush, IPen pen, Rect rect) + public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) { var next = NextDrawAs(); diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs index a8c5579a4b..504256b932 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs @@ -14,10 +14,10 @@ namespace Avalonia.Rendering.SceneGraph { public EllipseNode( Matrix transform, - IBrush brush, - IPen pen, + IBrush? brush, + IPen? pen, Rect rect, - IDictionary childScenes = null) + IDictionary? childScenes = null) : base(rect.Inflate(pen?.Thickness ?? 0), transform) { Transform = transform; @@ -30,12 +30,12 @@ namespace Avalonia.Rendering.SceneGraph /// /// Gets the fill brush. /// - public IBrush Brush { get; } + public IBrush? Brush { get; } /// /// Gets the stroke pen. /// - public ImmutablePen Pen { get; } + public ImmutablePen? Pen { get; } /// /// Gets the transform with which the node will be drawn. @@ -47,9 +47,9 @@ namespace Avalonia.Rendering.SceneGraph /// public Rect Rect { get; } - public override IDictionary ChildScenes { get; } + public override IDictionary? ChildScenes { get; } - public bool Equals(Matrix transform, IBrush brush, IPen pen, Rect rect) + public bool Equals(Matrix transform, IBrush? brush, IPen? pen, Rect rect) { return transform == Transform && Equals(brush, Brush) && From 7983a496e37be5b980557263df8be959ba8af0fa Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 18 Dec 2021 22:25:09 +0100 Subject: [PATCH 041/260] Fix mistakes made when adding nullable annotations. I shouldn't be doing this stuff when I'm tired. --- src/Avalonia.Visuals/Media/DrawingContext.cs | 4 +-- .../SceneGraph/DeferredDrawingContextImpl.cs | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs index a2e3bdc6aa..2fbef8c89f 100644 --- a/src/Avalonia.Visuals/Media/DrawingContext.cs +++ b/src/Avalonia.Visuals/Media/DrawingContext.cs @@ -297,10 +297,10 @@ namespace Avalonia.Media public void Dispose() { - if (_context._states is null || _context._transformContainers is null) - throw new ObjectDisposedException(nameof(DrawingContext)); if (_type == PushedStateType.None) return; + if (_context._states is null || _context._transformContainers is null) + throw new ObjectDisposedException(nameof(DrawingContext)); if (_context._currentLevel != _level) throw new InvalidOperationException("Wrong Push/Pop state order"); _context._currentLevel--; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs index 934d2666b4..688cbd83c8 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs @@ -48,18 +48,21 @@ namespace Avalonia.Rendering.SceneGraph /// public UpdateState BeginUpdate(VisualNode node) { - _ = _node ?? throw new ArgumentNullException(nameof(node)); + _ = node ?? throw new ArgumentNullException(nameof(node)); - if (_childIndex < _node.Children.Count) + if (_node != null) { - _node.ReplaceChild(_childIndex, node); - } - else - { - _node.AddChild(node); - } + if (_childIndex < _node.Children.Count) + { + _node.ReplaceChild(_childIndex, node); + } + else + { + _node.AddChild(node); + } - ++_childIndex; + ++_childIndex; + } var state = new UpdateState(this, _node, _childIndex, _drawOperationindex); _node = node; @@ -406,7 +409,7 @@ namespace Avalonia.Rendering.SceneGraph { public UpdateState( DeferredDrawingContextImpl owner, - VisualNode node, + VisualNode? node, int childIndex, int drawOperationIndex) { @@ -436,7 +439,7 @@ namespace Avalonia.Rendering.SceneGraph } public DeferredDrawingContextImpl Owner { get; } - public VisualNode Node { get; } + public VisualNode? Node { get; } public int ChildIndex { get; } public int DrawOperationIndex { get; } } From ba258ffa90290cbbc74c7a24e47ad4d5b3601020 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 18 Dec 2021 22:25:20 +0100 Subject: [PATCH 042/260] Don't pass null where it shouldn't be. --- .../Rendering/SceneGraph/TextNodeTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/TextNodeTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/TextNodeTests.cs index 7c5ec5ddfd..a6eaea334c 100644 --- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/TextNodeTests.cs +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/TextNodeTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering.SceneGraph; using Moq; @@ -15,7 +16,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph { var target = new TextNode( Matrix.Identity, - null, + Brushes.Black, new Point(10, 10), Mock.Of(x => x.Bounds == new Rect(5, 5, 50, 50))); From f092724fd8ec2c6b86b7bdde947296117355dd62 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 18 Dec 2021 22:52:04 +0100 Subject: [PATCH 043/260] Fixed another mistake made when adding nullable annotations. --- src/Avalonia.Visuals/Rendering/DirtyVisuals.cs | 5 ----- src/Avalonia.Visuals/VisualTree/VisualExtensions.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs index a49cdd89bc..00bc236b9c 100644 --- a/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs +++ b/src/Avalonia.Visuals/Rendering/DirtyVisuals.cs @@ -31,11 +31,6 @@ namespace Avalonia.Rendering /// The dirty visual. public void Add(IVisual visual) { - if (visual.VisualRoot is null) - { - return; - } - if (_deferring > 0) { _deferredChanges.Add(visual); diff --git a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs index 79b250ba05..128a39c082 100644 --- a/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs +++ b/src/Avalonia.Visuals/VisualTree/VisualExtensions.cs @@ -19,7 +19,7 @@ namespace Avalonia.VisualTree /// The number of steps from the visual to the ancestor or -1 if /// is not a descendent of . /// - public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual ancestor) + public static int CalculateDistanceFromAncestor(this IVisual visual, IVisual? ancestor) { IVisual? v = visual ?? throw new ArgumentNullException(nameof(visual)); var result = 0; From 7f20078e4f3226ab98899ecd27355104eef6dcb7 Mon Sep 17 00:00:00 2001 From: abdesol Date: Sun, 19 Dec 2021 08:52:31 +0300 Subject: [PATCH 044/260] removed focus fix and added test in sandbox --- samples/Sandbox/MainWindow.axaml.cs | 27 +++++++++++++++++++++++++-- src/Avalonia.Controls/Button.cs | 17 ++++++++++------- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 3d54036d29..93378dd736 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -5,17 +5,40 @@ using Avalonia.Win32.WinRT.Composition; namespace Sandbox { - public class MainWindow : Window + public partial class MainWindow : Window { public MainWindow() { - this.InitializeComponent(); + InitializeComponent(); +#if DEBUG this.AttachDevTools(); +#endif } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); + + var button = new Button + { + Content = "Tabulate to this button" + }; + + Content = button; + button.GotFocus += (_, _) => + { + button.IsEnabled = false; + button.Content = + $"Now this button is disabled ({nameof(IsEnabled)}:{button.IsEnabled},{nameof(IsEffectivelyEnabled)}:{button.IsEffectivelyEnabled}), but you can still press Enter"; + button.KeyDown += (_, _) => + { + button.Content = "button got fired by KeyDown event"; + }; + }; + button.Click += (_, _) => + { + button.Content = "It just has been clicked."; + }; } } } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 8b22cdd4ec..8537c9acbc 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -304,15 +304,18 @@ namespace Avalonia.Controls /// protected virtual void OnClick() { - OpenFlyout(); + if (IsEffectivelyEnabled) + { + OpenFlyout(); - var e = new RoutedEventArgs(ClickEvent); - RaiseEvent(e); + var e = new RoutedEventArgs(ClickEvent); + RaiseEvent(e); - if (!e.Handled && Command?.CanExecute(CommandParameter) == true) - { - Command.Execute(CommandParameter); - e.Handled = true; + if (!e.Handled && Command?.CanExecute(CommandParameter) == true) + { + Command.Execute(CommandParameter); + e.Handled = true; + } } } From 845ff07f1fc8d0f31836f73133487a8fb0ce245a Mon Sep 17 00:00:00 2001 From: abdesol Date: Sun, 19 Dec 2021 09:55:59 +0300 Subject: [PATCH 045/260] Button_Invokes_Doesnt_Execute_When_Button_Disabled test added --- samples/Sandbox/MainWindow.axaml.cs | 27 ++----------------- .../ButtonTests.cs | 14 ++++++++++ 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/samples/Sandbox/MainWindow.axaml.cs b/samples/Sandbox/MainWindow.axaml.cs index 93378dd736..3d54036d29 100644 --- a/samples/Sandbox/MainWindow.axaml.cs +++ b/samples/Sandbox/MainWindow.axaml.cs @@ -5,40 +5,17 @@ using Avalonia.Win32.WinRT.Composition; namespace Sandbox { - public partial class MainWindow : Window + public class MainWindow : Window { public MainWindow() { - InitializeComponent(); -#if DEBUG + this.InitializeComponent(); this.AttachDevTools(); -#endif } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - - var button = new Button - { - Content = "Tabulate to this button" - }; - - Content = button; - button.GotFocus += (_, _) => - { - button.IsEnabled = false; - button.Content = - $"Now this button is disabled ({nameof(IsEnabled)}:{button.IsEnabled},{nameof(IsEffectivelyEnabled)}:{button.IsEffectivelyEnabled}), but you can still press Enter"; - button.KeyDown += (_, _) => - { - button.Content = "button got fired by KeyDown event"; - }; - }; - button.Click += (_, _) => - { - button.Content = "It just has been clicked."; - }; } } } diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index e7a42c2d93..dcef91589d 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -295,6 +295,20 @@ namespace Avalonia.Controls.UnitTests target.CommandParameter = false; Assert.False(target.IsEffectivelyEnabled); } + + [Fact] + public void Button_Invokes_Doesnt_Execute_When_Button_Disabled() + { + var target = new Button(); + var raised = 0; + + target.IsEnabled = false; + target.Click += (s, e) => ++raised; + + target.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); + + Assert.Equal(1, raised); + } private class TestButton : Button, IRenderRoot { From b2ae473f2a834ed4eb1738e1819abbf0b150468c Mon Sep 17 00:00:00 2001 From: abdesol Date: Sun, 19 Dec 2021 10:11:45 +0300 Subject: [PATCH 046/260] changed assert value of Button_Invokes_Doesnt_Execute_When_Button_Disabled from 1 to 0 --- tests/Avalonia.Controls.UnitTests/ButtonTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs index dcef91589d..f4acf5ea37 100644 --- a/tests/Avalonia.Controls.UnitTests/ButtonTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ButtonTests.cs @@ -307,7 +307,7 @@ namespace Avalonia.Controls.UnitTests target.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent)); - Assert.Equal(1, raised); + Assert.Equal(0, raised); } private class TestButton : Button, IRenderRoot From 69c1a37372374fa38f80b97996f24a63d88a1024 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Sun, 19 Dec 2021 11:18:44 +0200 Subject: [PATCH 047/260] some very initial implementation --- samples/ControlCatalog/App.xaml | 1 + samples/ControlCatalog/App.xaml.cs | 1 - samples/ControlCatalog/MainView.xaml.cs | 10 +-- src/Avalonia.Themes.Fluent/FluentTheme.cs | 77 +++++++++++++++++++++-- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 227b31bf20..d6ffa04944 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -5,6 +5,7 @@ x:CompileBindings="True" x:Class="ControlCatalog.App"> + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 36b6fc2dcd..085de2ae7a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -96,7 +96,6 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, FluentLight); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index abedda3c85..3898e9da85 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -11,6 +11,7 @@ using Avalonia.Media.Immutable; using Avalonia.Platform; using ControlCatalog.Pages; using ControlCatalog.Models; +using Avalonia.Themes.Fluent; namespace ControlCatalog { @@ -43,14 +44,7 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - Application.Current.Styles[0] = theme switch - { - CatalogTheme.FluentLight => App.FluentLight, - CatalogTheme.FluentDark => App.FluentDark, - CatalogTheme.DefaultLight => App.DefaultLight, - CatalogTheme.DefaultDark => App.DefaultDark, - _ => Application.Current.Styles[0] - }; + (Application.Current.Styles[0] as FluentTheme).Mode = FluentThemeMode.Dark; } }; diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 43b71567fa..812bd22839 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Avalonia.Controls; using Avalonia.Markup.Xaml; +using Avalonia.Markup.Xaml.Styling; using Avalonia.Styling; #nullable enable @@ -22,6 +23,7 @@ namespace Avalonia.Themes.Fluent private readonly Uri _baseUri; private IStyle[]? _loaded; private bool _isLoading; + private FluentThemeMode _mode; /// /// Initializes a new instance of the class. @@ -44,7 +46,20 @@ namespace Avalonia.Themes.Fluent /// /// Gets or sets the mode of the fluent theme (light, dark). /// - public FluentThemeMode Mode { get; set; } + public FluentThemeMode Mode + { + get => _mode; + set + { + if (_mode != value) + { + _mode = value; + (Loaded as Styles)[3] = FluentDark[0]; + (Loaded as Styles)[4] = FluentDark[1]; + } + + } + } public IResourceHost? Owner => (Loaded as IResourceProvider)?.Owner; @@ -58,8 +73,25 @@ namespace Avalonia.Themes.Fluent if (_loaded == null) { _isLoading = true; - var loaded = (IStyle)AvaloniaXamlLoader.Load(GetUri(), _baseUri); - _loaded = new[] { loaded }; + Styles? resultStyle = new Styles(); + + resultStyle.AddRange(SharedStyles); + + if (Mode == FluentThemeMode.Light) + { + for (int i = 0; i < FluentLight.Count; i++) + { + resultStyle.Add(FluentLight[i]); + } + } + else if (Mode == FluentThemeMode.Dark) + { + for (int i = 0; i < FluentDark.Count; i++) + { + resultStyle.Add(FluentDark[i]); + } + } + _loaded = new[] { resultStyle }; _isLoading = false; } @@ -105,10 +137,43 @@ namespace Avalonia.Themes.Fluent void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - private Uri GetUri() => Mode switch + private static Styles SharedStyles = new Styles + { + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml") + } + }; + + private static Styles FluentLight = new Styles { - FluentThemeMode.Dark => new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml", UriKind.Absolute), - _ => new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml", UriKind.Absolute), + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml") + } + }; + private static Styles FluentDark = new Styles + { + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") + }, + new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml") + } }; } } From 032b78cd0698215e084f5352159c77706239429a Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Sun, 19 Dec 2021 11:34:16 +0200 Subject: [PATCH 048/260] wip --- src/Avalonia.Themes.Fluent/FluentTheme.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 812bd22839..73bf5b7226 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -21,7 +21,7 @@ namespace Avalonia.Themes.Fluent public class FluentTheme : IStyle, IResourceProvider { private readonly Uri _baseUri; - private IStyle[]? _loaded; + private IStyle? _loaded; private bool _isLoading; private FluentThemeMode _mode; @@ -91,17 +91,17 @@ namespace Avalonia.Themes.Fluent resultStyle.Add(FluentDark[i]); } } - _loaded = new[] { resultStyle }; + _loaded = resultStyle; _isLoading = false; } - return _loaded?[0]!; + return _loaded; } } bool IResourceNode.HasResources => (Loaded as IResourceProvider)?.HasResources ?? false; - IReadOnlyList IStyle.Children => _loaded ?? Array.Empty(); + IReadOnlyList IStyle.Children => _loaded?.Children ?? Array.Empty(); public event EventHandler OwnerChanged { From 9c8b68ff5d0f5fd6cf5a4158b2aa4c093e6d3047 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Sun, 19 Dec 2021 12:09:08 +0200 Subject: [PATCH 049/260] more refactorings --- src/Avalonia.Themes.Fluent/FluentTheme.cs | 97 ++++++++++++----------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 73bf5b7226..08d9dc317d 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -21,8 +21,11 @@ namespace Avalonia.Themes.Fluent public class FluentTheme : IStyle, IResourceProvider { private readonly Uri _baseUri; - private IStyle? _loaded; + private Styles _fluentDark = new(); + private Styles _fluentLight = new(); + private Styles _sharedStyles = new(); private bool _isLoading; + private IStyle? _loaded; private FluentThemeMode _mode; /// @@ -32,6 +35,7 @@ namespace Avalonia.Themes.Fluent public FluentTheme(Uri baseUri) { _baseUri = baseUri; + InitStyles(baseUri); } /// @@ -41,6 +45,7 @@ namespace Avalonia.Themes.Fluent public FluentTheme(IServiceProvider serviceProvider) { _baseUri = ((IUriContext)serviceProvider.GetService(typeof(IUriContext))).BaseUri; + InitStyles(_baseUri); } /// @@ -54,8 +59,8 @@ namespace Avalonia.Themes.Fluent if (_mode != value) { _mode = value; - (Loaded as Styles)[3] = FluentDark[0]; - (Loaded as Styles)[4] = FluentDark[1]; + (Loaded as Styles)![1] = _fluentDark[0]; + (Loaded as Styles)![2] = _fluentDark[1]; } } @@ -73,23 +78,17 @@ namespace Avalonia.Themes.Fluent if (_loaded == null) { _isLoading = true; - Styles? resultStyle = new Styles(); - - resultStyle.AddRange(SharedStyles); + Styles? resultStyle = new Styles() { _sharedStyles }; if (Mode == FluentThemeMode.Light) { - for (int i = 0; i < FluentLight.Count; i++) - { - resultStyle.Add(FluentLight[i]); - } + resultStyle.Add(_fluentLight[0]); + resultStyle.Add(_fluentLight[1]); } else if (Mode == FluentThemeMode.Dark) { - for (int i = 0; i < FluentDark.Count; i++) - { - resultStyle.Add(FluentDark[i]); - } + resultStyle.Add(_fluentDark[0]); + resultStyle.Add(_fluentDark[1]); } _loaded = resultStyle; _isLoading = false; @@ -137,43 +136,47 @@ namespace Avalonia.Themes.Fluent void IResourceProvider.AddOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.AddOwner(owner); void IResourceProvider.RemoveOwner(IResourceHost owner) => (Loaded as IResourceProvider)?.RemoveOwner(owner); - private static Styles SharedStyles = new Styles + private void InitStyles(Uri baseUri) { - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + _sharedStyles = new Styles { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml") - } - }; + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml") + }, + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml") + }, + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Controls/FluentControls.xaml") + } + }; - private static Styles FluentLight = new Styles - { - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + _fluentLight = new Styles { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml") - } - }; - private static Styles FluentDark = new Styles - { - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") - }, - new StyleInclude(new Uri("resm:Styles?assembly=Avalonia.Themes.Fluent")) + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml") + }, + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml") + } + }; + + _fluentDark = new Styles { - Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml") - } - }; + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml") + }, + new StyleInclude(baseUri) + { + Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml") + } + }; + } } } From 22be08125735ab59155c549f2a534b29bfbf335e Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Sun, 19 Dec 2021 13:10:05 +0200 Subject: [PATCH 050/260] more refactorings --- src/Avalonia.Themes.Fluent/FluentTheme.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 08d9dc317d..1c61c03c97 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -59,8 +59,18 @@ namespace Avalonia.Themes.Fluent if (_mode != value) { _mode = value; - (Loaded as Styles)![1] = _fluentDark[0]; - (Loaded as Styles)![2] = _fluentDark[1]; + if (_mode == FluentThemeMode.Dark) + { + (Loaded as Styles)![1] = _fluentDark[0]; + (Loaded as Styles)![2] = _fluentDark[1]; + } + else + { + (Loaded as Styles)![1] = _fluentLight[0]; + (Loaded as Styles)![2] = _fluentLight[1]; + } + + } } From 2de57da8ff54c43ca49c85105cedeb2733d0ab2a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 19 Dec 2021 23:26:14 +0100 Subject: [PATCH 051/260] Added nullable annotations to Avalonia.Layout. --- src/Avalonia.Layout/AttachedLayout.cs | 6 +- src/Avalonia.Layout/Avalonia.Layout.csproj | 1 + src/Avalonia.Layout/ElementManager.cs | 40 +++++------ src/Avalonia.Layout/FlowLayoutAlgorithm.cs | 68 +++++++++---------- .../IFlowLayoutAlgorithmDelegates.cs | 4 +- src/Avalonia.Layout/LayoutContext.cs | 4 +- src/Avalonia.Layout/LayoutContextAdapter.cs | 2 +- src/Avalonia.Layout/LayoutHelper.cs | 2 +- src/Avalonia.Layout/LayoutQueue.cs | 1 + src/Avalonia.Layout/Layoutable.cs | 2 +- .../NonVirtualizingLayoutContext.cs | 4 +- src/Avalonia.Layout/StackLayout.cs | 22 +++--- src/Avalonia.Layout/UniformGridLayout.cs | 26 +++---- src/Avalonia.Layout/UniformGridLayoutState.cs | 2 +- src/Avalonia.Layout/Utils/ListUtils.cs | 5 -- .../VirtualLayoutContextAdapter.cs | 4 +- .../VirtualizingLayoutContext.cs | 2 +- src/Avalonia.Layout/WrapLayout/UvMeasure.cs | 2 +- src/Avalonia.Layout/WrapLayout/WrapItem.cs | 2 +- src/Avalonia.Layout/WrapLayout/WrapLayout.cs | 6 +- .../WrapLayout/WrapLayoutState.cs | 9 ++- 21 files changed, 107 insertions(+), 107 deletions(-) diff --git a/src/Avalonia.Layout/AttachedLayout.cs b/src/Avalonia.Layout/AttachedLayout.cs index 047c01343f..6c884641f8 100644 --- a/src/Avalonia.Layout/AttachedLayout.cs +++ b/src/Avalonia.Layout/AttachedLayout.cs @@ -12,17 +12,17 @@ namespace Avalonia.Layout /// public abstract class AttachedLayout : AvaloniaObject { - public string LayoutId { get; set; } + public string? LayoutId { get; set; } /// /// Occurs when the measurement state (layout) has been invalidated. /// - public event EventHandler MeasureInvalidated; + public event EventHandler? MeasureInvalidated; /// /// Occurs when the arrange state (layout) has been invalidated. /// - public event EventHandler ArrangeInvalidated; + public event EventHandler? ArrangeInvalidated; /// /// Initializes any per-container state the layout requires when it is attached to an diff --git a/src/Avalonia.Layout/Avalonia.Layout.csproj b/src/Avalonia.Layout/Avalonia.Layout.csproj index 2842ccf3a8..c1cd19c394 100644 --- a/src/Avalonia.Layout/Avalonia.Layout.csproj +++ b/src/Avalonia.Layout/Avalonia.Layout.csproj @@ -9,4 +9,5 @@ + diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index 681d23a61f..039eb52317 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -13,10 +13,10 @@ namespace Avalonia.Layout { internal class ElementManager { - private readonly List _realizedElements = new List(); + private readonly List _realizedElements = new List(); private readonly List _realizedElementLayoutBounds = new List(); private int _firstRealizedDataIndex; - private VirtualizingLayoutContext _context; + private VirtualizingLayoutContext? _context; private bool IsVirtualizingContext { @@ -58,7 +58,7 @@ namespace Avalonia.Layout // Make sure there is enough space for the bounds. // Note: We could optimize when the count becomes smaller, but keeping // it always up to date is the simplest option for now. - _realizedElementLayoutBounds.Resize(count); + _realizedElementLayoutBounds.Resize(count, default); } } } @@ -66,12 +66,12 @@ namespace Avalonia.Layout public int GetRealizedElementCount() { - return IsVirtualizingContext ? _realizedElements.Count : _context.ItemCount; + return IsVirtualizingContext ? _realizedElements.Count : _context!.ItemCount; } public ILayoutable GetAt(int realizedIndex) { - ILayoutable element; + ILayoutable? element; if (IsVirtualizingContext) { @@ -80,7 +80,7 @@ namespace Avalonia.Layout // Sentinel. Create the element now since we need it. int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex); Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "Creating element for sentinal with data index {Index}", dataIndex); - element = _context.GetOrCreateElementAt( + element = _context!.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _realizedElements[realizedIndex] = element; @@ -93,12 +93,12 @@ namespace Avalonia.Layout else { // realizedIndex and dataIndex are the same (everything is realized) - element = _context.GetOrCreateElementAt( + element = _context!.GetOrCreateElementAt( realizedIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } - return element; + return element!; } public void Add(ILayoutable element, int dataIndex) @@ -112,7 +112,7 @@ namespace Avalonia.Layout _realizedElementLayoutBounds.Add(default); } - public void Insert(int realizedIndex, int dataIndex, ILayoutable element) + public void Insert(int realizedIndex, int dataIndex, ILayoutable? element) { if (realizedIndex == 0) { @@ -136,7 +136,7 @@ namespace Avalonia.Layout if (elementRef != null) { - _context.RecycleElement(elementRef); + _context!.RecycleElement(elementRef); } } @@ -203,26 +203,26 @@ namespace Avalonia.Layout else { // Non virtualized - everything is realized - return index >= 0 && index < _context.ItemCount; + return index >= 0 && index < _context!.ItemCount; } } - public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context.ItemCount; + public bool IsIndexValidInData(int currentIndex) => (uint)currentIndex < _context!.ItemCount; - public ILayoutable GetRealizedElement(int dataIndex) + public ILayoutable? GetRealizedElement(int dataIndex) { return IsVirtualizingContext ? GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) : - _context.GetOrCreateElementAt( + _context!.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } - public void EnsureElementRealized(bool forward, int dataIndex, string layoutId) + public void EnsureElementRealized(bool forward, int dataIndex, string? layoutId) { if (IsDataIndexRealized(dataIndex) == false) { - var element = _context.GetOrCreateElementAt( + var element = _context!.GetOrCreateElementAt( dataIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); @@ -273,14 +273,14 @@ namespace Avalonia.Layout { case NotifyCollectionChangedAction.Add: { - OnItemsAdded(args.NewStartingIndex, args.NewItems.Count); + OnItemsAdded(args.NewStartingIndex, args.NewItems!.Count); } break; case NotifyCollectionChangedAction.Replace: { - int oldSize = args.OldItems.Count; - int newSize = args.NewItems.Count; + int oldSize = args.OldItems!.Count; + int newSize = args.NewItems!.Count; int oldStartIndex = args.OldStartingIndex; int newStartIndex = args.NewStartingIndex; @@ -301,7 +301,7 @@ namespace Avalonia.Layout if (elementRef != null) { - _context.RecycleElement(elementRef); + _context!.RecycleElement(elementRef); _realizedElements[realizedIndex] = null; } } diff --git a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs index 63343fd1a7..40b392225f 100644 --- a/src/Avalonia.Layout/FlowLayoutAlgorithm.cs +++ b/src/Avalonia.Layout/FlowLayoutAlgorithm.cs @@ -16,8 +16,8 @@ namespace Avalonia.Layout private Size _lastAvailableSize; private double _lastItemSpacing; private bool _collectionChangePending; - private VirtualizingLayoutContext _context; - private IFlowLayoutAlgorithmDelegates _algorithmCallbacks; + private VirtualizingLayoutContext? _context; + private IFlowLayoutAlgorithmDelegates? _algorithmCallbacks; private Rect _lastExtent; private int _firstRealizedDataIndexInsideRealizationWindow = -1; private int _lastRealizedDataIndexInsideRealizationWindow = -1; @@ -46,7 +46,7 @@ namespace Avalonia.Layout } } - private Rect RealizationRect => IsVirtualizingContext ? _context.RealizationRect : new Rect(Size.Infinity); + private Rect RealizationRect => IsVirtualizingContext ? _context!.RealizationRect : new Rect(Size.Infinity); public void InitializeForContext(VirtualizingLayoutContext context, IFlowLayoutAlgorithmDelegates callbacks) { @@ -76,7 +76,7 @@ namespace Avalonia.Layout int maxItemsPerLine, ScrollOrientation orientation, bool disableVirtualization, - string layoutId) + string? layoutId) { _orientation.ScrollOrientation = orientation; @@ -87,7 +87,7 @@ namespace Avalonia.Layout layoutId, realizationRect); - var suggestedAnchorIndex = _context.RecommendedAnchorIndex; + var suggestedAnchorIndex = _context!.RecommendedAnchorIndex; if (_elementManager.IsIndexValidInData(suggestedAnchorIndex)) { var anchorRealized = _elementManager.IsDataIndexRealized(suggestedAnchorIndex); @@ -124,7 +124,7 @@ namespace Avalonia.Layout VirtualizingLayoutContext context, bool isWrapping, LineAlignment lineAlignment, - string layoutId) + string? layoutId) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: ArrangeLayout", layoutId); ArrangeVirtualizingLayout(finalSize, lineAlignment, isWrapping, layoutId); @@ -149,7 +149,7 @@ namespace Avalonia.Layout Size availableSize, VirtualizingLayoutContext context) { - var measureSize = _algorithmCallbacks.Algorithm_GetMeasureSize(index, availableSize, context); + var measureSize = _algorithmCallbacks!.Algorithm_GetMeasureSize(index, availableSize, context); element.Measure(measureSize); var provisionalArrangeSize = _algorithmCallbacks.Algorithm_GetProvisionalArrangeSize(index, measureSize, element.DesiredSize, context); _algorithmCallbacks.Algorithm_OnElementMeasured(element, index, availableSize, measureSize, element.DesiredSize, provisionalArrangeSize, context); @@ -161,7 +161,7 @@ namespace Avalonia.Layout Size availableSize, bool isWrapping, double minItemSpacing, - string layoutId) + string? layoutId) { int anchorIndex = -1; var anchorPosition= new Point(); @@ -170,7 +170,7 @@ namespace Avalonia.Layout if (!IsVirtualizingContext) { // Non virtualizing host, start generating from the element 0 - anchorIndex = context.ItemCount > 0 ? 0 : -1; + anchorIndex = context!.ItemCount > 0 ? 0 : -1; } else { @@ -183,7 +183,7 @@ namespace Avalonia.Layout _lastItemSpacing != minItemSpacing || _collectionChangePending); - var suggestedAnchorIndex = _context.RecommendedAnchorIndex; + var suggestedAnchorIndex = _context!.RecommendedAnchorIndex; var isAnchorSuggestionValid = suggestedAnchorIndex >= 0 && _elementManager.IsDataIndexRealized(suggestedAnchorIndex); @@ -191,10 +191,10 @@ namespace Avalonia.Layout if (isAnchorSuggestionValid) { Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId}: Using suggested anchor {Anchor}", layoutId, suggestedAnchorIndex); - anchorIndex = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement( + anchorIndex = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement( suggestedAnchorIndex, availableSize, - context).Index; + context!).Index; if (_elementManager.IsDataIndexRealized(anchorIndex)) { @@ -235,7 +235,7 @@ namespace Avalonia.Layout // The anchor is based on the realization window because a connected ItemsRepeater might intersect the realization window // but not the visible window. In that situation, we still need to produce a valid anchor. - var anchorInfo = _algorithmCallbacks.Algorithm_GetAnchorForRealizationRect(availableSize, context); + var anchorInfo = _algorithmCallbacks!.Algorithm_GetAnchorForRealizationRect(availableSize, context!); anchorIndex = anchorInfo.Index; anchorPosition = _orientation.MinorMajorPoint(0, anchorInfo.Offset); } @@ -259,12 +259,12 @@ namespace Avalonia.Layout Logger.TryGet(LogEventLevel.Verbose, "Repeater")?.Log(this, "{LayoutId} Disconnected Window - throwing away all realized elements", layoutId); _elementManager.ClearRealizedRange(); - var anchor = _context.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); + var anchor = _context!.GetOrCreateElementAt(anchorIndex, ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _elementManager.Add(anchor, anchorIndex); } var anchorElement = _elementManager.GetRealizedElement(anchorIndex); - var desiredSize = MeasureElement(anchorElement, anchorIndex, availableSize, _context); + var desiredSize = MeasureElement(anchorElement!, anchorIndex, availableSize, _context!); var layoutBounds = new Rect(anchorPosition.X, anchorPosition.Y, desiredSize.Width, desiredSize.Height); _elementManager.SetLayoutBoundsForDataIndex(anchorIndex, layoutBounds); @@ -296,7 +296,7 @@ namespace Avalonia.Layout double lineSpacing, int maxItemsPerLine, bool disableVirtualization, - string layoutId) + string? layoutId) { if (anchorIndex != -1) { @@ -322,7 +322,7 @@ namespace Avalonia.Layout // Ensure layout element. _elementManager.EnsureElementRealized(direction == GenerateDirection.Forward, currentIndex, layoutId); var currentElement = _elementManager.GetRealizedElement(currentIndex); - var desiredSize = MeasureElement(currentElement, currentIndex, availableSize, _context); + var desiredSize = MeasureElement(currentElement!, currentIndex, availableSize, _context!); ++count; // Lay it out. @@ -333,7 +333,7 @@ namespace Avalonia.Layout if (direction == GenerateDirection.Forward) { double remainingSpace = _orientation.Minor(availableSize) - (_orientation.MinorStart(previousElementBounds) + _orientation.MinorSize(previousElementBounds) + minItemSpacing + _orientation.Minor(desiredSize)); - if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) + if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) { // No more space in this row. wrap to next row. _orientation.SetMinorStart(ref currentBounds, 0); @@ -371,7 +371,7 @@ namespace Avalonia.Layout { // Backward double remainingSpace = _orientation.MinorStart(previousElementBounds) - (_orientation.Minor(desiredSize) + minItemSpacing); - if (countInLine >= maxItemsPerLine || _algorithmCallbacks.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) + if (countInLine >= maxItemsPerLine || _algorithmCallbacks!.Algorithm_ShouldBreakLine(currentIndex, remainingSpace)) { // Does not fit, wrap to the previous row var availableSizeMinor = _orientation.Minor(availableSize); @@ -432,13 +432,13 @@ namespace Avalonia.Layout // account for that element in the indices inside the realization window. if (direction == GenerateDirection.Forward) { - int dataCount = _context.ItemCount; + int dataCount = _context!.ItemCount; _lastRealizedDataIndexInsideRealizationWindow = previousIndex == dataCount - 1 ? dataCount - 1 : previousIndex - 1; _lastRealizedDataIndexInsideRealizationWindow = Math.Max(0, _lastRealizedDataIndexInsideRealizationWindow); } else { - int dataCount = _context.ItemCount; + int dataCount = _context!.ItemCount; _firstRealizedDataIndexInsideRealizationWindow = previousIndex == 0 ? 0 : previousIndex + 1; _firstRealizedDataIndexInsideRealizationWindow = Math.Min(dataCount - 1, _firstRealizedDataIndexInsideRealizationWindow); } @@ -454,7 +454,7 @@ namespace Avalonia.Layout { _elementManager.ClearRealizedRange(); // FlowLayout requires that the anchor is the first element in the row. - var internalAnchor = _algorithmCallbacks.Algorithm_GetAnchorForTargetElement(index, availableSize, context); + var internalAnchor = _algorithmCallbacks!.Algorithm_GetAnchorForTargetElement(index, availableSize, context); // No need to set the position of the anchor. // (0,0) is fine for now since the extent can @@ -487,7 +487,7 @@ namespace Avalonia.Layout } else { - var realizationRect = _context.RealizationRect; + var realizationRect = _context!.RealizationRect; var elementBounds = _elementManager.GetLayoutBoundsForDataIndex(index); var elementMajorStart = _orientation.MajorStart(elementBounds); @@ -510,11 +510,11 @@ namespace Avalonia.Layout return shouldContinue; } - private Rect EstimateExtent(Size availableSize, string layoutId) + private Rect EstimateExtent(Size availableSize, string? layoutId) { - ILayoutable firstRealizedElement = null; + ILayoutable? firstRealizedElement = null; Rect firstBounds = new Rect(); - ILayoutable lastRealizedElement = null; + ILayoutable? lastRealizedElement = null; Rect lastBounds = new Rect(); int firstDataIndex = -1; int lastDataIndex = -1; @@ -531,9 +531,9 @@ namespace Avalonia.Layout lastBounds = _elementManager.GetLayoutBoundsForRealizedIndex(last); } - Rect extent = _algorithmCallbacks.Algorithm_GetExtent( + Rect extent = _algorithmCallbacks!.Algorithm_GetExtent( availableSize, - _context, + _context!, firstRealizedElement, firstDataIndex, firstBounds, @@ -563,7 +563,7 @@ namespace Avalonia.Layout if (_orientation.MajorStart(currentBounds) != currentLineOffset) { // Staring a new line - _algorithmCallbacks.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context); + _algorithmCallbacks!.Algorithm_OnLineArranged(currentDataIndex - countInLine, countInLine, currentLineSize, _context!); countInLine = 0; currentLineOffset = _orientation.MajorStart(currentBounds); currentLineSize = 0; @@ -575,7 +575,7 @@ namespace Avalonia.Layout } // Raise for the last line. - _algorithmCallbacks.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context); + _algorithmCallbacks!.Algorithm_OnLineArranged(_lastRealizedDataIndexInsideRealizationWindow - countInLine + 1, countInLine, currentLineSize, _context!); } } } @@ -584,7 +584,7 @@ namespace Avalonia.Layout Size finalSize, LineAlignment lineAlignment, bool isWrapping, - string layoutId) + string? layoutId) { // Walk through the realized elements one line at a time and // align them, Then call element.Arrange with the arranged bounds. @@ -636,7 +636,7 @@ namespace Avalonia.Layout LineAlignment lineAlignment, bool isWrapping, Size finalSize, - string layoutId) + string? layoutId) { for (int rangeIndex = lineStartIndex; rangeIndex < lineStartIndex + countInLine; ++rangeIndex) { @@ -723,11 +723,11 @@ namespace Avalonia.Layout { if (IsVirtualizingContext) { - _context.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y); + _context!.LayoutOrigin = new Point(_lastExtent.X, _lastExtent.Y); } } - public ILayoutable GetElementIfRealized(int dataIndex) + public ILayoutable? GetElementIfRealized(int dataIndex) { if (_elementManager.IsDataIndexRealized(dataIndex)) { diff --git a/src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs b/src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs index 907a3adf0f..e3eae0caf7 100644 --- a/src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs +++ b/src/Avalonia.Layout/IFlowLayoutAlgorithmDelegates.cs @@ -21,10 +21,10 @@ namespace Avalonia.Layout Rect Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable firstRealized, + ILayoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable lastRealized, + ILayoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds); void Algorithm_OnElementMeasured( diff --git a/src/Avalonia.Layout/LayoutContext.cs b/src/Avalonia.Layout/LayoutContext.cs index dadce58c0c..90bfc0200c 100644 --- a/src/Avalonia.Layout/LayoutContext.cs +++ b/src/Avalonia.Layout/LayoutContext.cs @@ -14,7 +14,7 @@ namespace Avalonia.Layout /// /// Gets or sets an object that represents the state of a layout. /// - public object LayoutState + public object? LayoutState { get => LayoutStateCore; set => LayoutStateCore = value; @@ -23,6 +23,6 @@ namespace Avalonia.Layout /// /// Implements the behavior of in a derived or custom LayoutContext. /// - protected virtual object LayoutStateCore { get; set; } + protected virtual object? LayoutStateCore { get; set; } } } diff --git a/src/Avalonia.Layout/LayoutContextAdapter.cs b/src/Avalonia.Layout/LayoutContextAdapter.cs index 695866df94..25132c2802 100644 --- a/src/Avalonia.Layout/LayoutContextAdapter.cs +++ b/src/Avalonia.Layout/LayoutContextAdapter.cs @@ -16,7 +16,7 @@ namespace Avalonia.Layout _nonVirtualizingContext = nonVirtualizingContext; } - protected override object LayoutStateCore + protected override object? LayoutStateCore { get => _nonVirtualizingContext.LayoutState; set => _nonVirtualizingContext.LayoutState = value; diff --git a/src/Avalonia.Layout/LayoutHelper.cs b/src/Avalonia.Layout/LayoutHelper.cs index 2fe59b49ca..71a2fa085b 100644 --- a/src/Avalonia.Layout/LayoutHelper.cs +++ b/src/Avalonia.Layout/LayoutHelper.cs @@ -101,7 +101,7 @@ namespace Avalonia.Layout if (result == 0 || double.IsNaN(result) || double.IsInfinity(result)) { - throw new Exception($"Invalid LayoutScaling returned from {visualRoot.GetType()}"); + throw new Exception($"Invalid LayoutScaling returned from {visualRoot!.GetType()}"); } return result; diff --git a/src/Avalonia.Layout/LayoutQueue.cs b/src/Avalonia.Layout/LayoutQueue.cs index 1a9eb6b785..24adeb0793 100644 --- a/src/Avalonia.Layout/LayoutQueue.cs +++ b/src/Avalonia.Layout/LayoutQueue.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; namespace Avalonia.Layout { internal class LayoutQueue : IReadOnlyCollection, IDisposable + where T : notnull { private struct Info { diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 7568ea8e09..09e0c4263a 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -794,7 +794,7 @@ namespace Avalonia.Layout /// /// The sender. /// The event args. - private void LayoutManagedLayoutUpdated(object sender, EventArgs e) => _layoutUpdated?.Invoke(this, e); + private void LayoutManagedLayoutUpdated(object? sender, EventArgs e) => _layoutUpdated?.Invoke(this, e); /// /// Tests whether any of a 's properties include negative values, diff --git a/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs index cef551f32e..91348f5e6d 100644 --- a/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs +++ b/src/Avalonia.Layout/NonVirtualizingLayoutContext.cs @@ -12,7 +12,7 @@ namespace Avalonia.Layout /// public abstract class NonVirtualizingLayoutContext : LayoutContext { - private VirtualizingLayoutContext _contextAdapter; + private VirtualizingLayoutContext? _contextAdapter; /// /// Gets the collection of child controls from the container that provides the context. @@ -26,6 +26,6 @@ namespace Avalonia.Layout protected abstract IReadOnlyList ChildrenCore { get; } internal VirtualizingLayoutContext GetVirtualizingContextAdapter() => - _contextAdapter ?? (_contextAdapter = new LayoutContextAdapter(this)); + _contextAdapter ??= new LayoutContextAdapter(this); } } diff --git a/src/Avalonia.Layout/StackLayout.cs b/src/Avalonia.Layout/StackLayout.cs index 4a93c8344f..32e9c4dc8e 100644 --- a/src/Avalonia.Layout/StackLayout.cs +++ b/src/Avalonia.Layout/StackLayout.cs @@ -78,10 +78,10 @@ namespace Avalonia.Layout internal Rect GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable firstRealized, + ILayoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable lastRealized, + ILayoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -89,7 +89,7 @@ namespace Avalonia.Layout // Constants int itemsCount = context.ItemCount; - var stackState = (StackLayoutState)context.LayoutState; + var stackState = (StackLayoutState)context.LayoutState!; double averageElementSize = GetAverageElementSize(availableSize, context, stackState) + Spacing; _orientation.SetMinorSize(ref extent, stackState.MaxArrangeBounds); @@ -131,7 +131,7 @@ namespace Avalonia.Layout { if (context is VirtualizingLayoutContext virtualContext) { - var stackState = (StackLayoutState)virtualContext.LayoutState; + var stackState = (StackLayoutState)virtualContext.LayoutState!; var provisionalArrangeSizeWinRt = provisionalArrangeSize; stackState.OnElementMeasured( index, @@ -177,7 +177,7 @@ namespace Avalonia.Layout if (targetIndex >= 0 && targetIndex < itemsCount) { index = targetIndex; - var state = (StackLayoutState)context.LayoutState; + var state = (StackLayoutState)context.LayoutState!; double averageElementSize = GetAverageElementSize(availableSize, context, state) + Spacing; offset = index * averageElementSize + _orientation.MajorStart(state.FlowAlgorithm.LastExtent); } @@ -188,10 +188,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable firstRealized, + ILayoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable lastRealized, + ILayoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -234,7 +234,7 @@ namespace Avalonia.Layout if (itemsCount > 0) { var realizationRect = context.RealizationRect; - var state = (StackLayoutState)context.LayoutState; + var state = (StackLayoutState)context.LayoutState!; var lastExtent = state.FlowAlgorithm.LastExtent; double averageElementSize = GetAverageElementSize(availableSize, context, state) + Spacing; @@ -279,13 +279,13 @@ namespace Avalonia.Layout protected internal override void UninitializeForContextCore(VirtualizingLayoutContext context) { - var stackState = (StackLayoutState)context.LayoutState; + var stackState = (StackLayoutState)context.LayoutState!; stackState.UninitializeForContext(context); } protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize) { - ((StackLayoutState)context.LayoutState).OnMeasureStart(); + ((StackLayoutState)context.LayoutState!).OnMeasureStart(); var desiredSize = GetFlowAlgorithm(context).Measure( availableSize, @@ -358,6 +358,6 @@ namespace Avalonia.Layout private void InvalidateLayout() => InvalidateMeasure(); - private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState).FlowAlgorithm; + private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((StackLayoutState)context.LayoutState!).FlowAlgorithm; } } diff --git a/src/Avalonia.Layout/UniformGridLayout.cs b/src/Avalonia.Layout/UniformGridLayout.cs index 6b147590ab..508113834a 100644 --- a/src/Avalonia.Layout/UniformGridLayout.cs +++ b/src/Avalonia.Layout/UniformGridLayout.cs @@ -258,7 +258,7 @@ namespace Avalonia.Layout Size availableSize, VirtualizingLayoutContext context) { - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; return new Size(gridState.EffectiveItemWidth, gridState.EffectiveItemHeight); } @@ -268,7 +268,7 @@ namespace Avalonia.Layout Size desiredSize, VirtualizingLayoutContext context) { - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; return new Size(gridState.EffectiveItemWidth, gridState.EffectiveItemHeight); } @@ -285,7 +285,7 @@ namespace Avalonia.Layout var realizationRect = context.RealizationRect; if (itemsCount > 0 && _orientation.MajorSize(realizationRect) > 0) { - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; var lastExtent = gridState.FlowAlgorithm.LastExtent; var itemsPerLine = Math.Min( // note use of unsigned ints Math.Max(1u, (uint)(_orientation.Minor(availableSize) / GetMinorSizeWithSpacing(context))), @@ -324,7 +324,7 @@ namespace Avalonia.Layout Math.Max(1u, _maximumRowsOrColumns)); int indexOfFirstInLine = (targetIndex / itemsPerLine) * itemsPerLine; index = indexOfFirstInLine; - var state = context.LayoutState as UniformGridLayoutState; + var state = (UniformGridLayoutState)context.LayoutState!; offset = _orientation.MajorStart(GetLayoutRectForDataIndex(availableSize, indexOfFirstInLine, state.FlowAlgorithm.LastExtent, context)); } @@ -338,10 +338,10 @@ namespace Avalonia.Layout Rect IFlowLayoutAlgorithmDelegates.Algorithm_GetExtent( Size availableSize, VirtualizingLayoutContext context, - ILayoutable firstRealized, + ILayoutable? firstRealized, int firstRealizedItemIndex, Rect firstRealizedLayoutBounds, - ILayoutable lastRealized, + ILayoutable? lastRealized, int lastRealizedItemIndex, Rect lastRealizedLayoutBounds) { @@ -421,7 +421,7 @@ namespace Avalonia.Layout protected internal override void UninitializeForContextCore(VirtualizingLayoutContext context) { - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; gridState.UninitializeForContext(context); } @@ -429,7 +429,7 @@ namespace Avalonia.Layout { // Set the width and height on the grid state. If the user already set them then use the preset. // If not, we have to measure the first element and get back a size which we're going to be using for the rest of the items. - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; gridState.EnsureElementSize(availableSize, context, _minItemWidth, _minItemHeight, _itemsStretch, Orientation, MinRowSpacing, MinColumnSpacing, _maximumRowsOrColumns); var desiredSize = GetFlowAlgorithm(context).Measure( @@ -467,7 +467,7 @@ namespace Avalonia.Layout // Always invalidate layout to keep the view accurate. InvalidateLayout(); - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; gridState.ClearElementOnDataSourceChange(context, args); } @@ -517,7 +517,7 @@ namespace Avalonia.Layout private double GetMinorSizeWithSpacing(VirtualizingLayoutContext context) { var minItemSpacing = MinItemSpacing; - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; return _orientation.ScrollOrientation == ScrollOrientation.Vertical? gridState.EffectiveItemWidth + minItemSpacing : gridState.EffectiveItemHeight + minItemSpacing; @@ -526,7 +526,7 @@ namespace Avalonia.Layout private double GetMajorSizeWithSpacing(VirtualizingLayoutContext context) { var lineSpacing = LineSpacing; - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; return _orientation.ScrollOrientation == ScrollOrientation.Vertical ? gridState.EffectiveItemHeight + lineSpacing : gridState.EffectiveItemWidth + lineSpacing; @@ -544,7 +544,7 @@ namespace Avalonia.Layout int rowIndex = (int)(index / itemsPerLine); int indexInRow = index - (rowIndex * itemsPerLine); - var gridState = (UniformGridLayoutState)context.LayoutState; + var gridState = (UniformGridLayoutState)context.LayoutState!; Rect bounds = _orientation.MinorMajorRect( indexInRow * GetMinorSizeWithSpacing(context) + _orientation.MinorStart(lastExtent), rowIndex * GetMajorSizeWithSpacing(context) + _orientation.MajorStart(lastExtent), @@ -556,6 +556,6 @@ namespace Avalonia.Layout private void InvalidateLayout() => InvalidateMeasure(); - private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState).FlowAlgorithm; + private FlowLayoutAlgorithm GetFlowAlgorithm(VirtualizingLayoutContext context) => ((UniformGridLayoutState)context.LayoutState!).FlowAlgorithm; } } diff --git a/src/Avalonia.Layout/UniformGridLayoutState.cs b/src/Avalonia.Layout/UniformGridLayoutState.cs index 282bbab1a8..d6b5a30bfc 100644 --- a/src/Avalonia.Layout/UniformGridLayoutState.cs +++ b/src/Avalonia.Layout/UniformGridLayoutState.cs @@ -18,7 +18,7 @@ namespace Avalonia.Layout // If it does not, then we need to do context.GetElement(0) at which point we have requested an element and are on point to clear it. // If we are responsible for clearing element 0 we keep m_cachedFirstElement valid. // If we are not (because FlowLayoutAlgorithm is holding it for us) then we just null out this field and use the one from FlowLayoutAlgorithm. - private ILayoutable _cachedFirstElement; + private ILayoutable? _cachedFirstElement; internal FlowLayoutAlgorithm FlowAlgorithm { get; } = new FlowLayoutAlgorithm(); internal double EffectiveItemWidth { get; private set; } diff --git a/src/Avalonia.Layout/Utils/ListUtils.cs b/src/Avalonia.Layout/Utils/ListUtils.cs index eb2480acd3..9ca8bb0525 100644 --- a/src/Avalonia.Layout/Utils/ListUtils.cs +++ b/src/Avalonia.Layout/Utils/ListUtils.cs @@ -23,10 +23,5 @@ namespace Avalonia.Layout.Utils list.AddRange(Enumerable.Repeat(value, size - cur)); } } - - public static void Resize(this List list, int count) - { - Resize(list, count, default); - } } } diff --git a/src/Avalonia.Layout/VirtualLayoutContextAdapter.cs b/src/Avalonia.Layout/VirtualLayoutContextAdapter.cs index 80ccee2114..f068187523 100644 --- a/src/Avalonia.Layout/VirtualLayoutContextAdapter.cs +++ b/src/Avalonia.Layout/VirtualLayoutContextAdapter.cs @@ -6,14 +6,14 @@ namespace Avalonia.Layout public class VirtualLayoutContextAdapter : NonVirtualizingLayoutContext { private readonly VirtualizingLayoutContext _virtualizingContext; - private ChildrenCollection _children; + private ChildrenCollection? _children; public VirtualLayoutContextAdapter(VirtualizingLayoutContext virtualizingContext) { _virtualizingContext = virtualizingContext; } - protected override object LayoutStateCore + protected override object? LayoutStateCore { get => _virtualizingContext.LayoutState; set => _virtualizingContext.LayoutState = value; diff --git a/src/Avalonia.Layout/VirtualizingLayoutContext.cs b/src/Avalonia.Layout/VirtualizingLayoutContext.cs index 079b91a90f..f48c9b0ef2 100644 --- a/src/Avalonia.Layout/VirtualizingLayoutContext.cs +++ b/src/Avalonia.Layout/VirtualizingLayoutContext.cs @@ -43,7 +43,7 @@ namespace Avalonia.Layout /// public abstract class VirtualizingLayoutContext : LayoutContext { - private NonVirtualizingLayoutContext _contextAdapter; + private NonVirtualizingLayoutContext? _contextAdapter; /// /// Gets the number of items in the data. diff --git a/src/Avalonia.Layout/WrapLayout/UvMeasure.cs b/src/Avalonia.Layout/WrapLayout/UvMeasure.cs index e10e6bd0f8..91fa459acb 100644 --- a/src/Avalonia.Layout/WrapLayout/UvMeasure.cs +++ b/src/Avalonia.Layout/WrapLayout/UvMeasure.cs @@ -27,7 +27,7 @@ namespace Avalonia.Layout } } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj is UvMeasure measure) { diff --git a/src/Avalonia.Layout/WrapLayout/WrapItem.cs b/src/Avalonia.Layout/WrapLayout/WrapItem.cs index 2f0b39a2d1..e823ade6d4 100644 --- a/src/Avalonia.Layout/WrapLayout/WrapItem.cs +++ b/src/Avalonia.Layout/WrapLayout/WrapItem.cs @@ -20,6 +20,6 @@ namespace Avalonia.Layout public UvMeasure? Position { get; internal set; } - public ILayoutable Element { get; internal set; } + public ILayoutable? Element { get; internal set; } } } diff --git a/src/Avalonia.Layout/WrapLayout/WrapLayout.cs b/src/Avalonia.Layout/WrapLayout/WrapLayout.cs index ded2afc3dd..d12a83d1d7 100644 --- a/src/Avalonia.Layout/WrapLayout/WrapLayout.cs +++ b/src/Avalonia.Layout/WrapLayout/WrapLayout.cs @@ -88,7 +88,7 @@ namespace Avalonia.Layout /// protected internal override void OnItemsChangedCore(VirtualizingLayoutContext context, object source, NotifyCollectionChangedEventArgs args) { - var state = (WrapLayoutState)context.LayoutState; + var state = (WrapLayoutState)context.LayoutState!; switch (args.Action) { @@ -126,7 +126,7 @@ namespace Avalonia.Layout var realizationBounds = new UvBounds(Orientation, context.RealizationRect); var position = UvMeasure.Zero; - var state = (WrapLayoutState)context.LayoutState; + var state = (WrapLayoutState)context.LayoutState!; if (state.Orientation != Orientation) { state.SetOrientation(Orientation); @@ -261,7 +261,7 @@ namespace Avalonia.Layout var spacingMeasure = new UvMeasure(Orientation, HorizontalSpacing, VerticalSpacing); var realizationBounds = new UvBounds(Orientation, context.RealizationRect); - var state = (WrapLayoutState)context.LayoutState; + var state = (WrapLayoutState)context.LayoutState!; bool Arrange(WrapItem item, bool isLast = false) { if (item.Measure.HasValue == false) diff --git a/src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs b/src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs index 7955ea983c..720ab96830 100644 --- a/src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs +++ b/src/Avalonia.Layout/WrapLayout/WrapLayoutState.cs @@ -62,8 +62,11 @@ namespace Avalonia.Layout internal void SetOrientation(Orientation orientation) { - foreach (var item in _items.Where(i => i.Measure.HasValue)) + foreach (var item in _items) { + if (!item.Measure.HasValue) + continue; + UvMeasure measure = item.Measure.Value; double v = measure.V; measure.V = measure.U; @@ -120,10 +123,10 @@ namespace Avalonia.Layout } lastPosition = item.Position; - maxV = Math.Max(maxV, item.Measure.Value.V); + maxV = Math.Max(maxV, item.Measure!.Value.V); } - double totalHeight = lastPosition.Value.V + maxV; + double totalHeight = lastPosition!.Value.V + maxV; if (calculateAverage) { return (totalHeight / itemCount) * _context.ItemCount; From ecb981da2d1ca386a350d108b72e30835d77f044 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 20 Dec 2021 09:30:10 +0100 Subject: [PATCH 052/260] Small tidy up. --- src/Avalonia.Layout/ElementManager.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Layout/ElementManager.cs b/src/Avalonia.Layout/ElementManager.cs index 039eb52317..9a033ca447 100644 --- a/src/Avalonia.Layout/ElementManager.cs +++ b/src/Avalonia.Layout/ElementManager.cs @@ -75,7 +75,9 @@ namespace Avalonia.Layout if (IsVirtualizingContext) { - if (_realizedElements[realizedIndex] == null) + element = _realizedElements[realizedIndex]; + + if (element == null) { // Sentinel. Create the element now since we need it. int dataIndex = GetDataIndexFromRealizedRangeIndex(realizedIndex); @@ -85,10 +87,6 @@ namespace Avalonia.Layout ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); _realizedElements[realizedIndex] = element; } - else - { - element = _realizedElements[realizedIndex]; - } } else { @@ -98,7 +96,7 @@ namespace Avalonia.Layout ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle); } - return element!; + return element; } public void Add(ILayoutable element, int dataIndex) From f9956e5ba96eedfe5e2872c84125e80f5ad14faa Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Mon, 20 Dec 2021 10:50:48 +0100 Subject: [PATCH 053/260] fixes:(XMLDOC): update xml comment (#6999) * fixes:(XMLDOC): update xml comment * Update src/Avalonia.Controls/Grid.cs Co-authored-by: Steven Kirk Co-authored-by: Steven Kirk --- src/Avalonia.Controls/Grid.cs | 2 +- src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 4e60b52f83..74401c450a 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -978,7 +978,7 @@ namespace Avalonia.Controls /// width is not registered in columns. /// Passed through to MeasureCell. /// When "true" cells' desired height is not registered in rows. - /// return true when desired size has changed + /// When the method exits, indicates whether the desired size has changed. private void MeasureCellsGroup( int cellsHead, Size referenceSize, diff --git a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs index 32818dfdd2..4a7d4d4ac8 100644 --- a/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs +++ b/src/Skia/Avalonia.Skia/Gpu/ISkiaGpu.cs @@ -20,8 +20,8 @@ namespace Avalonia.Skia /// /// Creates an offscreen render target surface /// - /// size in pixels - /// current Skia render session + /// size in pixels. + /// An optional custom render session. ISkiaSurface TryCreateSurface(PixelSize size, ISkiaGpuRenderSession session); } From 1aa0d5b68c2660bb2038ea6460ad41c2dbd7d4fa Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 20 Dec 2021 15:09:52 +0100 Subject: [PATCH 054/260] fixes: null annotation --- src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs | 2 +- src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs index 620477b6cd..a9fc18474c 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorGrammar.cs @@ -515,7 +515,7 @@ namespace Avalonia.Markup.Parsers public string Value { get; set; } = string.Empty; - public override bool Equals(object obj) + public override bool Equals(object? obj) { return obj is AttachedPropertySyntax syntax && syntax.Xmlns == Xmlns diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index e2d6570790..1b7ee9025e 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -119,7 +119,7 @@ namespace Avalonia.Markup.Parsers } { - object typedValue; + object? typedValue; if (TypeUtilities.TryConvert( targetAttachedProperty.PropertyType, From f8f4b91d93a4b0545c9b2045913ada25991db9b1 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 17 Dec 2021 20:10:56 +0100 Subject: [PATCH 055/260] Update Calendar in CalendarDatePicker Popup properly - Use `TemplateBinding` - Reflect changes of the blackout dates collection --- .../Calendar/CalendarDatePicker.cs | 63 ++++++++++++++----- .../CalendarDatePicker.xaml | 6 +- .../Controls/CalendarDatePicker.xaml | 6 +- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index af3fdbd662..ced72651af 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -1,10 +1,11 @@ -// (c) Copyright Microsoft Corporation. +// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using Avalonia.Controls.Primitives; @@ -425,9 +426,6 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(true); - DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e)); - DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e)); - DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e)); IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e)); SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e)); SelectedDateFormatProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateFormatChanged(e)); @@ -443,6 +441,7 @@ namespace Avalonia.Controls FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek; _defaultText = string.Empty; DisplayDate = DateTime.Today; + BlackoutDates.CollectionChanged += BlackoutDates_CollectionChanged; } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -459,18 +458,12 @@ namespace Avalonia.Controls if (_calendar != null) { _calendar.SelectionMode = CalendarSelectionMode.SingleDate; - _calendar.SelectedDate = SelectedDate; - SetCalendarDisplayDate(DisplayDate); - SetCalendarDisplayDateStart(DisplayDateStart); - SetCalendarDisplayDateEnd(DisplayDateEnd); _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp; _calendar.DisplayDateChanged += Calendar_DisplayDateChanged; _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged; _calendar.PointerReleased += Calendar_PointerReleased; _calendar.KeyDown += Calendar_KeyDown; - //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged); - //_calendar.IsTabStop = true; var currentBlackoutDays = BlackoutDates; BlackoutDates = _calendar.BlackoutDates; @@ -670,11 +663,6 @@ namespace Avalonia.Controls var addedDate = (DateTime?)e.NewValue; var removedDate = (DateTime?)e.OldValue; - if (_calendar != null && addedDate != _calendar.SelectedDate) - { - _calendar.SelectedDate = addedDate; - } - if (SelectedDate != null) { DateTime day = SelectedDate.Value; @@ -1200,5 +1188,50 @@ namespace Avalonia.Controls return newD; } } + + private void BlackoutDates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (CalendarDateRange item in e.NewItems) + { + _calendar.BlackoutDates.Add(item); + } + break; + + case NotifyCollectionChangedAction.Move: + // We do not need to handle this case as the result will not have an effect. + break; + case NotifyCollectionChangedAction.Remove: + foreach (CalendarDateRange item in e.OldItems) + { + _calendar.BlackoutDates.Remove(item); + } + break; + + case NotifyCollectionChangedAction.Replace: + foreach (CalendarDateRange item in e.OldItems) + { + _calendar.BlackoutDates.Remove(item); + } + foreach (CalendarDateRange item in e.NewItems) + { + _calendar.BlackoutDates.Add(item); + } + break; + + case NotifyCollectionChangedAction.Reset: + _calendar.BlackoutDates.Clear(); + foreach (CalendarDateRange item in BlackoutDates) + { + _calendar.BlackoutDates.Add(item); + } + break; + + default: + throw new ArgumentOutOfRangeException(nameof(e.Action), e.Action, "Invalid NotifyCollectionChangedAction"); + } + } } } diff --git a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml index 57b77f70ea..3e2e497d2c 100644 --- a/src/Avalonia.Themes.Default/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Default/CalendarDatePicker.xaml @@ -115,7 +115,11 @@ StaysOpen="False"> + IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}" + SelectedDate="{TemplateBinding SelectedDate, Mode=TwoWay}" + DisplayDate="{TemplateBinding DisplayDate}" + DisplayDateStart="{TemplateBinding DisplayDateStart}" + DisplayDateEnd="{TemplateBinding DisplayDateEnd}" /> diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index 995cca2b82..6c4e94caf1 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -129,7 +129,11 @@ IsLightDismissEnabled="True"> + IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}" + SelectedDate="{TemplateBinding SelectedDate, Mode=TwoWay}" + DisplayDate="{TemplateBinding DisplayDate}" + DisplayDateStart="{TemplateBinding DisplayDateStart}" + DisplayDateEnd="{TemplateBinding DisplayDateEnd}" /> From f69e3e642efc67f431476370c024338b7d04f1b2 Mon Sep 17 00:00:00 2001 From: Tim U Date: Mon, 20 Dec 2021 09:52:14 +0100 Subject: [PATCH 056/260] Fix: BlackoutDates test was failing - Revert listen to colleciton changes as the collection is already synced - Setup the needed bindings in the test control --- .../Calendar/CalendarDatePicker.cs | 47 ------------------- .../CalendarDatePickerTests.cs | 6 ++- 2 files changed, 5 insertions(+), 48 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index ced72651af..acfcfecdd8 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -5,7 +5,6 @@ using System; using System.Collections.ObjectModel; -using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using Avalonia.Controls.Primitives; @@ -441,7 +440,6 @@ namespace Avalonia.Controls FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek; _defaultText = string.Empty; DisplayDate = DateTime.Today; - BlackoutDates.CollectionChanged += BlackoutDates_CollectionChanged; } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -1188,50 +1186,5 @@ namespace Avalonia.Controls return newD; } } - - private void BlackoutDates_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (CalendarDateRange item in e.NewItems) - { - _calendar.BlackoutDates.Add(item); - } - break; - - case NotifyCollectionChangedAction.Move: - // We do not need to handle this case as the result will not have an effect. - break; - case NotifyCollectionChangedAction.Remove: - foreach (CalendarDateRange item in e.OldItems) - { - _calendar.BlackoutDates.Remove(item); - } - break; - - case NotifyCollectionChangedAction.Replace: - foreach (CalendarDateRange item in e.OldItems) - { - _calendar.BlackoutDates.Remove(item); - } - foreach (CalendarDateRange item in e.NewItems) - { - _calendar.BlackoutDates.Add(item); - } - break; - - case NotifyCollectionChangedAction.Reset: - _calendar.BlackoutDates.Clear(); - foreach (CalendarDateRange item in BlackoutDates) - { - _calendar.BlackoutDates.Add(item); - } - break; - - default: - throw new ArgumentOutOfRangeException(nameof(e.Action), e.Action, "Invalid NotifyCollectionChangedAction"); - } - } } } diff --git a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs index d77c7b87fa..a73e14939d 100644 --- a/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/CalendarDatePickerTests.cs @@ -105,7 +105,11 @@ namespace Avalonia.Controls.UnitTests var calendar = new Calendar { - Name = "PART_Calendar" + Name = "PART_Calendar", + [!Calendar.SelectedDateProperty] = control[!CalendarDatePicker.SelectedDateProperty], + [!Calendar.DisplayDateProperty] = control[!CalendarDatePicker.DisplayDateProperty], + [!Calendar.DisplayDateStartProperty] = control[!CalendarDatePicker.DisplayDateStartProperty], + [!Calendar.DisplayDateEndProperty] = control[!CalendarDatePicker.DisplayDateEndProperty] }.RegisterInNameScope(scope); var popup = new Popup From 0f3b97a7525ec209a946fa89d0262b25888a4a8a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 20 Dec 2021 20:14:34 +0300 Subject: [PATCH 057/260] Use pointer position relative to the scroll gesture recognizer target --- .../GestureRecognizers/ScrollGestureRecognizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index 84a26a0cc3..7532676f18 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -85,7 +85,7 @@ namespace Avalonia.Input.GestureRecognizers { if (e.Pointer == _tracking) { - var rootPoint = e.GetPosition(null); + var rootPoint = e.GetPosition(_target); if (!_scrolling) { if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) From dea04d8a7455cee215860ea6ab1de063ee1b121e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Dec 2021 17:20:57 +0000 Subject: [PATCH 058/260] Update readme.md --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index a8a6399f2f..9895d3b68f 100644 --- a/readme.md +++ b/readme.md @@ -3,6 +3,8 @@
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) +Tips: BTC: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx + ## 📖 About AvaloniaUI Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. From e8f6f72ebaa610c9448282cb06659c31a8f691c2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 20 Dec 2021 17:26:54 +0000 Subject: [PATCH 059/260] Update readme.md --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 9895d3b68f..3efc3211ee 100644 --- a/readme.md +++ b/readme.md @@ -4,6 +4,7 @@ [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) Tips: BTC: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx +This will be shared with the community and awarded for significant contributions. ## 📖 About AvaloniaUI From e7286a8df87ca4ec1940bcd46f07dff926295aa2 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 20 Dec 2021 19:57:13 +0100 Subject: [PATCH 060/260] Remove unused private voids --- .../Calendar/CalendarDatePicker.cs | 52 ------------------- 1 file changed, 52 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index acfcfecdd8..a856ee071c 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -578,58 +578,6 @@ namespace Avalonia.Controls SetSelectedDate(); } - private void SetCalendarDisplayDate(DateTime value) - { - if (DateTimeHelper.CompareYearMonth(_calendar.DisplayDate, value) != 0) - { - _calendar.DisplayDate = DisplayDate; - if (DateTime.Compare(_calendar.DisplayDate, DisplayDate) != 0) - { - DisplayDate = _calendar.DisplayDate; - } - } - } - private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e) - { - if (_calendar != null) - { - var value = (DateTime)e.NewValue; - SetCalendarDisplayDate(value); - } - } - private void SetCalendarDisplayDateStart(DateTime? value) - { - _calendar.DisplayDateStart = value; - if (_calendar.DisplayDateStart.HasValue && DisplayDateStart.HasValue && DateTime.Compare(_calendar.DisplayDateStart.Value, DisplayDateStart.Value) != 0) - { - DisplayDateStart = _calendar.DisplayDateStart; - } - } - private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e) - { - if (_calendar != null) - { - var value = (DateTime?)e.NewValue; - SetCalendarDisplayDateStart(value); - } - } - private void SetCalendarDisplayDateEnd(DateTime? value) - { - _calendar.DisplayDateEnd = value; - if (_calendar.DisplayDateEnd.HasValue && DisplayDateEnd.HasValue && DateTime.Compare(_calendar.DisplayDateEnd.Value, DisplayDateEnd.Value) != 0) - { - DisplayDateEnd = _calendar.DisplayDateEnd; - } - - } - private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e) - { - if (_calendar != null) - { - var value = (DateTime?)e.NewValue; - SetCalendarDisplayDateEnd(value); - } - } private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e) { var oldValue = (bool)e.OldValue; From 077364f2555f4e9fc3657e150cd07abb63e9d737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Su=C3=A1rez=20Ruiz?= Date: Tue, 21 Dec 2021 17:47:13 +0100 Subject: [PATCH 061/260] Allow launch ControlCatalog in Android --- .../ControlCatalog.Android.csproj | 8 ++- .../Avalonia.Android/AndroidPlatform.cs | 3 +- src/Android/Avalonia.Android/Stubs.cs | 56 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 src/Android/Avalonia.Android/Stubs.cs diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 1a68c4d732..617b6b6ab0 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -32,7 +32,7 @@ True False False - armeabi-v7a;x86 + armeabi-v7a;x86;x86_64 Xamarin False False @@ -51,7 +51,7 @@ True False False - armeabi-v7a,x86 + armeabi-v7a,x86;x86_64 Xamarin False False @@ -125,6 +125,10 @@ {42472427-4774-4c81-8aff-9f27b8e31721} Avalonia.Layout + + {c42d2fc1-a531-4ed4-84b9-89aec7c962fc} + Avalonia.Themes.Fluent + {eb582467-6abb-43a1-b052-e981ba910e3a} Avalonia.Visuals diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 5e11d8eab2..57f22e7a05 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -43,11 +43,12 @@ namespace Avalonia.Android AvaloniaLocator.CurrentMutable .Bind().ToTransient() .Bind().ToTransient() + .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() .Bind().ToConstant(Instance) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton() diff --git a/src/Android/Avalonia.Android/Stubs.cs b/src/Android/Avalonia.Android/Stubs.cs new file mode 100644 index 0000000000..f36c01dbc8 --- /dev/null +++ b/src/Android/Avalonia.Android/Stubs.cs @@ -0,0 +1,56 @@ +using System; +using System.IO; +using Avalonia.Platform; + +namespace Avalonia.Android +{ + class WindowingPlatformStub : IWindowingPlatform + { + public IWindowImpl CreateWindow() => throw new NotSupportedException(); + + public IWindowImpl CreateEmbeddableWindow() => throw new NotSupportedException(); + + public ITrayIconImpl CreateTrayIcon() => null; + } + + class PlatformIconLoaderStub : IPlatformIconLoader + { + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) + { + using (var stream = new MemoryStream()) + { + bitmap.Save(stream); + return LoadIcon(stream); + } + } + + public IWindowIconImpl LoadIcon(Stream stream) + { + var ms = new MemoryStream(); + stream.CopyTo(ms); + return new IconStub(ms); + } + + public IWindowIconImpl LoadIcon(string fileName) + { + using (var file = File.Open(fileName, FileMode.Open)) + return LoadIcon(file); + } + } + + public class IconStub : IWindowIconImpl + { + private readonly MemoryStream _ms; + + public IconStub(MemoryStream stream) + { + _ms = stream; + } + + public void Save(Stream outputStream) + { + _ms.Position = 0; + _ms.CopyTo(outputStream); + } + } +} From 03d6ca9374f932a7d411dc3a9b2003fc0cb277eb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 22 Dec 2021 00:33:26 -0500 Subject: [PATCH 062/260] Better fix for #6830 Previous numericupdown bugfix (see https://github.com/AvaloniaUI/Avalonia/pull/6830/files) introduced another bug (see https://github.com/AvaloniaUI/Avalonia/discussions/7230#discussioncomment-1855837). This PR solves both problems. --- src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml index 1e58259358..36ab07e3e3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml @@ -38,7 +38,7 @@ BorderBrush="{TemplateBinding BorderBrush}" CornerRadius="{TemplateBinding CornerRadius}" Padding="0" - MinWidth="{TemplateBinding MinWidth}" + MinWidth="0" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" AllowSpin="{TemplateBinding AllowSpin}" @@ -50,7 +50,7 @@ BorderBrush="Transparent" Margin="-1" Padding="{TemplateBinding Padding}" - MinWidth="{TemplateBinding MinWidth}" + MinWidth="0" Foreground="{TemplateBinding Foreground}" FontSize="{TemplateBinding FontSize}" Watermark="{TemplateBinding Watermark}" From daee30758e478bdd1d222b242de2012f280863b6 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Wed, 22 Dec 2021 11:50:25 +0200 Subject: [PATCH 063/260] Update readme --- readme.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 3efc3211ee..b38552a010 100644 --- a/readme.md +++ b/readme.md @@ -3,9 +3,6 @@
[![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) [![downloads](https://img.shields.io/nuget/dt/avalonia)](https://www.nuget.org/packages/Avalonia) [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) ![Size](https://img.shields.io/github/repo-size/avaloniaui/avalonia.svg) -Tips: BTC: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx -This will be shared with the community and awarded for significant contributions. - ## 📖 About AvaloniaUI Avalonia is a cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows via .NET Framework and .NET Core, Linux via Xorg, macOS. Avalonia is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. @@ -78,6 +75,12 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou Avalonia is licenced under the [MIT licence](licence.md). +## Support Avalonia + +**BTC**: bc1q05wx78qemgy9x6ytl5ljk2xrt00yqargyjm8gx + +This will be shared with the community and awarded for significant contributions. + ### Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)] From 328199b2d22aafcb194f961508ac8b59d394b820 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 14:02:28 +0100 Subject: [PATCH 064/260] Added nullable annotations to Avalonia.Styling. --- src/Avalonia.Styling/Avalonia.Styling.csproj | 1 + .../Controls/ChildNameScope.cs | 12 ++--- src/Avalonia.Styling/Controls/INameScope.cs | 4 +- src/Avalonia.Styling/Controls/NameScope.cs | 30 ++++++----- .../Controls/NameScopeExtensions.cs | 28 +++++------ .../Controls/NameScopeLocator.cs | 8 +-- .../Controls/PseudoClassesExtensions.cs | 2 +- .../Controls/ResourceDictionary.cs | 2 +- .../Controls/ResourceNodeExtensions.cs | 6 +-- src/Avalonia.Styling/INamed.cs | 2 +- .../LogicalTree/ControlLocator.cs | 16 +++--- src/Avalonia.Styling/LogicalTree/ILogical.cs | 6 +-- .../LogicalTree/LogicalExtensions.cs | 26 +++++----- .../LogicalTreeAttachmentEventArgs.cs | 7 +-- src/Avalonia.Styling/StyledElement.cs | 18 +++---- .../Styling/Activators/NthChildActivator.cs | 2 +- .../Styling/Activators/StyleClassActivator.cs | 2 +- src/Avalonia.Styling/Styling/ChildSelector.cs | 6 +-- src/Avalonia.Styling/Styling/OrSelector.cs | 2 +- .../Styling/PropertyEqualsSelector.cs | 2 +- src/Avalonia.Styling/Styling/Selectors.cs | 50 +++++++++++-------- src/Avalonia.Styling/Styling/Styles.cs | 14 +++--- .../Styling/TemplateSelector.cs | 6 +-- src/Avalonia.Visuals/Visual.cs | 2 +- .../Markup/Parsers/SelectorParser.cs | 6 +-- 25 files changed, 132 insertions(+), 128 deletions(-) diff --git a/src/Avalonia.Styling/Avalonia.Styling.csproj b/src/Avalonia.Styling/Avalonia.Styling.csproj index 20b3183b00..3548749846 100644 --- a/src/Avalonia.Styling/Avalonia.Styling.csproj +++ b/src/Avalonia.Styling/Avalonia.Styling.csproj @@ -9,4 +9,5 @@ + diff --git a/src/Avalonia.Styling/Controls/ChildNameScope.cs b/src/Avalonia.Styling/Controls/ChildNameScope.cs index e6707e71db..58114a57fd 100644 --- a/src/Avalonia.Styling/Controls/ChildNameScope.cs +++ b/src/Avalonia.Styling/Controls/ChildNameScope.cs @@ -15,20 +15,20 @@ namespace Avalonia.Controls public void Register(string name, object element) => _inner.Register(name, element); - public SynchronousCompletionAsyncResult FindAsync(string name) + public SynchronousCompletionAsyncResult FindAsync(string name) { var found = Find(name); if (found != null) - return new SynchronousCompletionAsyncResult(found); + return new SynchronousCompletionAsyncResult(found); // Not found and both current and parent scope are in completed state if(IsCompleted) - return new SynchronousCompletionAsyncResult(null); + return new SynchronousCompletionAsyncResult(null); return DoFindAsync(name); } - public SynchronousCompletionAsyncResult DoFindAsync(string name) + public SynchronousCompletionAsyncResult DoFindAsync(string name) { - var src = new SynchronousCompletionAsyncResultSource(); + var src = new SynchronousCompletionAsyncResultSource(); void ParentSearch() { @@ -56,7 +56,7 @@ namespace Avalonia.Controls return src.AsyncResult; } - public object Find(string name) + public object? Find(string name) { var found = _inner.Find(name); if (found != null) diff --git a/src/Avalonia.Styling/Controls/INameScope.cs b/src/Avalonia.Styling/Controls/INameScope.cs index 2d5295fe45..1ca7db2f37 100644 --- a/src/Avalonia.Styling/Controls/INameScope.cs +++ b/src/Avalonia.Styling/Controls/INameScope.cs @@ -22,14 +22,14 @@ namespace Avalonia.Controls /// /// The name. /// The element, or null if the name was not found. - SynchronousCompletionAsyncResult FindAsync(string name); + SynchronousCompletionAsyncResult FindAsync(string name); /// /// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack /// /// The name. /// The element, or null if the name was not found. - object Find(string name); + object? Find(string name); /// /// Marks the name scope as completed, no further registrations will be allowed diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs index 62a04eac8b..77f98f85c4 100644 --- a/src/Avalonia.Styling/Controls/NameScope.cs +++ b/src/Avalonia.Styling/Controls/NameScope.cs @@ -22,8 +22,8 @@ namespace Avalonia.Controls private readonly Dictionary _inner = new Dictionary(); - private readonly Dictionary> _pendingSearches = - new Dictionary>(); + private readonly Dictionary> _pendingSearches = + new Dictionary>(); /// /// Gets the value of the attached on a styled element. @@ -32,7 +32,7 @@ namespace Avalonia.Controls /// The value of the NameScope attached property. public static INameScope GetNameScope(StyledElement styled) { - Contract.Requires(styled != null); + _ = styled ?? throw new ArgumentNullException(nameof(styled)); return styled.GetValue(NameScopeProperty); } @@ -44,7 +44,7 @@ namespace Avalonia.Controls /// The value to set. public static void SetNameScope(StyledElement styled, INameScope value) { - Contract.Requires(styled != null); + _ = styled ?? throw new ArgumentNullException(nameof(styled)); styled.SetValue(NameScopeProperty, value); } @@ -54,12 +54,11 @@ namespace Avalonia.Controls { if (IsCompleted) throw new InvalidOperationException("NameScope is completed, no further registrations are allowed"); - Contract.Requires(name != null); - Contract.Requires(element != null); - object existing; + _ = name ?? throw new ArgumentNullException(nameof(name)); + _ = element ?? throw new ArgumentNullException(nameof(element)); - if (_inner.TryGetValue(name, out existing)) + if (_inner.TryGetValue(name, out var existing)) { if (existing != element) { @@ -77,27 +76,26 @@ namespace Avalonia.Controls } } - public SynchronousCompletionAsyncResult FindAsync(string name) + public SynchronousCompletionAsyncResult FindAsync(string name) { var found = Find(name); if (found != null) - return new SynchronousCompletionAsyncResult(found); + return new SynchronousCompletionAsyncResult(found); if (IsCompleted) - return new SynchronousCompletionAsyncResult((object)null); + return new SynchronousCompletionAsyncResult(null); if (!_pendingSearches.TryGetValue(name, out var tcs)) // We are intentionally running continuations synchronously here - _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource(); + _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource(); return tcs.AsyncResult; } /// - public object Find(string name) + public object? Find(string name) { - Contract.Requires(name != null); + _ = name ?? throw new ArgumentNullException(nameof(name)); - object result; - _inner.TryGetValue(name, out result); + _inner.TryGetValue(name, out var result); return result; } diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs index 75630711b8..3895b6ceb9 100644 --- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs +++ b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs @@ -17,11 +17,11 @@ namespace Avalonia.Controls /// The name scope. /// The name. /// The named element or null if not found. - public static T Find(this INameScope nameScope, string name) + public static T? Find(this INameScope nameScope, string name) where T : class { - Contract.Requires(nameScope != null); - Contract.Requires(name != null); + _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope)); + _ = name ?? throw new ArgumentNullException(nameof(name)); var result = nameScope.Find(name); @@ -31,7 +31,7 @@ namespace Avalonia.Controls $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'."); } - return (T)result; + return (T?)result; } /// @@ -41,11 +41,11 @@ namespace Avalonia.Controls /// The control to take the name scope from. /// The name. /// The named element or null if not found. - public static T Find(this ILogical anchor, string name) + public static T? Find(this ILogical anchor, string name) where T : class { - Contract.Requires(anchor != null); - Contract.Requires(name != null); + _ = anchor ?? throw new ArgumentNullException(nameof(anchor)); + _ = name ?? throw new ArgumentNullException(nameof(name)); var styledAnchor = anchor as StyledElement; if (styledAnchor == null) return null; @@ -64,8 +64,8 @@ namespace Avalonia.Controls public static T Get(this INameScope nameScope, string name) where T : class { - Contract.Requires(nameScope != null); - Contract.Requires(name != null); + _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope)); + _ = name ?? throw new ArgumentNullException(nameof(name)); var result = nameScope.Find(name); @@ -94,9 +94,9 @@ namespace Avalonia.Controls public static T Get(this ILogical anchor, string name) where T : class { - Contract.Requires(anchor != null); - Contract.Requires(name != null); - + _ = anchor ?? throw new ArgumentNullException(nameof(anchor)); + _ = name ?? throw new ArgumentNullException(nameof(name)); + var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor); if (nameScope == null) throw new InvalidOperationException( @@ -105,9 +105,9 @@ namespace Avalonia.Controls return nameScope.Get(name); } - public static INameScope FindNameScope(this ILogical control) + public static INameScope? FindNameScope(this ILogical control) { - Contract.Requires(control != null); + _ = control ?? throw new ArgumentNullException(nameof(control)); var scope = control.GetSelfAndLogicalAncestors() .OfType() diff --git a/src/Avalonia.Styling/Controls/NameScopeLocator.cs b/src/Avalonia.Styling/Controls/NameScopeLocator.cs index 51f4c5c4eb..f0ce7f8a5b 100644 --- a/src/Avalonia.Styling/Controls/NameScopeLocator.cs +++ b/src/Avalonia.Styling/Controls/NameScopeLocator.cs @@ -11,9 +11,9 @@ namespace Avalonia.Controls /// /// The scope relative from which the object should be resolved. /// The name of the object to find. - public static IObservable Track(INameScope scope, string name) + public static IObservable Track(INameScope scope, string name) { - return new NeverEndingSynchronousCompletionAsyncResultObservable(scope.FindAsync(name)); + return new NeverEndingSynchronousCompletionAsyncResultObservable(scope.FindAsync(name)); } // This class is implemented in such weird way because for some reason @@ -22,7 +22,7 @@ namespace Avalonia.Controls private class NeverEndingSynchronousCompletionAsyncResultObservable : IObservable { - private T _value; + private T? _value; private SynchronousCompletionAsyncResult? _asyncResult; public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult task) @@ -47,7 +47,7 @@ namespace Avalonia.Controls observer.OnNext(_asyncResult.Value.GetResult()); }); else - observer.OnNext(_value); + observer.OnNext(_value!); return Disposable.Empty; } diff --git a/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs b/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs index 7b28d1a911..4cddc8c8fb 100644 --- a/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs +++ b/src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls /// True to add the pseudoclass or false to remove. public static void Set(this IPseudoClasses classes, string name, bool value) { - Contract.Requires(classes != null); + _ = classes ?? throw new ArgumentNullException(nameof(classes)); if (value) { diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index e797f8cf8b..3af14daa83 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -177,7 +177,7 @@ namespace Avalonia.Controls } } - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty); } diff --git a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs index be1069a4da..513b3f2424 100644 --- a/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls observer.OnNext(Convert(_target.FindResource(_key))); } - private void ResourcesChanged(object sender, ResourcesChangedEventArgs e) + private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) { PublishNext(Convert(_target.FindResource(_key))); } @@ -159,7 +159,7 @@ namespace Avalonia.Controls } } - private void OwnerChanged(object sender, EventArgs e) + private void OwnerChanged(object? sender, EventArgs e) { if (_owner is object) { @@ -176,7 +176,7 @@ namespace Avalonia.Controls PublishNext(); } - private void ResourcesChanged(object sender, ResourcesChangedEventArgs e) + private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) { PublishNext(); } diff --git a/src/Avalonia.Styling/INamed.cs b/src/Avalonia.Styling/INamed.cs index 1f8d269b07..df83f3b6ef 100644 --- a/src/Avalonia.Styling/INamed.cs +++ b/src/Avalonia.Styling/INamed.cs @@ -8,6 +8,6 @@ namespace Avalonia /// /// Gets the element name. /// - string Name { get; } + string? Name { get; } } } diff --git a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs index 6d0302ace4..f60ed5aab8 100644 --- a/src/Avalonia.Styling/LogicalTree/ControlLocator.cs +++ b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs @@ -9,19 +9,19 @@ namespace Avalonia.LogicalTree /// public static class ControlLocator { - public static IObservable Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null) + public static IObservable Track(ILogical relativeTo, int ancestorLevel, Type? ancestorType = null) { return new ControlTracker(relativeTo, ancestorLevel, ancestorType); } - private class ControlTracker : LightweightObservableBase + private class ControlTracker : LightweightObservableBase { private readonly ILogical _relativeTo; private readonly int _ancestorLevel; - private readonly Type _ancestorType; - private ILogical _value; + private readonly Type? _ancestorType; + private ILogical? _value; - public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType) + public ControlTracker(ILogical relativeTo, int ancestorLevel, Type? ancestorType) { _relativeTo = relativeTo; _ancestorLevel = ancestorLevel; @@ -43,18 +43,18 @@ namespace Avalonia.LogicalTree _value = null; } - protected override void Subscribed(IObserver observer, bool first) + protected override void Subscribed(IObserver observer, bool first) { observer.OnNext(_value); } - private void Attached(object sender, LogicalTreeAttachmentEventArgs e) + private void Attached(object? sender, LogicalTreeAttachmentEventArgs e) { Update(); PublishNext(_value); } - private void Detached(object sender, LogicalTreeAttachmentEventArgs e) + private void Detached(object? sender, LogicalTreeAttachmentEventArgs e) { _value = null; PublishNext(null); diff --git a/src/Avalonia.Styling/LogicalTree/ILogical.cs b/src/Avalonia.Styling/LogicalTree/ILogical.cs index 9c4223618f..caff3d8150 100644 --- a/src/Avalonia.Styling/LogicalTree/ILogical.cs +++ b/src/Avalonia.Styling/LogicalTree/ILogical.cs @@ -12,12 +12,12 @@ namespace Avalonia.LogicalTree /// /// Raised when the control is attached to a rooted logical tree. /// - event EventHandler AttachedToLogicalTree; + event EventHandler? AttachedToLogicalTree; /// /// Raised when the control is detached from a rooted logical tree. /// - event EventHandler DetachedFromLogicalTree; + event EventHandler? DetachedFromLogicalTree; /// /// Gets a value indicating whether the element is attached to a rooted logical tree. @@ -27,7 +27,7 @@ namespace Avalonia.LogicalTree /// /// Gets the logical parent. /// - ILogical LogicalParent { get; } + ILogical? LogicalParent { get; } /// /// Gets the logical children. diff --git a/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs b/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs index 458ab0fce2..74720c0a77 100644 --- a/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs +++ b/src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs @@ -15,14 +15,14 @@ namespace Avalonia.LogicalTree /// The logical's ancestors. public static IEnumerable GetLogicalAncestors(this ILogical logical) { - Contract.Requires(logical != null); + _ = logical ?? throw new ArgumentNullException(nameof(logical)); - logical = logical.LogicalParent; + ILogical? l = logical.LogicalParent; - while (logical != null) + while (l != null) { - yield return logical; - logical = logical.LogicalParent; + yield return l; + l = l.LogicalParent; } } @@ -48,14 +48,14 @@ 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) { return null; } - ILogical parent = includeSelf ? logical : logical.LogicalParent; + var parent = includeSelf ? logical : logical.LogicalParent; while (parent != 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) { @@ -140,7 +140,7 @@ namespace Avalonia.LogicalTree /// /// The logical. /// The parent, or null if the logical is unparented. - public static ILogical GetLogicalParent(this ILogical logical) + public static ILogical? GetLogicalParent(this ILogical logical) { return logical.LogicalParent; } @@ -153,7 +153,7 @@ namespace Avalonia.LogicalTree /// /// The parent, or null if the logical is unparented or its parent is not of type . /// - public static T GetLogicalParent(this ILogical logical) where T : class + public static T? GetLogicalParent(this ILogical logical) where T : class { return logical.LogicalParent as T; } @@ -165,7 +165,7 @@ namespace Avalonia.LogicalTree /// The logical siblings. public static IEnumerable GetLogicalSiblings(this ILogical logical) { - ILogical parent = logical.LogicalParent; + var parent = logical.LogicalParent; if (parent != null) { @@ -187,7 +187,7 @@ namespace Avalonia.LogicalTree /// public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) { - ILogical current = target?.LogicalParent; + var current = target?.LogicalParent; while (current != null) { @@ -202,7 +202,7 @@ namespace Avalonia.LogicalTree return false; } - private static T FindDescendantOfTypeCore(ILogical logical) where T : class + private static T? FindDescendantOfTypeCore(ILogical logical) where T : class { var logicalChildren = logical.LogicalChildren; var logicalChildrenCount = logicalChildren.Count; diff --git a/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs b/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs index 891724599c..826fc5b2a4 100644 --- a/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs +++ b/src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs @@ -19,11 +19,8 @@ namespace Avalonia.LogicalTree ILogical source, ILogical parent) { - Contract.Requires(root != null); - Contract.Requires(source != null); - - Root = root; - Source = source; + Root = root ?? throw new ArgumentNullException(nameof(root)); + Source = source ?? throw new ArgumentNullException(nameof(source)); Parent = parent; } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 2292f5c518..5f498623e1 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -427,7 +427,7 @@ namespace Avalonia if (_logicalRoot != null) { - var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old); + var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old!); OnDetachedFromLogicalTreeCore(e); } @@ -435,7 +435,7 @@ namespace Avalonia if (newRoot is object) { - var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent); + var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent!); OnAttachedToLogicalTreeCore(e); } else if (parent is null) @@ -495,21 +495,21 @@ namespace Avalonia DetachStylesFromThisAndDescendents(allStyles); } - protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: - SetLogicalParent(e.NewItems); + SetLogicalParent(e.NewItems!); break; case NotifyCollectionChangedAction.Remove: - ClearLogicalParent(e.OldItems); + ClearLogicalParent(e.OldItems!); break; case NotifyCollectionChangedAction.Replace: - ClearLogicalParent(e.OldItems); - SetLogicalParent(e.NewItems); + ClearLogicalParent(e.OldItems!); + SetLogicalParent(e.NewItems!); break; case NotifyCollectionChangedAction.Reset: @@ -729,7 +729,7 @@ namespace Avalonia for (var i = 0; i < count; i++) { - var logical = (ILogical) children[i]; + var logical = (ILogical) children[i]!; if (logical.LogicalParent is null) { @@ -744,7 +744,7 @@ namespace Avalonia for (var i = 0; i < count; i++) { - var logical = (ILogical) children[i]; + var logical = (ILogical) children[i]!; if (logical.LogicalParent == this) { diff --git a/src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs b/src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs index 803809a8ce..6f54cd5904 100644 --- a/src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs +++ b/src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs @@ -37,7 +37,7 @@ namespace Avalonia.Styling.Activators _provider.ChildIndexChanged -= ChildIndexChanged; } - private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e) + private void ChildIndexChanged(object? sender, ChildIndexChangedEventArgs e) { // Run matching again if: // 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index. diff --git a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs index 7906a29cb5..98d3f16a0a 100644 --- a/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs +++ b/src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs @@ -66,7 +66,7 @@ namespace Avalonia.Styling.Activators _classes.CollectionChanged -= ClassesChangedHandler; } - private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e) + private void ClassesChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Move) { diff --git a/src/Avalonia.Styling/Styling/ChildSelector.cs b/src/Avalonia.Styling/Styling/ChildSelector.cs index 85e7aaabde..5c92182b80 100644 --- a/src/Avalonia.Styling/Styling/ChildSelector.cs +++ b/src/Avalonia.Styling/Styling/ChildSelector.cs @@ -6,7 +6,7 @@ namespace Avalonia.Styling internal class ChildSelector : Selector { private readonly Selector _parent; - private string _selectorString; + private string? _selectorString; public ChildSelector(Selector parent) { @@ -25,7 +25,7 @@ namespace Avalonia.Styling public override bool IsCombinator => true; /// - public override Type TargetType => null; + public override Type? TargetType => null; public override string ToString() { @@ -64,6 +64,6 @@ namespace Avalonia.Styling } } - protected override Selector MovePrevious() => null; + protected override Selector? MovePrevious() => null; } } diff --git a/src/Avalonia.Styling/Styling/OrSelector.cs b/src/Avalonia.Styling/Styling/OrSelector.cs index 8251915504..3d6db9b01e 100644 --- a/src/Avalonia.Styling/Styling/OrSelector.cs +++ b/src/Avalonia.Styling/Styling/OrSelector.cs @@ -120,7 +120,7 @@ namespace Avalonia.Styling } else { - while (!result.IsAssignableFrom(selector.TargetType)) + while (result is not null && !result.IsAssignableFrom(selector.TargetType)) { result = result.BaseType; } diff --git a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs index ebdfff222a..1cd1a650ef 100644 --- a/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs @@ -109,7 +109,7 @@ namespace Avalonia.Styling var converter = TypeDescriptor.GetConverter(propertyType); if (converter?.CanConvertFrom(valueType) == true) { - return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value)); + return Equals(propertyValue, converter.ConvertFrom(null, CultureInfo.InvariantCulture, value!)); } return false; diff --git a/src/Avalonia.Styling/Styling/Selectors.cs b/src/Avalonia.Styling/Styling/Selectors.cs index 64d0a0e96b..7c66469cf1 100644 --- a/src/Avalonia.Styling/Styling/Selectors.cs +++ b/src/Avalonia.Styling/Styling/Selectors.cs @@ -25,10 +25,14 @@ namespace Avalonia.Styling /// The previous selector. /// The name of the style class. /// The selector. - public static Selector Class(this Selector previous, string name) + public static Selector Class(this Selector? previous, string name) { - Contract.Requires(name != null); - Contract.Requires(!string.IsNullOrWhiteSpace(name)); + _ = name ?? throw new ArgumentNullException(nameof(name)); + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Name may not be empty", nameof(name)); + } var tac = previous as TypeNameAndClassSelector; @@ -48,7 +52,7 @@ namespace Avalonia.Styling /// /// The previous selector. /// The selector. - public static Selector Descendant(this Selector previous) + public static Selector Descendant(this Selector? previous) { return new DescendantSelector(previous); } @@ -59,9 +63,9 @@ namespace Avalonia.Styling /// The previous selector. /// The type. /// The selector. - public static Selector Is(this Selector previous, Type type) + public static Selector Is(this Selector? previous, Type type) { - Contract.Requires(type != null); + _ = type ?? throw new ArgumentNullException(nameof(type)); return TypeNameAndClassSelector.Is(previous, type); } @@ -72,7 +76,7 @@ namespace Avalonia.Styling /// The type. /// The previous selector. /// The selector. - public static Selector Is(this Selector previous) where T : IStyleable + public static Selector Is(this Selector? previous) where T : IStyleable { return previous.Is(typeof(T)); } @@ -83,10 +87,14 @@ namespace Avalonia.Styling /// The previous selector. /// The name. /// The selector. - public static Selector Name(this Selector previous, string name) + public static Selector Name(this Selector? previous, string name) { - Contract.Requires(name != null); - Contract.Requires(!string.IsNullOrWhiteSpace(name)); + _ = name ?? throw new ArgumentNullException(nameof(name)); + + if (string.IsNullOrWhiteSpace(name)) + { + throw new ArgumentException("Name may not be empty", nameof(name)); + } var tac = previous as TypeNameAndClassSelector; @@ -107,7 +115,7 @@ namespace Avalonia.Styling /// The previous selector. /// The selector to be not-ed. /// The selector. - public static Selector Not(this Selector previous, Func argument) + public static Selector Not(this Selector? previous, Func argument) { return new NotSelector(previous, argument(null)); } @@ -118,7 +126,7 @@ namespace Avalonia.Styling /// The previous selector. /// The selector to be not-ed. /// The selector. - public static Selector Not(this Selector previous, Selector argument) + public static Selector Not(this Selector? previous, Selector argument) { return new NotSelector(previous, argument); } @@ -126,7 +134,7 @@ namespace Avalonia.Styling /// /// /// The selector. - public static Selector NthChild(this Selector previous, int step, int offset) + public static Selector NthChild(this Selector? previous, int step, int offset) { return new NthChildSelector(previous, step, offset); } @@ -134,7 +142,7 @@ namespace Avalonia.Styling /// /// /// The selector. - public static Selector NthLastChild(this Selector previous, int step, int offset) + public static Selector NthLastChild(this Selector? previous, int step, int offset) { return new NthLastChildSelector(previous, step, offset); } @@ -145,9 +153,9 @@ namespace Avalonia.Styling /// The previous selector. /// The type. /// The selector. - public static Selector OfType(this Selector previous, Type type) + public static Selector OfType(this Selector? previous, Type type) { - Contract.Requires(type != null); + _ = type ?? throw new ArgumentNullException(nameof(type)); return TypeNameAndClassSelector.OfType(previous, type); } @@ -158,7 +166,7 @@ namespace Avalonia.Styling /// The type. /// The previous selector. /// The selector. - public static Selector OfType(this Selector previous) where T : IStyleable + public static Selector OfType(this Selector? previous) where T : IStyleable { return previous.OfType(typeof(T)); } @@ -191,9 +199,9 @@ namespace Avalonia.Styling /// The property. /// The property value. /// The selector. - public static Selector PropertyEquals(this Selector previous, AvaloniaProperty property, object value) + public static Selector PropertyEquals(this Selector? previous, AvaloniaProperty property, object? value) { - Contract.Requires(property != null); + _ = property ?? throw new ArgumentNullException(nameof(property)); return new PropertyEqualsSelector(previous, property, value); } @@ -205,9 +213,9 @@ namespace Avalonia.Styling /// The property. /// The property value. /// The selector. - public static Selector PropertyEquals(this Selector previous, AvaloniaProperty property, object value) + public static Selector PropertyEquals(this Selector? previous, AvaloniaProperty property, object? value) { - Contract.Requires(property != null); + _ = property ?? throw new ArgumentNullException(nameof(property)); return new PropertyEqualsSelector(previous, property, value); } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index c752bdfeb8..81502f1570 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -262,7 +262,7 @@ namespace Avalonia.Styling } } - private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { static IReadOnlyList ToReadOnlyList(IList list) { @@ -282,7 +282,7 @@ namespace Avalonia.Styling { for (var i = 0; i < items.Count; ++i) { - var style = (IStyle)items[i]; + var style = (IStyle)items[i]!; if (Owner is object && style is IResourceProvider resourceProvider) { @@ -299,7 +299,7 @@ namespace Avalonia.Styling { for (var i = 0; i < items.Count; ++i) { - var style = (IStyle)items[i]; + var style = (IStyle)items[i]!; if (Owner is object && style is IResourceProvider resourceProvider) { @@ -315,14 +315,14 @@ namespace Avalonia.Styling switch (e.Action) { case NotifyCollectionChangedAction.Add: - Add(e.NewItems); + Add(e.NewItems!); break; case NotifyCollectionChangedAction.Remove: - Remove(e.OldItems); + Remove(e.OldItems!); break; case NotifyCollectionChangedAction.Replace: - Remove(e.OldItems); - Add(e.NewItems); + Remove(e.OldItems!); + Add(e.NewItems!); break; case NotifyCollectionChangedAction.Reset: throw new InvalidOperationException("Reset should not be called on Styles."); diff --git a/src/Avalonia.Styling/Styling/TemplateSelector.cs b/src/Avalonia.Styling/Styling/TemplateSelector.cs index 5ea8defeda..e8051efa6d 100644 --- a/src/Avalonia.Styling/Styling/TemplateSelector.cs +++ b/src/Avalonia.Styling/Styling/TemplateSelector.cs @@ -5,7 +5,7 @@ namespace Avalonia.Styling internal class TemplateSelector : Selector { private readonly Selector _parent; - private string _selectorString; + private string? _selectorString; public TemplateSelector(Selector parent) { @@ -24,7 +24,7 @@ namespace Avalonia.Styling public override bool IsCombinator => true; /// - public override Type TargetType => null; + public override Type? TargetType => null; public override string ToString() { @@ -48,6 +48,6 @@ namespace Avalonia.Styling return _parent.Match(templatedParent, subscribe); } - protected override Selector MovePrevious() => null; + protected override Selector? MovePrevious() => null; } } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 78c6d9c057..324b253a0f 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -377,7 +377,7 @@ namespace Avalonia } } - protected override void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + protected override void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.LogicalChildrenCollectionChanged(sender, e); VisualRoot?.Renderer?.RecalculateChildren(this); diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs index 1b7ee9025e..5b6522064a 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs @@ -138,16 +138,16 @@ namespace Avalonia.Markup.Parsers break; case SelectorGrammar.ChildSyntax child: - result = result.Child(); + result = result!.Child(); break; case SelectorGrammar.DescendantSyntax descendant: result = result.Descendant(); break; case SelectorGrammar.TemplateSyntax template: - result = result.Template(); + result = result!.Template(); break; case SelectorGrammar.NotSyntax not: - result = result.Not(x => Create(not.Argument)); + result = result.Not(x => Create(not.Argument)!); break; case SelectorGrammar.NthChildSyntax nth: result = result.NthChild(nth.Step, nth.Offset); From e84b5f824475a05ab54a9e49b8246b42bb1aa4e7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 15:07:55 +0100 Subject: [PATCH 065/260] Added AvaloniaLocation.GetRequiredService. Using the pattern from elsewhere e.g. https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.dependencyinjection.serviceproviderserviceextensions.getrequiredservice?view=dotnet-plat-ext-6.0 --- src/Avalonia.Base/AvaloniaLocator.cs | 10 ++++++++++ src/Avalonia.Controls/SystemDialog.cs | 9 +++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaLocator.cs b/src/Avalonia.Base/AvaloniaLocator.cs index 0510852ea7..8ec245507b 100644 --- a/src/Avalonia.Base/AvaloniaLocator.cs +++ b/src/Avalonia.Base/AvaloniaLocator.cs @@ -125,6 +125,16 @@ namespace Avalonia { return (T?) resolver.GetService(typeof (T)); } + + public static object GetRequiredService(this IAvaloniaDependencyResolver resolver, Type t) + { + return resolver.GetService(t) ?? throw new InvalidOperationException($"Unable to locate '{t}'."); + } + + public static T GetRequiredService(this IAvaloniaDependencyResolver resolver) + { + return (T?)resolver.GetService(typeof(T)) ?? throw new InvalidOperationException($"Unable to locate '{typeof(T)}'."); + } } } diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs index 3fac10b2d1..3487f427d7 100644 --- a/src/Avalonia.Controls/SystemDialog.cs +++ b/src/Avalonia.Controls/SystemDialog.cs @@ -66,8 +66,7 @@ namespace Avalonia.Controls { if(parent == null) throw new ArgumentNullException(nameof(parent)); - var service = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate ISystemDialogImpl."); + var service = AvaloniaLocator.Current.GetRequiredService(); return (await service.ShowFileDialogAsync(this, parent) ?? Array.Empty()).FirstOrDefault(); } @@ -95,8 +94,7 @@ namespace Avalonia.Controls { if(parent == null) throw new ArgumentNullException(nameof(parent)); - var service = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate ISystemDialogImpl."); + var service = AvaloniaLocator.Current.GetRequiredService(); return service.ShowFileDialogAsync(this, parent); } } @@ -125,8 +123,7 @@ namespace Avalonia.Controls { if(parent == null) throw new ArgumentNullException(nameof(parent)); - var service = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate ISystemDialogImpl."); + var service = AvaloniaLocator.Current.GetRequiredService(); return service.ShowFolderDialogAsync(this, parent); } } From 1fbcd61e6f83f6d827af66148e8e8a34d06bb818 Mon Sep 17 00:00:00 2001 From: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Wed, 22 Dec 2021 16:19:10 +0200 Subject: [PATCH 066/260] Fix DoubleTapped on touch (#7213) DoubleTapped now can be triggered by touch Fixed DoubleTapped triggering logic, previously it could be triggered only when you click twice, didn't work when you click 4 times,6, etc. Fixed Tapped to be triggered only once when you double-click, now it triggers Tapped once and DoubleTapped once instead of triggering Tapped two times and DoubleTapped once.Matches UWP behaviour. --- src/Avalonia.Input/Gestures.cs | 18 +- src/Avalonia.Input/PointerEventArgs.cs | 9 +- src/Avalonia.Input/TouchDevice.cs | 44 ++- .../TouchDeviceTests.cs | 272 ++++++++++++++++++ 4 files changed, 323 insertions(+), 20 deletions(-) create mode 100644 tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 639b4ef117..a5ee558f47 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -6,6 +6,7 @@ namespace Avalonia.Input { public static class Gestures { + private static bool s_isDoubleTapped = false; public static readonly RoutedEvent TappedEvent = RoutedEvent.Register( "Tapped", RoutingStrategies.Bubble, @@ -81,20 +82,23 @@ namespace Avalonia.Input var e = (PointerPressedEventArgs)ev; var visual = (IVisual)ev.Source; -#pragma warning disable CS0618 // Type or member is obsolete - var clickCount = e.ClickCount; -#pragma warning restore CS0618 // Type or member is obsolete - if (clickCount <= 1) + if (e.ClickCount <= 1) { + s_isDoubleTapped = false; s_lastPress.SetTarget(ev.Source); } - else if (clickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) + else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) { if (s_lastPress.TryGetTarget(out var target) && target == e.Source) { + s_isDoubleTapped = true; e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); } } + else + { + s_isDoubleTapped = false; + } } } @@ -112,7 +116,9 @@ namespace Avalonia.Input { e.Source.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); } - else + //s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called. + //This behaviour matches UWP behaviour. + else if (s_isDoubleTapped == false) { e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e)); } diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index ba39f7ca8e..8c86cd4637 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -114,7 +114,7 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { - private readonly int _obsoleteClickCount; + private readonly int _clickCount; public PointerPressedEventArgs( IInteractive source, @@ -123,15 +123,14 @@ namespace Avalonia.Input ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, - int obsoleteClickCount = 1) + int clickCount = 1) : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) { - _obsoleteClickCount = obsoleteClickCount; + _clickCount = clickCount; } - [Obsolete("Use DoubleTapped event or Gestures.DoubleRightTapped attached event")] - public int ClickCount => _obsoleteClickCount; + public int ClickCount => _clickCount; [Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")] public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton(); diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index d6ad836f37..0f832d9add 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Avalonia.Input.Raw; -using Avalonia.VisualTree; +using Avalonia.Platform; namespace Avalonia.Input { @@ -16,7 +16,9 @@ namespace Avalonia.Input { private readonly Dictionary _pointers = new Dictionary(); private bool _disposed; - + private int _clickCount; + private Rect _lastClickRect; + private ulong _lastClickTime; KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) => (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask); @@ -27,10 +29,10 @@ namespace Avalonia.Input rv |= RawInputModifiers.LeftMouseButton; return rv; } - + public void ProcessRawEvent(RawInputEventArgs ev) { - if(_disposed) + if (_disposed) return; var args = (RawTouchEventArgs)ev; if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) @@ -43,16 +45,40 @@ namespace Avalonia.Input PointerType.Touch, _pointers.Count == 0); pointer.Capture(hit); } - + var target = pointer.Captured ?? args.Root; if (args.Type == RawPointerEventType.TouchBegin) { + if (_pointers.Count > 1) + { + _clickCount = 1; + _lastClickTime = 0; + _lastClickRect = new Rect(); + } + else + { + var settings = AvaloniaLocator.Current.GetService(); + if (settings == null) + { + throw new Exception("IPlatformSettings can not be null"); + } + if (!_lastClickRect.Contains(args.Position) + || ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds) + { + _clickCount = 0; + } + ++_clickCount; + _lastClickTime = ev.Timestamp; + _lastClickRect = new Rect(args.Position, new Size()) + .Inflate(new Thickness(16, 16)); + } + target.RaiseEvent(new PointerPressedEventArgs(target, pointer, args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, true), PointerUpdateKind.LeftButtonPressed), - GetKeyModifiers(args.InputModifiers))); + GetKeyModifiers(args.InputModifiers), _clickCount)); } if (args.Type == RawPointerEventType.TouchEnd) @@ -84,12 +110,12 @@ namespace Avalonia.Input GetKeyModifiers(args.InputModifiers))); } - + } public void Dispose() { - if(_disposed) + if (_disposed) return; var values = _pointers.Values.ToList(); _pointers.Clear(); @@ -97,6 +123,6 @@ namespace Avalonia.Input foreach (var p in values) p.Dispose(); } - + } } diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs new file mode 100644 index 0000000000..6c4416be47 --- /dev/null +++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs @@ -0,0 +1,272 @@ +using System; +using Avalonia.Input.Raw; +using Avalonia.Platform; +using Avalonia.UnitTests; +using Moq; +using Xunit; + +namespace Avalonia.Input.UnitTests +{ + public class TouchDeviceTests + { + [Fact] + public void Tapped_Event_Is_Fired_With_Touch() + { + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isTapped = false; + var executedTimes = 0; + root.Tapped += (a, e) => + { + isTapped = true; + executedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + Assert.True(isTapped); + Assert.Equal(1, executedTimes); + } + } + + [Fact] + public void DoubleTapped_Event_Is_Fired_With_Touch() + { + var platformSettingsMock = new Mock(); + platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(platformSettingsMock.Object); + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1); + Assert.Equal(1, tappedExecutedTimes); + Assert.True(isDoubleTapped); + Assert.Equal(1, doubleTappedExecutedTimes); + } + } + + [Theory] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + [InlineData(5)] + public void PointerPressed_Counts_Clicks_Correctly(int clickCount) + { + var platformSettingsMock = new Mock(); + platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(platformSettingsMock.Object); + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var pointerPressedExecutedTimes = 0; + var pointerPressedClicks = 0; + root.PointerPressed += (a, e) => + { + pointerPressedClicks = e.ClickCount; + pointerPressedExecutedTimes++; + }; + for (int i = 0; i < clickCount; i++) + { + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i); + } + + Assert.Equal(clickCount, pointerPressedExecutedTimes); + Assert.Equal(pointerPressedClicks, clickCount); + } + } + + [Fact] + public void DoubleTapped_Not_Fired_When_Click_Too_Late() + { + var platformSettingsMock = new Mock(); + platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(platformSettingsMock.Object); + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + TapOnce(InputManager.Instance, touchDevice, root, 21, 1); + Assert.Equal(2, tappedExecutedTimes); + Assert.False(isDoubleTapped); + Assert.Equal(0, doubleTappedExecutedTimes); + } + } + + [Fact] + public void DoubleTapped_Not_Fired_When_Second_Click_Is_From_Different_Touch_Contact() + { + var tmp = new Mock(); + tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(tmp.Object); + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); + Assert.Equal(2, tappedExecutedTimes); + Assert.False(isDoubleTapped); + Assert.Equal(0, doubleTappedExecutedTimes); + } + } + + [Fact] + public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts() + { + var tmp = new Mock(); + tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(tmp.Object); + using (UnitTestApplication.Start( + new TestServices(inputManager: new InputManager()))) + { + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var pointerPressedExecutedTimes = 0; + var tappedExecutedTimes = 0; + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + root.PointerPressed += (a, e) => + { + pointerPressedExecutedTimes++; + switch (pointerPressedExecutedTimes) + { + case <= 2: + Assert.True(e.ClickCount == 1); + break; + case 3: + Assert.True(e.ClickCount == 2); + break; + case 4: + Assert.True(e.ClickCount == 3); + break; + case 5: + Assert.True(e.ClickCount == 4); + break; + case 6: + Assert.True(e.ClickCount == 5); + break; + case 7: + Assert.True(e.ClickCount == 1); + break; + case 8: + Assert.True(e.ClickCount == 1); + break; + case 9: + Assert.True(e.ClickCount == 2); + break; + default: + break; + } + }; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8); + Assert.Equal(6, tappedExecutedTimes); + Assert.Equal(9, pointerPressedExecutedTimes); + Assert.True(isDoubleTapped); + Assert.Equal(3, doubleTappedExecutedTimes); + + } + } + private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds) + { + for (int i = 0; i < touchPointIds.Length; i++) + { + inputManager.ProcessInput(new RawTouchEventArgs(device, 0, + root, + type, + new Point(0, 0), + RawInputModifiers.None, + touchPointIds[i])); + } + } + + + private static void TapOnce(IInputManager inputManager, TouchDevice device, IInputRoot root, ulong timestamp = 0, long touchPointId = 0) + { + inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp, + root, + RawPointerEventType.TouchBegin, + new Point(0, 0), + RawInputModifiers.None, + touchPointId)); + inputManager.ProcessInput(new RawTouchEventArgs(device, timestamp, + root, + RawPointerEventType.TouchEnd, + new Point(0, 0), + RawInputModifiers.None, + touchPointId)); + } + } +} From a0c10cd6bf6c4e2be2e0c8170a690df0bc8d989e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 15:23:05 +0100 Subject: [PATCH 067/260] Use GetRequiredService throughout. --- src/Avalonia.Base/Threading/Dispatcher.cs | 6 +----- src/Avalonia.Visuals/Animation/RenderLoopClock.cs | 4 +--- src/Avalonia.Visuals/Media/CombinedGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/EllipseGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs | 7 ++----- src/Avalonia.Visuals/Media/FormattedText.cs | 3 +-- src/Avalonia.Visuals/Media/GeometryGroup.cs | 3 +-- src/Avalonia.Visuals/Media/GlyphRun.cs | 3 +-- src/Avalonia.Visuals/Media/Imaging/Bitmap.cs | 3 +-- src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs | 3 +-- src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs | 3 +-- src/Avalonia.Visuals/Media/LineGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/PathGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/PolylineGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/RectangleGeometry.cs | 3 +-- src/Avalonia.Visuals/Media/StreamGeometry.cs | 3 +-- src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs | 3 +-- src/Avalonia.X11/X11CursorFactory.cs | 6 ++---- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 3 +-- src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs | 3 +-- src/Windows/Avalonia.Win32/FramebufferManager.cs | 3 +-- 21 files changed, 23 insertions(+), 51 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index 949c11fbe0..ffc013268d 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -56,11 +56,7 @@ namespace Avalonia.Threading /// public void MainLoop(CancellationToken cancellationToken) { - var platform = AvaloniaLocator.Current.GetService(); - - if (platform is null) - throw new InvalidOperationException("Unable to locate IPlatformThreadingInterface"); - + var platform = AvaloniaLocator.Current.GetRequiredService(); cancellationToken.Register(() => platform.Signal(DispatcherPriority.Send)); platform.RunLoop(cancellationToken); } diff --git a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs index 942e7eb7c6..de86b5fee2 100644 --- a/src/Avalonia.Visuals/Animation/RenderLoopClock.cs +++ b/src/Avalonia.Visuals/Animation/RenderLoopClock.cs @@ -9,9 +9,7 @@ namespace Avalonia.Animation { protected override void Stop() { - var loop = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRenderLoop."); - loop.Remove(this); + AvaloniaLocator.Current.GetRequiredService().Remove(this); } bool IRenderLoopTask.NeedsUpdate => HasSubscriptions; diff --git a/src/Avalonia.Visuals/Media/CombinedGeometry.cs b/src/Avalonia.Visuals/Media/CombinedGeometry.cs index b76c496c4e..8f080d05c7 100644 --- a/src/Avalonia.Visuals/Media/CombinedGeometry.cs +++ b/src/Avalonia.Visuals/Media/CombinedGeometry.cs @@ -154,8 +154,7 @@ namespace Avalonia.Media if (g1 is object && g2 is object) { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); return factory.CreateCombinedGeometry(GeometryCombineMode, g1, g2); } else if (GeometryCombineMode == GeometryCombineMode.Intersect) diff --git a/src/Avalonia.Visuals/Media/EllipseGeometry.cs b/src/Avalonia.Visuals/Media/EllipseGeometry.cs index 7f9abaf531..8211855324 100644 --- a/src/Avalonia.Visuals/Media/EllipseGeometry.cs +++ b/src/Avalonia.Visuals/Media/EllipseGeometry.cs @@ -98,8 +98,7 @@ namespace Avalonia.Media /// protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); if (Rect != default) return factory.CreateEllipseGeometry(Rect); diff --git a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs index 203c8dc221..a54dad7623 100644 --- a/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs +++ b/src/Avalonia.Visuals/Media/Fonts/FontFamilyLoader.cs @@ -32,9 +32,7 @@ namespace Avalonia.Media.Fonts /// private static IEnumerable GetFontAssetsBySource(FontFamilyKey fontFamilyKey) { - var assetLoader = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IAssetLoader."); - + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var availableAssets = assetLoader.GetAssets(fontFamilyKey.Source, fontFamilyKey.BaseUri); var matchingAssets = @@ -51,8 +49,7 @@ namespace Avalonia.Media.Fonts /// private static IEnumerable GetFontAssetsByExpression(FontFamilyKey fontFamilyKey) { - var assetLoader = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IAssetLoader."); + var assetLoader = AvaloniaLocator.Current.GetRequiredService(); var fileName = GetFileName(fontFamilyKey, out var fileExtension, out var location); diff --git a/src/Avalonia.Visuals/Media/FormattedText.cs b/src/Avalonia.Visuals/Media/FormattedText.cs index 499761d96c..f6129eaf6a 100644 --- a/src/Avalonia.Visuals/Media/FormattedText.cs +++ b/src/Avalonia.Visuals/Media/FormattedText.cs @@ -24,8 +24,7 @@ namespace Avalonia.Media /// public FormattedText() { - _platform = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + _platform = AvaloniaLocator.Current.GetRequiredService(); } /// diff --git a/src/Avalonia.Visuals/Media/GeometryGroup.cs b/src/Avalonia.Visuals/Media/GeometryGroup.cs index 01d4923456..c6b666b2eb 100644 --- a/src/Avalonia.Visuals/Media/GeometryGroup.cs +++ b/src/Avalonia.Visuals/Media/GeometryGroup.cs @@ -61,8 +61,7 @@ namespace Avalonia.Media { if (_children?.Count > 0) { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); return factory.CreateGeometryGroup(FillRule, _children); } diff --git a/src/Avalonia.Visuals/Media/GlyphRun.cs b/src/Avalonia.Visuals/Media/GlyphRun.cs index e15a19306a..53b35fb31b 100644 --- a/src/Avalonia.Visuals/Media/GlyphRun.cs +++ b/src/Avalonia.Visuals/Media/GlyphRun.cs @@ -627,8 +627,7 @@ namespace Avalonia.Media throw new InvalidOperationException(); } - var platformRenderInterface = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface"); + var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this); } diff --git a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs index 020940d9c6..647ce13528 100644 --- a/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/Bitmap.cs @@ -169,8 +169,7 @@ namespace Avalonia.Media.Imaging private static IPlatformRenderInterface GetFactory() { - return AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + return AvaloniaLocator.Current.GetRequiredService(); } } } diff --git a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs index fe8890b3ba..94a5abd918 100644 --- a/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs @@ -54,8 +54,7 @@ namespace Avalonia.Media.Imaging /// The platform-specific implementation. private static IRenderTargetBitmapImpl CreateImpl(PixelSize size, Vector dpi) { - IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + IPlatformRenderInterface factory = AvaloniaLocator.Current.GetRequiredService(); return factory.CreateRenderTargetBitmap(size, dpi); } diff --git a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs index 4dfb22440d..40a24db338 100644 --- a/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs +++ b/src/Avalonia.Visuals/Media/Imaging/WriteableBitmap.cs @@ -93,8 +93,7 @@ namespace Avalonia.Media.Imaging private static IPlatformRenderInterface GetFactory() { - return AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + return AvaloniaLocator.Current.GetRequiredService(); } } } diff --git a/src/Avalonia.Visuals/Media/LineGeometry.cs b/src/Avalonia.Visuals/Media/LineGeometry.cs index b01a223aca..6ac92ea33b 100644 --- a/src/Avalonia.Visuals/Media/LineGeometry.cs +++ b/src/Avalonia.Visuals/Media/LineGeometry.cs @@ -70,8 +70,7 @@ namespace Avalonia.Media /// protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); return factory.CreateLineGeometry(StartPoint, EndPoint); } diff --git a/src/Avalonia.Visuals/Media/PathGeometry.cs b/src/Avalonia.Visuals/Media/PathGeometry.cs index 0b6a157280..2c8a51c541 100644 --- a/src/Avalonia.Visuals/Media/PathGeometry.cs +++ b/src/Avalonia.Visuals/Media/PathGeometry.cs @@ -88,8 +88,7 @@ namespace Avalonia.Media if (figures is null) return null; - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); var geometry = factory.CreateStreamGeometry(); using (var ctx = new StreamGeometryContext(geometry.Open())) diff --git a/src/Avalonia.Visuals/Media/PolylineGeometry.cs b/src/Avalonia.Visuals/Media/PolylineGeometry.cs index 8e040a6043..dd3c298b5b 100644 --- a/src/Avalonia.Visuals/Media/PolylineGeometry.cs +++ b/src/Avalonia.Visuals/Media/PolylineGeometry.cs @@ -76,8 +76,7 @@ namespace Avalonia.Media protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); var geometry = factory.CreateStreamGeometry(); using (var context = geometry.Open()) diff --git a/src/Avalonia.Visuals/Media/RectangleGeometry.cs b/src/Avalonia.Visuals/Media/RectangleGeometry.cs index 97b4869d71..0bf9eb5664 100644 --- a/src/Avalonia.Visuals/Media/RectangleGeometry.cs +++ b/src/Avalonia.Visuals/Media/RectangleGeometry.cs @@ -49,8 +49,7 @@ namespace Avalonia.Media protected override IGeometryImpl? CreateDefiningGeometry() { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); return factory.CreateRectangleGeometry(Rect); } diff --git a/src/Avalonia.Visuals/Media/StreamGeometry.cs b/src/Avalonia.Visuals/Media/StreamGeometry.cs index b034ff7823..fb79488e0f 100644 --- a/src/Avalonia.Visuals/Media/StreamGeometry.cs +++ b/src/Avalonia.Visuals/Media/StreamGeometry.cs @@ -66,8 +66,7 @@ namespace Avalonia.Media { if (_impl == null) { - var factory = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface."); + var factory = AvaloniaLocator.Current.GetRequiredService(); _impl = factory.CreateStreamGeometry(); } diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs index 43e00f3215..82d3892975 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderTimer.cs @@ -77,8 +77,7 @@ namespace Avalonia.Rendering /// protected virtual IDisposable StartCore(Action tick) { - _runtime ??= AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRuntimePlatform."); + _runtime ??= AvaloniaLocator.Current.GetRequiredService(); return _runtime.StartSystemTimer( TimeSpan.FromSeconds(1.0 / FramesPerSecond), diff --git a/src/Avalonia.X11/X11CursorFactory.cs b/src/Avalonia.X11/X11CursorFactory.cs index 8e5cac1400..60f035cd7e 100644 --- a/src/Avalonia.X11/X11CursorFactory.cs +++ b/src/Avalonia.X11/X11CursorFactory.cs @@ -94,10 +94,8 @@ namespace Avalonia.X11 { var size = Marshal.SizeOf() + (bitmap.PixelSize.Width * bitmap.PixelSize.Height * 4); - var runtimePlatform = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRuntimePlatform"); - var platformRenderInterface = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IPlatformRenderInterface"); + var runtimePlatform = AvaloniaLocator.Current.GetRequiredService(); + var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); _pixelSize = bitmap.PixelSize; _blob = runtimePlatform.AllocBlob(size); diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index a971c8e997..3e9849d4dc 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -106,8 +106,7 @@ namespace Avalonia.Web.Blazor public IRenderer CreateRenderer(IRenderRoot root) { - var loop = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRenderLoop."); + var loop = AvaloniaLocator.Current.GetRequiredService(); return new DeferredRenderer(root, loop); } diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs index 4804016ab2..f30a36b8c9 100644 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs @@ -98,8 +98,7 @@ namespace Avalonia.Web.Blazor private static IRuntimePlatform GetRuntimePlatform() { - return AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRuntimePlatform."); + return AvaloniaLocator.Current.GetRequiredService(); } } } diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs index 119edcebbf..7f4b1c976d 100644 --- a/src/Windows/Avalonia.Win32/FramebufferManager.cs +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -107,8 +107,7 @@ namespace Avalonia.Win32 private static FramebufferData AllocateFramebufferData(int width, int height) { - var service = AvaloniaLocator.Current.GetService() ?? - throw new InvalidOperationException("Unable to locate IRuntimePlatform."); + var service = AvaloniaLocator.Current.GetRequiredService(); var bitmapBlob = service.AllocBlob(width * height * _bytesPerPixel); return new FramebufferData(bitmapBlob, width, height); From ffac3eb0278ea1281aea3aa281d910663c6f010d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 14:24:46 +0100 Subject: [PATCH 068/260] Added nullable annotations to Avalonia.Animation. --- src/Avalonia.Animation/Animatable.cs | 16 +++---- src/Avalonia.Animation/Animation.cs | 46 +++++++++++++------ src/Avalonia.Animation/AnimationInstance`1.cs | 33 +++++++------ src/Avalonia.Animation/AnimatorKeyFrame.cs | 20 ++++---- .../Animators/Animator`1.cs | 9 ++-- .../Avalonia.Animation.csproj | 4 ++ src/Avalonia.Animation/Clock.cs | 2 +- src/Avalonia.Animation/Cue.cs | 8 ++-- .../DisposeAnimationInstanceSubject.cs | 8 ++-- src/Avalonia.Animation/Easing/Easing.cs | 4 +- .../Easing/EasingTypeConverter.cs | 4 +- src/Avalonia.Animation/IAnimation.cs | 2 +- src/Avalonia.Animation/IAnimationSetter.cs | 4 +- src/Avalonia.Animation/IAnimator.cs | 4 +- src/Avalonia.Animation/ITransition.cs | 2 +- src/Avalonia.Animation/IterationCount.cs | 2 +- .../IterationCountTypeConverter.cs | 4 +- src/Avalonia.Animation/KeyFrame.cs | 4 +- .../KeySplineTypeConverter.cs | 4 +- src/Avalonia.Animation/Transition.cs | 21 +++++++-- src/Avalonia.Animation/TransitionInstance.cs | 6 +-- .../Animation/Animators/BaseBrushAnimator.cs | 7 ++- .../Animators/GradientBrushAnimator.cs | 5 ++ .../Animators/SolidColorBrushAnimator.cs | 5 ++ .../Animation/Animators/TransformAnimator.cs | 7 ++- 25 files changed, 142 insertions(+), 89 deletions(-) diff --git a/src/Avalonia.Animation/Animatable.cs b/src/Avalonia.Animation/Animatable.cs index 4811028f85..50fc5ac73b 100644 --- a/src/Avalonia.Animation/Animatable.cs +++ b/src/Avalonia.Animation/Animatable.cs @@ -157,7 +157,7 @@ namespace Avalonia.Animation state.Instance?.Dispose(); state.Instance = transition.Apply( this, - Clock ?? AvaloniaLocator.Current.GetService(), + Clock ?? AvaloniaLocator.Current.GetRequiredService(), oldValue, newValue); return; @@ -169,7 +169,7 @@ namespace Avalonia.Animation base.OnPropertyChangedCore(change); } - private void TransitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void TransitionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (!_transitionsEnabled) { @@ -179,14 +179,14 @@ namespace Avalonia.Animation switch (e.Action) { case NotifyCollectionChangedAction.Add: - AddTransitions(e.NewItems); + AddTransitions(e.NewItems!); break; case NotifyCollectionChangedAction.Remove: - RemoveTransitions(e.OldItems); + RemoveTransitions(e.OldItems!); break; case NotifyCollectionChangedAction.Replace: - RemoveTransitions(e.OldItems); - AddTransitions(e.NewItems); + RemoveTransitions(e.OldItems!); + AddTransitions(e.NewItems!); break; case NotifyCollectionChangedAction.Reset: throw new NotSupportedException("Transitions collection cannot be reset."); @@ -204,7 +204,7 @@ namespace Avalonia.Animation for (var i = 0; i < items.Count; ++i) { - var t = (ITransition)items[i]; + var t = (ITransition)items[i]!; _transitionState.Add(t, new TransitionState { @@ -222,7 +222,7 @@ namespace Avalonia.Animation for (var i = 0; i < items.Count; ++i) { - var t = (ITransition)items[i]; + var t = (ITransition)items[i]!; if (_transitionState.TryGetValue(t, out var state)) { diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs index a4515db514..03b2d17e44 100644 --- a/src/Avalonia.Animation/Animation.cs +++ b/src/Avalonia.Animation/Animation.cs @@ -203,7 +203,7 @@ namespace Avalonia.Animation /// /// The animation setter. /// The property animator type. - public static Type GetAnimator(IAnimationSetter setter) + public static Type? GetAnimator(IAnimationSetter setter) { if (s_animators.TryGetValue(setter, out var type)) { @@ -254,7 +254,7 @@ namespace Avalonia.Animation Animators.Insert(0, (condition, typeof(TAnimator))); } - private static Type GetAnimatorType(AvaloniaProperty property) + private static Type? GetAnimatorType(AvaloniaProperty property) { foreach (var (condition, type) in Animators) { @@ -276,6 +276,11 @@ namespace Avalonia.Animation { foreach (var setter in keyframe.Setters) { + if (setter.Property is null) + { + throw new InvalidOperationException("No Setter property assigned."); + } + var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property); if (handler == null) @@ -305,7 +310,7 @@ namespace Avalonia.Animation foreach (var (handlerType, property) in handlerList) { - var newInstance = (IAnimator)Activator.CreateInstance(handlerType); + var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!; newInstance.Property = property; newAnimatorInstances.Add(newInstance); } @@ -321,32 +326,43 @@ namespace Avalonia.Animation } /// - public IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete) + public IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete) { var (animators, subscriptions) = InterpretKeyframes(control); if (animators.Count == 1) { - subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); + var subscription = animators[0].Apply(this, control, clock, match, onComplete); + + if (subscription is not null) + { + subscriptions.Add(subscription); + } } else { var completionTasks = onComplete != null ? new List() : null; foreach (IAnimator animator in animators) { - Action animatorOnComplete = null; + Action? animatorOnComplete = null; if (onComplete != null) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); animatorOnComplete = () => tcs.SetResult(null); - completionTasks.Add(tcs.Task); + completionTasks!.Add(tcs.Task); + } + + var subscription = animator.Apply(this, control, clock, match, animatorOnComplete); + + if (subscription is not null) + { + subscriptions.Add(subscription); } - subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete)); } if (onComplete != null) { - Task.WhenAll(completionTasks).ContinueWith( - (_, state) => ((Action)state).Invoke(), + Task.WhenAll(completionTasks!).ContinueWith( + (_, state) => ((Action)state!).Invoke(), onComplete); } } @@ -354,25 +370,25 @@ namespace Avalonia.Animation } /// - public Task RunAsync(Animatable control, IClock clock = null) + public Task RunAsync(Animatable control, IClock? clock = null) { return RunAsync(control, clock, default); } /// - public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) + public Task RunAsync(Animatable control, IClock? clock = null, CancellationToken cancellationToken = default) { if (cancellationToken.IsCancellationRequested) { return Task.CompletedTask; } - var run = new TaskCompletionSource(); + var run = new TaskCompletionSource(); if (this.IterationCount == IterationCount.Infinite) run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); - IDisposable subscriptions = null, cancellation = null; + IDisposable? subscriptions = null, cancellation = null; subscriptions = this.Apply(control, clock, Observable.Return(true), () => { run.TrySetResult(null); diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index cf79640150..52cd4b324f 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -31,15 +31,15 @@ namespace Avalonia.Animation private TimeSpan _initialDelay; private TimeSpan _iterationDelay; private TimeSpan _duration; - private Easings.Easing _easeFunc; - private Action _onCompleteAction; + private Easings.Easing? _easeFunc; + private Action? _onCompleteAction; private Func _interpolator; - private IDisposable _timerSub; + private IDisposable? _timerSub; private readonly IClock _baseClock; - private IClock _clock; - private EventHandler _propertyChangedDelegate; + private IClock? _clock; + private EventHandler? _propertyChangedDelegate; - public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action OnComplete, Func Interpolator) + public AnimationInstance(Animation animation, Animatable control, Animator animator, IClock baseClock, Action? OnComplete, Func Interpolator) { _animator = animator; _animation = animation; @@ -47,6 +47,9 @@ namespace Avalonia.Animation _onCompleteAction = OnComplete; _interpolator = Interpolator; _baseClock = baseClock; + _lastInterpValue = default!; + _firstKFValue = default!; + _neutralValue = default!; FetchProperties(); } @@ -82,7 +85,7 @@ namespace Avalonia.Animation _targetControl.PropertyChanged -= _propertyChangedDelegate; _timerSub?.Dispose(); - _clock.PlayState = PlayState.Stop; + _clock!.PlayState = PlayState.Stop; } protected override void Subscribed() @@ -108,6 +111,8 @@ namespace Avalonia.Animation private void ApplyFinalFill() { + if (_animator.Property is null) + throw new InvalidOperationException("Animator has no property specified."); if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue); } @@ -130,12 +135,12 @@ namespace Avalonia.Animation private void DoPlayStates() { - if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) + if (_clock!.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) DoComplete(); if (!_gotFirstKFValue) { - _firstKFValue = (T)_animator.First().Value; + _firstKFValue = (T)_animator.First().Value!; _gotFirstKFValue = true; } } @@ -169,7 +174,7 @@ namespace Avalonia.Animation // and snap the last iteration value to exact values. if ((_currentIteration + 1) > _iterationCount) { - var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0); + var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0); _lastInterpValue = _interpolator(easedTime, _neutralValue); DoComplete(); } @@ -203,7 +208,7 @@ namespace Avalonia.Animation normalizedTime = 1 - normalizedTime; // Ease and interpolate - var easedTime = _easeFunc.Ease(normalizedTime); + var easedTime = _easeFunc!.Ease(normalizedTime); _lastInterpValue = _interpolator(easedTime, _neutralValue); PublishNext(_lastInterpValue); @@ -223,14 +228,14 @@ namespace Avalonia.Animation private void UpdateNeutralValue() { - var property = _animator.Property; + var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified."); var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); _neutralValue = baseValue != AvaloniaProperty.UnsetValue ? - (T)baseValue : (T)_targetControl.GetValue(property); + (T)baseValue! : (T)_targetControl.GetValue(property)!; } - private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation) { diff --git a/src/Avalonia.Animation/AnimatorKeyFrame.cs b/src/Avalonia.Animation/AnimatorKeyFrame.cs index f6a0c12be4..8af31f2948 100644 --- a/src/Avalonia.Animation/AnimatorKeyFrame.cs +++ b/src/Avalonia.Animation/AnimatorKeyFrame.cs @@ -12,22 +12,22 @@ namespace Avalonia.Animation /// public class AnimatorKeyFrame : AvaloniaObject { - public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect(nameof(Value), k => k.Value, (k, v) => k.Value = v); + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect(nameof(Value), k => k.Value, (k, v) => k.Value = v); public AnimatorKeyFrame() { } - public AnimatorKeyFrame(Type animatorType, Cue cue) + public AnimatorKeyFrame(Type? animatorType, Cue cue) { AnimatorType = animatorType; Cue = cue; KeySpline = null; } - public AnimatorKeyFrame(Type animatorType, Cue cue, KeySpline keySpline) + public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline) { AnimatorType = animatorType; Cue = cue; @@ -35,14 +35,14 @@ namespace Avalonia.Animation } internal bool isNeutral; - public Type AnimatorType { get; } + public Type? AnimatorType { get; } public Cue Cue { get; } - public KeySpline KeySpline { get; } - public AvaloniaProperty Property { get; private set; } + public KeySpline? KeySpline { get; } + public AvaloniaProperty? Property { get; private set; } - private object _value; + private object? _value; - public object Value + public object? Value { get => _value; set => SetAndRaise(ValueProperty, ref _value, value); @@ -80,7 +80,7 @@ namespace Avalonia.Animation throw new InvalidCastException($"KeyFrame value doesnt match property type."); } - return (T)typeConv.ConvertTo(Value, typeof(T)); + return (T)typeConv.ConvertTo(Value, typeof(T))!; } } } diff --git a/src/Avalonia.Animation/Animators/Animator`1.cs b/src/Avalonia.Animation/Animators/Animator`1.cs index 23afa76bf6..248ca61c1d 100644 --- a/src/Avalonia.Animation/Animators/Animator`1.cs +++ b/src/Avalonia.Animation/Animators/Animator`1.cs @@ -24,7 +24,7 @@ namespace Avalonia.Animation.Animators /// /// Gets or sets the target property for the keyframe. /// - public AvaloniaProperty Property { get; set; } + public AvaloniaProperty? Property { get; set; } public Animator() { @@ -33,7 +33,7 @@ namespace Avalonia.Animation.Animators } /// - public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete) + public virtual IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable match, Action? onComplete) { if (!_isVerifiedAndConverted) VerifyConvertKeyFrames(); @@ -106,13 +106,16 @@ namespace Avalonia.Animation.Animators public virtual IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + throw new InvalidOperationException("Animator has no property specified."); + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } /// /// Runs the KeyFrames Animation. /// - internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete) + internal IDisposable Run(Animation animation, Animatable control, IClock? clock, Action? onComplete) { var instance = new AnimationInstance( animation, diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 85938ad958..9e3758658c 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -2,9 +2,13 @@ netstandard2.0;net6.0 + + + + diff --git a/src/Avalonia.Animation/Clock.cs b/src/Avalonia.Animation/Clock.cs index 5c2b7ce0dd..5afd2ae705 100644 --- a/src/Avalonia.Animation/Clock.cs +++ b/src/Avalonia.Animation/Clock.cs @@ -4,7 +4,7 @@ namespace Avalonia.Animation { public class Clock : ClockBase { - public static IClock GlobalClock => AvaloniaLocator.Current.GetService(); + public static IClock GlobalClock => AvaloniaLocator.Current.GetRequiredService(); private readonly IDisposable _parentSubscription; diff --git a/src/Avalonia.Animation/Cue.cs b/src/Avalonia.Animation/Cue.cs index 7da7a9382b..6578148b07 100644 --- a/src/Avalonia.Animation/Cue.cs +++ b/src/Avalonia.Animation/Cue.cs @@ -30,7 +30,7 @@ namespace Avalonia.Animation /// /// Parses a string to a object. /// - public static Cue Parse(string value, CultureInfo culture) + public static Cue Parse(string value, CultureInfo? culture) { string v = value; @@ -72,14 +72,14 @@ namespace Avalonia.Animation public class CueTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return Cue.Parse((string)value, culture); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs index 696f43d006..7283eaeedf 100644 --- a/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs +++ b/src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs @@ -8,15 +8,15 @@ namespace Avalonia.Animation /// internal class DisposeAnimationInstanceSubject : IObserver, IDisposable { - private IDisposable _lastInstance; + private IDisposable? _lastInstance; private bool _lastMatch; private Animator _animator; private Animation _animation; private Animatable _control; - private Action _onComplete; - private IClock _clock; + private Action? _onComplete; + private IClock? _clock; - public DisposeAnimationInstanceSubject(Animator animator, Animation animation, Animatable control, IClock clock, Action onComplete) + public DisposeAnimationInstanceSubject(Animator animator, Animation animation, Animatable control, IClock? clock, Action? onComplete) { this._animator = animator; this._animation = animation; diff --git a/src/Avalonia.Animation/Easing/Easing.cs b/src/Avalonia.Animation/Easing/Easing.cs index e006459652..2f4b93dab1 100644 --- a/src/Avalonia.Animation/Easing/Easing.cs +++ b/src/Avalonia.Animation/Easing/Easing.cs @@ -15,7 +15,7 @@ namespace Avalonia.Animation.Easings /// public abstract double Ease(double progress); - static Dictionary _easingTypes; + static Dictionary? _easingTypes; static readonly Type s_thisType = typeof(Easing); @@ -48,7 +48,7 @@ namespace Avalonia.Animation.Easings if (_easingTypes.ContainsKey(e)) { var type = _easingTypes[e]; - return (Easing)Activator.CreateInstance(type); + return (Easing)Activator.CreateInstance(type)!; } else { diff --git a/src/Avalonia.Animation/Easing/EasingTypeConverter.cs b/src/Avalonia.Animation/Easing/EasingTypeConverter.cs index 6613f6d393..3d67d54a6f 100644 --- a/src/Avalonia.Animation/Easing/EasingTypeConverter.cs +++ b/src/Avalonia.Animation/Easing/EasingTypeConverter.cs @@ -6,12 +6,12 @@ namespace Avalonia.Animation.Easings { public class EasingTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return Easing.Parse((string)value); } diff --git a/src/Avalonia.Animation/IAnimation.cs b/src/Avalonia.Animation/IAnimation.cs index d037834630..436a765d27 100644 --- a/src/Avalonia.Animation/IAnimation.cs +++ b/src/Avalonia.Animation/IAnimation.cs @@ -12,7 +12,7 @@ namespace Avalonia.Animation /// /// Apply the animation to the specified control and run it when produces true. /// - IDisposable Apply(Animatable control, IClock clock, IObservable match, Action onComplete = null); + IDisposable Apply(Animatable control, IClock? clock, IObservable match, Action? onComplete = null); /// /// Run the animation on the specified control. diff --git a/src/Avalonia.Animation/IAnimationSetter.cs b/src/Avalonia.Animation/IAnimationSetter.cs index 2d22377286..6a1d3539e2 100644 --- a/src/Avalonia.Animation/IAnimationSetter.cs +++ b/src/Avalonia.Animation/IAnimationSetter.cs @@ -2,7 +2,7 @@ namespace Avalonia.Animation { public interface IAnimationSetter { - AvaloniaProperty Property { get; set; } - object Value { get; set; } + AvaloniaProperty? Property { get; set; } + object? Value { get; set; } } } diff --git a/src/Avalonia.Animation/IAnimator.cs b/src/Avalonia.Animation/IAnimator.cs index d0fb173c54..f64ac9f913 100644 --- a/src/Avalonia.Animation/IAnimator.cs +++ b/src/Avalonia.Animation/IAnimator.cs @@ -11,11 +11,11 @@ namespace Avalonia.Animation /// /// The target property. /// - AvaloniaProperty Property {get; set;} + AvaloniaProperty? Property {get; set;} /// /// Applies the current KeyFrame group to the specified control. /// - IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable match, Action onComplete); + IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable match, Action? onComplete); } } diff --git a/src/Avalonia.Animation/ITransition.cs b/src/Avalonia.Animation/ITransition.cs index ade2ec8b9e..241ca208d1 100644 --- a/src/Avalonia.Animation/ITransition.cs +++ b/src/Avalonia.Animation/ITransition.cs @@ -10,7 +10,7 @@ namespace Avalonia.Animation /// /// Applies the transition to the specified . /// - IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue); + IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue); /// /// Gets the property to be animated. diff --git a/src/Avalonia.Animation/IterationCount.cs b/src/Avalonia.Animation/IterationCount.cs index 9463718608..3b52cdab49 100644 --- a/src/Avalonia.Animation/IterationCount.cs +++ b/src/Avalonia.Animation/IterationCount.cs @@ -97,7 +97,7 @@ namespace Avalonia.Animation /// /// The object with which to test equality. /// True if the objects are equal, otherwise false. - public override bool Equals(object o) + public override bool Equals(object? o) { if (o == null) { diff --git a/src/Avalonia.Animation/IterationCountTypeConverter.cs b/src/Avalonia.Animation/IterationCountTypeConverter.cs index 1c63f8cdf1..f64972ff5c 100644 --- a/src/Avalonia.Animation/IterationCountTypeConverter.cs +++ b/src/Avalonia.Animation/IterationCountTypeConverter.cs @@ -6,12 +6,12 @@ namespace Avalonia.Animation { public class IterationCountTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return IterationCount.Parse((string)value); } diff --git a/src/Avalonia.Animation/KeyFrame.cs b/src/Avalonia.Animation/KeyFrame.cs index c2cc1aa051..3ab7a70d90 100644 --- a/src/Avalonia.Animation/KeyFrame.cs +++ b/src/Avalonia.Animation/KeyFrame.cs @@ -19,7 +19,7 @@ namespace Avalonia.Animation { private TimeSpan _ktimeSpan; private Cue _kCue; - private KeySpline _kKeySpline; + private KeySpline? _kKeySpline; public KeyFrame() { @@ -79,7 +79,7 @@ namespace Avalonia.Animation /// Gets or sets the KeySpline of this . /// /// The key spline. - public KeySpline KeySpline + public KeySpline? KeySpline { get { diff --git a/src/Avalonia.Animation/KeySplineTypeConverter.cs b/src/Avalonia.Animation/KeySplineTypeConverter.cs index b026206e5f..eecad3c3ac 100644 --- a/src/Avalonia.Animation/KeySplineTypeConverter.cs +++ b/src/Avalonia.Animation/KeySplineTypeConverter.cs @@ -12,12 +12,12 @@ namespace Avalonia.Animation /// public class KeySplineTypeConverter : TypeConverter { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string); } - public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { return KeySpline.Parse((string)value, CultureInfo.InvariantCulture); } diff --git a/src/Avalonia.Animation/Transition.cs b/src/Avalonia.Animation/Transition.cs index 4115c95c0f..d307f348c4 100644 --- a/src/Avalonia.Animation/Transition.cs +++ b/src/Avalonia.Animation/Transition.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Avalonia.Animation.Easings; namespace Avalonia.Animation @@ -8,7 +9,7 @@ namespace Avalonia.Animation /// public abstract class Transition : AvaloniaObject, ITransition { - private AvaloniaProperty _prop; + private AvaloniaProperty? _prop; /// /// Gets or sets the duration of the transition. @@ -26,7 +27,8 @@ namespace Avalonia.Animation public Easing Easing { get; set; } = new LinearEasing(); /// - public AvaloniaProperty Property + [DisallowNull] + public AvaloniaProperty? Property { get { @@ -42,16 +44,25 @@ namespace Avalonia.Animation } } + AvaloniaProperty ITransition.Property + { + get => Property ?? throw new InvalidOperationException("Transition has no property specified."); + set => Property = value; + } + /// /// Apply interpolation to the property. /// public abstract IObservable DoTransition(IObservable progress, T oldValue, T newValue); /// - public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) + public virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue) { - var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue, (T)newValue); + if (Property is null) + throw new InvalidOperationException("Transition has no property specified."); + + var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue!, (T)newValue!); return control.Bind((AvaloniaProperty)Property, transition, Data.BindingPriority.Animation); } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Animation/TransitionInstance.cs b/src/Avalonia.Animation/TransitionInstance.cs index b522d1961e..9c9494ff87 100644 --- a/src/Avalonia.Animation/TransitionInstance.cs +++ b/src/Avalonia.Animation/TransitionInstance.cs @@ -10,11 +10,11 @@ namespace Avalonia.Animation /// internal class TransitionInstance : SingleSubscriberObservableBase, IObserver { - private IDisposable _timerSubscription; + private IDisposable? _timerSubscription; private TimeSpan _delay; private TimeSpan _duration; private readonly IClock _baseClock; - private TransitionClock _clock; + private TransitionClock? _clock; public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) { @@ -67,7 +67,7 @@ namespace Avalonia.Animation protected override void Unsubscribed() { _timerSubscription?.Dispose(); - _clock.PlayState = PlayState.Stop; + _clock!.PlayState = PlayState.Stop; } protected override void Subscribed() diff --git a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs index a2c4b0313b..5f22254fb5 100644 --- a/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs @@ -38,8 +38,8 @@ namespace Avalonia.Animation.Animators } /// - public override IDisposable Apply(Animation animation, Animatable control, IClock clock, - IObservable match, Action onComplete) + public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock, + IObservable match, Action? onComplete) { if (TryCreateCustomRegisteredAnimator(out var animator) || TryCreateGradientAnimator(out animator) @@ -135,9 +135,8 @@ namespace Avalonia.Animation.Animators private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator) { - if (_brushAnimators.Count > 0) + if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType) { - var firstKeyType = this[0].Value.GetType(); foreach (var (match, animatorType) in _brushAnimators) { if (!match(firstKeyType)) diff --git a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs index 864e12413f..0979de16d0 100644 --- a/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs @@ -58,6 +58,11 @@ namespace Avalonia.Animation.Animators public override IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } diff --git a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs index 7c6372aae2..57f9f3c1a5 100644 --- a/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs @@ -24,6 +24,11 @@ namespace Avalonia.Animation.Animators public override IDisposable BindAnimation(Animatable control, IObservable instance) { + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + return control.Bind((AvaloniaProperty)Property, instance, BindingPriority.Animation); } } diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index 1d7bfd3748..34ec8ac503 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -14,10 +14,15 @@ namespace Avalonia.Animation.Animators DoubleAnimator? _doubleAnimator; /// - public override IDisposable? Apply(Animation animation, Animatable control, IClock clock, IObservable obsMatch, Action onComplete) + public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable obsMatch, Action? onComplete) { var ctrl = (Visual)control; + if (Property is null) + { + throw new InvalidOperationException("Animator has no property specified."); + } + // Check if the Target Property is Transform derived. if (typeof(Transform).IsAssignableFrom(Property.OwnerType)) { From 98d061ec30b65b63ec98d9c45e52332d65a6db21 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Dec 2021 15:53:12 +0100 Subject: [PATCH 069/260] Fix failing unit tests. --- .../AnimatableTests.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs index 26d8059eec..1d5296bebd 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimatableTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_To_Initial_Style() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTarget(); var control = new Control @@ -74,6 +74,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Applied_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -170,6 +171,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -195,6 +197,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Disposed_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); var sub = new Mock(); @@ -211,6 +214,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void New_Transition_Is_Applied_When_Local_Value_Changes() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -239,6 +243,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); @@ -266,6 +271,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Animation_Is_Cancelled_When_Transition_Removed() { + using var app = Start(); var target = CreateTarget(); var control = CreateControl(target.Object); var sub = new Mock(); @@ -285,7 +291,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Animation_Is_Cancelled_When_New_Style_Activates() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTarget(); var control = CreateStyledControl(target.Object); @@ -301,7 +307,7 @@ namespace Avalonia.Animation.UnitTests target.Verify(x => x.Apply( control, - It.IsAny(), + It.IsAny(), 1.0, 0.5), Times.Once); @@ -315,7 +321,7 @@ namespace Avalonia.Animation.UnitTests [Fact] public void Transition_From_Style_Trigger_Is_Applied() { - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { var target = CreateTransition(Control.WidthProperty); var control = CreateStyledControl(transition2: target.Object); @@ -326,7 +332,7 @@ namespace Avalonia.Animation.UnitTests target.Verify(x => x.Apply( control, - It.IsAny(), + It.IsAny(), double.NaN, 100.0), Times.Once); @@ -337,7 +343,7 @@ namespace Avalonia.Animation.UnitTests public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound() { // Issue #4059 - using (UnitTestApplication.Start(TestServices.RealStyler)) + using (Start()) { Border target; var clock = new TestClock(); @@ -428,6 +434,13 @@ namespace Avalonia.Animation.UnitTests control.EndBatchUpdate(); } + private static IDisposable Start() + { + var clock = new MockGlobalClock(); + var services = TestServices.RealStyler.With(globalClock: clock); + return UnitTestApplication.Start(services); + } + private static Mock CreateTarget() { return CreateTransition(Visual.OpacityProperty); From 7fad72cca614198b8e294292236f12cffe3b0b3d Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 22 Dec 2021 19:32:40 +0200 Subject: [PATCH 070/260] fix --- src/Avalonia.Input/TouchDevice.cs | 2 +- .../TouchDeviceTests.cs | 348 ++++++++---------- 2 files changed, 163 insertions(+), 187 deletions(-) diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 0f832d9add..af8851a82b 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -61,7 +61,7 @@ namespace Avalonia.Input var settings = AvaloniaLocator.Current.GetService(); if (settings == null) { - throw new Exception("IPlatformSettings can not be null"); + throw new Exception("IPlatformSettings can not be null."); } if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds) diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs index 6c4416be47..4700ead548 100644 --- a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Reactive.Disposables; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.UnitTests; @@ -12,56 +13,47 @@ namespace Avalonia.Input.UnitTests [Fact] public void Tapped_Event_Is_Fired_With_Touch() { - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) + using var app = UnitTestApp(new TimeSpan(200)); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isTapped = false; + var executedTimes = 0; + root.Tapped += (a, e) => { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); + isTapped = true; + executedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + Assert.True(isTapped); + Assert.Equal(1, executedTimes); - var isTapped = false; - var executedTimes = 0; - root.Tapped += (a, e) => - { - isTapped = true; - executedTimes++; - }; - TapOnce(InputManager.Instance, touchDevice, root); - Assert.True(isTapped); - Assert.Equal(1, executedTimes); - } } [Fact] public void DoubleTapped_Event_Is_Fired_With_Touch() { - var platformSettingsMock = new Mock(); - platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); - AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(platformSettingsMock.Object); - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) + using var app = UnitTestApp(new TimeSpan(200)); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); - - var isDoubleTapped = false; - var doubleTappedExecutedTimes = 0; - var tappedExecutedTimes = 0; - root.DoubleTapped += (a, e) => - { - isDoubleTapped = true; - doubleTappedExecutedTimes++; - }; - root.Tapped += (a, e) => - { - tappedExecutedTimes++; - }; - TapOnce(InputManager.Instance, touchDevice, root); - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1); - Assert.Equal(1, tappedExecutedTimes); - Assert.True(isDoubleTapped); - Assert.Equal(1, doubleTappedExecutedTimes); - } + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 1); + Assert.Equal(1, tappedExecutedTimes); + Assert.True(isDoubleTapped); + Assert.Equal(1, doubleTappedExecutedTimes); } [Theory] @@ -72,172 +64,156 @@ namespace Avalonia.Input.UnitTests [InlineData(5)] public void PointerPressed_Counts_Clicks_Correctly(int clickCount) { - var platformSettingsMock = new Mock(); - platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); - AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(platformSettingsMock.Object); - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) - { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); + using var app = UnitTestApp(new TimeSpan(200)); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); - var pointerPressedExecutedTimes = 0; - var pointerPressedClicks = 0; - root.PointerPressed += (a, e) => - { - pointerPressedClicks = e.ClickCount; - pointerPressedExecutedTimes++; - }; - for (int i = 0; i < clickCount; i++) - { - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i); - } - - Assert.Equal(clickCount, pointerPressedExecutedTimes); - Assert.Equal(pointerPressedClicks, clickCount); + var pointerPressedExecutedTimes = 0; + var pointerPressedClicks = 0; + root.PointerPressed += (a, e) => + { + pointerPressedClicks = e.ClickCount; + pointerPressedExecutedTimes++; + }; + for (int i = 0; i < clickCount; i++) + { + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: i); } + + Assert.Equal(clickCount, pointerPressedExecutedTimes); + Assert.Equal(pointerPressedClicks, clickCount); } [Fact] public void DoubleTapped_Not_Fired_When_Click_Too_Late() { - var platformSettingsMock = new Mock(); - platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20)); - AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(platformSettingsMock.Object); - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) + using var app = UnitTestApp(new TimeSpan(0, 0, 0, 0, 20)); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); + tappedExecutedTimes++; + }; + TapOnce(InputManager.Instance, touchDevice, root); + TapOnce(InputManager.Instance, touchDevice, root, 21, 1); + Assert.Equal(2, tappedExecutedTimes); + Assert.False(isDoubleTapped); + Assert.Equal(0, doubleTappedExecutedTimes); - var isDoubleTapped = false; - var doubleTappedExecutedTimes = 0; - var tappedExecutedTimes = 0; - root.DoubleTapped += (a, e) => - { - isDoubleTapped = true; - doubleTappedExecutedTimes++; - }; - root.Tapped += (a, e) => - { - tappedExecutedTimes++; - }; - TapOnce(InputManager.Instance, touchDevice, root); - TapOnce(InputManager.Instance, touchDevice, root, 21, 1); - Assert.Equal(2, tappedExecutedTimes); - Assert.False(isDoubleTapped); - Assert.Equal(0, doubleTappedExecutedTimes); - } } [Fact] public void DoubleTapped_Not_Fired_When_Second_Click_Is_From_Different_Touch_Contact() { - var tmp = new Mock(); - tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); - AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(tmp.Object); - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) + using var app = UnitTestApp(new TimeSpan(200)); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); + + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + var tappedExecutedTimes = 0; + root.DoubleTapped += (a, e) => { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); - - var isDoubleTapped = false; - var doubleTappedExecutedTimes = 0; - var tappedExecutedTimes = 0; - root.DoubleTapped += (a, e) => - { - isDoubleTapped = true; - doubleTappedExecutedTimes++; - }; - root.Tapped += (a, e) => - { - tappedExecutedTimes++; - }; - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); - Assert.Equal(2, tappedExecutedTimes); - Assert.False(isDoubleTapped); - Assert.Equal(0, doubleTappedExecutedTimes); - } + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); + Assert.Equal(2, tappedExecutedTimes); + Assert.False(isDoubleTapped); + Assert.Equal(0, doubleTappedExecutedTimes); } [Fact] public void Click_Counting_Should_Work_Correctly_With_Few_Touch_Contacts() { - var tmp = new Mock(); - tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200)); - AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(tmp.Object); - using (UnitTestApplication.Start( - new TestServices(inputManager: new InputManager()))) - { - var root = new TestRoot(); - var touchDevice = new TouchDevice(); + using var app = UnitTestApp(new TimeSpan(200)); - var pointerPressedExecutedTimes = 0; - var tappedExecutedTimes = 0; - var isDoubleTapped = false; - var doubleTappedExecutedTimes = 0; - root.PointerPressed += (a, e) => - { - pointerPressedExecutedTimes++; - switch (pointerPressedExecutedTimes) - { - case <= 2: - Assert.True(e.ClickCount == 1); - break; - case 3: - Assert.True(e.ClickCount == 2); - break; - case 4: - Assert.True(e.ClickCount == 3); - break; - case 5: - Assert.True(e.ClickCount == 4); - break; - case 6: - Assert.True(e.ClickCount == 5); - break; - case 7: - Assert.True(e.ClickCount == 1); - break; - case 8: - Assert.True(e.ClickCount == 1); - break; - case 9: - Assert.True(e.ClickCount == 2); - break; - default: - break; - } - }; - root.DoubleTapped += (a, e) => - { - isDoubleTapped = true; - doubleTappedExecutedTimes++; - }; - root.Tapped += (a, e) => - { - tappedExecutedTimes++; - }; - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2); - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3); - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4); - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7); - SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7); - TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8); - Assert.Equal(6, tappedExecutedTimes); - Assert.Equal(9, pointerPressedExecutedTimes); - Assert.True(isDoubleTapped); - Assert.Equal(3, doubleTappedExecutedTimes); + var root = new TestRoot(); + var touchDevice = new TouchDevice(); - } + var pointerPressedExecutedTimes = 0; + var tappedExecutedTimes = 0; + var isDoubleTapped = false; + var doubleTappedExecutedTimes = 0; + root.PointerPressed += (a, e) => + { + pointerPressedExecutedTimes++; + switch (pointerPressedExecutedTimes) + { + case <= 2: + Assert.True(e.ClickCount == 1); + break; + case 3: + Assert.True(e.ClickCount == 2); + break; + case 4: + Assert.True(e.ClickCount == 3); + break; + case 5: + Assert.True(e.ClickCount == 4); + break; + case 6: + Assert.True(e.ClickCount == 5); + break; + case 7: + Assert.True(e.ClickCount == 1); + break; + case 8: + Assert.True(e.ClickCount == 1); + break; + case 9: + Assert.True(e.ClickCount == 2); + break; + default: + break; + } + }; + root.DoubleTapped += (a, e) => + { + isDoubleTapped = true; + doubleTappedExecutedTimes++; + }; + root.Tapped += (a, e) => + { + tappedExecutedTimes++; + }; + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 0, 1); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 0, 1); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 2); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 3); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 4); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchBegin, 5, 6, 7); + SendXTouchContactsWithIds(InputManager.Instance, touchDevice, root, RawPointerEventType.TouchEnd, 5, 6, 7); + TapOnce(InputManager.Instance, touchDevice, root, touchPointId: 8); + Assert.Equal(6, tappedExecutedTimes); + Assert.Equal(9, pointerPressedExecutedTimes); + Assert.True(isDoubleTapped); + Assert.Equal(3, doubleTappedExecutedTimes); + } + private IDisposable UnitTestApp(TimeSpan doubleClickTime = new TimeSpan()) + { + var unitTestApp = UnitTestApplication.Start( + new TestServices(inputManager: new InputManager())); + var scope = AvaloniaLocator.EnterScope(); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.DoubleClickTime).Returns(doubleClickTime); + AvaloniaLocator.CurrentMutable.BindToSelf(this) + .Bind().ToConstant(iSettingsMock.Object); + return new CompositeDisposable(unitTestApp, scope); } private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds) { From c2145db99bad500028cd87411745874c946fc774 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 22 Dec 2021 22:41:52 +0200 Subject: [PATCH 071/260] more fixes --- tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs index 4700ead548..dc9eb9d3b4 100644 --- a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs @@ -1,5 +1,4 @@ using System; -using System.Reactive.Disposables; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.UnitTests; @@ -208,12 +207,11 @@ namespace Avalonia.Input.UnitTests { var unitTestApp = UnitTestApplication.Start( new TestServices(inputManager: new InputManager())); - var scope = AvaloniaLocator.EnterScope(); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.DoubleClickTime).Returns(doubleClickTime); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(iSettingsMock.Object); - return new CompositeDisposable(unitTestApp, scope); + return unitTestApp; } private static void SendXTouchContactsWithIds(IInputManager inputManager, TouchDevice device, IInputRoot root, RawPointerEventType type, params long[] touchPointIds) { From 5992d89b7d528e4b0b2cea543510fb94d781bf5b Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 22 Dec 2021 17:11:28 -0600 Subject: [PATCH 072/260] Add a DataType property on CompiledBindingExtension to enable specifying the start type explicitly on a binding. Fixes #6158 --- .../Transformers/AvaloniaXamlIlBindingPathTransformer.cs | 9 ++++++++- .../MarkupExtensions/CompiledBindingExtension.cs | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs index 4d21a29eb9..048a6220c5 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathTransformer.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using XamlX.Ast; using XamlX.Transform; +using XamlX.Transform.Transformers; using XamlX.TypeSystem; namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers @@ -15,7 +16,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { IXamlType startType = null; var sourceProperty = binding.Children.OfType().FirstOrDefault(c => c.Property.Name == "Source"); - if ((sourceProperty?.Values.Count ?? 0) == 1) + var dataTypeProperty = binding.Children.OfType().FirstOrDefault(c => c.Property.Name == "DataType"); + if (sourceProperty?.Values.Count is 1) { var sourceValue = sourceProperty.Values[0]; switch (sourceValue) @@ -99,6 +101,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers } } + if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text) + { + startType = TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type; + } + Func startTypeResolver = startType is not null ? () => startType : () => { var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 41de2355aa..0125156750 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -71,5 +71,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions public CompiledBindingPath Path { get; set; } public object Source { get; set; } + + public Type DataType { get; set; } } } From 46ffcf9eee6bddeb2710803b85e4c25ad34b81a5 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Wed, 22 Dec 2021 17:19:34 -0600 Subject: [PATCH 073/260] Support the DataType property in a binding in the DataContext property. --- .../AvaloniaXamlIlDataContextTypeTransformer.cs | 6 ++++++ 1 file changed, 6 insertions(+) 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 24dd3f7771..cf691db860 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs @@ -155,6 +155,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers { Func startTypeResolver = () => { + var dataTypeProperty = obj.Children.OfType().FirstOrDefault(c => c.Property.Name == "DataType"); + if (dataTypeProperty?.Values.Count is 1 && dataTypeProperty.Values[0] is XamlAstTextNode text) + { + return TypeReferenceResolver.ResolveType(context, text.Text, isMarkupExtension: false, text, strict: true).Type; + } + var parentDataContextNode = context.ParentNodes().OfType().FirstOrDefault(); if (parentDataContextNode is null) { From 9275e599090bb3a3428411ef8c8c4107f2624fa6 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 22 Dec 2021 21:36:48 -0500 Subject: [PATCH 074/260] Do not recalculate pointer popup position on parent moved --- src/Avalonia.Controls/Primitives/Popup.cs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index ffab7f86d1..a47149a9e0 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -429,16 +429,20 @@ namespace Avalonia.Controls.Primitives (x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler).DisposeWith(handlerCleanup); - SubscribeToEventHandler>(window.PlatformImpl, WindowPositionChanged, - (x, handler) => x.PositionChanged += handler, - (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup); - - if (placementTarget is Layoutable layoutTarget) + // Recalculate popup position on parent moved/resized, but not if placement was on pointer + if (PlacementMode != PlacementMode.Pointer) { - // If the placement target is moved, update the popup position - SubscribeToEventHandler(layoutTarget, PlacementTargetLayoutUpdated, - (x, handler) => x.LayoutUpdated += handler, - (x, handler) => x.LayoutUpdated -= handler).DisposeWith(handlerCleanup); + SubscribeToEventHandler>(window.PlatformImpl, WindowPositionChanged, + (x, handler) => x.PositionChanged += handler, + (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup); + + if (placementTarget is Layoutable layoutTarget) + { + // If the placement target is moved, update the popup position + SubscribeToEventHandler(layoutTarget, PlacementTargetLayoutUpdated, + (x, handler) => x.LayoutUpdated += handler, + (x, handler) => x.LayoutUpdated -= handler).DisposeWith(handlerCleanup); + } } } else if (topLevel is PopupRoot parentPopupRoot) From 50a97dc22f4f48090dd2a8f01e20aa71c8a586df Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 22 Dec 2021 21:37:05 -0500 Subject: [PATCH 075/260] Add more popup tests --- .../Primitives/PopupTests.cs | 237 +++++++++++++++++- 1 file changed, 224 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 24e4631aff..9c1822ff5c 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -16,6 +16,8 @@ using Avalonia.VisualTree; using Xunit; using Avalonia.Input; using Avalonia.Rendering; +using System.Threading.Tasks; +using Avalonia.Threading; namespace Avalonia.Controls.UnitTests.Primitives { @@ -597,27 +599,44 @@ namespace Avalonia.Controls.UnitTests.Primitives } [Fact] - public void Popup_Should_Follow_Placement_Target_On_Window_Move() + public void Popup_Should_Not_Follow_Placement_Target_On_Window_Move_If_Pointer() { using (CreateServices()) { - var popup = new Popup { Width = 400, Height = 200 }; + var popup = new Popup + { + Width = 400, + Height = 200, + PlacementMode = PlacementMode.Pointer + }; var window = PreparedWindow(popup); + window.Show(); popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + var raised = false; if (popup.Host is PopupRoot popupRoot) { - // Moving the window must move the popup (screen coordinates have changed) - var raised = false; popupRoot.PositionChanged += (_, args) => { - Assert.Equal(new PixelPoint(10, 10), args.Point); raised = true; }; - window.Position = new PixelPoint(10, 10); - Assert.True(raised); } + else if (popup.Host is OverlayPopupHost overlayPopupHost) + { + overlayPopupHost.PropertyChanged += (_, args) => + { + if (args.Property == Canvas.TopProperty + || args.Property == Canvas.LeftProperty) + { + raised = true; + } + }; + } + window.Position = new PixelPoint(10, 10); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + Assert.False(raised); } } @@ -634,30 +653,222 @@ namespace Avalonia.Controls.UnitTests.Primitives HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; - var popup = new Popup() { PlacementTarget = placementTarget, Width = 10, Height = 10 }; + var popup = new Popup() + { + PlacementTarget = placementTarget, + PlacementMode = PlacementMode.Bottom, + Width = 10, + Height = 10 + }; ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget); var window = PreparedWindow(placementTarget); window.Show(); popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10)); + var raised = false; + // Resizing the window to 700x500 must move the popup to (345,255) as this is the new + // location of the placement target if (popup.Host is PopupRoot popupRoot) { - // Resizing the window to 700x500 must move the popup to (345,255) as this is the new - // location of the placement target - var raised = false; popupRoot.PositionChanged += (_, args) => { Assert.Equal(new PixelPoint(345, 255), args.Point); raised = true; }; - window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified); - Assert.True(raised); } + else if (popup.Host is OverlayPopupHost overlayPopupHost) + { + overlayPopupHost.PropertyChanged += (_, args) => + { + if ((args.Property == Canvas.TopProperty + || args.Property == Canvas.LeftProperty) + && Canvas.GetLeft(overlayPopupHost) == 345 + && Canvas.GetTop(overlayPopupHost) == 255) + { + raised = true; + } + }; + } + window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + Assert.True(raised); + } + } + + [Fact] + public void Popup_Should_Not_Follow_Placement_Target_On_Window_Resize_If_Pointer_If_Pointer() + { + using (CreateServices()) + { + + var placementTarget = new Panel() + { + Width = 10, + Height = 10, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + var popup = new Popup() + { + PlacementTarget = placementTarget, + PlacementMode = PlacementMode.Pointer, + Width = 10, + Height = 10 + }; + ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget); + + var window = PreparedWindow(placementTarget); + window.Show(); + popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + + // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window + Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10)); + + var raised = false; + if (popup.Host is PopupRoot popupRoot) + { + popupRoot.PositionChanged += (_, args) => + { + raised = true; + }; + + } + else if (popup.Host is OverlayPopupHost overlayPopupHost) + { + overlayPopupHost.PropertyChanged += (_, args) => + { + if (args.Property == Canvas.TopProperty + || args.Property == Canvas.LeftProperty) + { + raised = true; + } + }; + } + window.PlatformImpl?.Resize(new Size(700D, 500D), PlatformResizeReason.Unspecified); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + Assert.False(raised); + } + } + + [Fact] + public void Popup_Should_Follow_Placement_Target_On_Target_Moved() + { + using (CreateServices()) + { + var placementTarget = new Panel() + { + Width = 10, + Height = 10, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + var popup = new Popup() + { + PlacementTarget = placementTarget, + PlacementMode = PlacementMode.Bottom, + Width = 10, + Height = 10 + }; + ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget); + + var window = PreparedWindow(placementTarget); + window.Show(); + popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + + // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window + Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10)); + + var raised = false; + // Margin will move placement target + if (popup.Host is PopupRoot popupRoot) + { + popupRoot.PositionChanged += (_, args) => + { + Assert.Equal(new PixelPoint(400, 305), args.Point); + raised = true; + }; + + } + else if (popup.Host is OverlayPopupHost overlayPopupHost) + { + overlayPopupHost.PropertyChanged += (_, args) => + { + if ((args.Property == Canvas.TopProperty + || args.Property == Canvas.LeftProperty) + && Canvas.GetLeft(overlayPopupHost) == 400 + && Canvas.GetTop(overlayPopupHost) == 305) + { + raised = true; + } + }; + } + placementTarget.Margin = new Thickness(10, 0, 0, 0); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + Assert.True(raised); + } + } + + [Fact] + public void Popup_Should_Not_Follow_Placement_Target_On_Target_Moved_If_Pointer() + { + using (CreateServices()) + { + + var placementTarget = new Panel() + { + Width = 10, + Height = 10, + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center + }; + var popup = new Popup() + { + PlacementTarget = placementTarget, + PlacementMode = PlacementMode.Pointer, + Width = 10, + Height = 10 + }; + ((ISetLogicalParent)popup).SetParent(popup.PlacementTarget); + + var window = PreparedWindow(placementTarget); + window.Show(); + popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + + // The target's initial placement is (395,295) which is a 10x10 panel centered in a 800x600 window + Assert.Equal(placementTarget.Bounds, new Rect(395D, 295D, 10, 10)); + + var raised = false; + if (popup.Host is PopupRoot popupRoot) + { + popupRoot.PositionChanged += (_, args) => + { + raised = true; + }; + + } + else if (popup.Host is OverlayPopupHost overlayPopupHost) + { + overlayPopupHost.PropertyChanged += (_, args) => + { + if (args.Property == Canvas.TopProperty + || args.Property == Canvas.LeftProperty) + { + raised = true; + } + }; + } + placementTarget.Margin = new Thickness(10, 0, 0, 0); + Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout); + Assert.False(raised); } } From 240050dd8a7395bdab3c309d00b86fd3f0689ea6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 10:42:05 +0300 Subject: [PATCH 076/260] Stopgap measure to fix #7245 --- src/Avalonia.X11/X11Window.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 982bca6439..211a8e4f79 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1157,7 +1157,7 @@ namespace Avalonia.X11 public ITextInputMethodImpl TextInputMethod => _ime; public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => - _transparencyHelper.SetTransparencyRequest(transparencyLevel); + _transparencyHelper?.SetTransparencyRequest(transparencyLevel); public void SetWindowManagerAddShadowHint(bool enabled) { From 6ef87ff047f6fddc7afdc2bd2ff073f4141cb313 Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 23 Dec 2021 10:23:26 +0100 Subject: [PATCH 077/260] Improve ButtonSpinner OnPointerWheel - Only handle the PointerWheelEvent if the control has the focus - When the pointer wheel should change the selection, mark the event as handled. So any surrounding ScrollViewer does not do the scroll. - Make the Control Focusable by default in Style --- src/Avalonia.Controls/ButtonSpinner.cs | 7 ++++--- src/Avalonia.Themes.Default/ButtonSpinner.xaml | 1 + src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index 5fe2cf3704..5a967ab488 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -1,4 +1,4 @@ -using System; +using System; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Data; @@ -196,13 +196,14 @@ namespace Avalonia.Controls protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { base.OnPointerWheelChanged(e); - if (!e.Handled && AllowSpin) + + if (AllowSpin && IsFocused) { if (e.Delta.Y != 0) { var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true); OnSpin(spinnerEventArgs); - e.Handled = spinnerEventArgs.Handled; + e.Handled = true; } } } diff --git a/src/Avalonia.Themes.Default/ButtonSpinner.xaml b/src/Avalonia.Themes.Default/ButtonSpinner.xaml index ce2b85d2b5..561ca8bedd 100644 --- a/src/Avalonia.Themes.Default/ButtonSpinner.xaml +++ b/src/Avalonia.Themes.Default/ButtonSpinner.xaml @@ -5,6 +5,7 @@ + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 085de2ae7a..b21b9d80f0 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -16,12 +16,12 @@ namespace ControlCatalog DataContext = new ApplicationViewModel(); } - private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") }; - private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + public static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") }; @@ -31,8 +31,7 @@ namespace ControlCatalog new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml") - }, - DataGridFluent + } }; public static Styles FluentLight = new Styles @@ -40,8 +39,7 @@ namespace ControlCatalog new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml") - }, - DataGridFluent + } }; public static Styles DefaultLight = new Styles @@ -65,8 +63,7 @@ namespace ControlCatalog new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") - }, - DataGridDefault + } }; public static Styles DefaultDark = new Styles @@ -90,8 +87,7 @@ namespace ControlCatalog new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog")) { Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml") - }, - DataGridDefault + } }; public override void Initialize() diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 3898e9da85..b36c2b6997 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -3,15 +3,12 @@ using System.Collections; using Avalonia; using Avalonia.Controls; using Avalonia.Markup.Xaml; -using Avalonia.Markup.Xaml.MarkupExtensions; -using Avalonia.Markup.Xaml.Styling; -using Avalonia.Markup.Xaml.XamlIl; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; -using ControlCatalog.Pages; -using ControlCatalog.Models; using Avalonia.Themes.Fluent; +using ControlCatalog.Models; +using ControlCatalog.Pages; namespace ControlCatalog { @@ -44,7 +41,47 @@ namespace ControlCatalog { if (themes.SelectedItem is CatalogTheme theme) { - (Application.Current.Styles[0] as FluentTheme).Mode = FluentThemeMode.Dark; + var themeStyle = Application.Current.Styles[0]; + if (theme == CatalogTheme.FluentLight) + { + if (themeStyle is FluentTheme fluentTheme) + { + if (fluentTheme.Mode == FluentThemeMode.Dark) + { + fluentTheme.Mode = FluentThemeMode.Light; + } + } + else + { + Application.Current.Styles[0] = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); + Application.Current.Styles[1] = App.DataGridFluent; + } + } + else if (theme == CatalogTheme.FluentDark) + { + if (themeStyle is FluentTheme fluentTheme) + { + if (fluentTheme.Mode == FluentThemeMode.Light) + { + fluentTheme.Mode = FluentThemeMode.Dark; + } + } + else + { + Application.Current.Styles[0] = new FluentTheme(new Uri("avares://ControlCatalog/Styles")) { Mode = FluentThemeMode.Dark }; + Application.Current.Styles[1] = App.DataGridFluent; + } + } + else if (theme == CatalogTheme.DefaultLight) + { + Application.Current.Styles[0] = App.DefaultLight; + Application.Current.Styles[1] = App.DataGridDefault; + } + else if (theme == CatalogTheme.DefaultDark) + { + Application.Current.Styles[0] = App.DefaultDark; + Application.Current.Styles[1] = App.DataGridDefault; + } } }; diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 1c61c03c97..5790de42b1 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -18,7 +18,7 @@ namespace Avalonia.Themes.Fluent /// /// Includes the fluent theme in an application. /// - public class FluentTheme : IStyle, IResourceProvider + public class FluentTheme : AvaloniaObject, IStyle, IResourceProvider { private readonly Uri _baseUri; private Styles _fluentDark = new(); @@ -26,7 +26,6 @@ namespace Avalonia.Themes.Fluent private Styles _sharedStyles = new(); private bool _isLoading; private IStyle? _loaded; - private FluentThemeMode _mode; /// /// Initializes a new instance of the class. @@ -48,31 +47,32 @@ namespace Avalonia.Themes.Fluent InitStyles(_baseUri); } + + public static readonly StyledProperty ModeProperty = + AvaloniaProperty.Register(nameof(Mode)); /// /// Gets or sets the mode of the fluent theme (light, dark). /// public FluentThemeMode Mode { - get => _mode; - set + get => GetValue(ModeProperty); + set => SetValue(ModeProperty, value); + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + if (change.Property == ModeProperty) { - if (_mode != value) + if (Mode == FluentThemeMode.Dark) { - _mode = value; - if (_mode == FluentThemeMode.Dark) - { - (Loaded as Styles)![1] = _fluentDark[0]; - (Loaded as Styles)![2] = _fluentDark[1]; - } - else - { - (Loaded as Styles)![1] = _fluentLight[0]; - (Loaded as Styles)![2] = _fluentLight[1]; - } - - + (Loaded as Styles)![1] = _fluentDark[0]; + (Loaded as Styles)![2] = _fluentDark[1]; + } + else + { + (Loaded as Styles)![1] = _fluentLight[0]; + (Loaded as Styles)![2] = _fluentLight[1]; } - } } From 5172771a79a032e9df38c25fa3636d042c6e57b2 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Dec 2021 15:53:14 +0200 Subject: [PATCH 081/260] refactoring --- src/Avalonia.Themes.Fluent/FluentTheme.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/FluentTheme.cs b/src/Avalonia.Themes.Fluent/FluentTheme.cs index 5790de42b1..53be41e4d1 100644 --- a/src/Avalonia.Themes.Fluent/FluentTheme.cs +++ b/src/Avalonia.Themes.Fluent/FluentTheme.cs @@ -88,23 +88,19 @@ namespace Avalonia.Themes.Fluent if (_loaded == null) { _isLoading = true; - Styles? resultStyle = new Styles() { _sharedStyles }; if (Mode == FluentThemeMode.Light) { - resultStyle.Add(_fluentLight[0]); - resultStyle.Add(_fluentLight[1]); + _loaded = new Styles() { _sharedStyles , _fluentLight[0], _fluentLight[1] }; } else if (Mode == FluentThemeMode.Dark) { - resultStyle.Add(_fluentDark[0]); - resultStyle.Add(_fluentDark[1]); + _loaded = new Styles() { _sharedStyles, _fluentDark[0], _fluentDark[1] }; } - _loaded = resultStyle; _isLoading = false; } - return _loaded; + return _loaded!; } } From c6264191137fff72ca9ffd1f2a1b523d4ef79a7c Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Dec 2021 16:00:43 +0200 Subject: [PATCH 082/260] more refactorings --- samples/ControlCatalog/App.xaml | 2 -- samples/ControlCatalog/App.xaml.cs | 20 +++++--------------- samples/ControlCatalog/MainView.xaml.cs | 4 ++-- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index ab3aa5a4ae..227b31bf20 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -5,8 +5,6 @@ x:CompileBindings="True" x:Class="ControlCatalog.App"> - - diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index b21b9d80f0..4e86c60285 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.Styling; using Avalonia.Styling; +using Avalonia.Themes.Fluent; using ControlCatalog.ViewModels; namespace ControlCatalog @@ -26,21 +27,9 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") }; - public static Styles FluentDark = new Styles - { - new StyleInclude(new Uri("avares://ControlCatalog/Styles")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/FluentDark.xaml") - } - }; + public static FluentTheme FluentDark = new FluentTheme(new Uri("avares://ControlCatalog/Styles")) { Mode = FluentThemeMode.Dark }; - public static Styles FluentLight = new Styles - { - new StyleInclude(new Uri("avares://ControlCatalog/Styles")) - { - Source = new Uri("avares://Avalonia.Themes.Fluent/FluentLight.xaml") - } - }; + public static FluentTheme FluentLight = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); public static Styles DefaultLight = new Styles { @@ -92,7 +81,8 @@ namespace ControlCatalog public override void Initialize() { - + Styles.Insert(0, FluentLight); + Styles.Insert(1, DataGridFluent); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index b36c2b6997..696062db3f 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -53,7 +53,7 @@ namespace ControlCatalog } else { - Application.Current.Styles[0] = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); + Application.Current.Styles[0] = App.FluentLight; Application.Current.Styles[1] = App.DataGridFluent; } } @@ -68,7 +68,7 @@ namespace ControlCatalog } else { - Application.Current.Styles[0] = new FluentTheme(new Uri("avares://ControlCatalog/Styles")) { Mode = FluentThemeMode.Dark }; + Application.Current.Styles[0] = App.FluentDark; Application.Current.Styles[1] = App.DataGridFluent; } } From 4fbb93a03cac66ceb63d9426be1f2e84663827ef Mon Sep 17 00:00:00 2001 From: Giacomo Gozzi Date: Thu, 23 Dec 2021 17:49:00 +0300 Subject: [PATCH 083/260] Fix #7227 --- .../GestureRecognizers/ScrollGestureRecognizer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs index 7532676f18..f8cedb636f 100644 --- a/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -71,7 +71,7 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId();; - _trackedRootPoint = e.GetPosition(null); + _trackedRootPoint = e.GetPosition(_target); } } From 7daccf91ae96422fba7208428b1df8e61424a20a Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Dec 2021 17:37:11 +0200 Subject: [PATCH 084/260] cleanup --- samples/ControlCatalog/App.xaml.cs | 6 ++---- samples/ControlCatalog/MainView.xaml.cs | 5 +++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 4e86c60285..aba22a4af6 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -27,9 +27,7 @@ namespace ControlCatalog Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml") }; - public static FluentTheme FluentDark = new FluentTheme(new Uri("avares://ControlCatalog/Styles")) { Mode = FluentThemeMode.Dark }; - - public static FluentTheme FluentLight = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); + public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles")); public static Styles DefaultLight = new Styles { @@ -81,7 +79,7 @@ namespace ControlCatalog public override void Initialize() { - Styles.Insert(0, FluentLight); + Styles.Insert(0, Fluent); Styles.Insert(1, DataGridFluent); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 696062db3f..d36ec3d272 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -53,7 +53,7 @@ namespace ControlCatalog } else { - Application.Current.Styles[0] = App.FluentLight; + Application.Current.Styles[0] = App.Fluent; Application.Current.Styles[1] = App.DataGridFluent; } } @@ -68,7 +68,8 @@ namespace ControlCatalog } else { - Application.Current.Styles[0] = App.FluentDark; + App.Fluent.Mode = FluentThemeMode.Dark; + Application.Current.Styles[0] = App.Fluent; Application.Current.Styles[1] = App.DataGridFluent; } } From 3bfafc5f9397893ca247b149091a1ba02167fd6b Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Thu, 23 Dec 2021 17:56:30 +0200 Subject: [PATCH 085/260] more cleanup --- samples/ControlCatalog/MainView.xaml.cs | 28 +++++++------------------ 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index d36ec3d272..0579355831 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -44,34 +44,22 @@ namespace ControlCatalog var themeStyle = Application.Current.Styles[0]; if (theme == CatalogTheme.FluentLight) { - if (themeStyle is FluentTheme fluentTheme) + if (App.Fluent.Mode != FluentThemeMode.Light) { - if (fluentTheme.Mode == FluentThemeMode.Dark) - { - fluentTheme.Mode = FluentThemeMode.Light; - } - } - else - { - Application.Current.Styles[0] = App.Fluent; - Application.Current.Styles[1] = App.DataGridFluent; + App.Fluent.Mode = FluentThemeMode.Light; } + Application.Current.Styles[0] = App.Fluent; + Application.Current.Styles[1] = App.DataGridFluent; } else if (theme == CatalogTheme.FluentDark) { - if (themeStyle is FluentTheme fluentTheme) - { - if (fluentTheme.Mode == FluentThemeMode.Light) - { - fluentTheme.Mode = FluentThemeMode.Dark; - } - } - else + + if (App.Fluent.Mode != FluentThemeMode.Dark) { App.Fluent.Mode = FluentThemeMode.Dark; - Application.Current.Styles[0] = App.Fluent; - Application.Current.Styles[1] = App.DataGridFluent; } + Application.Current.Styles[0] = App.Fluent; + Application.Current.Styles[1] = App.DataGridFluent; } else if (theme == CatalogTheme.DefaultLight) { From 1ed495afe2d01b40a512506532bafe86c5b6f903 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:39:35 +0300 Subject: [PATCH 086/260] Use microcom generator from nuget --- Avalonia.sln | 27 - build/MicroCom.targets | 35 -- nukebuild/MicroComGen.cs | 8 +- nukebuild/_build.csproj | 5 +- src/Avalonia.MicroCom/MicroComRuntime.cs | 6 + src/Avalonia.MicroCom/MicroComVtblBase.cs | 5 + src/Avalonia.Native/Avalonia.Native.csproj | 5 +- src/Directory.Build.props | 6 + src/Shared/ModuleInitializer.cs | 10 + .../Avalonia.Win32/Avalonia.Win32.csproj | 6 +- src/tools/MicroComGenerator/Ast.cs | 241 --------- src/tools/MicroComGenerator/AstParser.cs | 232 --------- .../CSharpGen.InterfaceGen.cs | 484 ------------------ .../MicroComGenerator/CSharpGen.Utils.cs | 111 ---- src/tools/MicroComGenerator/CSharpGen.cs | 155 ------ src/tools/MicroComGenerator/CppGen.cs | 119 ----- src/tools/MicroComGenerator/Extensions.cs | 97 ---- .../MicroComGenerator.csproj | 10 - src/tools/MicroComGenerator/ParseException.cs | 27 - src/tools/MicroComGenerator/Program.cs | 52 -- src/tools/MicroComGenerator/TokenParser.cs | 417 --------------- 21 files changed, 38 insertions(+), 2020 deletions(-) delete mode 100644 build/MicroCom.targets create mode 100644 src/Shared/ModuleInitializer.cs delete mode 100644 src/tools/MicroComGenerator/Ast.cs delete mode 100644 src/tools/MicroComGenerator/AstParser.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.Utils.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.cs delete mode 100644 src/tools/MicroComGenerator/CppGen.cs delete mode 100644 src/tools/MicroComGenerator/Extensions.cs delete mode 100644 src/tools/MicroComGenerator/MicroComGenerator.csproj delete mode 100644 src/tools/MicroComGenerator/ParseException.cs delete mode 100644 src/tools/MicroComGenerator/Program.cs delete mode 100644 src/tools/MicroComGenerator/TokenParser.cs diff --git a/Avalonia.sln b/Avalonia.sln index 3c2fc7437b..4e7b4cc318 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" @@ -2027,30 +2025,6 @@ Global {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU @@ -2253,7 +2227,6 @@ Global {3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} - {AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/build/MicroCom.targets b/build/MicroCom.targets deleted file mode 100644 index 029d7f95f5..0000000000 --- a/build/MicroCom.targets +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - false - all - true - TargetFramework=net6.0 - - - - - - - - - - - - - - - - - - <_AvaloniaPatchComInterop>true - - - diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs index 06c8acbf23..b1e546cb97 100644 --- a/nukebuild/MicroComGen.cs +++ b/nukebuild/MicroComGen.cs @@ -1,14 +1,14 @@ using System.IO; -using MicroComGenerator; +using MicroCom.CodeGenerator; using Nuke.Common; partial class Build : NukeBuild { Target GenerateCppHeaders => _ => _.Executes(() => { - var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); - var ast = AstParser.Parse(text); + var file = MicroComCodeGenerator.Parse( + File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", - CppGen.GenerateCpp(ast)); + file.GenerateCppHeader()); }); } \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index b28d3eb700..acb001f2e7 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -16,6 +16,7 @@ + @@ -37,10 +38,6 @@ - - MicroComGenerator\%(Filename)%(Extension) - - diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs index 85507674d2..e0f524146a 100644 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ b/src/Avalonia.MicroCom/MicroComRuntime.cs @@ -36,7 +36,13 @@ namespace Avalonia.MicroCom public static T CreateProxyFor(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); public static T CreateProxyFor(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); + + public static T CreateProxyOrNullFor(void* pObject, bool ownsHandle) where T : class => + pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); + public static T CreateProxyOrNullFor(IntPtr pObject, bool ownsHandle) where T : class => + pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle); + public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle); public static IntPtr GetNativeIntPtr(this T obj, bool owned = false) where T : IUnknown diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs index 2f0607c0a8..7092f8131a 100644 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs @@ -21,6 +21,11 @@ namespace Avalonia.MicroCom AddMethod((AddRefDelegate)Release); } + protected void AddMethod(void* f) + { + _methods.Add(new IntPtr(f)); + } + protected void AddMethod(Delegate d) { GCHandle.Alloc(d); diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c37bef0488..366ce97955 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -6,6 +6,7 @@ true netstandard2.0;net6.0 true + Avalonia.MicroCom @@ -20,7 +21,7 @@ - + + - diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a3f0c01a51..869602e452 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,4 +2,10 @@ + + + Shared\_ModuleInitializer.cs + false + + diff --git a/src/Shared/ModuleInitializer.cs b/src/Shared/ModuleInitializer.cs new file mode 100644 index 0000000000..c14b150c1a --- /dev/null +++ b/src/Shared/ModuleInitializer.cs @@ -0,0 +1,10 @@ +namespace System.Runtime.CompilerServices +{ +#if !NET5_0_OR_GREATER + internal class ModuleInitializerAttribute : Attribute + { + + } +#endif +} + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c8f08a5799..a0290ee5e8 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -8,9 +8,9 @@ - - + + + - diff --git a/src/tools/MicroComGenerator/Ast.cs b/src/tools/MicroComGenerator/Ast.cs deleted file mode 100644 index e9a55308be..0000000000 --- a/src/tools/MicroComGenerator/Ast.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace MicroComGenerator.Ast -{ - public class AstAttributeNode - { - public string Name { get; set; } - public string Value { get; set; } - - public AstAttributeNode(string name, string value) - { - Name = name; - Value = value; - } - - public override string ToString() => $"{Name} = {Value}"; - public AstAttributeNode Clone() => new AstAttributeNode(Name, Value); - } - - public class AstAttributes : List - { - public bool HasAttribute(string a) => this.Any(x => x.Name == a); - - public AstAttributes Clone() - { - var rv= new AstAttributes(); - rv.AddRange(this.Select(x => x.Clone())); - return rv; - } - } - - public interface IAstNodeWithAttributes - { - public AstAttributes Attributes { get; set; } - } - - public class AstEnumNode : List, IAstNodeWithAttributes - { - public AstAttributes Attributes { get; set; } = new AstAttributes(); - public string Name { get; set; } - public override string ToString() => "Enum " + Name; - - public AstEnumNode Clone() - { - var rv = new AstEnumNode { Name = Name, Attributes = Attributes.Clone() }; - rv.AddRange(this.Select(x => x.Clone())); - return rv; - } - } - - public class AstEnumMemberNode - { - public string Name { get; set; } - public string Value { get; set; } - - public AstEnumMemberNode(string name, string value) - { - Name = name; - Value = value; - } - - public override string ToString() => $"Enum member {Name} = {Value}"; - public AstEnumMemberNode Clone() => new AstEnumMemberNode(Name, Value); - } - - public class AstStructNode : List, IAstNodeWithAttributes - { - public AstAttributes Attributes { get; set; } = new AstAttributes(); - public string Name { get; set; } - public override string ToString() => "Struct " + Name; - - public AstStructNode Clone() - { - var rv = new AstStructNode { Name = Name, Attributes = Attributes.Clone() }; - rv.AddRange(this.Select(x => x.Clone())); - return rv; - } - } - - public class AstTypeNode - { - public string Name { get; set; } - public int PointerLevel { get; set; } - public bool IsLink { get; set; } - - public string Format() => Name + new string('*', PointerLevel) - + (IsLink ? "&" : ""); - public override string ToString() => Format(); - public AstTypeNode Clone() => new AstTypeNode() { - Name = Name, - PointerLevel = PointerLevel, - IsLink = IsLink - }; - } - - public class AstStructMemberNode : IAstNodeWithAttributes - { - public string Name { get; set; } - public AstTypeNode Type { get; set; } - - public override string ToString() => $"Struct member {Type.Format()} {Name}"; - public AstStructMemberNode Clone() => new AstStructMemberNode() { Name = Name, Type = Type.Clone() }; - public AstAttributes Attributes { get; set; } = new AstAttributes(); - } - - public class AstInterfaceNode : List, IAstNodeWithAttributes - { - public AstAttributes Attributes { get; set; } = new AstAttributes(); - public string Name { get; set; } - public string Inherits { get; set; } - - public override string ToString() - { - if (Inherits == null) - return Name; - return $"Interface {Name} : {Inherits}"; - } - public AstInterfaceNode Clone() - { - var rv = new AstInterfaceNode { Name = Name, Inherits = Inherits, Attributes = Attributes.Clone() }; - rv.AddRange(this.Select(x => x.Clone())); - return rv; - } - } - - public class AstInterfaceMemberNode : List, IAstNodeWithAttributes - { - public string Name { get; set; } - public AstTypeNode ReturnType { get; set; } - public AstAttributes Attributes { get; set; } = new AstAttributes(); - - public AstInterfaceMemberNode Clone() - { - var rv = new AstInterfaceMemberNode() - { - Name = Name, Attributes = Attributes.Clone(), ReturnType = ReturnType - }; - rv.AddRange(this.Select(x => x.Clone())); - return rv; - } - - public override string ToString() => - $"Interface member {ReturnType.Format()} {Name} ({string.Join(", ", this.Select(x => x.Format()))})"; - } - - public class AstInterfaceMemberArgumentNode : IAstNodeWithAttributes - { - public string Name { get; set; } - public AstTypeNode Type { get; set; } - public AstAttributes Attributes { get; set; } = new AstAttributes(); - - - public string Format() => $"{Type.Format()} {Name}"; - public override string ToString() => "Argument " + Format(); - - public AstInterfaceMemberArgumentNode Clone() => new AstInterfaceMemberArgumentNode - { - Name = Name, Type = Type.Clone(), Attributes = Attributes.Clone() - }; - } - - public static class AstExtensions - { - public static bool HasAttribute(this IAstNodeWithAttributes node, string s) => node.Attributes.HasAttribute(s); - - public static string GetAttribute(this IAstNodeWithAttributes node, string s) - { - var value = node.Attributes.FirstOrDefault(a => a.Name == s)?.Value; - if (value == null) - throw new CodeGenException("Expected attribute " + s + " for node " + node); - return value; - } - - public static string GetAttributeOrDefault(this IAstNodeWithAttributes node, string s) - => node.Attributes.FirstOrDefault(a => a.Name == s)?.Value; - } - - class AstVisitor - { - protected virtual void VisitType(AstTypeNode type) - { - } - - protected virtual void VisitArgument(AstInterfaceMemberArgumentNode argument) - { - VisitType(argument.Type); - } - - protected virtual void VisitInterfaceMember(AstInterfaceMemberNode member) - { - foreach(var a in member) - VisitArgument(a); - VisitType(member.ReturnType); - } - - protected virtual void VisitInterface(AstInterfaceNode iface) - { - foreach(var m in iface) - VisitInterfaceMember(m); - } - - protected virtual void VisitStructMember(AstStructMemberNode member) - { - VisitType(member.Type); - } - - protected virtual void VisitStruct(AstStructNode node) - { - foreach(var m in node) - VisitStructMember(m); - } - - public virtual void VisitAst(AstIdlNode ast) - { - foreach(var iface in ast.Interfaces) - VisitInterface(iface); - foreach (var s in ast.Structs) - VisitStruct(s); - } - - - } - - public class AstIdlNode : IAstNodeWithAttributes - { - public AstAttributes Attributes { get; set; } = new AstAttributes(); - public List Enums { get; set; } = new List(); - public List Structs { get; set; } = new List(); - public List Interfaces { get; set; } = new List(); - - public AstIdlNode Clone() => new AstIdlNode() - { - Attributes = Attributes.Clone(), - Enums = Enums.Select(x => x.Clone()).ToList(), - Structs = Structs.Select(x => x.Clone()).ToList(), - Interfaces = Interfaces.Select(x => x.Clone()).ToList() - }; - } -} diff --git a/src/tools/MicroComGenerator/AstParser.cs b/src/tools/MicroComGenerator/AstParser.cs deleted file mode 100644 index 388a8eb018..0000000000 --- a/src/tools/MicroComGenerator/AstParser.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System.Collections.Generic; -using MicroComGenerator.Ast; - -namespace MicroComGenerator -{ - public class AstParser - { - public static AstIdlNode Parse(string source) - { - var parser = new TokenParser(source); - var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) }; - - while (!parser.Eof) - { - var attrs = ParseLocalAttributes(ref parser); - if (parser.TryConsume(";")) - continue; - if (parser.TryParseKeyword("enum")) - idl.Enums.Add(ParseEnum(attrs, ref parser)); - else if (parser.TryParseKeyword("struct")) - idl.Structs.Add(ParseStruct(attrs, ref parser)); - else if (parser.TryParseKeyword("interface")) - idl.Interfaces.Add(ParseInterface(attrs, ref parser)); - else - throw new ParseException("Unexpected character", ref parser); - } - - return idl; - } - - static AstAttributes ParseGlobalAttributes(ref TokenParser parser) - { - var rv = new AstAttributes(); - while (!parser.Eof) - { - parser.SkipWhitespace(); - if (parser.TryConsume('@')) - { - var ident = parser.ParseIdentifier("-"); - var value = parser.ReadToEol().Trim(); - if (value == "@@") - { - parser.Advance(1); - value = ""; - while (true) - { - var l = parser.ReadToEol(); - if (l == "@@") - break; - else - value = value.Length == 0 ? l : (value + "\n" + l); - parser.Advance(1); - } - - } - rv.Add(new AstAttributeNode(ident, value)); - } - else - return rv; - } - - return rv; - } - - static AstAttributes ParseLocalAttributes(ref TokenParser parser) - { - var rv = new AstAttributes(); - while (parser.TryConsume("[")) - { - while (!parser.TryConsume("]") && !parser.Eof) - { - if (parser.TryConsume(',')) - continue; - - // Get identifier - var ident = parser.ParseIdentifier("-"); - - // No value, end of attribute list - if (parser.TryConsume(']')) - { - rv.Add(new AstAttributeNode(ident, null)); - break; - } - // No value, next attribute - else if (parser.TryConsume(',')) - rv.Add(new AstAttributeNode(ident, null)); - // Has value - else if (parser.TryConsume('(')) - { - var value = parser.ReadTo(')'); - parser.Consume(')'); - rv.Add(new AstAttributeNode(ident, value)); - } - else - throw new ParseException("Unexpected character", ref parser); - } - - if (parser.Eof) - throw new ParseException("Unexpected EOF", ref parser); - } - - return rv; - } - - static void EnsureOpenBracket(ref TokenParser parser) - { - if (!parser.TryConsume('{')) - throw new ParseException("{ expected", ref parser); - } - - static AstEnumNode ParseEnum(AstAttributes attrs, ref TokenParser parser) - { - var name = parser.ParseIdentifier(); - EnsureOpenBracket(ref parser); - var rv = new AstEnumNode { Name = name, Attributes = attrs }; - while (!parser.TryConsume('}') && !parser.Eof) - { - if (parser.TryConsume(',')) - continue; - - var ident = parser.ParseIdentifier(); - - // Automatic value - if (parser.TryConsume(',') || parser.Peek == '}') - { - rv.Add(new AstEnumMemberNode(ident, null)); - continue; - } - - if (!parser.TryConsume('=')) - throw new ParseException("Unexpected character", ref parser); - - var value = parser.ReadToAny(",}").Trim(); - rv.Add(new AstEnumMemberNode(ident, value)); - - if (parser.Eof) - throw new ParseException("Unexpected EOF", ref parser); - } - - - return rv; - } - - static AstTypeNode ParseType(ref TokenParser parser) - { - var ident = parser.ParseIdentifier(); - var t = new AstTypeNode { Name = ident }; - while (parser.TryConsume('*')) - t.PointerLevel++; - if (parser.TryConsume("&")) - t.IsLink = true; - return t; - } - - static AstStructNode ParseStruct(AstAttributes attrs, ref TokenParser parser) - { - var name = parser.ParseIdentifier(); - EnsureOpenBracket(ref parser); - var rv = new AstStructNode { Name = name, Attributes = attrs }; - while (!parser.TryConsume('}') && !parser.Eof) - { - var memberAttrs = ParseLocalAttributes(ref parser); - var t = ParseType(ref parser); - bool parsedAtLeastOneMember = false; - while (!parser.TryConsume(';')) - { - // Skip any , - while (parser.TryConsume(',')) { } - - var ident = parser.ParseIdentifier(); - parsedAtLeastOneMember = true; - rv.Add(new AstStructMemberNode { Name = ident, Type = t, Attributes = memberAttrs}); - } - - if (!parsedAtLeastOneMember) - throw new ParseException("Expected at least one enum member with declared type " + t, ref parser); - } - - return rv; - } - - static AstInterfaceNode ParseInterface(AstAttributes interfaceAttrs, ref TokenParser parser) - { - var interfaceName = parser.ParseIdentifier(); - string inheritsFrom = null; - if (parser.TryConsume(":")) - inheritsFrom = parser.ParseIdentifier(); - - EnsureOpenBracket(ref parser); - var rv = new AstInterfaceNode - { - Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom - }; - while (!parser.TryConsume('}') && !parser.Eof) - { - var memberAttrs = ParseLocalAttributes(ref parser); - var returnType = ParseType(ref parser); - var name = parser.ParseIdentifier(); - var member = new AstInterfaceMemberNode - { - Name = name, ReturnType = returnType, Attributes = memberAttrs - }; - rv.Add(member); - - parser.Consume('('); - while (true) - { - if (parser.TryConsume(')')) - break; - - var argumentAttrs = ParseLocalAttributes(ref parser); - var type = ParseType(ref parser); - var argName = parser.ParseIdentifier(); - member.Add(new AstInterfaceMemberArgumentNode - { - Name = argName, Type = type, Attributes = argumentAttrs - }); - - if (parser.TryConsume(')')) - break; - if (parser.TryConsume(',')) - continue; - throw new ParseException("Unexpected character", ref parser); - } - - parser.Consume(';'); - } - - return rv; - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs deleted file mode 100644 index adb8faf938..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs +++ /dev/null @@ -1,484 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using MicroComGenerator.Ast; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; - -// ReSharper disable CoVariantArrayConversion - -// HERE BE DRAGONS - -namespace MicroComGenerator -{ - public partial class CSharpGen - { - abstract class Arg - { - public string Name; - public string NativeType; - public AstAttributes Attributes { get; set; } - public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner; - - public virtual void PreMarshal(List body) - { - } - - public virtual void PreMarshalForReturn(List body) => - throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return"); - - public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name); - public abstract string ManagedType { get; } - public virtual string ReturnManagedType => ManagedType; - - public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") }; - - - public virtual void BackPreMarshal(List body) - { - } - - public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name); - public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar); - - } - - class InterfaceReturnArg : Arg - { - public string InterfaceType; - public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName); - public override string ManagedType => InterfaceType; - - private string PName => "__marshal_" + Name; - - public override void PreMarshalForReturn(List body) - { - body.Add(ParseStatement("void* " + PName + " = null;")); - } - - public override StatementSyntax[] ReturnMarshalResult() => new[] - { - ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + - PName + ", true);") - }; - - public override ExpressionSyntax BackMarshalValue() - { - return ParseExpression("INVALID"); - } - - public override ExpressionSyntax BackMarshalReturn(string resultVar) - { - return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)"); - } - } - - class InterfaceArg : Arg - { - public string InterfaceType; - - public override ExpressionSyntax Value(bool isHresultReturn) => - ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")"); - - public override string ManagedType => InterfaceType; - - public override StatementSyntax[] ReturnMarshalResult() => new[] - { - ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + - Name + ", true);") - }; - - public override ExpressionSyntax BackMarshalValue() - { - return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" + - Name + ", false)"); - } - - public override ExpressionSyntax BackMarshalReturn(string resultVar) - { - return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)"); - } - } - - class BypassArg : Arg - { - public string Type { get; set; } - public int PointerLevel; - public override string ManagedType => Type + new string('*', PointerLevel); - public override string ReturnManagedType => Type + new string('*', PointerLevel - 1); - - public override ExpressionSyntax Value(bool isHresultReturn) - { - if (isHresultReturn) - return ParseExpression("&" + Name); - return base.Value(false); - } - - public override void PreMarshalForReturn(List body) - { - if (PointerLevel == 0) - base.PreMarshalForReturn(body); - else - body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;")); - } - } - - class StringArg : Arg - { - private string BName => "__bytemarshal_" + Name; - private string FName => "__fixedmarshal_" + Name; - - public override void PreMarshal(List body) - { - body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];")); - body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name}.Length, {BName}, 0);")); - } - - public override StatementSyntax CreateFixed(StatementSyntax inner) - { - return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner); - } - - public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName); - public override string ManagedType => "string"; - public override ExpressionSyntax BackMarshalValue() - { - return ParseExpression( - $"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))"); - } - } - - string ConvertNativeType(string type) - { - if (type == "size_t") - return "System.IntPtr"; - if (type == "HRESULT") - return "int"; - return type; - } - - Arg ConvertArg(AstInterfaceMemberArgumentNode node) - { - var arg = ConvertArg(node.Name, node.Type); - arg.Attributes = node.Attributes.Clone(); - return arg; - } - - Arg ConvertArg(string name, AstTypeNode type) - { - type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel }; - - if (type.PointerLevel == 2) - { - if (IsInterface(type)) - return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" }; - } - else if (type.PointerLevel == 1) - { - if (IsInterface(type)) - return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" }; - if (type.Name == "char") - return new StringArg { Name = name, NativeType = "byte*" }; - } - - return new BypassArg - { - Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString() - }; - } - - - void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface, - ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl, - List vtblCtor, int num) - { - // Prepare method information - if (member.Name == "GetRenderingDevice") - Console.WriteLine(); - var args = member.Select(ConvertArg).ToList(); - var returnArg = ConvertArg("__result", member.ReturnType); - bool isHresult = member.ReturnType.Name == "HRESULT"; - bool isHresultLastArgumentReturn = isHresult - && args.Count > 0 - && (args.Last().Name == "ppv" - || args.Last().Name == "retOut" - || args.Last().Name == "ret" - || args.Last().Attributes.HasAttribute("out") - || args.Last().Attributes.HasAttribute("retval") - ) - && ((member.Last().Type.PointerLevel > 0 - && !IsInterface(member.Last().Type)) - || member.Last().Type.PointerLevel == 2); - - bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0; - - - // Generate method signature - MethodDeclarationSyntax GenerateManagedSig(string returnType, string name, - IEnumerable<(string n, string t)> args) - => MethodDeclaration(ParseTypeName(returnType), name).WithParameterList( - ParameterList( - SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t)))))); - - var managedSig = - isHresult ? - GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void", - member.Name, - (isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) : - GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType))); - - iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon())); - - // Prepare args for marshaling - var preMarshal = new List(); - if (!isVoidReturn) - preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;")); - - for (var idx = 0; idx < args.Count; idx++) - { - if (isHresultLastArgumentReturn && idx == args.Count - 1) - args[idx].PreMarshalForReturn(preMarshal); - else - args[idx].PreMarshal(preMarshal); - } - - // Generate call expression - ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType, - args.Select(x => x.NativeType).ToList())) - .AddArgumentListArguments(Argument(ParseExpression("PPV"))) - .AddArgumentListArguments(args - .Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray()) - .AddArgumentListArguments(Argument(ParseExpression("(*PPV)[base.VTableSize + " + num + "]"))); - - if (!isVoidReturn) - callExpr = CastExpression(ParseTypeName(returnArg.NativeType), callExpr); - - // Save call result if needed - if (!isVoidReturn) - callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"), - callExpr); - - - // Wrap call into fixed() blocks - StatementSyntax callStatement = ExpressionStatement(callExpr); - foreach (var arg in args) - callStatement = arg.CreateFixed(callStatement); - - // Build proxy body - var proxyBody = Block() - .AddStatements(preMarshal.ToArray()) - .AddStatements(callStatement); - - // Process return value - if (!isVoidReturn) - { - if (isHresult) - { - proxyBody = proxyBody.AddStatements( - ParseStatement( - $"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);")); - - if (isHresultLastArgumentReturn) - proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult()); - } - else - proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult()); - } - - // Add the proxy method - proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword) - .WithBody(proxyBody)); - - - // Generate VTable method - var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name + "Delegate") - .AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr"))) - .AddParameterListParameters(args.Select(x => - Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray()) - .AddAttribute("System.Runtime.InteropServices.UnmanagedFunctionPointer", - "System.Runtime.InteropServices.CallingConvention.StdCall"); - - var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name) - .WithParameterList(shadowDelegate.ParameterList) - .AddModifiers(Token(SyntaxKind.StaticKeyword)); - - var backPreMarshal = new List(); - foreach (var arg in args) - arg.BackPreMarshal(backPreMarshal); - - backPreMarshal.Add( - ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);")); - - var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn); - - StatementSyntax backCallStatement; - - var backCallExpr = - IsPropertyRewriteCandidate(managedSig) ? - ParseExpression("__target." + member.Name.Substring(3)) : - InvocationExpression(ParseExpression("__target." + member.Name)) - .WithArgumentList(ArgumentList(SeparatedList( - (isHresultLastArgumentReturn ? args.SkipLast(1) : args) - .Select(a => - Argument(a.BackMarshalValue()))))); - - if (isBackVoidReturn) - backCallStatement = ExpressionStatement(backCallExpr); - else - { - backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr)); - if (isHresultLastArgumentReturn) - { - backCallStatement = Block(backCallStatement, - ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, - ParseExpression("*" + args.Last().Name), - args.Last().BackMarshalReturn("__result") - ))); - - } - else - backCallStatement = Block(backCallStatement, - ReturnStatement(returnArg.BackMarshalReturn("__result"))); - } - - BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement); - - - var exceptions = new List() - { - CatchClause( - CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null, - Block( - ParseStatement( - "Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"), - isHresult ? ParseStatement("return unchecked((int)0x80004005u);") - : isVoidReturn ? EmptyStatement() : ParseStatement("return default;") - )) - }; - - if (isHresult) - exceptions.Insert(0, CatchClause( - CatchDeclaration(ParseTypeName("System.Runtime.InteropServices.COMException"), - Identifier("__com_exception__")), - null, Block(ParseStatement("return __com_exception__.ErrorCode;")))); - - backBodyBlock = Block( - TryStatement( - List(exceptions)) - .WithBlock(Block(backBodyBlock)) - ); - if (isHresult) - backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;")); - - - backBodyBlock = Block() - .AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;")) - .AddStatements(backBodyBlock.Statements.ToArray()); - - shadowMethod = shadowMethod.WithBody(backBodyBlock); - - vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod); - vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" + - shadowMethod.Identifier.Text + ");")); - - - - - } - - class LocalInteropHelper - { - public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop"); - private HashSet _existing = new HashSet(); - - public ExpressionSyntax GetCaller(string returnType, List args) - { - string ConvertType(string t) => t.EndsWith("*") ? "void*" : t; - returnType = ConvertType(returnType); - args = args.Select(ConvertType).ToList(); - - var name = "CalliStdCall" + returnType.Replace("*", "_ptr"); - var signature = returnType + "::" + name + "::" + string.Join("::", args); - if (_existing.Add(signature)) - { - Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name) - .AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword) - .AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*"))) - .AddParameterListParameters(args.Select((x, i) => - Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray()) - .AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*"))) - .WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null")))))); - } - - return ParseExpression("LocalInterop." + name); - } - } - - - void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs, - AstInterfaceNode iface) - { - var guidString = iface.GetAttribute("uuid"); - var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown"; - - var ifaceDec = InterfaceDeclaration(iface.Name) - .WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits) - .AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword)); - - var proxyClassName = "__MicroCom" + iface.Name + "Proxy"; - var proxy = ClassDeclaration(proxyClassName) - .AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility), Token(SyntaxKind.PartialKeyword)) - .WithBaseType(inheritsUnknown ? - "Avalonia.MicroCom.MicroComProxyBase" : - ("__MicroCom" + iface.Inherits + "Proxy")) - .AddBaseListTypes(SimpleBaseType(ParseTypeName(iface.Name))); - - - // Generate vtable - var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable") - .AddModifiers(Token(SyntaxKind.UnsafeKeyword)); - - vtbl = vtbl.WithBaseType(inheritsUnknown ? - "Avalonia.MicroCom.MicroComVtblBase" : - "__MicroCom" + iface.Inherits + "VTable"); - - var vtblCtor = new List(); - for (var idx = 0; idx < iface.Count; idx++) - GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx); - - vtbl = vtbl.AddMembers( - ConstructorDeclaration(vtbl.Identifier.Text) - .AddModifiers(Token(SyntaxKind.PublicKeyword)) - .WithBody(Block(vtblCtor)) - ) - .AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit") - .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword)) - .WithExpressionBody(ArrowExpressionClause( - ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" + - iface.Name + "), new " + vtbl.Identifier.Text + "().CreateVTable())"))) - .WithSemicolonToken(Semicolon())); - - - // Finalize proxy code - proxy = proxy.AddMembers( - MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit") - .AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword)) - .WithBody(Block( - ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" + - iface.Name + "), new Guid(\"" + guidString + "\"), (p, owns) => new " + - proxyClassName + "(p, owns));") - ))) - .AddMembers(ParseMemberDeclaration("public " + proxyClassName + - "(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}")) - .AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " + - iface.Count + ";")); - - ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec)); - implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl)); - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs deleted file mode 100644 index 28baaa65f8..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.Utils.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using MicroComGenerator.Ast; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Formatting; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace MicroComGenerator -{ - public partial class CSharpGen - { - - CompilationUnitSyntax Unit() - => CompilationUnit().WithUsings(List(new[] - { - "System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom" - } - .Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u))))); - - string Format(CompilationUnitSyntax unit) - { - var cw = new AdhocWorkspace(); - return - "#pragma warning disable 108\n" + - Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options - .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true) - .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, - true) - .WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true) - .WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true) - - ).ToFullString(); - } - - - SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken); - - static VariableDeclarationSyntax DeclareVar(string type, string name, - ExpressionSyntax initializer = null) - => VariableDeclaration(ParseTypeName(type), - SingletonSeparatedList(VariableDeclarator(name) - .WithInitializer(initializer == null ? null : EqualsValueClause(initializer)))); - - FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value) - => FieldDeclaration( - VariableDeclaration(ParseTypeName(type), - SingletonSeparatedList( - VariableDeclarator(name).WithInitializer(EqualsValueClause(value)) - )) - ).WithSemicolonToken(Semicolon()) - .WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword))); - - FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) => - DeclareField(type, name, null, modifiers); - - FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer, - params SyntaxKind[] modifiers) => - FieldDeclaration( - VariableDeclaration(ParseTypeName(type), - SingletonSeparatedList( - VariableDeclarator(name).WithInitializer(initializer)))) - .WithSemicolonToken(Semicolon()) - .WithModifiers(TokenList(modifiers.Select(x => Token(x)))); - - bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method) - { - - return - method.ReturnType.ToFullString() != "void" - && method.Identifier.Text.StartsWith("Get") - && method.ParameterList.Parameters.Count == 0; - } - - TypeDeclarationSyntax RewriteMethodsToProperties(T decl) where T : TypeDeclarationSyntax - { - var replace = new Dictionary(); - foreach (var method in decl.Members.OfType().ToList()) - { - if (IsPropertyRewriteCandidate(method)) - { - var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration); - if (method.Body != null) - getter = getter.WithBody(method.Body); - else - getter = getter.WithSemicolonToken(Semicolon()); - - replace[method] = PropertyDeclaration(method.ReturnType, - method.Identifier.Text.Substring(3)) - .WithModifiers(method.Modifiers).AddAccessorListAccessors(getter); - - } - } - - return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]); - } - - bool IsInterface(string name) - { - if (name == "IUnknown") - return true; - return _idl.Interfaces.Any(i => i.Name == name); - } - - private bool IsInterface(AstTypeNode type) => IsInterface(type.Name); - - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.cs b/src/tools/MicroComGenerator/CSharpGen.cs deleted file mode 100644 index ff4c351fd9..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using MicroComGenerator.Ast; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -// ReSharper disable CoVariantArrayConversion - -namespace MicroComGenerator -{ - public partial class CSharpGen - { - private readonly AstIdlNode _idl; - private List _extraUsings; - private string _namespace; - private SyntaxKind _visibility; - private LocalInteropHelper _localInterop = new LocalInteropHelper(); - - public CSharpGen(AstIdlNode idl) - { - _idl = idl.Clone(); - new AstRewriter(_idl.Attributes.Where(a => a.Name == "clr-map") - .Select(x => x.Value.Trim().Split(' ')) - .ToDictionary(x => x[0], x => x[1]) - ).VisitAst(_idl); - - _extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList(); - _namespace = _idl.GetAttribute("clr-namespace"); - var visibilityString = _idl.GetAttribute("clr-access"); - - if (visibilityString == "internal") - _visibility = SyntaxKind.InternalKeyword; - else if (visibilityString == "public") - _visibility = SyntaxKind.PublicKeyword; - else - throw new CodeGenException("Invalid clr-access attribute"); - } - - class AstRewriter : AstVisitor - { - private readonly Dictionary _typeMap = new Dictionary(); - - public AstRewriter(Dictionary typeMap) - { - _typeMap = typeMap; - } - - void ConvertIntPtr(AstTypeNode type) - { - if (type.Name == "void" && type.PointerLevel > 0) - { - type.Name = "IntPtr"; - type.PointerLevel--; - } - } - - protected override void VisitStructMember(AstStructMemberNode member) - { - if (member.HasAttribute("intptr")) - ConvertIntPtr(member.Type); - base.VisitStructMember(member); - } - - protected override void VisitType(AstTypeNode type) - { - if (type.IsLink) - { - type.PointerLevel++; - type.IsLink = false; - } - - if (_typeMap.TryGetValue(type.Name, out var mapped)) - type.Name = mapped; - - base.VisitType(type); - } - - protected override void VisitArgument(AstInterfaceMemberArgumentNode argument) - { - if (argument.HasAttribute("intptr")) - { - if(argument.Name == "retOut") - Console.WriteLine(); - ConvertIntPtr(argument.Type); - } - - base.VisitArgument(argument); - } - - protected override void VisitInterfaceMember(AstInterfaceMemberNode member) - { - if (member.HasAttribute("intptr")) - ConvertIntPtr(member.ReturnType); - if (member.HasAttribute("propget") && !member.Name.StartsWith("Get")) - member.Name = "Get" + member.Name; - if (member.HasAttribute("propput") && !member.Name.StartsWith("Set")) - member.Name = "Set" + member.Name; - base.VisitInterfaceMember(member); - } - } - - - public string Generate() - { - var ns = NamespaceDeclaration(ParseName(_namespace)); - var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl")); - ns = GenerateEnums(ns); - ns = GenerateStructs(ns); - foreach (var i in _idl.Interfaces) - GenerateInterface(ref ns, ref implNs, i); - - implNs = implNs.AddMembers(_localInterop.Class); - var unit = Unit().AddMembers(ns, implNs); - - return Format(unit); - } - - NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns) - { - return ns.AddMembers(_idl.Enums.Select(e => - { - var dec = EnumDeclaration(e.Name) - .WithModifiers(TokenList(Token(_visibility))) - .WithMembers(SeparatedList(e.Select(m => - { - var member = EnumMemberDeclaration(m.Name); - if (m.Value != null) - return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value))); - return member; - }))); - if (e.HasAttribute("flags")) - dec = dec.AddAttribute("System.Flags"); - return dec; - }).ToArray()); - } - - NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns) - { - return ns.AddMembers(_idl.Structs.Select(e => - StructDeclaration(e.Name) - .WithModifiers(TokenList(Token(_visibility))) - .AddAttribute("System.Runtime.InteropServices.StructLayout", "System.Runtime.InteropServices.LayoutKind.Sequential") - .AddModifiers(Token(SyntaxKind.UnsafeKeyword)) - .WithMembers(new SyntaxList(SeparatedList(e.Select(m => - DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword))))) - ).ToArray()); - } - - - - } -} diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs deleted file mode 100644 index b053088ca9..0000000000 --- a/src/tools/MicroComGenerator/CppGen.cs +++ /dev/null @@ -1,119 +0,0 @@ -using System; -using System.Linq; -using System.Text; -using MicroComGenerator.Ast; - -namespace MicroComGenerator -{ - public class CppGen - { - static string ConvertType(AstTypeNode type) - { - var name = type.Name; - if (name == "byte") - name = "unsigned char"; - else if(name == "uint") - name = "unsigned int"; - - type = type.Clone(); - type.Name = name; - return type.Format(); - } - - public static string GenerateCpp(AstIdlNode idl) - { - var sb = new StringBuilder(); - var preamble = idl.GetAttributeOrDefault("cpp-preamble"); - if (preamble != null) - sb.AppendLine(preamble); - - foreach (var s in idl.Structs) - sb.AppendLine("struct " + s.Name + ";"); - - foreach (var s in idl.Interfaces) - sb.AppendLine("struct " + s.Name + ";"); - - foreach (var en in idl.Enums) - { - sb.Append("enum "); - if (en.Attributes.Any(a => a.Name == "class-enum")) - sb.Append("class "); - sb.AppendLine(en.Name).AppendLine("{"); - - foreach (var m in en) - { - sb.Append(" ").Append(m.Name); - if (m.Value != null) - sb.Append(" = ").Append(m.Value); - sb.AppendLine(","); - } - - sb.AppendLine("};"); - } - - foreach (var s in idl.Structs) - { - sb.Append("struct ").AppendLine(s.Name).AppendLine("{"); - foreach (var m in s) - sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";"); - - sb.AppendLine("};"); - } - - foreach (var i in idl.Interfaces) - { - var guidString = i.GetAttribute("uuid"); - var guid = Guid.Parse(guidString).ToString().Replace("-", ""); - - - sb.Append("COMINTERFACE(").Append(i.Name).Append(", ") - .Append(guid.Substring(0, 8)).Append(", ") - .Append(guid.Substring(8, 4)).Append(", ") - .Append(guid.Substring(12, 4)); - for (var c = 0; c < 8; c++) - { - sb.Append(", ").Append(guid.Substring(16 + c * 2, 2)); - } - - sb.Append(") : "); - if (i.HasAttribute("cpp-virtual-inherits")) - sb.Append("virtual "); - sb.AppendLine(i.Inherits ?? "IUnknown") - .AppendLine("{"); - - foreach (var m in i) - { - sb.Append(" ") - .Append("virtual ") - .Append(ConvertType(m.ReturnType)) - .Append(" ").Append(m.Name).Append(" ("); - if (m.Count == 0) - sb.AppendLine(") = 0;"); - else - { - sb.AppendLine(); - for (var c = 0; c < m.Count; c++) - { - var arg = m[c]; - sb.Append(" "); - if (arg.Attributes.Any(a => a.Name == "const")) - sb.Append("const "); - sb.Append(ConvertType(arg.Type)) - .Append(" ") - .Append(arg.Name); - if (c != m.Count - 1) - sb.Append(", "); - sb.AppendLine(); - } - - sb.AppendLine(" ) = 0;"); - } - } - - sb.AppendLine("};"); - } - - return sb.ToString(); - } - } -} diff --git a/src/tools/MicroComGenerator/Extensions.cs b/src/tools/MicroComGenerator/Extensions.cs deleted file mode 100644 index c8a4c8f45c..0000000000 --- a/src/tools/MicroComGenerator/Extensions.cs +++ /dev/null @@ -1,97 +0,0 @@ - -using System; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -namespace MicroComGenerator -{ - public static class Extensions - { - public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers) - { - if (modifiers == null) - return cl; - return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); - } - - public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers) - { - if (modifiers == null) - return cl; - return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); - } - - public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers) - { - if (modifiers == null) - return cl; - return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); - } - - public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers) - { - if (modifiers == null) - return cl; - return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); - } - - public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers) - { - if (modifiers == null) - return cl; - return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray()); - } - - public static string WithLowerFirst(this string s) - { - if (string.IsNullOrEmpty(s)) - return s; - return char.ToLowerInvariant(s[0]) + s.Substring(1); - } - - public static ExpressionSyntax MemberAccess(params string[] identifiers) - { - if (identifiers == null || identifiers.Length == 0) - throw new ArgumentException(); - var expr = (ExpressionSyntax)IdentifierName(identifiers[0]); - for (var c = 1; c < identifiers.Length; c++) - expr = MemberAccess(expr, identifiers[c]); - return expr; - } - - public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers) - { - foreach (var i in identifiers) - expr = MemberAccess(expr, i); - return expr; - } - - public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) => - MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier)); - - public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt) - { - return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt))); - } - - public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt) - { - return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt))); - } - - public static T AddAttribute(this T member, string attribute, params string[] args) where T : MemberDeclarationSyntax - { - return (T)member.AddAttributeLists(AttributeList(SingletonSeparatedList( - Attribute(ParseName(attribute), AttributeArgumentList( - SeparatedList(args.Select(a => AttributeArgument(ParseExpression(a))))))))); - } - - public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s) - ? s - : s.StartsWith(prefix) - ? s.Substring(prefix.Length) - : s; - } -} diff --git a/src/tools/MicroComGenerator/MicroComGenerator.csproj b/src/tools/MicroComGenerator/MicroComGenerator.csproj deleted file mode 100644 index 68895b96ca..0000000000 --- a/src/tools/MicroComGenerator/MicroComGenerator.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - Exe - net6.0 - - - - - - diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs deleted file mode 100644 index cb54918100..0000000000 --- a/src/tools/MicroComGenerator/ParseException.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; - -namespace MicroComGenerator -{ - class ParseException : Exception - { - public int Line { get; } - public int Position { get; } - - public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}") - { - Line = line; - Position = position; - } - - public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position) - { - } - } - - class CodeGenException : Exception - { - public CodeGenException(string message) : base(message) - { - } - } -} diff --git a/src/tools/MicroComGenerator/Program.cs b/src/tools/MicroComGenerator/Program.cs deleted file mode 100644 index 2468b1b5a4..0000000000 --- a/src/tools/MicroComGenerator/Program.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using CommandLine; - -namespace MicroComGenerator -{ - class Program - { - public class Options - { - [Option('i', "input", Required = true, HelpText = "Input IDL file")] - public string Input { get; set; } - - [Option("cpp", Required = false, HelpText = "C++ output file")] - public string CppOutput { get; set; } - - [Option("cs", Required = false, HelpText = "C# output file")] - public string CSharpOutput { get; set; } - - } - - static int Main(string[] args) - { - var p = Parser.Default.ParseArguments(args); - if (p is NotParsed) - { - return 1; - } - - var opts = ((Parsed)p).Value; - - var text = File.ReadAllText(opts.Input); - var ast = AstParser.Parse(text); - - if (opts.CppOutput != null) - File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast)); - - if (opts.CSharpOutput != null) - { - File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate()); - - // HACK: Can't work out how to get the VS project system's fast up-to-date checks - // to ignore the generated code, so as a workaround set the write time to that of - // the input. - File.SetLastWriteTime(opts.CSharpOutput, File.GetLastWriteTime(opts.Input)); - } - - return 0; - } - } -} diff --git a/src/tools/MicroComGenerator/TokenParser.cs b/src/tools/MicroComGenerator/TokenParser.cs deleted file mode 100644 index ea8850b8e4..0000000000 --- a/src/tools/MicroComGenerator/TokenParser.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Globalization; -using System.IO; - -namespace MicroComGenerator -{ - internal ref struct TokenParser - { - private ReadOnlySpan _s; - public int Position { get; private set; } - public int Line { get; private set; } - public TokenParser(ReadOnlySpan s) - { - _s = s; - Position = 0; - Line = 0; - } - - public void SkipWhitespace() - { - while (true) - { - if(_s.Length == 0) - return; - if (char.IsWhiteSpace(_s[0])) - Advance(1); - else if (_s[0] == '/' && _s.Length>1) - { - if (_s[1] == '/') - SkipOneLineComment(); - else if (_s[1] == '*') - SkipMultiLineComment(); - else - return; - } - else - return; - } - } - - void SkipOneLineComment() - { - while (true) - { - if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r') - Advance(1); - else - return; - } - } - - void SkipMultiLineComment() - { - var l = Line; - var p = Position; - while (true) - { - if (_s.Length == 0) - throw new ParseException("No matched */ found for /*", l, p); - - if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/') - { - Advance(2); - return; - } - - Advance(1); - } - } - - static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || - (ch >= 'A' && ch <= 'Z'); - - public void Consume(char c) - { - if (!TryConsume(c)) - throw new ParseException("Expected " + c, Line, Position); - } - public bool TryConsume(char c) - { - SkipWhitespace(); - if (_s.Length == 0 || _s[0] != c) - return false; - - Advance(1); - return true; - } - - public bool TryConsume(string s) - { - SkipWhitespace(); - if (_s.Length < s.Length) - return false; - for (var c = 0; c < s.Length; c++) - { - if (_s[c] != s[c]) - return false; - } - - Advance(s.Length); - return true; - } - - public bool TryConsumeAny(ReadOnlySpan chars, out char token) - { - SkipWhitespace(); - token = default; - if (_s.Length == 0) - return false; - - foreach (var c in chars) - { - if (c == _s[0]) - { - token = c; - Advance(1); - return true; - } - } - - return false; - } - - - public bool TryParseKeyword(string keyword) - { - SkipWhitespace(); - if (keyword.Length > _s.Length) - return false; - for(var c=0; c keyword.Length && IsAlphaNumeric(_s[keyword.Length])) - return false; - - Advance(keyword.Length); - return true; - } - - public bool TryParseKeywordLowerCase(string keywordInLowerCase) - { - SkipWhitespace(); - if (keywordInLowerCase.Length > _s.Length) - return false; - for(var c=0; c keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length])) - return false; - - Advance(keywordInLowerCase.Length); - return true; - } - - public void Advance(int c) - { - while (c > 0) - { - if (_s[0] == '\n') - { - Line++; - Position = 0; - } - else - Position++; - - _s = _s.Slice(1); - c--; - } - } - - public int Length => _s.Length; - public bool Eof - { - get - { - SkipWhitespace(); - return Length == 0; - } - } - - public char Peek - { - get - { - if (_s.Length == 0) - throw new ParseException("Unexpected EOF", Line, Position); - return _s[0]; - } - } - - public string ParseIdentifier(ReadOnlySpan extraValidChars) - { - if (!TryParseIdentifier(extraValidChars, out var ident)) - throw new ParseException("Identifier expected", Line, Position); - return ident.ToString(); - } - - public string ParseIdentifier() - { - if (!TryParseIdentifier(out var ident)) - throw new ParseException("Identifier expected", Line, Position); - return ident.ToString(); - } - - public bool TryParseIdentifier(ReadOnlySpan extraValidChars, out ReadOnlySpan res) - { - res = ReadOnlySpan.Empty; - SkipWhitespace(); - if (_s.Length == 0) - return false; - var first = _s[0]; - if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) - return false; - int len = 1; - for (var c = 1; c < _s.Length; c++) - { - var ch = _s[c]; - if (IsAlphaNumeric(ch) || ch == '_') - len++; - else - { - var found = false; - foreach(var vc in extraValidChars) - if (vc == ch) - { - found = true; - break; - } - - if (found) - len++; - else - break; - } - } - - res = _s.Slice(0, len); - Advance(len); - return true; - } - - public bool TryParseIdentifier(out ReadOnlySpan res) - { - res = ReadOnlySpan.Empty; - SkipWhitespace(); - if (_s.Length == 0) - return false; - var first = _s[0]; - if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_')) - return false; - int len = 1; - for (var c = 1; c < _s.Length; c++) - { - var ch = _s[c]; - if (IsAlphaNumeric(ch) || ch == '_') - len++; - else - break; - } - - res = _s.Slice(0, len); - Advance(len); - return true; - } - - public string ReadToEol() - { - var initial = _s; - var len = 0; - while (true) - { - if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r') - { - len++; - Advance(1); - } - else - return initial.Slice(0, len).ToString(); - } - } - - public string ReadTo(char c) - { - var initial = _s; - var len = 0; - var l = Line; - var p = Position; - while (true) - { - if (_s.Length == 0) - throw new ParseException("Expected " + c + " before EOF", l, p); - - if (_s[0] != c) - { - len++; - Advance(1); - } - else - return initial.Slice(0, len).ToString(); - } - } - - public string ReadToAny(ReadOnlySpan chars) - { - var initial = _s; - var len = 0; - var l = Line; - var p = Position; - while (true) - { - if (_s.Length == 0) - throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p); - - var foundTerminator = false; - foreach (var term in chars) - { - if (_s[0] == term) - { - foundTerminator = true; - break; - } - } - - if (!foundTerminator) - { - len++; - Advance(1); - } - else - return initial.Slice(0, len).ToString(); - } - } - - public bool TryParseCall(out ReadOnlySpan res) - { - res = ReadOnlySpan.Empty; - SkipWhitespace(); - if (_s.Length == 0) - return false; - var first = _s[0]; - if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z'))) - return false; - int len = 1; - for (var c = 1; c < _s.Length; c++) - { - var ch = _s[c]; - if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.') - len++; - else - break; - } - - res = _s.Slice(0, len); - - // Find '(' - for (var c = len; c < _s.Length; c++) - { - if(char.IsWhiteSpace(_s[c])) - continue; - if(_s[c]=='(') - { - Advance(c + 1); - return true; - } - - return false; - - } - - return false; - - } - - - public bool TryParseFloat(out float res) - { - res = 0; - SkipWhitespace(); - if (_s.Length == 0) - return false; - - var len = 0; - var dotCount = 0; - for (var c = 0; c < _s.Length; c++) - { - var ch = _s[c]; - if (ch >= '0' && ch <= '9') - len = c + 1; - else if (ch == '.' && dotCount == 0) - { - len = c + 1; - dotCount++; - } - else - break; - } - - var span = _s.Slice(0, len); - -#if NETSTANDARD2_0 - if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res)) - return false; -#else - if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res)) - return false; -#endif - Advance(len); - return true; - } - - public override string ToString() => _s.ToString(); - - } -} From e1e2c52e0d905fa582f30c5d7458eb8e5fff670a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:50:05 +0300 Subject: [PATCH 087/260] Detected package downgrade --- nukebuild/_build.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index acb001f2e7..f8e2f358d6 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,6 @@ - From 767de9f642c9355b82054cfaae8e2cb7ddd2eb6f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:55:56 +0300 Subject: [PATCH 088/260] Namespace --- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index a0290ee5e8..8dfdd3159c 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,6 +3,7 @@ netstandard2.0;net6.0 true Avalonia.Win32 + Avalonia.MicroCom From 8f60013a1e22bfa6a2f098a66d4a7e781ff11af3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 21:05:04 +0300 Subject: [PATCH 089/260] CI: generate c++ headers with nuke --- azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 903146cdd7..497402fe4b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,8 +58,10 @@ jobs: displayName: 'Generate avalonia-native' inputs: script: | - export PATH="`pwd`/sdk:$PATH" - cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h + export COREHOST_TRACE=0 + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + ./build.sh --target GenerateCppHeaders --configuration Release - task: Xcode@5 inputs: From 9e5050cab99a0a55fbfb07ac12998feb18765df1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 24 Dec 2021 00:16:34 +0300 Subject: [PATCH 090/260] Fixed some of Rider's complaints --- src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs | 2 +- .../Media/TextFormatting/Unicode/LineBreakEnumerator.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs index 66223e513d..88b1e3c807 100644 --- a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs +++ b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs @@ -180,7 +180,7 @@ namespace Avalonia.Utilities { var r = _data[c]; if (r?.TryGetTarget(out var sub) == true) - sub.OnEvent(sender, eventArgs); + sub!.OnEvent(sender, eventArgs); else needCompact = true; } diff --git a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs index 40891a700d..0f6b81975a 100644 --- a/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs +++ b/src/Avalonia.Visuals/Media/TextFormatting/Unicode/LineBreakEnumerator.cs @@ -80,12 +80,12 @@ namespace Avalonia.Media.TextFormatting.Unicode } } - var shouldBreak = GetSimpleBreak() ?? (bool?)GetPairTableBreak(lastClass); + var shouldBreak = GetSimpleBreak() ?? GetPairTableBreak(lastClass); // Rule LB8a _lb8a = _nextClass == LineBreakClass.ZWJ; - if (shouldBreak.Value) + if (shouldBreak) { Current = new LineBreak(FindPriorNonWhitespace(_lastPosition), _lastPosition); return true; From 494cb0110559c85c0793830fcf1bf902ce725a8a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 24 Dec 2021 11:12:13 +0300 Subject: [PATCH 091/260] Updated microcom --- nukebuild/_build.csproj | 2 +- src/Avalonia.Native/Avalonia.Native.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index f8e2f358d6..52b60b7d0f 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 366ce97955..9d2471b0c5 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 8dfdd3159c..10edf42897 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -9,7 +9,7 @@ - + From 69525b8d55c87481b91c8d22f6f72e088a31d8a8 Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 24 Dec 2021 14:43:58 +0100 Subject: [PATCH 092/260] Use IsKeyboradFocusWithin instead of IsFocused --- src/Avalonia.Controls/ButtonSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ButtonSpinner.cs b/src/Avalonia.Controls/ButtonSpinner.cs index 5a967ab488..f1c1a55a17 100644 --- a/src/Avalonia.Controls/ButtonSpinner.cs +++ b/src/Avalonia.Controls/ButtonSpinner.cs @@ -197,7 +197,7 @@ namespace Avalonia.Controls { base.OnPointerWheelChanged(e); - if (AllowSpin && IsFocused) + if (AllowSpin && IsKeyboardFocusWithin) { if (e.Delta.Y != 0) { From fe42fbfe6b3d7fcd6951a7aca60749c83b05f42d Mon Sep 17 00:00:00 2001 From: Sergey Volkov Date: Sat, 25 Dec 2021 00:56:20 +0300 Subject: [PATCH 093/260] Add ViewContract to ViewModelViewHost --- src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 52 +++++++++++--- .../ViewModelViewHostTest.cs | 72 ++++++++++++++++++- 2 files changed, 113 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index c88323d674..16dee00ebc 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -3,7 +3,7 @@ using System.Reactive.Disposables; using ReactiveUI; using Splat; -namespace Avalonia.ReactiveUI +namespace Avalonia.ReactiveUI { /// /// This content control will automatically load the View associated with @@ -18,6 +18,12 @@ namespace Avalonia.ReactiveUI public static readonly AvaloniaProperty ViewModelProperty = AvaloniaProperty.Register(nameof(ViewModel)); + /// + /// for the property. + /// + public static readonly StyledProperty ViewContractProperty = + AvaloniaProperty.Register(nameof(ViewContract)); + /// /// Initializes a new instance of the class. /// @@ -25,8 +31,8 @@ namespace Avalonia.ReactiveUI { this.WhenActivated(disposables => { - this.WhenAnyValue(x => x.ViewModel) - .Subscribe(NavigateToViewModel) + this.WhenAnyValue(x => x.ViewModel, x => x.ViewContract) + .Subscribe(tuple => NavigateToViewModel(tuple.Item1, tuple.Item2)) .DisposeWith(disposables); }); } @@ -39,7 +45,16 @@ namespace Avalonia.ReactiveUI get => GetValue(ViewModelProperty); set => SetValue(ViewModelProperty, value); } - + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => GetValue(ViewContractProperty); + set => SetValue(ViewContractProperty, value); + } + /// /// Gets or sets the view locator. /// @@ -49,7 +64,8 @@ namespace Avalonia.ReactiveUI /// Invoked when ReactiveUI router navigates to a view model. /// /// ViewModel to which the user navigates. - private void NavigateToViewModel(object? viewModel) + /// The contract for view resolution. + private void NavigateToViewModel(object? viewModel, string? contract) { if (viewModel == null) { @@ -57,17 +73,33 @@ namespace Avalonia.ReactiveUI Content = DefaultContent; return; } - + var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel); + var viewInstance = viewLocator.ResolveView(viewModel, contract); if (viewInstance == null) { - this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + if (contract == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + } + else + { + this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content."); + } + Content = DefaultContent; return; } - - this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + + if (contract == null) + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + } + else + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'."); + } + viewInstance.ViewModel = viewModel; if (viewInstance is IStyledElement styled) styled.DataContext = viewModel; diff --git a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs index 35d1cbf62d..858c476227 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/ViewModelViewHostTest.cs @@ -12,15 +12,23 @@ namespace Avalonia.ReactiveUI.UnitTests public class FirstView : ReactiveUserControl { } + public class AlternativeFirstView : ReactiveUserControl { } + public class SecondViewModel : ReactiveObject { } public class SecondView : ReactiveUserControl { } + public class AlternativeSecondView : ReactiveUserControl { } + + public static string AlternativeViewContract => "AlternativeView"; + public ViewModelViewHostTest() { Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); Locator.CurrentMutable.Register(() => new FirstView(), typeof(IViewFor)); Locator.CurrentMutable.Register(() => new SecondView(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new AlternativeFirstView(), typeof(IViewFor), AlternativeViewContract); + Locator.CurrentMutable.Register(() => new AlternativeSecondView(), typeof(IViewFor), AlternativeViewContract); } [Fact] @@ -67,5 +75,67 @@ namespace Avalonia.ReactiveUI.UnitTests Assert.Equal(first, ((FirstView)host.Content).DataContext); Assert.Equal(first, ((FirstView)host.Content).ViewModel); } + + [Fact] + public void ViewModelViewHost_View_Should_Stay_In_Sync_With_ViewModel_And_Contract() + { + var defaultContent = new TextBlock(); + var host = new ViewModelViewHost + { + DefaultContent = defaultContent, + PageTransition = null + }; + + var root = new TestRoot + { + Child = host + }; + + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + var first = new FirstViewModel(); + host.ViewModel = first; + + host.ViewContract = null; + Assert.NotNull(host.Content); + Assert.Equal(typeof(FirstView), host.Content.GetType()); + Assert.Equal(first, ((FirstView)host.Content).DataContext); + Assert.Equal(first, ((FirstView)host.Content).ViewModel); + + host.ViewContract = AlternativeViewContract; + Assert.NotNull(host.Content); + Assert.Equal(typeof(AlternativeFirstView), host.Content.GetType()); + Assert.Equal(first, ((AlternativeFirstView)host.Content).DataContext); + Assert.Equal(first, ((AlternativeFirstView)host.Content).ViewModel); + + var second = new SecondViewModel(); + host.ViewModel = second; + + host.ViewContract = null; + Assert.NotNull(host.Content); + Assert.Equal(typeof(SecondView), host.Content.GetType()); + Assert.Equal(second, ((SecondView)host.Content).DataContext); + Assert.Equal(second, ((SecondView)host.Content).ViewModel); + + host.ViewContract = AlternativeViewContract; + Assert.NotNull(host.Content); + Assert.Equal(typeof(AlternativeSecondView), host.Content.GetType()); + Assert.Equal(second, ((AlternativeSecondView)host.Content).DataContext); + Assert.Equal(second, ((AlternativeSecondView)host.Content).ViewModel); + + host.ViewModel = null; + + host.ViewContract = null; + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + + host.ViewContract = AlternativeViewContract; + Assert.NotNull(host.Content); + Assert.Equal(typeof(TextBlock), host.Content.GetType()); + Assert.Equal(defaultContent, host.Content); + } } -} \ No newline at end of file +} From af902ae969573099b67dc506d7516ca2dde1a165 Mon Sep 17 00:00:00 2001 From: Sergey Volkov Date: Sat, 25 Dec 2021 00:56:36 +0300 Subject: [PATCH 094/260] Add ViewContract to RoutedViewHost --- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 57 ++++++++++++--- .../RoutedViewHostTest.cs | 73 +++++++++++++++++++ 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 63456bc13a..a475cf5eac 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -57,7 +57,13 @@ namespace Avalonia.ReactiveUI /// public static readonly StyledProperty RouterProperty = AvaloniaProperty.Register(nameof(Router)); - + + /// + /// for the property. + /// + public static readonly StyledProperty ViewContractProperty = + AvaloniaProperty.Register(nameof(ViewContract)); + /// /// Initializes a new instance of the class. /// @@ -70,15 +76,18 @@ namespace Avalonia.ReactiveUI .Where(router => router == null)! .Cast(); + var viewContract = this.WhenAnyValue(x => x.ViewContract); + this.WhenAnyValue(x => x.Router) .Where(router => router != null) .SelectMany(router => router!.CurrentViewModel) .Merge(routerRemoved) - .Subscribe(NavigateToViewModel) + .CombineLatest(viewContract) + .Subscribe(tuple => NavigateToViewModel(tuple.First, tuple.Second)) .DisposeWith(disposables); }); } - + /// /// Gets or sets the of the view model stack. /// @@ -87,17 +96,27 @@ namespace Avalonia.ReactiveUI get => GetValue(RouterProperty); set => SetValue(RouterProperty, value); } - + + /// + /// Gets or sets the view contract. + /// + public string? ViewContract + { + get => GetValue(ViewContractProperty); + set => SetValue(ViewContractProperty, value); + } + /// /// Gets or sets the ReactiveUI view locator used by this router. /// public IViewLocator? ViewLocator { get; set; } - + /// /// Invoked when ReactiveUI router navigates to a view model. /// /// ViewModel to which the user navigates. - private void NavigateToViewModel(object? viewModel) + /// The contract for view resolution. + private void NavigateToViewModel(object? viewModel, string? contract) { if (Router == null) { @@ -112,17 +131,33 @@ namespace Avalonia.ReactiveUI Content = DefaultContent; return; } - + var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current; - var viewInstance = viewLocator.ResolveView(viewModel); + var viewInstance = viewLocator.ResolveView(viewModel, contract); if (viewInstance == null) { - this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + if (contract == null) + { + this.Log().Warn($"Couldn't find view for '{viewModel}'. Is it registered? Falling back to default content."); + } + else + { + this.Log().Warn($"Couldn't find view with contract '{contract}' for '{viewModel}'. Is it registered? Falling back to default content."); + } + Content = DefaultContent; return; } - - this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + + if (contract == null) + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel}."); + } + else + { + this.Log().Info($"Ready to show {viewInstance} with autowired {viewModel} and contract '{contract}'."); + } + viewInstance.ViewModel = viewModel; if (viewInstance is IDataContextProvider provider) provider.DataContext = viewModel; diff --git a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs index b82b1b1acc..244b5abc4e 100644 --- a/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs +++ b/tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs @@ -29,6 +29,8 @@ namespace Avalonia.ReactiveUI.UnitTests public class FirstRoutableView : ReactiveUserControl { } + public class AlternativeFirstRoutableView : ReactiveUserControl { } + public class SecondRoutableViewModel : ReactiveObject, IRoutableViewModel { public string UrlPathSegment => "second"; @@ -38,16 +40,22 @@ namespace Avalonia.ReactiveUI.UnitTests public class SecondRoutableView : ReactiveUserControl { } + public class AlternativeSecondRoutableView : ReactiveUserControl { } + public class ScreenViewModel : ReactiveObject, IScreen { public RoutingState Router { get; } = new RoutingState(); } + public static string AlternativeViewContract => "AlternativeView"; + public RoutedViewHostTest() { Locator.CurrentMutable.RegisterConstant(new AvaloniaActivationForViewFetcher(), typeof(IActivationForViewFetcher)); Locator.CurrentMutable.Register(() => new FirstRoutableView(), typeof(IViewFor)); Locator.CurrentMutable.Register(() => new SecondRoutableView(), typeof(IViewFor)); + Locator.CurrentMutable.Register(() => new AlternativeFirstRoutableView(), typeof(IViewFor), AlternativeViewContract); + Locator.CurrentMutable.Register(() => new AlternativeSecondRoutableView(), typeof(IViewFor), AlternativeViewContract); } [Fact] @@ -101,6 +109,71 @@ namespace Avalonia.ReactiveUI.UnitTests Assert.Equal(defaultContent, host.Content); } + [Fact] + public void RoutedViewHost_Should_Stay_In_Sync_With_RoutingState_And_Contract() + { + var screen = new ScreenViewModel(); + var defaultContent = new TextBlock(); + var host = new RoutedViewHost + { + Router = screen.Router, + DefaultContent = defaultContent, + PageTransition = null + }; + + var root = new TestRoot + { + Child = host + }; + + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(defaultContent, host.Content); + + var first = new FirstRoutableViewModel(); + screen.Router.Navigate.Execute(first).Subscribe(); + + host.ViewContract = null; + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(first, ((FirstRoutableView)host.Content).DataContext); + Assert.Equal(first, ((FirstRoutableView)host.Content).ViewModel); + + host.ViewContract = AlternativeViewContract; + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext); + Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel); + + var second = new SecondRoutableViewModel(); + screen.Router.Navigate.Execute(second).Subscribe(); + + host.ViewContract = null; + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(second, ((SecondRoutableView)host.Content).DataContext); + Assert.Equal(second, ((SecondRoutableView)host.Content).ViewModel); + + host.ViewContract = AlternativeViewContract; + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).DataContext); + Assert.Equal(second, ((AlternativeSecondRoutableView)host.Content).ViewModel); + + screen.Router.NavigateBack.Execute(Unit.Default).Subscribe(); + + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).DataContext); + Assert.Equal(first, ((AlternativeFirstRoutableView)host.Content).ViewModel); + + screen.Router.NavigateBack.Execute(Unit.Default).Subscribe(); + + Assert.NotNull(host.Content); + Assert.IsType(host.Content); + Assert.Equal(defaultContent, host.Content); + } + [Fact] public void RoutedViewHost_Should_Show_Default_Content_When_Router_Is_Null() { From c39cc1b0833fa74c6a911a75426de1b64c00910a Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Sat, 25 Dec 2021 07:13:23 +0100 Subject: [PATCH 095/260] feat(DevTools): Allow to attach DevTools at Application (#6771) * feat(DevTools): Allow to attach DevTools at Application * fixes(DevTools): PointerOverElement when attach to Application * feat: Add AvaloniaVersion * feat: IsDevelopmentBuild * feat: Add some useful properties to Application Decorator * fixes: Hide Layout Viewer when select appliction node * fix: removed MachineId * fixes: DesignerSupportTests fails * fix(DevTools): Update XML Comment and nits * fix(DevTools): Avoid interaction of Layout Visualizer with keyboard when it is hidden. * Added some comment * fixes: Code formatting * fixes(DevTools): Strip unnecessary property from Application Decorator * fixes(ControlCatalog): remove AttachDevTools from DecoratedWindow * fixes(DevTools): Application doesn't close when the last window closed when DevTools is open * fixes: Missing Application properties decoration * fixes(DevTools): Nullable annotations * fixes(DevTools): Unified the behavior of AttachDevTools * fixes(DevTools): typo * fixes(DevTools): Null Annotation Co-authored-by: Max Katz --- samples/ControlCatalog/App.xaml.cs | 5 + .../ControlCatalog/DecoratedWindow.xaml.cs | 1 - samples/ControlCatalog/MainWindow.xaml.cs | 5 +- .../DevToolsExtensions.cs | 44 +++++++ .../Diagnostics/Behaviors/ColumnDefinition.cs | 47 ++++++++ .../Diagnostics/Controls/Application.cs | 109 ++++++++++++++++++ .../Diagnostics/DevTools.cs | 95 ++++++++++++--- .../Diagnostics/DevToolsOptions.cs | 1 + .../Diagnostics/KeyGestureExtesions.cs | 28 +++++ .../ViewModels/ControlDetailsViewModel.cs | 27 ++--- .../Diagnostics/ViewModels/LogicalTreeNode.cs | 90 ++++++++++++++- .../Diagnostics/ViewModels/MainViewModel.cs | 66 ++++++++--- .../Diagnostics/ViewModels/TreeNode.cs | 9 +- .../ViewModels/TreeNodeCollection.cs | 14 +++ .../Diagnostics/ViewModels/VisualTreeNode.cs | 80 ++++++++++++- .../Diagnostics/Views/ControlDetailsView.xaml | 22 +++- .../Diagnostics/Views/MainView.xaml | 2 +- .../Diagnostics/Views/MainWindow.xaml.cs | 21 ++-- .../Diagnostics/Views/TreePageView.xaml.cs | 7 +- 19 files changed, 593 insertions(+), 80 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index aba22a4af6..816356bee9 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -94,6 +94,11 @@ namespace ControlCatalog singleViewLifetime.MainView = new MainView(); base.OnFrameworkInitializationCompleted(); + + this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + { + StartupScreenIndex = 1, + }); } } } diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs index bdf5b8fbee..a1383b9107 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml.cs +++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs @@ -11,7 +11,6 @@ namespace ControlCatalog public DecoratedWindow() { this.InitializeComponent(); - this.AttachDevTools(); } void SetupSide(string name, StandardCursorType cursor, WindowEdge edge) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index dd62698cc7..20591103b7 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -17,10 +17,7 @@ namespace ControlCatalog public MainWindow() { this.InitializeComponent(); - this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() - { - StartupScreenIndex = 1, - }); + //Renderer.DrawFps = true; //Renderer.DrawDirtyRects = Renderer.DrawFps = true; diff --git a/src/Avalonia.Diagnostics/DevToolsExtensions.cs b/src/Avalonia.Diagnostics/DevToolsExtensions.cs index aa585dca40..68deaf8335 100644 --- a/src/Avalonia.Diagnostics/DevToolsExtensions.cs +++ b/src/Avalonia.Diagnostics/DevToolsExtensions.cs @@ -37,5 +37,49 @@ namespace Avalonia { DevTools.Attach(root, options); } + + /// + /// Attaches DevTools to a Application, to be opened with the specified options. + /// + /// The Application to attach DevTools to. + public static void AttachDevTools(this Application application) + { + DevTools.Attach(application, new DevToolsOptions()); + } + + /// + /// Attaches DevTools to a Application, to be opened with the specified options. + /// + /// The Application to attach DevTools to. + /// Additional settings of DevTools. + /// + /// Attach DevTools should only be called after application initialization is complete. A good point is + /// + /// + /// + /// public class App : Application + /// { + /// public override void OnFrameworkInitializationCompleted() + /// { + /// if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) + /// { + /// desktopLifetime.MainWindow = new MainWindow(); + /// } + /// else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) + /// singleViewLifetime.MainView = new MainView(); + /// + /// base.OnFrameworkInitializationCompleted(); + /// this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + /// { + /// StartupScreenIndex = 1, + /// }); + /// } + /// } + /// + /// + public static void AttachDevTools(this Application application, DevToolsOptions options) + { + DevTools.Attach(application, options); + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs b/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs new file mode 100644 index 0000000000..beccf022d7 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Behaviors/ColumnDefinition.cs @@ -0,0 +1,47 @@ +namespace Avalonia.Diagnostics.Behaviors +{ + /// + /// See discussion https://github.com/AvaloniaUI/Avalonia/discussions/6773 + /// + static class ColumnDefinition + { + private readonly static Avalonia.Controls.GridLength ZeroWidth = + new Avalonia.Controls.GridLength(0, Avalonia.Controls.GridUnitType.Pixel); + + private readonly static AttachedProperty LastWidthProperty = + AvaloniaProperty.RegisterAttached("LastWidth" + , typeof(ColumnDefinition) + , default); + + public readonly static AttachedProperty IsVisibleProperty = + AvaloniaProperty.RegisterAttached("IsVisible" + , typeof(ColumnDefinition) + , true + , coerce: (element, visibility) => + { + + var lastWidth = element.GetValue(LastWidthProperty); + if (visibility == true && lastWidth is { }) + { + element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, lastWidth); + } + else if (visibility == false) + { + element.SetValue(LastWidthProperty, element.GetValue(Avalonia.Controls.ColumnDefinition.WidthProperty)); + element.SetValue(Avalonia.Controls.ColumnDefinition.WidthProperty, ZeroWidth); + } + return visibility; + } + ); + + public static bool GetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition) + { + return columnDefinition.GetValue(IsVisibleProperty); + } + + public static void SetIsVisible(Avalonia.Controls.ColumnDefinition columnDefinition, bool visibility) + { + columnDefinition.SetValue(IsVisibleProperty, visibility); + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs new file mode 100644 index 0000000000..b2f2974440 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -0,0 +1,109 @@ +using System; +using Avalonia.Controls; +using Lifetimes = Avalonia.Controls.ApplicationLifetimes; +using App = Avalonia.Application; + +namespace Avalonia.Diagnostics.Controls +{ + class Application : AvaloniaObject + , Input.ICloseable + + { + private readonly App _application; + private static readonly Version s_version = typeof(IAvaloniaObject).Assembly?.GetName()?.Version + ?? Version.Parse("0.0.00"); + public event EventHandler? Closed; + + public Application(App application) + { + _application = application; + + if (_application.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller) + { + EventHandler eh = default!; + eh = (s, e) => + { + controller.Exit -= eh; + Closed?.Invoke(s, e); + }; + controller.Exit += eh; + } + + } + + internal App Instance => _application; + + /// + /// Defines the property. + /// + public object? DataContext => + _application.DataContext; + + /// + /// Gets or sets the application's global data templates. + /// + /// + /// The application's global data templates. + /// + public Avalonia.Controls.Templates.DataTemplates DataTemplates => + _application.DataTemplates; + + /// + /// Gets the application's focus manager. + /// + /// + /// The application's focus manager. + /// + public Input.IFocusManager? FocusManager => + _application.FocusManager; + + /// + /// Gets the application's input manager. + /// + /// + /// The application's input manager. + /// + public Input.InputManager? InputManager => + _application.InputManager; + + /// + /// Gets the application clipboard. + /// + public Input.Platform.IClipboard? Clipboard => + _application.Clipboard; + + /// + /// Gets the application's global resource dictionary. + /// + public IResourceDictionary Resources => + _application.Resources; + + /// + /// Gets the application's global styles. + /// + /// + /// The application's global styles. + /// + /// + /// Global styles apply to all windows in the application. + /// + public Styling.Styles Styles => + _application.Styles; + + /// + /// Application lifetime, use it for things like setting the main window and exiting the app from code + /// Currently supported lifetimes are: + /// - + /// - + /// - + /// + public Lifetimes.IApplicationLifetime? ApplicationLifetime => + _application.ApplicationLifetime; + + /// + /// Application name to be used for various platform-specific purposes + /// + public string? Name => + _application.Name; + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs index 2a386f106e..683c2e6549 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs @@ -1,17 +1,21 @@ using System; using System.Collections.Generic; using System.Reactive.Disposables; +using System.Reactive.Linq; using Avalonia.Controls; using Avalonia.Diagnostics.Views; using Avalonia.Input; +using Avalonia.Input.Raw; using Avalonia.Interactivity; namespace Avalonia.Diagnostics { public static class DevTools { - private static readonly Dictionary s_open = - new Dictionary(); + private static readonly Dictionary s_open = + new Dictionary(); + + private static bool s_attachedToApplication; public static IDisposable Attach(TopLevel root, KeyGesture gesture) { @@ -23,6 +27,11 @@ namespace Avalonia.Diagnostics public static IDisposable Attach(TopLevel root, DevToolsOptions options) { + if (s_attachedToApplication == true) + { + throw new ArgumentException("DevTools already attached to application", nameof(root)); + } + void PreviewKeyDown(object? sender, KeyEventArgs e) { if (options.Gesture.Matches(e)) @@ -37,45 +46,95 @@ namespace Avalonia.Diagnostics RoutingStrategies.Tunnel); } - public static IDisposable Open(TopLevel root) => Open(root, new DevToolsOptions()); + public static IDisposable Open(TopLevel root) => + Open(Application.Current,new DevToolsOptions(),root as Window); + + public static IDisposable Open(TopLevel root, DevToolsOptions options) => + Open(Application.Current, options, root as Window); - public static IDisposable Open(TopLevel root, DevToolsOptions options) + private static void DevToolsClosed(object? sender, EventArgs e) { - if (s_open.TryGetValue(root, out var window)) + var window = (MainWindow)sender!; + window.Closed -= DevToolsClosed; + if (window.Root is Controls.Application host) + { + s_open.Remove(host.Instance); + } + else { + s_open.Remove(window.Root!); + } + } + + internal static IDisposable Attach(Application? application, DevToolsOptions options, Window? owner = null) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + var result = Disposable.Empty; + // Skip if call on Design Mode + if (!Avalonia.Controls.Design.IsDesignMode + && !s_attachedToApplication) + { + + var lifeTime = application.ApplicationLifetime + as Avalonia.Controls.ApplicationLifetimes.IControlledApplicationLifetime; + + if (lifeTime is null) + { + throw new ArgumentNullException(nameof(Application.ApplicationLifetime)); + } + + if (application.InputManager is { }) + { + s_attachedToApplication = true; + + application.InputManager.PreProcess.OfType().Subscribe(e => + { + if (options.Gesture.Matches(e)) + { + result = Open(application, options, owner); + } + }); + + } + } + return result; + } + + private static IDisposable Open(Application? application, DevToolsOptions options, Window? owner = default) + { + if (application is null) + { + throw new ArgumentNullException(nameof(application)); + } + if (s_open.TryGetValue(application, out var window)) + { window.Activate(); } else { window = new MainWindow { - Root = root, + Root = new Controls.Application(application), Width = options.Size.Width, Height = options.Size.Height, }; window.SetOptions(options); window.Closed += DevToolsClosed; - s_open.Add(root, window); - - if (options.ShowAsChildWindow && root is Window inspectedWindow) + s_open.Add(application, window); + if (options.ShowAsChildWindow && owner is { }) { - window.Show(inspectedWindow); + window.Show(owner); } else { window.Show(); } } - return Disposable.Create(() => window?.Close()); } - - private static void DevToolsClosed(object? sender, EventArgs e) - { - var window = (MainWindow)sender!; - s_open.Remove(window.Root!); - window.Closed -= DevToolsClosed; - } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 5336dca65b..4181989388 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -16,6 +16,7 @@ namespace Avalonia.Diagnostics /// Gets or sets a value indicating whether DevTools should be displayed as a child window /// of the window being inspected. The default value is true. /// + /// This setting is ignored if DevTools is attached to public bool ShowAsChildWindow { get; set; } = true; /// diff --git a/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs b/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs new file mode 100644 index 0000000000..bb3ebc4708 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/KeyGestureExtesions.cs @@ -0,0 +1,28 @@ +using Avalonia.Input; +using Avalonia.Input.Raw; + +namespace Avalonia.Diagnostics +{ + static class KeyGestureExtesions + { + public static bool Matches(this KeyGesture gesture, RawKeyEventArgs keyEvent) => + keyEvent != null && + (KeyModifiers)(keyEvent.Modifiers & RawInputModifiers.KeyboardMask) == gesture.KeyModifiers && + ResolveNumPadOperationKey(keyEvent.Key) == ResolveNumPadOperationKey(gesture.Key); + + private static Key ResolveNumPadOperationKey(Key key) + { + switch (key) + { + case Key.Add: + return Key.OemPlus; + case Key.Subtract: + return Key.OemMinus; + case Key.Decimal: + return Key.OemPeriod; + default: + return key; + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs index 16293e35d2..c96172d5bb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs @@ -16,7 +16,7 @@ namespace Avalonia.Diagnostics.ViewModels { internal class ControlDetailsViewModel : ViewModelBase, IDisposable { - private readonly IVisual _control; + private readonly IAvaloniaObject _avaloniaObject; private IDictionary>? _propertyIndex; private PropertyViewModel? _selectedProperty; private DataGridCollectionView? _propertiesView; @@ -28,20 +28,21 @@ namespace Avalonia.Diagnostics.ViewModels private string? _selectedEntityName; private string? _selectedEntityType; - public ControlDetailsViewModel(TreePageViewModel treePage, IVisual control) + public ControlDetailsViewModel(TreePageViewModel treePage, IAvaloniaObject avaloniaObject) { - _control = control; + _avaloniaObject = avaloniaObject; - TreePage = treePage; - - Layout = new ControlLayoutViewModel(control); + TreePage = treePage; + Layout = avaloniaObject is IVisual + ? new ControlLayoutViewModel((IVisual)avaloniaObject) + : default; - NavigateToProperty(control, (control as IControl)?.Name ?? control.ToString()); + NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString()); AppliedStyles = new ObservableCollection(); PseudoClasses = new ObservableCollection(); - if (control is StyledElement styledElement) + if (avaloniaObject is StyledElement styledElement) { styledElement.Classes.CollectionChanged += OnClassesChanged; @@ -181,7 +182,7 @@ namespace Avalonia.Diagnostics.ViewModels set => RaiseAndSetIfChanged(ref _styleStatus, value); } - public ControlLayoutViewModel Layout { get; } + public ControlLayoutViewModel? Layout { get; } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { @@ -215,17 +216,17 @@ namespace Avalonia.Diagnostics.ViewModels public void Dispose() { - if (_control is INotifyPropertyChanged inpc) + if (_avaloniaObject is INotifyPropertyChanged inpc) { inpc.PropertyChanged -= ControlPropertyChanged; } - if (_control is AvaloniaObject ao) + if (_avaloniaObject is AvaloniaObject ao) { ao.PropertyChanged -= ControlPropertyChanged; } - if (_control is StyledElement se) + if (_avaloniaObject is StyledElement se) { se.Classes.CollectionChanged -= OnClassesChanged; } @@ -278,7 +279,7 @@ namespace Avalonia.Diagnostics.ViewModels } } - Layout.ControlPropertyChanged(sender, e); + Layout?.ControlPropertyChanged(sender, e); } private void ControlPropertyChanged(object? sender, PropertyChangedEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs index 04215fa8ae..d627eba154 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs @@ -1,23 +1,31 @@ using System; +using System.Reactive.Disposables; using Avalonia.Collections; using Avalonia.Controls; using Avalonia.LogicalTree; +using Lifetimes = Avalonia.Controls.ApplicationLifetimes; +using System.Linq; namespace Avalonia.Diagnostics.ViewModels { internal class LogicalTreeNode : TreeNode { - public LogicalTreeNode(ILogical logical, TreeNode? parent) - : base((Control)logical, parent) + public LogicalTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent) + : base(avaloniaObject, parent) { - Children = new LogicalTreeNodeCollection(this, logical); + Children = avaloniaObject switch + { + ILogical logical => new LogicalTreeNodeCollection(this, logical), + Controls.Application host => new ApplicationHostLogical(this, host), + _ => TreeNodeCollection.Empty + }; } public override TreeNodeCollection Children { get; } public static LogicalTreeNode[] Create(object control) { - var logical = control as ILogical; + var logical = control as IAvaloniaObject; return logical != null ? new[] { new LogicalTreeNode(logical, null) } : Array.Empty(); } @@ -41,10 +49,82 @@ namespace Avalonia.Diagnostics.ViewModels protected override void Initialize(AvaloniaList nodes) { _subscription = _control.LogicalChildren.ForEachItem( - (i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)), + (i, item) => nodes.Insert(i, new LogicalTreeNode((IAvaloniaObject)item, Owner)), (i, item) => nodes.RemoveAt(i), () => nodes.Clear()); } } + + internal class ApplicationHostLogical : TreeNodeCollection + { + readonly Controls.Application _application; + CompositeDisposable _subscriptions = new CompositeDisposable(2); + public ApplicationHostLogical(TreeNode owner, Controls.Application host) : + base(owner) + { + _application = host; + } + + protected override void Initialize(AvaloniaList nodes) + { + if (_application.ApplicationLifetime is Lifetimes.ISingleViewApplicationLifetime single) + { + nodes.Add(new LogicalTreeNode(single.MainView, Owner)); + } + if (_application.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime classic) + { + + for (int i = 0; i < classic.Windows.Count; i++) + { + var window = classic.Windows[i]; + if (window is Views.MainWindow) + { + continue; + } + nodes.Add(new LogicalTreeNode(window, Owner)); + } + _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + { + Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> + { + if (s is Views.MainWindow) + { + return; + } + nodes.Add(new LogicalTreeNode((IAvaloniaObject)s!,Owner)); + }), + Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=> + { + if (s is Views.MainWindow) + { + return; + } + var item = nodes.FirstOrDefault(node=>object.ReferenceEquals(node.Visual,s)); + if(!(item is null)) + { + nodes.Remove(item); + } + if(nodes.Count == 0) + { + if (Avalonia.Application.Current?.ApplicationLifetime is Lifetimes.IControlledApplicationLifetime controller) + { + controller.Shutdown(); + } + else + { + Environment.Exit(0); + } + } + }), + }; + } + } + + public override void Dispose() + { + _subscriptions?.Dispose(); + base.Dispose(); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 74f2f05948..d809768a47 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -4,12 +4,14 @@ using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Threading; +using System.Reactive.Linq; +using System.Linq; namespace Avalonia.Diagnostics.ViewModels { internal class MainViewModel : ViewModelBase, IDisposable { - private readonly TopLevel _root; + private readonly AvaloniaObject _root; private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _visualTree; private readonly EventsPageViewModel _events; @@ -17,13 +19,14 @@ namespace Avalonia.Diagnostics.ViewModels private ViewModelBase? _content; private int _selectedTab; private string? _focusedControl; - private string? _pointerOverElement; + private IInputElement? _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; private bool _freezePopups; - - public MainViewModel(TopLevel root) + private string? _pointerOverElementName; + private IInputRoot? _pointerOverRoot; + public MainViewModel(AvaloniaObject root) { _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); @@ -35,8 +38,24 @@ namespace Avalonia.Diagnostics.ViewModels if (KeyboardDevice.Instance is not null) KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged; SelectedTab = 0; - _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty) - .Subscribe(x => PointerOverElement = x?.GetType().Name); + if (root is TopLevel topLevel) + { + _pointerOverSubscription = topLevel.GetObservable(TopLevel.PointerOverElementProperty) + .Subscribe(x => PointerOverElement = x); + + } + else + { +#nullable disable + _pointerOverSubscription = InputManager.Instance.PreProcess + .OfType() + .Subscribe(e => + { + PointerOverRoot = e.Root; + PointerOverElement = e.Root.GetInputElementsAt(e.Position).FirstOrDefault(); + }); +#nullable restore + } Console = new ConsoleViewModel(UpdateConsoleContext); } @@ -51,13 +70,13 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } - + public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; set { - _root.Renderer.DrawDirtyRects = value; + ((TopLevel)_root).Renderer.DrawDirtyRects = value; RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); } } @@ -77,7 +96,7 @@ namespace Avalonia.Diagnostics.ViewModels get => _showFpsOverlay; set { - _root.Renderer.DrawFps = value; + ((TopLevel)_root).Renderer.DrawFps = value; RaiseAndSetIfChanged(ref _showFpsOverlay, value); } } @@ -150,12 +169,28 @@ namespace Avalonia.Diagnostics.ViewModels private set { RaiseAndSetIfChanged(ref _focusedControl, value); } } - public string? PointerOverElement + public IInputRoot? PointerOverRoot + { + get => _pointerOverRoot; + private set => RaiseAndSetIfChanged( ref _pointerOverRoot , value); + } + + public IInputElement? PointerOverElement { get { return _pointerOverElement; } - private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } + private set + { + RaiseAndSetIfChanged(ref _pointerOverElement, value); + PointerOverElementName = value?.GetType()?.Name; + } } - + + public string? PointerOverElementName + { + get => _pointerOverElementName; + private set => RaiseAndSetIfChanged(ref _pointerOverElementName, value); + } + private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; @@ -188,8 +223,11 @@ namespace Avalonia.Diagnostics.ViewModels _pointerOverSubscription.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); - _root.Renderer.DrawDirtyRects = false; - _root.Renderer.DrawFps = false; + if (_root is TopLevel top) + { + top.Renderer.DrawDirtyRects = false; + top.Renderer.DrawFps = false; + } } private void UpdateFocusedControl() diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs index 94707ac189..5459810db9 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs @@ -16,12 +16,13 @@ namespace Avalonia.Diagnostics.ViewModels private string _classes; private bool _isExpanded; - protected TreeNode(IVisual visual, TreeNode? parent, string? customName = null) + protected TreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) { _classes = string.Empty; Parent = parent; - Type = customName ?? visual.GetType().Name; - Visual = visual; + var visual = avaloniaObject ; + Type = customName ?? avaloniaObject.GetType().Name; + Visual = visual!; FontWeight = IsRoot ? FontWeight.Bold : FontWeight.Normal; if (visual is IControl control) @@ -76,7 +77,7 @@ namespace Avalonia.Diagnostics.ViewModels get; } - public IVisual Visual + public IAvaloniaObject Visual { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs index c007411f49..953afa280b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs @@ -10,6 +10,20 @@ namespace Avalonia.Diagnostics.ViewModels { internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList, IDisposable { + private class EmptyTreeNodeCollection : TreeNodeCollection + { + public EmptyTreeNodeCollection():base(default!) + { + + } + protected override void Initialize(AvaloniaList nodes) + { + + } + } + + static readonly internal TreeNodeCollection Empty = new EmptyTreeNodeCollection(); + private AvaloniaList? _inner; public TreeNodeCollection(TreeNode owner) => Owner = owner; diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index 6a430897ba..5e1128d4b7 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -7,15 +7,22 @@ using Avalonia.Controls.Diagnostics; using Avalonia.Controls.Primitives; using Avalonia.Styling; using Avalonia.VisualTree; +using Lifetimes = Avalonia.Controls.ApplicationLifetimes; +using System.Linq; namespace Avalonia.Diagnostics.ViewModels { internal class VisualTreeNode : TreeNode { - public VisualTreeNode(IVisual visual, TreeNode? parent, string? customName = null) - : base(visual, parent, customName) + public VisualTreeNode(IAvaloniaObject avaloniaObject, TreeNode? parent, string? customName = null) + : base(avaloniaObject, parent, customName) { - Children = new VisualTreeNodeCollection(this, visual); + Children = avaloniaObject switch + { + IVisual visual => new VisualTreeNodeCollection(this, visual), + Controls.Application host => new ApplicationHostVisuals(this, host), + _ => TreeNodeCollection.Empty + }; if (Visual is IStyleable styleable) IsInTemplate = styleable.TemplatedParent != null; @@ -27,7 +34,7 @@ namespace Avalonia.Diagnostics.ViewModels public static VisualTreeNode[] Create(object control) { - return control is IVisual visual ? + return control is IAvaloniaObject visual ? new[] { new VisualTreeNode(visual, null) } : Array.Empty(); } @@ -130,7 +137,7 @@ namespace Avalonia.Diagnostics.ViewModels _subscriptions.Add( _control.VisualChildren.ForEachItem( - (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)), + (i, item) => nodes.Insert(i, new VisualTreeNode((IAvaloniaObject)item, Owner)), (i, item) => nodes.RemoveAt(i), () => nodes.Clear())); } @@ -147,5 +154,68 @@ namespace Avalonia.Diagnostics.ViewModels public string? CustomName { get; } } } + + internal class ApplicationHostVisuals : TreeNodeCollection + { + readonly Controls.Application _application; + CompositeDisposable _subscriptions = new CompositeDisposable(2); + public ApplicationHostVisuals(TreeNode owner, Controls.Application host) : + base(owner) + { + _application = host; + } + + protected override void Initialize(AvaloniaList nodes) + { + if (_application.ApplicationLifetime is Lifetimes.ISingleViewApplicationLifetime single) + { + nodes.Add(new VisualTreeNode(single.MainView, Owner)); + } + if (_application.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime classic) + { + + for (int i = 0; i < classic.Windows.Count; i++) + { + var window = classic.Windows[i]; + if (window is Views.MainWindow) + { + continue; + } + nodes.Add(new VisualTreeNode(window, Owner)); + } + _subscriptions = new System.Reactive.Disposables.CompositeDisposable() + { + Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (s,e)=> + { + if (s is Views.MainWindow) + { + return; + } + nodes.Add(new VisualTreeNode((IAvaloniaObject)s!,Owner)); + }), + Window.WindowClosedEvent.AddClassHandler(typeof(Window), (s,e)=> + { + if (s is Views.MainWindow) + { + return; + } + var item = nodes.FirstOrDefault(node=>object.ReferenceEquals(node.Visual,s)); + if(!(item is null)) + { + nodes.Remove(item); + } + }), + }; + + + } + } + + public override void Dispose() + { + _subscriptions?.Dispose(); + base.Dispose(); + } + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index cf74fa1b34..7f924af144 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -4,6 +4,7 @@ xmlns:local="clr-namespace:Avalonia.Diagnostics.Views" xmlns:controls="clr-namespace:Avalonia.Diagnostics.Controls" xmlns:vm="clr-namespace:Avalonia.Diagnostics.ViewModels" + xmlns:lb="using:Avalonia.Diagnostics.Behaviors" x:Class="Avalonia.Diagnostics.Views.ControlDetailsView" x:Name="Main"> @@ -11,8 +12,20 @@ - - + + + + + + + + @@ -53,9 +66,10 @@ - + - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 6f2ac96a66..7e0dab1d6e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -73,7 +73,7 @@ Pointer Over: - + _frozenPopupStates; - private TopLevel? _root; + private AvaloniaObject? _root; public MainWindow() { @@ -50,23 +50,23 @@ namespace Avalonia.Diagnostics.Views this.Opened += lh; } - public TopLevel? Root + public AvaloniaObject? Root { get => _root; set { if (_root != value) { - if (_root != null) + if (_root is ICloseable oldClosable) { - _root.Closed -= RootClosed; + oldClosable.Closed -= RootClosed; } _root = value; - if (_root != null) + if (_root is ICloseable newClosable) { - _root.Closed += RootClosed; + newClosable.Closed += RootClosed; DataContext = new MainViewModel(_root); } else @@ -91,9 +91,9 @@ namespace Avalonia.Diagnostics.Views _frozenPopupStates.Clear(); - if (_root != null) + if (_root is ICloseable cloneable) { - _root.Closed -= RootClosed; + cloneable.Closed -= RootClosed; _root = null; } @@ -123,7 +123,7 @@ namespace Avalonia.Diagnostics.Views .FirstOrDefault(); } - private static List GetPopupRoots(IVisual root) + private static List GetPopupRoots(TopLevel root) { var popupRoots = new List(); @@ -160,7 +160,8 @@ namespace Avalonia.Diagnostics.Views return; } - var root = Root; + var root = Root as TopLevel + ?? vm.PointerOverRoot as TopLevel; if (root is null) { return; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs index 3543b1adea..649b65c521 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs @@ -47,7 +47,12 @@ namespace Avalonia.Diagnostics.Views return; } - var visual = (Visual)node.Visual; + var visual = node.Visual as Visual; + + if (visual is null) + { + return; + } _currentLayer = AdornerLayer.GetAdornerLayer(visual); From 1d34536236ee84dace3f8414ff3a73b71cbac654 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sun, 26 Dec 2021 22:47:31 +0300 Subject: [PATCH 096/260] [WASM] Fix wrong DPI value --- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index 3e9849d4dc..acd23a98b0 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -142,7 +142,7 @@ namespace Avalonia.Web.Blazor public Size ClientSize => _clientSize; public Size? FrameSize => null; - public double RenderScaling => 1; + public double RenderScaling => _currentSurface?.Scaling ?? 1; public IEnumerable Surfaces => new object[] { _currentSurface! }; From a0d0e3ac87d2883f6a3ceb46e375f699f02f31a2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 11:48:22 +0100 Subject: [PATCH 097/260] fix(DevTools): Move 'Show Implemented Interfaces' from Options menu to View menu --- .../Diagnostics/Views/MainView.xaml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 8b5af082e1..965d8e88e4 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -15,6 +15,15 @@ IsEnabled="False" /> + + + + + + + @@ -38,16 +47,6 @@ IsEnabled="False" /> - - - - - - - - From 215425c45a02bd7c0cad7a881d198a4fbac66568 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 12:17:37 +0100 Subject: [PATCH 098/260] fixes(DevTools): NRE when click 'Visualizze dirty rects' and 'Show fps overly' in Option menu --- .../Diagnostics/Controls/Application.cs | 12 ++++++- .../Diagnostics/ViewModels/MainViewModel.cs | 32 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index b2f2974440..316e31b37e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -28,7 +28,12 @@ namespace Avalonia.Diagnostics.Controls }; controller.Exit += eh; } - + RendererRoot = application.ApplicationLifetime switch + { + Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow.Renderer, + Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as VisualTree.IVisual)?.VisualRoot?.Renderer, + _ => null + }; } internal App Instance => _application; @@ -105,5 +110,10 @@ namespace Avalonia.Diagnostics.Controls /// public string? Name => _application.Name; + + /// + /// Gets the root of the visual tree, if the control is attached to a visual tree. + /// + internal Rendering.IRenderer? RendererRoot { get; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index d809768a47..c607542991 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -76,7 +76,20 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeDirtyRects; set { - ((TopLevel)_root).Renderer.DrawDirtyRects = value; + var changed = true; + if (_root is TopLevel topLevel && topLevel.Renderer is { }) + { + topLevel.Renderer.DrawDirtyRects = value; + } + else if (_root is Controls.Application app && app.RendererRoot is { }) + { + app.RendererRoot.DrawDirtyRects = value; + } + else + { + changed = false; + } + if (changed) RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); } } @@ -96,8 +109,21 @@ namespace Avalonia.Diagnostics.ViewModels get => _showFpsOverlay; set { - ((TopLevel)_root).Renderer.DrawFps = value; - RaiseAndSetIfChanged(ref _showFpsOverlay, value); + var changed = true; + if (_root is TopLevel topLevel && topLevel.Renderer is { }) + { + topLevel.Renderer.DrawFps = value; + } + else if (_root is Controls.Application app && app.RendererRoot is { }) + { + app.RendererRoot.DrawFps = value; + } + else + { + changed = false; + } + if(changed) + RaiseAndSetIfChanged(ref _showFpsOverlay, value); } } From 00a286f0e8bba92869eeec96abe1a2094f19dbae Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 27 Dec 2021 14:15:30 +0200 Subject: [PATCH 099/260] Add separate platform settings for touch. --- .../Avalonia.Android/AndroidPlatform.cs | 8 ++--- src/Avalonia.Input/TouchDevice.cs | 6 ++-- src/Avalonia.Native/AvaloniaNativePlatform.cs | 29 +++++++++++-------- .../Platform/ITouchPlatformSettings.cs | 11 +++++++ src/Avalonia.X11/Stubs.cs | 12 ++++---- src/Avalonia.X11/X11Platform.cs | 4 ++- src/Windows/Avalonia.Win32/Win32Platform.cs | 6 ++-- src/iOS/Avalonia.iOS/Platform.cs | 8 ++--- 8 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 57f22e7a05..a9ba1086d5 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -29,12 +29,12 @@ namespace Avalonia namespace Avalonia.Android { - class AndroidPlatform : IPlatformSettings + class AndroidPlatform : ITouchPlatformSettings { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; } - public Size DoubleClickSize => new Size(4, 4); - public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); + public Size TouchDoubleClickSize => new Size(4, 4); + public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200); public static void Initialize(Type appType, AndroidPlatformOptions options) { @@ -45,7 +45,7 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() - .Bind().ToConstant(Instance) + .Bind().ToConstant(Instance) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToSingleton() diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index af8851a82b..4970d4db32 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -58,20 +58,20 @@ namespace Avalonia.Input } else { - var settings = AvaloniaLocator.Current.GetService(); + var settings = AvaloniaLocator.Current.GetService(); if (settings == null) { throw new Exception("IPlatformSettings can not be null."); } if (!_lastClickRect.Contains(args.Position) - || ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds) + || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds) { _clickCount = 0; } ++_clickCount; _lastClickTime = ev.Timestamp; _lastClickRect = new Rect(args.Position, new Size()) - .Inflate(new Thickness(16, 16)); + .Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2)); } target.RaiseEvent(new PointerPressedEventArgs(target, pointer, diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 522db1b334..680f64f537 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -11,7 +11,7 @@ using Avalonia.Rendering; namespace Avalonia.Native { - class AvaloniaNativePlatform : IPlatformSettings, IWindowingPlatform + class AvaloniaNativePlatform : IPlatformSettings, ITouchPlatformSettings, IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions _options; @@ -26,9 +26,13 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO + public Size TouchDoubleClickSize => new Size(16, 16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; + public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { - var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true)); + var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor(factory, true)); result.DoInitialize(options); return result; @@ -55,14 +59,14 @@ namespace Avalonia.Native return Initialize(CreateAvaloniaNative(), options); } - public void SetupApplicationMenuExporter () + public void SetupApplicationMenuExporter() { var exporter = new AvaloniaNativeMenuExporter(_factory); } - public void SetupApplicationName () + public void SetupApplicationName() { - if(!string.IsNullOrWhiteSpace(Application.Current.Name)) + if (!string.IsNullOrWhiteSpace(Application.Current.Name)) { _factory.MacOptions.SetApplicationTitle(Application.Current.Name); } @@ -80,13 +84,13 @@ namespace Avalonia.Native GCHandle.FromIntPtr(handle).Free(); } } - + void DoInitialize(AvaloniaNativePlatformOptions options) { _options = options; - + var applicationPlatform = new AvaloniaNativeApplicationPlatform(); - + _factory.Initialize(new GCHandleDeallocator(), applicationPlatform); if (_factory.MacOptions != null) { @@ -102,6 +106,7 @@ namespace Avalonia.Native .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(this) + .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind().ToConstant(new RenderLoop()) @@ -118,7 +123,7 @@ namespace Avalonia.Native hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); - + if (_options.UseGpu) { try @@ -133,7 +138,7 @@ namespace Avalonia.Native } } - public ITrayIconImpl CreateTrayIcon () + public ITrayIconImpl CreateTrayIcon() { return new TrayIconImpl(_factory); } @@ -159,8 +164,8 @@ namespace Avalonia.Native ShowInDock = true; } - public bool ShowInDock - { + public bool ShowInDock + { get => _showInDock; set { diff --git a/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs b/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs new file mode 100644 index 0000000000..bda0dc3c33 --- /dev/null +++ b/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs @@ -0,0 +1,11 @@ +using System; + +namespace Avalonia.Platform +{ + public interface ITouchPlatformSettings + { + Size TouchDoubleClickSize { get; } + + TimeSpan TouchDoubleClickTime { get; } + } +} diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index ec694ba9a8..ca1c037592 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -1,17 +1,15 @@ using System; -using System.IO; -using System.Threading.Tasks; -using Avalonia.Controls; -using Avalonia.Controls.Platform; -using Avalonia.Input; -using Avalonia.Input.Platform; using Avalonia.Platform; namespace Avalonia.X11 { - class PlatformSettingsStub : IPlatformSettings + class PlatformSettingsStub : IPlatformSettings, ITouchPlatformSettings { public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + + public Size TouchDoubleClickSize => new Size(16, 16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index a18f91d301..e0d84e0a2e 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -68,6 +68,7 @@ namespace Avalonia.X11 //TODO: log if (options.UseDBusMenu) DBusHelper.TryInitialize(); + var platformSettingStub = new PlatformSettingsStub(); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) @@ -77,7 +78,8 @@ namespace Avalonia.X11 .Bind().ToFunc(() => KeyboardDevice) .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToConstant(new X11Clipboard(this)) - .Bind().ToConstant(new PlatformSettingsStub()) + .Bind().ToConstant(platformSettingStub) + .Bind().ToConstant(platformSettingStub) .Bind().ToConstant(new X11IconLoader(Info)) .Bind().ToConstant(new GtkSystemDialog()) .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()) diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 2947493c41..08d98d3ac1 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -101,7 +101,7 @@ namespace Avalonia namespace Avalonia.Win32 { - public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl + public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, ITouchPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -133,7 +133,8 @@ namespace Avalonia.Win32 UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - + public Size TouchDoubleClickSize => new Size(16,16); + public TimeSpan TouchDoubleClickTime => DoubleClickTime; public static void Initialize() { Initialize(new Win32PlatformOptions()); @@ -146,6 +147,7 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) + .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 2cac5e6bcf..fd5330b663 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -13,10 +13,10 @@ namespace Avalonia.iOS { public static EaglFeature GlFeature; public static DisplayLinkTimer Timer; - class PlatformSettings : IPlatformSettings + class PlatformSettings : ITouchPlatformSettings { - public Size DoubleClickSize { get; } = new Size(10, 10); - public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + public Size TouchDoubleClickSize => new Size(10, 10); + public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500); } public static void Register() @@ -30,7 +30,7 @@ namespace Avalonia.iOS .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) - .Bind().ToConstant(new PlatformSettings()) + .Bind().ToConstant(new PlatformSettings()) .Bind().ToConstant(new PlatformIconLoaderStub()) .Bind().ToSingleton() .Bind().ToSingleton() From 9af9a3f0813bd59c744e8a1e908c8a14e184761d Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Mon, 27 Dec 2021 14:53:15 +0200 Subject: [PATCH 100/260] fix tests --- src/Avalonia.Input/TouchDevice.cs | 2 +- tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 4970d4db32..a3881c42e7 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -61,7 +61,7 @@ namespace Avalonia.Input var settings = AvaloniaLocator.Current.GetService(); if (settings == null) { - throw new Exception("IPlatformSettings can not be null."); + throw new Exception("ITouchPlatformSettings can not be null."); } if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds) diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs index 7f6d711ccf..14b7df7260 100644 --- a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs @@ -208,10 +208,10 @@ namespace Avalonia.Input.UnitTests { var unitTestApp = UnitTestApplication.Start( new TestServices(inputManager: new InputManager())); - var iSettingsMock = new Mock(); - iSettingsMock.Setup(x => x.DoubleClickTime).Returns(doubleClickTime); + var iSettingsMock = new Mock(); + iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(iSettingsMock.Object); + .Bind().ToConstant(iSettingsMock.Object); return unitTestApp; } From 05021506e6165a004c7eec97e97318bc31203bde Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 27 Dec 2021 11:17:22 -0500 Subject: [PATCH 101/260] Update src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml --- src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 22f9ccf513..381fc68cf3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -15,7 +15,7 @@ IsEnabled="False" /> - + Date: Tue, 28 Dec 2021 13:25:05 +0200 Subject: [PATCH 102/260] add comments --- src/Android/Avalonia.Android/AndroidPlatform.cs | 4 ++++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 2 ++ src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs | 6 ++++++ src/Avalonia.X11/Stubs.cs | 2 ++ src/Windows/Avalonia.Win32/Win32Platform.cs | 4 ++++ src/iOS/Avalonia.iOS/Platform.cs | 3 +++ 6 files changed, 21 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index a9ba1086d5..3bf01a0f15 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -33,7 +33,11 @@ namespace Avalonia.Android { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; } + + /// public Size TouchDoubleClickSize => new Size(4, 4); + + /// public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200); public static void Initialize(Type appType, AndroidPlatformOptions options) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 680f64f537..f2d7f741fc 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -26,8 +26,10 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO + /// public Size TouchDoubleClickSize => new Size(16, 16); + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) diff --git a/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs b/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs index bda0dc3c33..bf65870d1b 100644 --- a/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs +++ b/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs @@ -4,8 +4,14 @@ namespace Avalonia.Platform { public interface ITouchPlatformSettings { + /// + /// Determines the size of the area within that you should click twice in order for a double click to be counted. + /// Size TouchDoubleClickSize { get; } + /// + /// Determines the time span that what will be used to determine the double-click. + /// TimeSpan TouchDoubleClickTime { get; } } } diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index ca1c037592..e98153e5a9 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -8,8 +8,10 @@ namespace Avalonia.X11 public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + /// public Size TouchDoubleClickSize => new Size(16, 16); + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; } } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 08d98d3ac1..d1196bbc8d 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -133,7 +133,11 @@ namespace Avalonia.Win32 UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK)); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); + + /// public Size TouchDoubleClickSize => new Size(16,16); + + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; public static void Initialize() { diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index fd5330b663..fa0db4518f 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -15,7 +15,10 @@ namespace Avalonia.iOS public static DisplayLinkTimer Timer; class PlatformSettings : ITouchPlatformSettings { + /// public Size TouchDoubleClickSize => new Size(10, 10); + + /// public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500); } From f9671bd4cc7e03431c50c9181705e4097517375b Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 28 Dec 2021 14:31:03 +0200 Subject: [PATCH 103/260] use getrequiredservice --- src/Avalonia.Input/TouchDevice.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index a3881c42e7..84974302eb 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -58,11 +58,8 @@ namespace Avalonia.Input } else { - var settings = AvaloniaLocator.Current.GetService(); - if (settings == null) - { - throw new Exception("ITouchPlatformSettings can not be null."); - } + var settings = AvaloniaLocator.Current.GetRequiredService(); + if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds) { From c69d8f07136e20a653fde2f3a5113b96b13bb8dc Mon Sep 17 00:00:00 2001 From: Luthfi Tri Atmaja Date: Wed, 29 Dec 2021 01:44:11 +0700 Subject: [PATCH 104/260] select text when clicking on a TextBox with shift key modifier --- src/Avalonia.Controls/TextBox.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 32428bea53..20d8a94c1a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1006,13 +1006,28 @@ namespace Avalonia.Controls if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border)) { var point = e.GetPosition(_presenter); - var index = CaretIndex = _presenter.GetCaretIndex(point); + var index = _presenter.GetCaretIndex(point); + var clickToSelect = index != CaretIndex && e.KeyModifiers.HasFlag(KeyModifiers.Shift); + + if (!clickToSelect) + { + CaretIndex = index; + } + #pragma warning disable CS0618 // Type or member is obsolete switch (e.ClickCount) #pragma warning restore CS0618 // Type or member is obsolete { case 1: - SelectionStart = SelectionEnd = index; + if (clickToSelect) + { + SelectionStart = Math.Min(index, CaretIndex); + SelectionEnd = Math.Max(index, CaretIndex); + } + else + { + SelectionStart = SelectionEnd = index; + } break; case 2: if (!StringUtils.IsStartOfWord(text, index)) From 21cc80932a9997a4da618a3be103f3a3e0851be8 Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 28 Dec 2021 22:19:33 +0200 Subject: [PATCH 105/260] use IPlatformSettings instead of creating a separate interface. --- src/Android/Avalonia.Android/AndroidPlatform.cs | 12 ++++++++---- .../Remote/PreviewerWindowingPlatform.cs | 4 ++++ src/Avalonia.Headless/HeadlessPlatformStubs.cs | 4 ++++ src/Avalonia.Input/TouchDevice.cs | 2 +- src/Avalonia.Native/AvaloniaNativePlatform.cs | 7 +++---- src/Avalonia.Visuals/ApiCompatBaseline.txt | 6 +++++- .../Platform/IPlatformSettings.cs | 10 ++++++++++ .../Platform/ITouchPlatformSettings.cs | 17 ----------------- src/Avalonia.X11/Stubs.cs | 6 +++--- src/Avalonia.X11/X11Platform.cs | 5 ++--- src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs | 4 ++++ .../Avalonia.Web.Blazor/WindowingPlatform.cs | 3 +++ src/Windows/Avalonia.Win32/Win32Platform.cs | 7 +++---- src/iOS/Avalonia.iOS/Platform.cs | 16 +++++++++------- .../TouchDeviceTests.cs | 4 ++-- 15 files changed, 61 insertions(+), 46 deletions(-) delete mode 100644 src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 3bf01a0f15..6a940a54f1 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -29,17 +29,21 @@ namespace Avalonia namespace Avalonia.Android { - class AndroidPlatform : ITouchPlatformSettings + class AndroidPlatform : IPlatformSettings { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; } - /// + /// public Size TouchDoubleClickSize => new Size(4, 4); - /// + /// public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200); + public Size DoubleClickSize => TouchDoubleClickSize; + + public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); + public static void Initialize(Type appType, AndroidPlatformOptions options) { Options = options; @@ -49,7 +53,7 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() - .Bind().ToConstant(Instance) + .Bind().ToConstant(Instance) .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToSingleton() diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs index ada63f5326..5d8b166b5a 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs @@ -64,5 +64,9 @@ namespace Avalonia.DesignerSupport.Remote public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + + public Size TouchDoubleClickSize => new Size(16, 16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; } } diff --git a/src/Avalonia.Headless/HeadlessPlatformStubs.cs b/src/Avalonia.Headless/HeadlessPlatformStubs.cs index ce4c31e27e..605659d464 100644 --- a/src/Avalonia.Headless/HeadlessPlatformStubs.cs +++ b/src/Avalonia.Headless/HeadlessPlatformStubs.cs @@ -67,6 +67,10 @@ namespace Avalonia.Headless { public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + + public Size TouchDoubleClickSize => new Size(16,16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; } class HeadlessSystemDialogsStub : ISystemDialogImpl diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 84974302eb..0069aa7961 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -58,7 +58,7 @@ namespace Avalonia.Input } else { - var settings = AvaloniaLocator.Current.GetRequiredService(); + var settings = AvaloniaLocator.Current.GetRequiredService(); if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds) diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index f2d7f741fc..5fa50f0e7f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -11,7 +11,7 @@ using Avalonia.Rendering; namespace Avalonia.Native { - class AvaloniaNativePlatform : IPlatformSettings, ITouchPlatformSettings, IWindowingPlatform + class AvaloniaNativePlatform : IPlatformSettings, IWindowingPlatform { private readonly IAvaloniaNativeFactory _factory; private AvaloniaNativePlatformOptions _options; @@ -26,10 +26,10 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO - /// + /// public Size TouchDoubleClickSize => new Size(16, 16); - /// + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) @@ -108,7 +108,6 @@ namespace Avalonia.Native .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) .Bind().ToConstant(this) - .Bind().ToConstant(this) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind().ToConstant(new RenderLoop()) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index ad9a5fd71a..dcb3246a63 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -79,4 +79,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. -Total Issues: 79 +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract. +InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract. +Total Issues: 84 diff --git a/src/Avalonia.Visuals/Platform/IPlatformSettings.cs b/src/Avalonia.Visuals/Platform/IPlatformSettings.cs index bcb00df5d6..e4b28e6575 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformSettings.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformSettings.cs @@ -7,5 +7,15 @@ namespace Avalonia.Platform Size DoubleClickSize { get; } TimeSpan DoubleClickTime { get; } + + /// + /// Determines the size of the area within that you should click twice in order for a double click to be counted. + /// + Size TouchDoubleClickSize { get; } + + /// + /// Determines the time span that what will be used to determine the double-click. + /// + TimeSpan TouchDoubleClickTime { get; } } } diff --git a/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs b/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs deleted file mode 100644 index bf65870d1b..0000000000 --- a/src/Avalonia.Visuals/Platform/ITouchPlatformSettings.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -namespace Avalonia.Platform -{ - public interface ITouchPlatformSettings - { - /// - /// Determines the size of the area within that you should click twice in order for a double click to be counted. - /// - Size TouchDoubleClickSize { get; } - - /// - /// Determines the time span that what will be used to determine the double-click. - /// - TimeSpan TouchDoubleClickTime { get; } - } -} diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs index e98153e5a9..f73512f1e8 100644 --- a/src/Avalonia.X11/Stubs.cs +++ b/src/Avalonia.X11/Stubs.cs @@ -3,15 +3,15 @@ using Avalonia.Platform; namespace Avalonia.X11 { - class PlatformSettingsStub : IPlatformSettings, ITouchPlatformSettings + class PlatformSettingsStub : IPlatformSettings { public Size DoubleClickSize { get; } = new Size(2, 2); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); - /// + /// public Size TouchDoubleClickSize => new Size(16, 16); - /// + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index e0d84e0a2e..ec3f29c806 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -68,7 +68,7 @@ namespace Avalonia.X11 //TODO: log if (options.UseDBusMenu) DBusHelper.TryInitialize(); - var platformSettingStub = new PlatformSettingsStub(); + AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) @@ -78,8 +78,7 @@ namespace Avalonia.X11 .Bind().ToFunc(() => KeyboardDevice) .Bind().ToConstant(new X11CursorFactory(Display)) .Bind().ToConstant(new X11Clipboard(this)) - .Bind().ToConstant(platformSettingStub) - .Bind().ToConstant(platformSettingStub) + .Bind().ToConstant(new PlatformSettingsStub()) .Bind().ToConstant(new X11IconLoader(Info)) .Bind().ToConstant(new GtkSystemDialog()) .Bind().ToConstant(new LinuxMountedVolumeInfoProvider()) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs index 642be28c69..dd60d5f09d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs @@ -18,5 +18,9 @@ namespace Avalonia.LinuxFramebuffer { public Size DoubleClickSize { get; } = new Size(4, 4); public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500); + + public Size TouchDoubleClickSize => new Size(16,16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; } } diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs index f30a36b8c9..979e66a785 100644 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs @@ -51,6 +51,9 @@ namespace Avalonia.Web.Blazor public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); + public Size TouchDoubleClickSize => new Size(16, 16); + + public TimeSpan TouchDoubleClickTime => DoubleClickTime; public void RunLoop(CancellationToken cancellationToken) { throw new NotSupportedException(); diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index d1196bbc8d..5cfbab40e4 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -101,7 +101,7 @@ namespace Avalonia namespace Avalonia.Win32 { - public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, ITouchPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl + public class Win32Platform : IPlatformThreadingInterface, IPlatformSettings, IWindowingPlatform, IPlatformIconLoader, IPlatformLifetimeEventsImpl { private static readonly Win32Platform s_instance = new Win32Platform(); private static Thread _uiThread; @@ -134,10 +134,10 @@ namespace Avalonia.Win32 public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); - /// + /// public Size TouchDoubleClickSize => new Size(16,16); - /// + /// public TimeSpan TouchDoubleClickTime => DoubleClickTime; public static void Initialize() { @@ -151,7 +151,6 @@ namespace Avalonia.Win32 .Bind().ToSingleton() .Bind().ToConstant(CursorFactory.Instance) .Bind().ToConstant(WindowsKeyboardDevice.Instance) - .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(s_instance) .Bind().ToConstant(new RenderLoop()) diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index fa0db4518f..88f60ace1f 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -1,11 +1,9 @@ using System; -using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; -using Avalonia.Shared.PlatformSupport; namespace Avalonia.iOS { @@ -13,15 +11,19 @@ namespace Avalonia.iOS { public static EaglFeature GlFeature; public static DisplayLinkTimer Timer; - class PlatformSettings : ITouchPlatformSettings + class PlatformSettings : IPlatformSettings { - /// + /// public Size TouchDoubleClickSize => new Size(10, 10); - /// + /// public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500); + + public Size DoubleClickSize => new Size(4, 4); + + public TimeSpan DoubleClickTime => TouchDoubleClickTime; } - + public static void Register() { GlFeature ??= new EaglFeature(); @@ -33,7 +35,7 @@ namespace Avalonia.iOS .Bind().ToConstant(new CursorFactoryStub()) .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToConstant(new ClipboardImpl()) - .Bind().ToConstant(new PlatformSettings()) + .Bind().ToConstant(new PlatformSettings()) .Bind().ToConstant(new PlatformIconLoaderStub()) .Bind().ToSingleton() .Bind().ToSingleton() diff --git a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs index 14b7df7260..80c5a45c1a 100644 --- a/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs +++ b/tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs @@ -208,10 +208,10 @@ namespace Avalonia.Input.UnitTests { var unitTestApp = UnitTestApplication.Start( new TestServices(inputManager: new InputManager())); - var iSettingsMock = new Mock(); + var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime); AvaloniaLocator.CurrentMutable.BindToSelf(this) - .Bind().ToConstant(iSettingsMock.Object); + .Bind().ToConstant(iSettingsMock.Object); return unitTestApp; } From 509d7c06f70b93d0ec65503255a5d56f9593f8f3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 29 Dec 2021 11:23:29 +0100 Subject: [PATCH 106/260] fixes: Null Annotaion --- src/Avalonia.Base/Threading/Dispatcher.cs | 2 +- src/Avalonia.Base/Threading/JobRunner.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index b7d5d5a5d2..908f431776 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -113,7 +113,7 @@ namespace Avalonia.Threading /// public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) { - Contract.Requires(action != null); + _ = action ?? throw new ArgumentNullException(nameof(action)); _jobRunner.Post(action, arg, priority); } diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 66f6243a35..f2aef0414c 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -197,7 +197,7 @@ namespace Avalonia.Threading { private readonly Action _action; private readonly T _parameter; - private readonly TaskCompletionSource _taskCompletionSource; + private readonly TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. @@ -212,7 +212,7 @@ namespace Avalonia.Threading _action = action; _parameter = parameter; Priority = priority; - _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } /// @@ -229,7 +229,7 @@ namespace Avalonia.Threading try { _action(_parameter); - _taskCompletionSource.SetResult(null); + _taskCompletionSource.SetResult(default); } catch (Exception e) { From eab9659487f370f8a589568c23d8df8f8d93f260 Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Thu, 30 Dec 2021 01:11:11 +0100 Subject: [PATCH 107/260] [DevTools] Allowed to take a screenshot of the currently selected control. (#4762) * [DevTools] Allowed to take a screenshot of the currently selected control. * feat(DevTools): Allowed to customize screenshot root folder. * feat(DevTools): Allowed screenshot filename by convention * fixes(DevTools): Mark Shot as async and catch eventualy RenderTo excepion * fixes: allignment of code comment * fixes: CS0173 when build on linux and macOS * Refactoring Shot method * fixes: typo in DefaultScreenshotRoot * fixes: assembly detection * fixes: code comment * fixes: nullable warnings * fixes: merge issue * fixes: removed unnecessary using * fixes: removed Dispatcher.UIThread.InvokeAsync * fixes: unexpected to the user results after call RenderTo * fixes: screenshot Clip * fixes: typo * fixes: remove workaraund * Apply suggestions from code review * feat(DevTools): Add Shot Button * feat(DevTools): Custom Screenshot handler * fixes(DevTools): Renamed Screenshots.FileHandler to Screenshots.FileConvetionHandler * feat(DevTools): Screenshots.BaseRenderToStreamHandler * feat(DevTools): Screenshots.FilePickerHandler * fixes(DevTools): marked VisualExtensions as internal * fixes(DevTools): Strip out FileConvetionHandler * fixes(DevTools): FilePickerHandler promote Title and ScreenshotsRoot properties to ctor * fixes(DevTools): Remove screenshot HotKey description form status bar * fixes(DevTools): Xml Comment * fixes(DevTools): Moved Screenshot command to File menu * fixes(DevTools): Removed reference to Avalonia.Dialogs Co-authored-by: Giuseppe Lippolis Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com> Co-authored-by: Max Katz --- .../Diagnostics/Convetions.cs | 17 ++++ .../Diagnostics/DevToolsOptions.cs | 10 ++- .../Diagnostics/IScreenshotHandler.cs | 17 ++++ .../Screenshots/BaseRenderToStreamHandler.cs | 29 +++++++ .../Screenshots/FilePickerHandler.cs | 85 +++++++++++++++++++ .../Diagnostics/ViewModels/MainViewModel.cs | 34 ++++++++ .../Diagnostics/Views/MainView.xaml | 30 +++++++ .../Diagnostics/Views/MainWindow.xaml | 4 +- .../Diagnostics/VisualExtensions.cs | 71 ++++++++++++++++ 9 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Convetions.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs new file mode 100644 index 0000000000..b41755f239 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs @@ -0,0 +1,17 @@ +using System; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics +{ + static class Convetions + { + public static string DefaultScreenshotsRoot => + System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create), + "Screenshots"); + + public static IScreenshotHandler DefaultScreenshotHandler { get; } = + new Screenshots.FilePickerHandler(); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 4181989388..33166f1147 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -1,4 +1,5 @@ -using Avalonia.Input; +using System; +using Avalonia.Input; namespace Avalonia.Diagnostics { @@ -28,5 +29,12 @@ namespace Avalonia.Diagnostics /// Get or set the startup screen index where the DevTools window will be displayed. /// public int? StartupScreenIndex { get; set; } + + /// + /// Allow to customizze SreenshotHandler + /// + /// Default handler is + public IScreenshotHandler ScreenshotHandler { get; set; } + = Convetions.DefaultScreenshotHandler; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs new file mode 100644 index 0000000000..3503ab56b3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics +{ + /// + /// Allowed to define custom handler for Shreeshot + /// + public interface IScreenshotHandler + { + /// + /// Handle the Screenshot + /// + /// + Task Take(IControl control); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs new file mode 100644 index 0000000000..c111b045d3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics.Screenshots +{ + /// + /// Base class for render Screenshto to stream + /// + public abstract class BaseRenderToStreamHandler : IScreenshotHandler + { + + /// + /// Get stream + /// + /// + /// stream to render the control + protected abstract Task GetStream(IControl control); + + public async Task Take(IControl control) + { + using var output = await GetStream(control); + if (output is { }) + { + control.RenderTo(output); + await output.FlushAsync(); + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs new file mode 100644 index 0000000000..fb57058ae9 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs @@ -0,0 +1,85 @@ +using System; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Avalonia.Controls; +using Lifetimes = Avalonia.Controls.ApplicationLifetimes; + +namespace Avalonia.Diagnostics.Screenshots +{ + /// + /// Show a FileSavePicker to select where save screenshot + /// + public sealed class FilePickerHandler : BaseRenderToStreamHandler + { + /// + /// Instance FilePickerHandler + /// + public FilePickerHandler() + { + + } + /// + /// Instance FilePickerHandler with specificated parameter + /// + /// SaveFilePicker Title + /// + public FilePickerHandler(string? title + , string? screenshotRoot = default + ) + { + if (title is { }) + Title = title; + if (screenshotRoot is { }) + ScreenshotsRoot = screenshotRoot; + } + /// + /// Get the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// + public string ScreenshotsRoot { get; } + = Convetions.DefaultScreenshotsRoot; + + /// + /// SaveFilePicker Title + /// + public string Title { get; } = "Save Screenshot to ..."; + + Window GetWindow(IControl control) + { + var window = control.VisualRoot as Window; + var app = Application.Current; + if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop) + { + window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow); + } + return window!; + } + + protected async override Task GetStream(IControl control) + { + Stream? output = default; + var result = await new SaveFileDialog() + { + Title = Title, + Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } }, + Directory = ScreenshotsRoot, + }.ShowAsync(GetWindow(control)); + if (!string.IsNullOrWhiteSpace(result)) + { + var foldler = Path.GetDirectoryName(result); + // Directory information for path, or null if path denotes a root directory or is + // null. Returns System.String.Empty if path does not contain directory information. + if (!string.IsNullOrWhiteSpace(foldler)) + { + if (!Directory.Exists(foldler)) + { + Directory.CreateDirectory(foldler); + } + output = new FileStream(result, FileMode.Create); + } + } + return output; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 8bc13a9525..c96ddf129e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,8 +1,11 @@ using System; using System.ComponentModel; +using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; +using Avalonia.Metadata; using Avalonia.Threading; using System.Reactive.Linq; using System.Linq; @@ -26,7 +29,9 @@ namespace Avalonia.Diagnostics.ViewModels private bool _freezePopups; private string? _pointerOverElementName; private IInputRoot? _pointerOverRoot; + private IScreenshotHandler? _screenshotHandler; private bool _showPropertyType; + public MainViewModel(AvaloniaObject root) { _root = root; @@ -285,9 +290,38 @@ namespace Avalonia.Diagnostics.ViewModels } public int? StartupScreenIndex { get; private set; } = default; + + [DependsOn(nameof(TreePageViewModel.SelectedNode))] + [DependsOn(nameof(Content))] + bool CanShot(object? parameter) + { + return Content is TreePageViewModel tree + && tree.SelectedNode != null + && tree.SelectedNode.Visual is VisualTree.IVisual visual + && visual.VisualRoot != null; + } + + async void Shot(object? parameter) + { + if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control + && _screenshotHandler is { } + ) + { + try + { + await _screenshotHandler.Take(control); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.Message); + //TODO: Notify error + } + } + } public void SetOptions(DevToolsOptions options) { + _screenshotHandler = options.ScreenshotHandler; StartupScreenIndex = options.StartupScreenIndex; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 381fc68cf3..91c8eb0983 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -6,6 +6,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 70d70f0b79..45bfe5ff81 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -18,6 +18,8 @@ - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs new file mode 100644 index 0000000000..a80ed2d4ef --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs @@ -0,0 +1,71 @@ +using System; +using System.IO; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Media.Imaging; +using Avalonia.VisualTree; + +namespace Avalonia.Diagnostics +{ + internal static class VisualExtensions + { + /// + /// Render control to the destination stream. + /// + /// Control to be rendered. + /// Destination stream. + /// Dpi quality. + public static void RenderTo(this IControl source, Stream destination, double dpi = 96) + { + if (source.TransformedBounds == null) + { + return; + } + var rect = source.TransformedBounds.Value.Clip; + var top = rect.TopLeft; + var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height); + var dpiVector = new Vector(dpi, dpi); + + // get Visual root + var root = (source.VisualRoot + ?? source.GetVisualRoot()) + as IControl ?? source; + + IDisposable? clipSetter = default; + IDisposable? clipToBoundsSetter = default; + IDisposable? renderTransformOriginSetter = default; + IDisposable? renderTransformSetter = default; + try + { + // Set clip region + var clipRegion = new Media.RectangleGeometry(rect); + clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation); + clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation); + + // Translate origin + renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty, + new RelativePoint(top, RelativeUnit.Absolute), + BindingPriority.Animation); + + renderTransformSetter = root.SetValue(Visual.RenderTransformProperty, + new Media.TranslateTransform(-top.X, -top.Y), + BindingPriority.Animation); + + using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector)) + { + bitmap.Render(root); + bitmap.Save(destination); + } + } + finally + { + // Restore values before trasformation + renderTransformSetter?.Dispose(); + renderTransformOriginSetter?.Dispose(); + clipSetter?.Dispose(); + clipToBoundsSetter?.Dispose(); + source?.InvalidateVisual(); + } + } + } +} From 3d2c7bc242cd5a546d4b834fc58eee862c395594 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 30 Dec 2021 10:32:39 +0000 Subject: [PATCH 108/260] Ensure Alpha is set for WGL display --- src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs index dc3c16b4a3..bc27589689 100644 --- a/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs +++ b/src/Windows/Avalonia.Win32/OpenGl/WglDisplay.cs @@ -95,6 +95,7 @@ namespace Avalonia.Win32.OpenGl WGL_DOUBLE_BUFFER_ARB, 1, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 32, + WGL_ALPHA_BITS_ARB, 8, WGL_DEPTH_BITS_ARB, 0, WGL_STENCIL_BITS_ARB, 0, 0, // End From 6e5569a8d9a2a9f55b14d6354966c72805cbea4f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 30 Dec 2021 12:52:55 +0000 Subject: [PATCH 109/260] Update azure-pipelines.yml for Azure Pipelines --- azure-pipelines.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 497402fe4b..ce27b130f5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -150,3 +150,9 @@ jobs: artifactName: 'Samples' condition: succeeded() + - task: GitHubComment@0 + displayName: 'Comment Package Version' + inputs: + gitHubConnection: 'GithubComments' + repositoryName: '$(Build.Repository.Name)' + comment: 'You can test this PR using the following package version. 0.10.999-cibuild00$(Build.BuildId)-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json)' \ No newline at end of file From 9e4d3fa8f23758255970285849238aa05174a341 Mon Sep 17 00:00:00 2001 From: avaloniaui-team <95628075+avaloniaui-team@users.noreply.github.com> Date: Thu, 30 Dec 2021 17:09:53 +0000 Subject: [PATCH 110/260] Update azure-pipelines.yml --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ce27b130f5..05b9a5219e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -155,4 +155,4 @@ jobs: inputs: gitHubConnection: 'GithubComments' repositoryName: '$(Build.Repository.Name)' - comment: 'You can test this PR using the following package version. 0.10.999-cibuild00$(Build.BuildId)-beta. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json)' \ No newline at end of file + comment: 'You can test this PR using the following package version. `0.10.999-cibuild00$(Build.BuildId)-beta`. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json)' From 878397978b9b8a25a1c26abc45b8000b2f0e7aff Mon Sep 17 00:00:00 2001 From: abdesol Date: Thu, 30 Dec 2021 20:49:13 +0300 Subject: [PATCH 111/260] Added Progress text to ProgressBar.xaml from Avalonia.Themes.Fluent --- src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index 7ad75588e1..a8bca77d3e 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -21,6 +21,7 @@ + @@ -29,7 +30,11 @@ - + + + + + From 1dfcce032e42595efbd2b549a20ee5df13aadf5d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 31 Dec 2021 00:42:36 +0000 Subject: [PATCH 112/260] Merge pull request #7291 from AvaloniaUI/features/comment-build-pr Features/comment build pr --- azure-pipelines.yml | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 05b9a5219e..40669f4f53 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -2,6 +2,33 @@ variables: MSBuildEnableWorkloadResolver: 'false' jobs: + +- job: GetPRNumber + pool: + vmImage: 'windows-2022' + variables: + SolutionDir: '$(Build.SourcesDirectory)' + steps: + + - task: PowerShell@2 + displayName: Get PR Number + inputs: + targetType: 'inline' + script: | + $prId = $env:System_PullRequest_PullRequestNumber + Write-Host "PR Number is:-" $env:System_PullRequest_PullRequestNumber + + if (!([string]::IsNullOrWhiteSpace($prId))) + { + Set-Content -Path $env:Build_ArtifactStagingDirectory\prId.txt -Value $prId + } + + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'PRNumber' + publishLocation: 'Container' + - job: Linux pool: vmImage: 'ubuntu-20.04' @@ -150,9 +177,3 @@ jobs: artifactName: 'Samples' condition: succeeded() - - task: GitHubComment@0 - displayName: 'Comment Package Version' - inputs: - gitHubConnection: 'GithubComments' - repositoryName: '$(Build.Repository.Name)' - comment: 'You can test this PR using the following package version. `0.10.999-cibuild00$(Build.BuildId)-beta`. (feed url: https://nuget.avaloniaui.net/repository/avalonia-all/index.json)' From 7ef5296898357b18164d3137341a9ad98451ed79 Mon Sep 17 00:00:00 2001 From: abdesol Date: Fri, 31 Dec 2021 07:00:22 +0300 Subject: [PATCH 113/260] Changed progress text foreground to template binding of the progress bar foreground --- src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index a8bca77d3e..e971eb46f2 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -21,7 +21,6 @@ - @@ -30,11 +29,10 @@ + + + - - - - From d8c89df26b850eb57cf29e7b3c993cc2993554eb Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sat, 1 Jan 2022 10:32:24 +0300 Subject: [PATCH 114/260] [WASM] Cursors --- .../Avalonia.Web.Blazor/AvaloniaView.razor.cs | 1 + src/Web/Avalonia.Web.Blazor/Cursor.cs | 92 +++++++++++++++++++ .../RazorViewTopLevelImpl.cs | 9 +- src/Web/Avalonia.Web.Blazor/WinStubs.cs | 21 ----- .../Avalonia.Web.Blazor/WindowingPlatform.cs | 2 +- 5 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 src/Web/Avalonia.Web.Blazor/Cursor.cs diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index bc2a2f3103..a682a25915 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -257,6 +257,7 @@ namespace Avalonia.Web.Blazor _inputHelper.Hide(); _inputHelper.SetCursor("default"); + _topLevelImpl.SetCssCursor = _inputHelper.SetCursor; Console.WriteLine("starting html canvas setup"); _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs new file mode 100644 index 0000000000..cd9010dfc2 --- /dev/null +++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs @@ -0,0 +1,92 @@ +using Avalonia.Input; +using Avalonia.Platform; + +namespace Avalonia.Web.Blazor +{ + public class CssCursor : ICursorImpl + { + public string? Value { get; set; } + + public CssCursor(StandardCursorType type) + { + Value = ToKeyword(type); + } + + /// + /// Create a cursor from base64 image + /// + public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) + { + Value = $"url(data:image/{format},{base64}) {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; + } + + /// + /// Create a cursor from url to *.cur file. + /// + public CssCursor(string url, StandardCursorType fallback) + { + Value = $"url('{url}'), {ToKeyword(fallback)}"; + } + + /// + /// Create a cursor from png/svg and hotspot position + /// + public CssCursor(string url, PixelPoint hotSpot, StandardCursorType fallback) + { + Value = $"url('{url}') {hotSpot.X} {hotSpot.Y}, {ToKeyword(fallback)}"; + } + + private static string ToKeyword(StandardCursorType type) => type switch + { + StandardCursorType.Hand => "pointer", + StandardCursorType.Cross => "crosshair", + StandardCursorType.Help => "help", + StandardCursorType.Ibeam => "text", + StandardCursorType.No => "not-allowed", + StandardCursorType.None => "none", + StandardCursorType.Wait => "progress", + StandardCursorType.AppStarting => "wait", + + StandardCursorType.DragMove => "move", + StandardCursorType.DragCopy => "copy", + StandardCursorType.DragLink => "alias", + + StandardCursorType.UpArrow => "default",/*not found matching one*/ + StandardCursorType.SizeWestEast => "ew-resize", + StandardCursorType.SizeNorthSouth => "ns-resize", + StandardCursorType.SizeAll => "move", + + StandardCursorType.TopSide => "n-resize", + StandardCursorType.BottomSide => "s-resize", + StandardCursorType.LeftSide => "w-resize", + StandardCursorType.RightSide => "e-resize", + StandardCursorType.TopLeftCorner => "nw-resize", + StandardCursorType.TopRightCorner => "ne-resize", + StandardCursorType.BottomLeftCorner => "sw-resize", + StandardCursorType.BottomRightCorner => "se-resize", + + _ => "default", + }; + + public void Dispose() {} + } + + internal class CssCursorFactory : ICursorFactory + { + public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) + { + using var imageStream = new MemoryStream(); + cursor.Save(imageStream); + + //not memory optimized because CryptoStream with ToBase64Transform is not supported in the browser. + var base64String = Convert.ToBase64String(imageStream.ToArray()); + return new CssCursor(base64String, "png", hotSpot, StandardCursorType.Arrow); + } + + ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) + { + return new CssCursor(cursorType); + } + } +} + diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index acd23a98b0..1b1fca7e26 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -126,8 +126,12 @@ namespace Avalonia.Web.Blazor public void SetCursor(ICursorImpl cursor) { - // nop - + var cur = cursor as CssCursor; + if (cur == null || cur.Value == null) + { + throw new NotSupportedException(); + } + SetCssCursor?.Invoke(cur.Value); } public IPopupImpl? CreatePopup() @@ -146,6 +150,7 @@ namespace Avalonia.Web.Blazor public IEnumerable Surfaces => new object[] { _currentSurface! }; + public Action? SetCssCursor { get; set; } public Action? Input { get; set; } public Action? Paint { get; set; } public Action? Resized { get; set; } diff --git a/src/Web/Avalonia.Web.Blazor/WinStubs.cs b/src/Web/Avalonia.Web.Blazor/WinStubs.cs index 13d83a9ee3..7b2bff6bfd 100644 --- a/src/Web/Avalonia.Web.Blazor/WinStubs.cs +++ b/src/Web/Avalonia.Web.Blazor/WinStubs.cs @@ -23,27 +23,6 @@ namespace Avalonia.Web.Blazor public Task GetDataAsync(string format) => Task.FromResult(new ()); } - internal class CursorStub : ICursorImpl - { - public void Dispose() - { - - } - } - - internal class CursorFactoryStub : ICursorFactory - { - public ICursorImpl CreateCursor(IBitmapImpl cursor, PixelPoint hotSpot) - { - return new CursorStub(); - } - - ICursorImpl ICursorFactory.GetCursor(StandardCursorType cursorType) - { - return new CursorStub(); - } - } - internal class IconLoaderStub : IPlatformIconLoader { private class IconStub : IWindowIconImpl diff --git a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs index 979e66a785..ac970d067f 100644 --- a/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs @@ -35,7 +35,7 @@ namespace Avalonia.Web.Blazor s_keyboard = new KeyboardDevice(); AvaloniaLocator.CurrentMutable .Bind().ToSingleton() - .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToConstant(s_keyboard) .Bind().ToConstant(instance) .Bind().ToConstant(instance) From e9d861bc6659640d9e61797ad8ddc65298d68c14 Mon Sep 17 00:00:00 2001 From: Abdella Solomon <69455299+Abdesol@users.noreply.github.com> Date: Sat, 1 Jan 2022 12:45:54 +0300 Subject: [PATCH 115/260] Changed text foreground of progressbar from template binding to SystemControlForegroundBaseHighBrush --- src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml index e971eb46f2..48b450a338 100644 --- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml @@ -30,7 +30,7 @@ - + From 100af8ffe307ba7664c3f8c0adfe695f62fab4ce Mon Sep 17 00:00:00 2001 From: Sergey Mikolaytis Date: Sat, 1 Jan 2022 12:46:26 +0300 Subject: [PATCH 116/260] [WASM] fix cursors. works fine --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 6 ++++-- src/Web/Avalonia.Web.Blazor/Cursor.cs | 5 +++-- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 11 ++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index a682a25915..4b0ada1f27 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -22,6 +22,7 @@ namespace Avalonia.Web.Blazor private DpiWatcherInterop _dpiWatcher = null!; private SKHtmlCanvasInterop.GLInfo? _jsGlInfo = null!; private InputHelperInterop _inputHelper = null!; + private InputHelperInterop _canvasHelper = null!; private ElementReference _htmlCanvas; private ElementReference _inputElement; private double _dpi; @@ -254,10 +255,11 @@ namespace Avalonia.Web.Blazor Threading.Dispatcher.UIThread.Post(async () => { _inputHelper = await InputHelperInterop.ImportAsync(Js, _inputElement); + _canvasHelper = await InputHelperInterop.ImportAsync(Js, _htmlCanvas); _inputHelper.Hide(); - _inputHelper.SetCursor("default"); - _topLevelImpl.SetCssCursor = _inputHelper.SetCursor; + _canvasHelper.SetCursor("default"); + _topLevelImpl.SetCssCursor = _canvasHelper.SetCursor; Console.WriteLine("starting html canvas setup"); _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); diff --git a/src/Web/Avalonia.Web.Blazor/Cursor.cs b/src/Web/Avalonia.Web.Blazor/Cursor.cs index cd9010dfc2..d921b2fa6c 100644 --- a/src/Web/Avalonia.Web.Blazor/Cursor.cs +++ b/src/Web/Avalonia.Web.Blazor/Cursor.cs @@ -5,6 +5,7 @@ namespace Avalonia.Web.Blazor { public class CssCursor : ICursorImpl { + public const string Default = "default"; public string? Value { get; set; } public CssCursor(StandardCursorType type) @@ -17,7 +18,7 @@ namespace Avalonia.Web.Blazor /// public CssCursor(string base64, string format, PixelPoint hotspot, StandardCursorType fallback) { - Value = $"url(data:image/{format},{base64}) {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; + Value = $"url(\"data:image/{format};base64,{base64}\") {hotspot.X} {hotspot.Y}, {ToKeyword(fallback)}"; } /// @@ -65,7 +66,7 @@ namespace Avalonia.Web.Blazor StandardCursorType.BottomLeftCorner => "sw-resize", StandardCursorType.BottomRightCorner => "se-resize", - _ => "default", + _ => Default, }; public void Dispose() {} diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index 1b1fca7e26..ac5f5044d8 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -21,6 +21,7 @@ namespace Avalonia.Web.Blazor private readonly Stopwatch _sw = Stopwatch.StartNew(); private readonly ITextInputMethodImpl _textInputMethod; private readonly TouchDevice _touchDevice; + private string _currentCursor = CssCursor.Default; public RazorViewTopLevelImpl(ITextInputMethodImpl textInputMethod) { @@ -127,11 +128,15 @@ namespace Avalonia.Web.Blazor public void SetCursor(ICursorImpl cursor) { var cur = cursor as CssCursor; - if (cur == null || cur.Value == null) + var val = CssCursor.Default; + if (cur != null && cur.Value != null) { - throw new NotSupportedException(); + val = cur.Value; + } + if (_currentCursor != val) + { + SetCssCursor?.Invoke(val); } - SetCssCursor?.Invoke(cur.Value); } public IPopupImpl? CreatePopup() From e7a36d011fd9b222ad1f762d822e0b74772aecd5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 1 Jan 2022 19:48:16 +0000 Subject: [PATCH 117/260] fix controlcatalog.web --- samples/ControlCatalog.Web/ControlCatalog.Web.csproj | 1 + samples/ControlCatalog/App.xaml.cs | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj index d463dfa84a..199fa85ad2 100644 --- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj +++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj @@ -2,6 +2,7 @@ net6.0 enable + True diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 816356bee9..505f486a6d 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -89,16 +89,18 @@ namespace ControlCatalog if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime) { desktopLifetime.MainWindow = new MainWindow(); + + this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() + { + StartupScreenIndex = 1, + }); } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime) + { singleViewLifetime.MainView = new MainView(); + } base.OnFrameworkInitializationCompleted(); - - this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() - { - StartupScreenIndex = 1, - }); } } } From c51086401454b08895164525fb3aad99f88ac6bb Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sun, 2 Jan 2022 05:20:39 +0300 Subject: [PATCH 118/260] [WASM] Fix cursors in macOS, fix default cursor set logic --- src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs | 6 +++++- src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs | 8 ++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs index 4b0ada1f27..7644514687 100644 --- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs +++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs @@ -259,7 +259,11 @@ namespace Avalonia.Web.Blazor _inputHelper.Hide(); _canvasHelper.SetCursor("default"); - _topLevelImpl.SetCssCursor = _canvasHelper.SetCursor; + _topLevelImpl.SetCssCursor = x => + { + _inputHelper.SetCursor(x);//macOS + _canvasHelper.SetCursor(x);//windows + }; Console.WriteLine("starting html canvas setup"); _interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame); diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs index ac5f5044d8..1d667c0f0c 100644 --- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs +++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs @@ -127,15 +127,11 @@ namespace Avalonia.Web.Blazor public void SetCursor(ICursorImpl cursor) { - var cur = cursor as CssCursor; - var val = CssCursor.Default; - if (cur != null && cur.Value != null) - { - val = cur.Value; - } + var val = (cursor as CssCursor)?.Value ?? CssCursor.Default; if (_currentCursor != val) { SetCssCursor?.Invoke(val); + _currentCursor = val; } } From 509b9d8f09eb1928608fe75354a43b6a11e0bf2c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 3 Jan 2022 11:50:43 +0100 Subject: [PATCH 119/260] Add additional null checks to WindowBaseImpl. Fixes #7231. --- src/Avalonia.Native/WindowImplBase.cs | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 4a3baa2788..87b7a7608e 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -28,18 +28,18 @@ namespace Avalonia.Native public string HandleDescriptor => "NSWindow"; - public IntPtr NSView => _native.ObtainNSViewHandle(); + public IntPtr NSView => _native?.ObtainNSViewHandle() ?? IntPtr.Zero; - public IntPtr NSWindow => _native.ObtainNSWindowHandle(); + public IntPtr NSWindow => _native?.ObtainNSWindowHandle() ?? IntPtr.Zero; public IntPtr GetNSViewRetained() { - return _native.ObtainNSViewHandleRetained(); + return _native?.ObtainNSViewHandleRetained() ?? IntPtr.Zero; } public IntPtr GetNSWindowRetained() { - return _native.ObtainNSWindowHandleRetained(); + return _native?.ObtainNSWindowHandleRetained() ?? IntPtr.Zero; } } @@ -260,7 +260,7 @@ namespace Avalonia.Native public void Activate() { - _native.Activate(); + _native?.Activate(); } public bool RawTextInputEvent(uint timeStamp, string text) @@ -322,7 +322,7 @@ namespace Avalonia.Native public void Resize(Size clientSize, PlatformResizeReason reason) { - _native.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); + _native?.Resize(clientSize.Width, clientSize.Height, (AvnPlatformResizeReason)reason); } public IRenderer CreateRenderer(IRenderRoot root) @@ -367,14 +367,14 @@ namespace Avalonia.Native public virtual void Show(bool activate, bool isDialog) { - _native.Show(activate.AsComBool(), isDialog.AsComBool()); + _native?.Show(activate.AsComBool(), isDialog.AsComBool()); } public PixelPoint Position { - get => _native.Position.ToAvaloniaPixelPoint(); - set => _native.SetPosition(value.ToAvnPoint()); + get => _native?.Position.ToAvaloniaPixelPoint() ?? default; + set => _native?.SetPosition(value.ToAvnPoint()); } public Point PointToClient(PixelPoint point) @@ -389,12 +389,12 @@ namespace Avalonia.Native public void Hide() { - _native.Hide(); + _native?.Hide(); } public void BeginMoveDrag(PointerPressedEventArgs e) { - _native.BeginMoveDrag(); + _native?.BeginMoveDrag(); } public Size MaxAutoSizeHint => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(1)) @@ -402,7 +402,7 @@ namespace Avalonia.Native public void SetTopmost(bool value) { - _native.SetTopMost(value.AsComBool()); + _native?.SetTopMost(value.AsComBool()); } public double RenderScaling => _native?.Scaling ?? 1; @@ -438,7 +438,7 @@ namespace Avalonia.Native public void SetMinMaxSize(Size minSize, Size maxSize) { - _native.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize()); + _native?.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize()); } public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) @@ -449,7 +449,7 @@ namespace Avalonia.Native internal void BeginDraggingSession(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard clipboard, IAvnDndResultCallback callback, IntPtr sourceHandle) { - _native.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); + _native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); } public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) From e6c08a13d70f7173494b83489806e2e1f10b442d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 19:04:06 +0300 Subject: [PATCH 120/260] Added reflection-free API for weak events --- .../NotifyCollectionChangedExtensions.cs | 21 +-- .../Data/Core/IndexerNodeBase.cs | 10 +- .../Core/Plugins/IndeiValidationPlugin.cs | 20 +- .../Plugins/InpcPropertyAccessorPlugin.cs | 24 +-- .../Utilities/IWeakEventSubscriber.cs | 12 ++ src/Avalonia.Base/Utilities/WeakEvent.cs | 175 ++++++++++++++++++ src/Avalonia.Base/Utilities/WeakEvents.cs | 40 ++++ src/Avalonia.Base/Utilities/WeakObservable.cs | 35 +++- .../Utilities/WeakSubscriptionManager.cs | 1 + src/Avalonia.Controls/NativeMenuItem.cs | 10 +- .../Repeater/ItemsRepeater.cs | 32 ++-- src/Avalonia.Controls/TopLevel.cs | 15 +- .../Utils/CollectionChangedEventManager.cs | 15 +- src/Avalonia.Layout/AttachedLayout.cs | 17 ++ .../PropertyInfoAccessorFactory.cs | 36 +--- .../Avalonia.Base.UnitTests/WeakEventTests.cs | 75 ++++++++ 16 files changed, 421 insertions(+), 117 deletions(-) create mode 100644 src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs create mode 100644 src/Avalonia.Base/Utilities/WeakEvent.cs create mode 100644 src/Avalonia.Base/Utilities/WeakEvents.cs create mode 100644 tests/Avalonia.Base.UnitTests/WeakEventTests.cs diff --git a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs index dcd32ddd76..689fcc89a4 100644 --- a/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs +++ b/src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs @@ -59,7 +59,7 @@ namespace Avalonia.Collections } private class WeakCollectionChangedObservable : LightweightObservableBase, - IWeakSubscriber + IWeakEventSubscriber { private WeakReference _sourceReference; @@ -68,31 +68,22 @@ namespace Avalonia.Collections _sourceReference = source; } - public void OnEvent(object? sender, NotifyCollectionChangedEventArgs e) + public void OnEvent(object? sender, + WeakEvent ev, + NotifyCollectionChangedEventArgs e) { PublishNext(e); } - protected override void Initialize() { if (_sourceReference.TryGetTarget(out var instance)) - { - WeakSubscriptionManager.Subscribe( - instance, - nameof(instance.CollectionChanged), - this); - } + WeakEvents.CollectionChanged.Subscribe(instance, this); } protected override void Deinitialize() { if (_sourceReference.TryGetTarget(out var instance)) - { - WeakSubscriptionManager.Unsubscribe( - instance, - nameof(instance.CollectionChanged), - this); - } + WeakEvents.CollectionChanged.Unsubscribe(instance, this); } } } diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs index e197e29103..a808827896 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs @@ -23,18 +23,16 @@ namespace Avalonia.Data.Core if (incc != null) { - inputs.Add(WeakObservable.FromEventPattern( - incc, - nameof(incc.CollectionChanged)) + inputs.Add(WeakObservable.FromEventPattern( + incc, WeakEvents.CollectionChanged) .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) .Select(_ => GetValue(target))); } if (inpc != null) { - inputs.Add(WeakObservable.FromEventPattern( - inpc, - nameof(inpc.PropertyChanged)) + inputs.Add(WeakObservable.FromEventPattern( + inpc, WeakEvents.PropertyChanged) .Where(x => ShouldUpdate(x.Sender, x.EventArgs)) .Select(_ => GetValue(target))); } diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 9f827daf94..1e7a0d5c8f 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -11,6 +11,12 @@ namespace Avalonia.Data.Core.Plugins /// public class IndeiValidationPlugin : IDataValidationPlugin { + private static readonly WeakEvent + ErrorsChangedWeakEvent = WeakEvent.Register( + (s, h) => s.ErrorsChanged += h, + (s, h) => s.ErrorsChanged -= h + ); + /// public bool Match(WeakReference reference, string memberName) { @@ -25,7 +31,7 @@ namespace Avalonia.Data.Core.Plugins return new Validator(reference, name, accessor); } - private class Validator : DataValidationBase, IWeakSubscriber + private class Validator : DataValidationBase, IWeakEventSubscriber { private readonly WeakReference _reference; private readonly string _name; @@ -37,7 +43,7 @@ namespace Avalonia.Data.Core.Plugins _name = name; } - void IWeakSubscriber.OnEvent(object? sender, DataErrorsChangedEventArgs e) + void IWeakEventSubscriber.OnEvent(object? notifyDataErrorInfo, WeakEvent ev, DataErrorsChangedEventArgs e) { if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName)) { @@ -51,10 +57,7 @@ namespace Avalonia.Data.Core.Plugins if (target != null) { - WeakSubscriptionManager.Subscribe( - target, - nameof(target.ErrorsChanged), - this); + ErrorsChangedWeakEvent.Subscribe(target, this); } base.SubscribeCore(); @@ -66,10 +69,7 @@ namespace Avalonia.Data.Core.Plugins if (target != null) { - WeakSubscriptionManager.Unsubscribe( - target, - nameof(target.ErrorsChanged), - this); + ErrorsChangedWeakEvent.Unsubscribe(target, this); } base.UnsubscribeCore(); diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index fd532f3014..33cecd10a7 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.ComponentModel; using System.Reflection; using Avalonia.Utilities; @@ -85,7 +86,7 @@ namespace Avalonia.Data.Core.Plugins return found; } - private class Accessor : PropertyAccessorBase, IWeakSubscriber + private class Accessor : PropertyAccessorBase, IWeakEventSubscriber { private readonly WeakReference _reference; private readonly PropertyInfo _property; @@ -129,7 +130,8 @@ namespace Avalonia.Data.Core.Plugins return false; } - void IWeakSubscriber.OnEvent(object? sender, PropertyChangedEventArgs e) + void IWeakEventSubscriber. + OnEvent(object? notifyPropertyChanged, WeakEvent ev, PropertyChangedEventArgs e) { if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { @@ -148,13 +150,8 @@ namespace Avalonia.Data.Core.Plugins { var inpc = GetReferenceTarget() as INotifyPropertyChanged; - if (inpc != null) - { - WeakSubscriptionManager.Unsubscribe( - inpc, - nameof(inpc.PropertyChanged), - this); - } + if (inpc != null) + WeakEvents.PropertyChanged.Unsubscribe(inpc, this); } private object? GetReferenceTarget() @@ -178,13 +175,8 @@ namespace Avalonia.Data.Core.Plugins { var inpc = GetReferenceTarget() as INotifyPropertyChanged; - if (inpc != null) - { - WeakSubscriptionManager.Subscribe( - inpc, - nameof(inpc.PropertyChanged), - this); - } + if (inpc != null) + WeakEvents.PropertyChanged.Subscribe(inpc, this); } } } diff --git a/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs b/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs new file mode 100644 index 0000000000..e48c0cb111 --- /dev/null +++ b/src/Avalonia.Base/Utilities/IWeakEventSubscriber.cs @@ -0,0 +1,12 @@ +using System; + +namespace Avalonia.Utilities; + +/// +/// Defines a listener to a event subscribed vis the . +/// +/// The type of the event arguments. +public interface IWeakEventSubscriber where TEventArgs : EventArgs +{ + void OnEvent(object? sender, WeakEvent ev, TEventArgs e); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs new file mode 100644 index 0000000000..430bc92838 --- /dev/null +++ b/src/Avalonia.Base/Utilities/WeakEvent.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace Avalonia.Utilities; + +/// +/// Manages subscriptions to events using weak listeners. +/// +public class WeakEvent : WeakEvent where TEventArgs : EventArgs where TSender : class +{ + private readonly Func, Action> _subscribe; + + readonly ConditionalWeakTable _subscriptions = new(); + + internal WeakEvent( + Action> subscribe, + Action> unsubscribe) + { + _subscribe = (t, s) => + { + subscribe(t, s); + return () => unsubscribe(t, s); + }; + } + + internal WeakEvent(Func, Action> subscribe) + { + _subscribe = subscribe; + } + + public void Subscribe(TSender target, IWeakEventSubscriber subscriber) + { + if (!_subscriptions.TryGetValue(target, out var subscription)) + _subscriptions.Add(target, subscription = new Subscription(this, target)); + subscription.Add(new WeakReference>(subscriber)); + } + + public void Unsubscribe(TSender target, IWeakEventSubscriber subscriber) + { + if (_subscriptions.TryGetValue(target, out var subscription)) + subscription.Remove(subscriber); + } + + private class Subscription + { + private readonly WeakEvent _ev; + private readonly TSender _target; + + private WeakReference>?[] _data = + new WeakReference>[16]; + private int _count; + private readonly Action _unsubscribe; + + public Subscription(WeakEvent ev, TSender target) + { + _ev = ev; + _target = target; + + _unsubscribe = ev._subscribe(target, OnEvent); + } + + void Destroy() + { + _unsubscribe(); + _ev._subscriptions.Remove(_target); + } + + public void Add(WeakReference> s) + { + if (_count == _data.Length) + { + //Extend capacity + var extendedData = new WeakReference>?[_data.Length * 2]; + Array.Copy(_data, extendedData, _data.Length); + _data = extendedData; + } + + _data[_count] = s; + _count++; + } + + public void Remove(IWeakEventSubscriber s) + { + var removed = false; + + for (int c = 0; c < _count; ++c) + { + var reference = _data[c]; + + if (reference != null && reference.TryGetTarget(out var instance) && instance == s) + { + _data[c] = null; + removed = true; + } + } + + if (removed) + { + Compact(); + } + } + + void Compact() + { + int empty = -1; + for (var c = 0; c < _count; c++) + { + var r = _data[c]; + //Mark current index as first empty + if (r == null && empty == -1) + empty = c; + //If current element isn't null and we have an empty one + if (r != null && empty != -1) + { + _data[c] = null; + _data[empty] = r; + empty++; + } + } + + if (empty != -1) + _count = empty; + if (_count == 0) + Destroy(); + } + + void OnEvent(object? sender, TEventArgs eventArgs) + { + var needCompact = false; + for (var c = 0; c < _count; c++) + { + var r = _data[c]; + if (r?.TryGetTarget(out var sub) == true) + sub!.OnEvent(_target, _ev, eventArgs); + else + needCompact = true; + } + + if (needCompact) + Compact(); + } + } + +} + +public class WeakEvent +{ + public static WeakEvent Register( + Action> subscribe, + Action> unsubscribe) where TSender : class where TEventArgs : EventArgs + { + return new WeakEvent(subscribe, unsubscribe); + } + + public static WeakEvent Register( + Func, Action> subscribe) where TSender : class where TEventArgs : EventArgs + { + return new WeakEvent(subscribe); + } + + public static WeakEvent Register( + Action subscribe, + Action unsubscribe) where TSender : class + { + return Register((s, h) => + { + EventHandler handler = (_, e) => h(s, e); + subscribe(s, handler); + return () => unsubscribe(s, handler); + }); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Utilities/WeakEvents.cs b/src/Avalonia.Base/Utilities/WeakEvents.cs new file mode 100644 index 0000000000..d1b5e7f12d --- /dev/null +++ b/src/Avalonia.Base/Utilities/WeakEvents.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Windows.Input; + +namespace Avalonia.Utilities; + +public class WeakEvents +{ + /// + /// Represents CollectionChanged event from + /// + public static readonly WeakEvent + CollectionChanged = WeakEvent.Register( + (c, s) => + { + NotifyCollectionChangedEventHandler handler = (_, e) => s(c, e); + c.CollectionChanged += handler; + return () => c.CollectionChanged -= handler; + }); + + /// + /// Represents PropertyChanged event from + /// + public static readonly WeakEvent + PropertyChanged = WeakEvent.Register( + (s, h) => + { + PropertyChangedEventHandler handler = (_, e) => h(s, e); + s.PropertyChanged += handler; + return () => s.PropertyChanged -= handler; + }); + + /// + /// Represents CanExecuteChanged event from + /// + public static readonly WeakEvent CommandCanExecuteChanged = + WeakEvent.Register((s, h) => s.CanExecuteChanged += h, + (s, h) => s.CanExecuteChanged -= h); +} \ No newline at end of file diff --git a/src/Avalonia.Base/Utilities/WeakObservable.cs b/src/Avalonia.Base/Utilities/WeakObservable.cs index 52edc7ad1a..6bf1d4082f 100644 --- a/src/Avalonia.Base/Utilities/WeakObservable.cs +++ b/src/Avalonia.Base/Utilities/WeakObservable.cs @@ -18,6 +18,7 @@ namespace Avalonia.Utilities /// Object instance that exposes the event to convert. /// Name of the event to convert. /// + [Obsolete("Use WeakEvent-based overload")] public static IObservable> FromEventPattern( TTarget target, string eventName) @@ -34,7 +35,9 @@ namespace Avalonia.Utilities }).Publish().RefCount(); } - private class Handler : IWeakSubscriber where TEventArgs : EventArgs + private class Handler + : IWeakSubscriber, + IWeakEventSubscriber where TEventArgs : EventArgs { private IObserver> _observer; @@ -47,6 +50,36 @@ namespace Avalonia.Utilities { _observer.OnNext(new EventPattern(sender, e)); } + + public void OnEvent(object? sender, WeakEvent ev, TEventArgs e) + { + _observer.OnNext(new EventPattern(sender, e)); + } } + + /// + /// Converts a WeakEvent conforming to the standard .NET event pattern into an observable + /// sequence, subscribing weakly. + /// + /// The type of target. + /// The type of the event args. + /// Object instance that exposes the event to convert. + /// The weak event to convert. + /// + public static IObservable> FromEventPattern( + TTarget target, WeakEvent ev) + where TEventArgs : EventArgs where TTarget : class + { + _ = target ?? throw new ArgumentNullException(nameof(target)); + _ = ev ?? throw new ArgumentNullException(nameof(ev)); + + return Observable.Create>(observer => + { + var handler = new Handler(observer); + ev.Subscribe(target, handler); + return () => ev.Unsubscribe(target, handler); + }).Publish().RefCount(); + } + } } diff --git a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs index 88b1e3c807..dc9e86cc32 100644 --- a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs +++ b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs @@ -19,6 +19,7 @@ namespace Avalonia.Utilities /// The event source. /// The name of the event. /// The subscriber. + [Obsolete("Use WeakEvent")] public static void Subscribe(TTarget target, string eventName, IWeakSubscriber subscriber) where TEventArgs : EventArgs { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 2ceaeb6dba..4d048f0fb0 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -33,7 +33,7 @@ namespace Avalonia.Controls } - class CanExecuteChangedSubscriber : IWeakSubscriber + class CanExecuteChangedSubscriber : IWeakEventSubscriber { private readonly NativeMenuItem _parent; @@ -42,7 +42,7 @@ namespace Avalonia.Controls _parent = parent; } - public void OnEvent(object sender, EventArgs e) + public void OnEvent(object? sender, WeakEvent ev, EventArgs e) { _parent.CanExecuteChanged(); } @@ -160,14 +160,12 @@ namespace Avalonia.Controls set { if (_command != null) - WeakSubscriptionManager.Unsubscribe(_command, - nameof(ICommand.CanExecuteChanged), _canExecuteChangedSubscriber); + WeakEvents.CommandCanExecuteChanged.Unsubscribe(_command, _canExecuteChangedSubscriber); SetAndRaise(CommandProperty, ref _command, value); if (_command != null) - WeakSubscriptionManager.Subscribe(_command, - nameof(ICommand.CanExecuteChanged), _canExecuteChangedSubscriber); + WeakEvents.CommandCanExecuteChanged.Subscribe(_command, _canExecuteChangedSubscriber); CanExecuteChanged(); } diff --git a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs index ecc0fa3a48..b40cf26df5 100644 --- a/src/Avalonia.Controls/Repeater/ItemsRepeater.cs +++ b/src/Avalonia.Controls/Repeater/ItemsRepeater.cs @@ -20,7 +20,7 @@ namespace Avalonia.Controls /// Represents a data-driven collection control that incorporates a flexible layout system, /// custom views, and virtualization. /// - public class ItemsRepeater : Panel, IChildIndexProvider + public class ItemsRepeater : Panel, IChildIndexProvider, IWeakEventSubscriber { /// /// Defines the property. @@ -723,14 +723,8 @@ namespace Avalonia.Controls { oldValue.UninitializeForContext(LayoutContext); - WeakEventHandlerManager.Unsubscribe( - oldValue, - nameof(AttachedLayout.MeasureInvalidated), - InvalidateMeasureForLayout); - WeakEventHandlerManager.Unsubscribe( - oldValue, - nameof(AttachedLayout.ArrangeInvalidated), - InvalidateArrangeForLayout); + AttachedLayout.MeasureInvalidatedWeakEvent.Unsubscribe(oldValue, this); + AttachedLayout.ArrangeInvalidatedWeakEvent.Unsubscribe(oldValue, this); // Walk through all the elements and make sure they are cleared foreach (var element in Children) @@ -748,14 +742,8 @@ namespace Avalonia.Controls { newValue.InitializeForContext(LayoutContext); - WeakEventHandlerManager.Subscribe( - newValue, - nameof(AttachedLayout.MeasureInvalidated), - InvalidateMeasureForLayout); - WeakEventHandlerManager.Subscribe( - newValue, - nameof(AttachedLayout.ArrangeInvalidated), - InvalidateArrangeForLayout); + AttachedLayout.MeasureInvalidatedWeakEvent.Subscribe(newValue, this); + AttachedLayout.ArrangeInvalidatedWeakEvent.Subscribe(newValue, this); } bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; @@ -806,9 +794,13 @@ namespace Avalonia.Controls _viewportManager.OnBringIntoViewRequested(e); } - private void InvalidateMeasureForLayout(object sender, EventArgs e) => InvalidateMeasure(); - - private void InvalidateArrangeForLayout(object sender, EventArgs e) => InvalidateArrange(); + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) + { + if(ev == AttachedLayout.ArrangeInvalidatedWeakEvent) + InvalidateArrange(); + else if (ev == AttachedLayout.MeasureInvalidatedWeakEvent) + InvalidateMeasure(); + } private VirtualizingLayoutContext GetLayoutContext() { diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 5d9a0c8eed..eaee5bdb50 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -34,7 +34,7 @@ namespace Avalonia.Controls IStyleHost, ILogicalRoot, ITextInputMethodRoot, - IWeakSubscriber + IWeakEventSubscriber { /// /// Defines the property. @@ -74,6 +74,12 @@ namespace Avalonia.Controls public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); + private static readonly WeakEvent + ResourcesChangedWeakEvent = WeakEvent.Register( + (s, h) => s.ResourcesChanged += h, + (s, h) => s.ResourcesChanged -= h + ); + private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; @@ -178,10 +184,7 @@ namespace Avalonia.Controls if (((IStyleHost)this).StylingParent is IResourceHost applicationResources) { - WeakSubscriptionManager.Subscribe( - applicationResources, - nameof(IResourceHost.ResourcesChanged), - this); + ResourcesChangedWeakEvent.Subscribe(applicationResources, this); } impl.LostFocus += PlatformImpl_LostFocus; @@ -286,7 +289,7 @@ namespace Avalonia.Controls /// IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice; - void IWeakSubscriber.OnEvent(object sender, ResourcesChangedEventArgs e) + void IWeakEventSubscriber.OnEvent(object sender, WeakEvent ev, ResourcesChangedEventArgs e) { ((ILogical)this).NotifyResourcesChanged(e); } diff --git a/src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs b/src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs index 1a190391b7..74705a0262 100644 --- a/src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs +++ b/src/Avalonia.Controls/Utils/CollectionChangedEventManager.cs @@ -83,7 +83,7 @@ namespace Avalonia.Controls.Utils "Collection listener not registered for this collection/listener combination."); } - private class Entry : IWeakSubscriber, IDisposable + private class Entry : IWeakEventSubscriber, IDisposable { private INotifyCollectionChanged _collection; @@ -91,23 +91,18 @@ namespace Avalonia.Controls.Utils { _collection = collection; Listeners = new List>(); - WeakSubscriptionManager.Subscribe( - _collection, - nameof(INotifyCollectionChanged.CollectionChanged), - this); + WeakEvents.CollectionChanged.Subscribe(_collection, this); } public List> Listeners { get; } public void Dispose() { - WeakSubscriptionManager.Unsubscribe( - _collection, - nameof(INotifyCollectionChanged.CollectionChanged), - this); + WeakEvents.CollectionChanged.Unsubscribe(_collection, this); } - void IWeakSubscriber.OnEvent(object? sender, NotifyCollectionChangedEventArgs e) + void IWeakEventSubscriber. + OnEvent(object? notifyCollectionChanged, WeakEvent ev, NotifyCollectionChangedEventArgs e) { static void Notify( INotifyCollectionChanged incc, diff --git a/src/Avalonia.Layout/AttachedLayout.cs b/src/Avalonia.Layout/AttachedLayout.cs index 6c884641f8..ece8bbe805 100644 --- a/src/Avalonia.Layout/AttachedLayout.cs +++ b/src/Avalonia.Layout/AttachedLayout.cs @@ -4,6 +4,7 @@ // Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. using System; +using Avalonia.Utilities; namespace Avalonia.Layout { @@ -19,10 +20,26 @@ namespace Avalonia.Layout /// public event EventHandler? MeasureInvalidated; + /// + /// Occurs when the measurement state (layout) has been invalidated. + /// + public static readonly WeakEvent MeasureInvalidatedWeakEvent = + WeakEvent.Register( + (s, h) => s.MeasureInvalidated += h, + (s, h) => s.MeasureInvalidated -= h); + /// /// Occurs when the arrange state (layout) has been invalidated. /// public event EventHandler? ArrangeInvalidated; + + /// + /// Occurs when the arrange state (layout) has been invalidated. + /// + public static readonly WeakEvent ArrangeInvalidatedWeakEvent = + WeakEvent.Register( + (s, h) => s.ArrangeInvalidated += h, + (s, h) => s.ArrangeInvalidated -= h); /// /// Initializes any per-container state the layout requires when it is attached to an diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs index b3f78bfbe3..c21a2d4299 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs @@ -72,7 +72,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings } } - internal class InpcPropertyAccessor : PropertyAccessorBase + internal class InpcPropertyAccessor : PropertyAccessorBase, IWeakEventSubscriber { protected readonly WeakReference _reference; private readonly IPropertyInfo _property; @@ -110,7 +110,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings return false; } - void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e) + public void OnEvent(object sender, WeakEvent ev, PropertyChangedEventArgs e) { if (e.PropertyName == _property.Name || string.IsNullOrEmpty(e.PropertyName)) { @@ -128,10 +128,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) { - WeakEventHandlerManager.Unsubscribe( - inpc, - nameof(INotifyPropertyChanged.PropertyChanged), - OnNotifyPropertyChanged); + WeakEvents.PropertyChanged.Unsubscribe(inpc, this); } } @@ -148,16 +145,11 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings private void SubscribeToChanges() { if (_reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc) - { - WeakEventHandlerManager.Subscribe( - inpc, - nameof(INotifyPropertyChanged.PropertyChanged), - OnNotifyPropertyChanged); - } + WeakEvents.PropertyChanged.Subscribe(inpc, this); } } - internal class IndexerAccessor : InpcPropertyAccessor + internal class IndexerAccessor : InpcPropertyAccessor, IWeakEventSubscriber { private int _index; @@ -172,27 +164,17 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { base.SubscribeCore(); if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc) - { - WeakEventHandlerManager.Subscribe( - incc, - nameof(INotifyCollectionChanged.CollectionChanged), - OnNotifyCollectionChanged); - } + WeakEvents.CollectionChanged.Subscribe(incc, this); } protected override void UnsubscribeCore() { base.UnsubscribeCore(); if (_reference.TryGetTarget(out var o) && o is INotifyCollectionChanged incc) - { - WeakEventHandlerManager.Unsubscribe( - incc, - nameof(INotifyCollectionChanged.CollectionChanged), - OnNotifyCollectionChanged); - } + WeakEvents.CollectionChanged.Unsubscribe(incc, this); } - - void OnNotifyCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) + + public void OnEvent(object? sender, WeakEvent ev, NotifyCollectionChangedEventArgs args) { if (ShouldNotifyListeners(args)) { diff --git a/tests/Avalonia.Base.UnitTests/WeakEventTests.cs b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs new file mode 100644 index 0000000000..81009f7c5f --- /dev/null +++ b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Utilities; +using Xunit; + +namespace Avalonia.Base.UnitTests +{ + public class WeakEventManagerTests + { + class EventSource + { + public event EventHandler Event; + + public void Fire() + { + Event?.Invoke(this, new EventArgs()); + } + + public static readonly WeakEvent WeakEvent = WeakEvent.Register( + (t, s) => t.Event += s, + (t, s) => t.Event -= s); + } + + class Subscriber : IWeakEventSubscriber + { + private readonly Action _onEvent; + + public Subscriber(Action onEvent) + { + _onEvent = onEvent; + } + + public void OnEvent(object sender, WeakEvent ev, EventArgs args) + { + _onEvent?.Invoke(); + } + } + + [Fact] + public void EventShouldBePassedToSubscriber() + { + bool handled = false; + var subscriber = new Subscriber(() => handled = true); + var source = new EventSource(); + EventSource.WeakEvent.Subscribe(source, subscriber); + + source.Fire(); + Assert.True(handled); + } + + + [Fact] + public void EventHandlerShouldNotBeKeptAlive() + { + bool handled = false; + var source = new EventSource(); + AddSubscriber(source, () => handled = true); + for (int c = 0; c < 10; c++) + { + GC.Collect(); + GC.Collect(3, GCCollectionMode.Forced, true); + } + source.Fire(); + Assert.False(handled); + } + + private void AddSubscriber(EventSource source, Action func) + { + EventSource.WeakEvent.Subscribe(source, new Subscriber(func)); + } + } +} From c2d627360cc256e503c2d78810dffd380af4b781 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Mon, 3 Jan 2022 19:15:45 +0300 Subject: [PATCH 121/260] [MacOS] fix quit menu item copy paste mistake --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index bda1c91750..5015c92e46 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -131,7 +131,7 @@ namespace Avalonia.Native }; quitItem.Click += (sender, args) => { - _applicationCommands.ShowAll(); + (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown(); }; result.Add(quitItem); } From 1221356df31623fd2cb036c8194ef18ee780012f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 19:20:44 +0300 Subject: [PATCH 122/260] apicompat --- src/Avalonia.Controls/ApiCompatBaseline.txt | 7 ++++++- src/Avalonia.Dialogs/ApiCompatBaseline.txt | 3 +++ tests/Avalonia.Base.UnitTests/WeakEventTests.cs | 10 +++++----- 3 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 src/Avalonia.Dialogs/ApiCompatBaseline.txt diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt index dd41c30e85..9b7d37e108 100644 --- a/src/Avalonia.Controls/ApiCompatBaseline.txt +++ b/src/Avalonia.Controls/ApiCompatBaseline.txt @@ -29,15 +29,20 @@ MembersMustExist : Member 'public void Avalonia.Controls.NumericUpDownValueChang MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.NewValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Window' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.WindowBase' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler)' is present in the implementation but not in the contract. +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Embedding.EmbeddableControlRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. MembersMustExist : Member 'public System.Action Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation. +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. @@ -57,4 +62,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. -Total Issues: 58 +Total Issues: 63 diff --git a/src/Avalonia.Dialogs/ApiCompatBaseline.txt b/src/Avalonia.Dialogs/ApiCompatBaseline.txt new file mode 100644 index 0000000000..9cb1b47015 --- /dev/null +++ b/src/Avalonia.Dialogs/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Dialogs: +CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Dialogs.AboutAvaloniaDialog' does not implement interface 'Avalonia.Utilities.IWeakSubscriber' in the implementation but it does in the contract. +Total Issues: 1 diff --git a/tests/Avalonia.Base.UnitTests/WeakEventTests.cs b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs index 81009f7c5f..2663b4858f 100644 --- a/tests/Avalonia.Base.UnitTests/WeakEventTests.cs +++ b/tests/Avalonia.Base.UnitTests/WeakEventTests.cs @@ -8,18 +8,18 @@ using Xunit; namespace Avalonia.Base.UnitTests { - public class WeakEventManagerTests + public class WeakEventTests { class EventSource { - public event EventHandler Event; + public event EventHandler Event; public void Fire() { Event?.Invoke(this, new EventArgs()); } - public static readonly WeakEvent WeakEvent = WeakEvent.Register( + public static readonly WeakEvent WeakEv = WeakEvent.Register( (t, s) => t.Event += s, (t, s) => t.Event -= s); } @@ -45,7 +45,7 @@ namespace Avalonia.Base.UnitTests bool handled = false; var subscriber = new Subscriber(() => handled = true); var source = new EventSource(); - EventSource.WeakEvent.Subscribe(source, subscriber); + EventSource.WeakEv.Subscribe(source, subscriber); source.Fire(); Assert.True(handled); @@ -69,7 +69,7 @@ namespace Avalonia.Base.UnitTests private void AddSubscriber(EventSource source, Action func) { - EventSource.WeakEvent.Subscribe(source, new Subscriber(func)); + EventSource.WeakEv.Subscribe(source, new Subscriber(func)); } } } From 059e367cbfa9026dcfdf8f239d28cbce44cf1ed0 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 19:24:31 +0300 Subject: [PATCH 123/260] Schedule weak event list Compact to be called later with Background dispatcher priority --- src/Avalonia.Base/Utilities/WeakEvent.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs index 430bc92838..c353e11263 100644 --- a/src/Avalonia.Base/Utilities/WeakEvent.cs +++ b/src/Avalonia.Base/Utilities/WeakEvent.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; +using Avalonia.Threading; namespace Avalonia.Utilities; @@ -53,6 +54,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event new WeakReference>[16]; private int _count; private readonly Action _unsubscribe; + private bool _compactScheduled; public Subscription(WeakEvent ev, TSender target) { @@ -99,12 +101,21 @@ public class WeakEvent : WeakEvent where TEventArgs : Event if (removed) { - Compact(); + ScheduleCompact(); } } + void ScheduleCompact() + { + if(_compactScheduled) + return; + _compactScheduled = true; + Dispatcher.UIThread.Post(Compact, DispatcherPriority.Background); + } + void Compact() { + _compactScheduled = false; int empty = -1; for (var c = 0; c < _count; c++) { @@ -140,7 +151,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event } if (needCompact) - Compact(); + ScheduleCompact(); } } From f010322a066b570eb1d979c4b1013934575149ed Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 19:50:22 +0300 Subject: [PATCH 124/260] Force compacting WeakEvent subscriber list in before assertions in tests --- .../ExpressionObserverTests_DataValidation.cs | 3 +++ .../Core/ExpressionObserverTests_Indexer.cs | 12 +++++++++ .../ExpressionObserverTests_Observable.cs | 11 +++++++- .../Core/ExpressionObserverTests_Property.cs | 27 +++++++++++++++++-- .../Plugins/IndeiValidationPluginTests.cs | 3 +++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs index ecc43aa3a5..43192584af 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_DataValidation.cs @@ -6,6 +6,7 @@ using System.Reactive.Linq; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Parsers; +using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -67,6 +68,8 @@ namespace Avalonia.Base.UnitTests.Data.Core Assert.Equal(1, data.ErrorsChangedSubscriptionCount); sub.Dispose(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.ErrorsChangedSubscriptionCount); } diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs index 6289ec46c7..20a4cb6d98 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Indexer.cs @@ -9,6 +9,7 @@ using Avalonia.Data.Core; using Avalonia.UnitTests; using Xunit; using Avalonia.Markup.Parsers; +using Avalonia.Threading; namespace Avalonia.Base.UnitTests.Data.Core { @@ -110,6 +111,9 @@ namespace Avalonia.Base.UnitTests.Data.Core data.Foo.Add("baz"); } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -127,6 +131,8 @@ namespace Avalonia.Base.UnitTests.Data.Core { data.Foo.RemoveAt(0); } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(new[] { "foo", "bar" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -145,6 +151,9 @@ namespace Avalonia.Base.UnitTests.Data.Core { data.Foo[1] = "baz"; } + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(new[] { "bar", "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -202,6 +211,9 @@ namespace Avalonia.Base.UnitTests.Data.Core data.Foo["foo"] = "bar2"; } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + var expected = new[] { "bar", "bar2" }; Assert.Equal(expected, result); Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs index a70d4574a6..4f88d2de7c 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Observable.cs @@ -5,6 +5,7 @@ using System.Reactive.Subjects; using Avalonia.Data; using Avalonia.Data.Core; using Avalonia.Markup.Parsers; +using Avalonia.Threading; using Avalonia.UnitTests; using Xunit; @@ -68,6 +69,8 @@ namespace Avalonia.Base.UnitTests.Data.Core Assert.Equal(new[] { "foo" }, result); sub.Dispose(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); GC.KeepAlive(data); @@ -109,10 +112,16 @@ namespace Avalonia.Base.UnitTests.Data.Core var sub = target.Subscribe(x => result.Add(x)); data1.Next.OnNext(data2); sync.ExecutePostedCallbacks(); - + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(new[] { new BindingNotification("foo") }, result); sub.Dispose(); + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, data1.PropertyChangedSubscriptionCount); GC.KeepAlive(data1); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs index 32cdd21e04..a9c62a3c4a 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs @@ -10,6 +10,7 @@ using Avalonia.UnitTests; using Xunit; using System.Threading.Tasks; using Avalonia.Markup.Parsers; +using Avalonia.Threading; namespace Avalonia.Base.UnitTests.Data.Core { @@ -182,6 +183,9 @@ namespace Avalonia.Base.UnitTests.Data.Core sub.Dispose(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, data.PropertyChangedSubscriptionCount); GC.KeepAlive(data); @@ -209,8 +213,11 @@ namespace Avalonia.Base.UnitTests.Data.Core data.RaisePropertyChanged(null); Assert.Equal(new[] { "foo", "bar", "bar" }, result); - + sub.Dispose(); + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); @@ -231,7 +238,9 @@ namespace Avalonia.Base.UnitTests.Data.Core Assert.Equal(new[] { "bar", "baz", null }, result); sub.Dispose(); - + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); @@ -253,6 +262,9 @@ namespace Avalonia.Base.UnitTests.Data.Core Assert.Equal(new[] { "bar", "baz", null }, result); sub.Dispose(); + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); @@ -297,6 +309,9 @@ namespace Avalonia.Base.UnitTests.Data.Core sub.Dispose(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); Assert.Equal(0, old.PropertyChangedSubscriptionCount); @@ -329,6 +344,9 @@ namespace Avalonia.Base.UnitTests.Data.Core result); sub.Dispose(); + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.PropertyChangedSubscriptionCount); Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount); @@ -412,6 +430,9 @@ namespace Avalonia.Base.UnitTests.Data.Core sub1.Dispose(); sub2.Dispose(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, data.PropertyChangedSubscriptionCount); GC.KeepAlive(data); @@ -535,6 +556,8 @@ namespace Avalonia.Base.UnitTests.Data.Core }, result); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, first.PropertyChangedSubscriptionCount); Assert.Equal(0, second.PropertyChangedSubscriptionCount); diff --git a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs index d8eddf6330..e8f1f38b90 100644 --- a/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs +++ b/tests/Avalonia.Base.UnitTests/Data/Core/Plugins/IndeiValidationPluginTests.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using Avalonia.Data; using Avalonia.Data.Core.Plugins; +using Avalonia.Threading; using Xunit; namespace Avalonia.Base.UnitTests.Data.Core.Plugins @@ -57,6 +58,8 @@ namespace Avalonia.Base.UnitTests.Data.Core.Plugins validator.Subscribe(_ => { }); Assert.Equal(1, data.ErrorsChangedSubscriptionCount); validator.Unsubscribe(); + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); Assert.Equal(0, data.ErrorsChangedSubscriptionCount); } From b6219db4ad844f7e461fbcabcfa07d00d39c0d92 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 20:44:37 +0300 Subject: [PATCH 125/260] More Dispatcher.UIThread.RunJobs in tests --- .../Data/BindingTests.cs | 7 +++++++ .../ExpressionObserverBuilderTests_Indexer.cs | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index a0dd565a87..055de999e2 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -12,6 +12,7 @@ using System.Runtime.CompilerServices; using Avalonia.UnitTests; using Avalonia.Data.Converters; using Avalonia.Data.Core; +using Avalonia.Threading; namespace Avalonia.Markup.UnitTests.Data { @@ -160,6 +161,9 @@ namespace Avalonia.Markup.UnitTests.Data target.Bind(TextBlock.TextProperty, new Binding("Foo", BindingMode.OneTime)); target.DataContext = source; + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, source.SubscriberCount); } @@ -608,6 +612,9 @@ namespace Avalonia.Markup.UnitTests.Data root.DataContext = source; } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(0, source.SubscriberCount); } diff --git a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs index 39d6152b69..dbf6ef2ce9 100644 --- a/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs +++ b/tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Indexer.cs @@ -10,6 +10,7 @@ using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Threading; using Xunit; namespace Avalonia.Markup.UnitTests.Parsers @@ -159,7 +160,10 @@ namespace Avalonia.Markup.UnitTests.Parsers { data.Foo.Add("baz"); } - + + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(new[] { AvaloniaProperty.UnsetValue, "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -178,6 +182,9 @@ namespace Avalonia.Markup.UnitTests.Parsers data.Foo.RemoveAt(0); } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(new[] { "foo", "bar" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -196,6 +203,9 @@ namespace Avalonia.Markup.UnitTests.Parsers data.Foo[1] = "baz"; } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + Assert.Equal(new[] { "bar", "baz" }, result); Assert.Null(((INotifyCollectionChangedDebug)data.Foo).GetCollectionChangedSubscribers()); @@ -252,6 +262,9 @@ namespace Avalonia.Markup.UnitTests.Parsers data.Foo["foo"] = "bar2"; } + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + var expected = new[] { "bar", "bar2" }; Assert.Equal(expected, result); Assert.Equal(0, data.Foo.PropertyChangedSubscriptionCount); From e516fe2e6043cce99926981dfdfc52fe09379878 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 20:45:55 +0300 Subject: [PATCH 126/260] More Dispatcher.UIThread.RunJobjs() in tests --- tests/Avalonia.LeakTests/AvaloniaObjectTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs index 54f9a87f94..19208b15f3 100644 --- a/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs +++ b/tests/Avalonia.LeakTests/AvaloniaObjectTests.cs @@ -1,5 +1,6 @@ using System; using System.Reactive.Subjects; +using Avalonia.Threading; using JetBrains.dotMemoryUnit; using Xunit; using Xunit.Abstractions; @@ -56,7 +57,9 @@ namespace Avalonia.LeakTests completeSource(); GC.Collect(); - + // Forces WeakEvent compact + Dispatcher.UIThread.RunJobs(); + GC.Collect(); Assert.False(weakSource.IsAlive); } From a175fbbe3ad2ad82be446925760baf12b9c65ce7 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 3 Jan 2022 19:06:39 +0100 Subject: [PATCH 127/260] Check for null visual parent. (#7298) It shouldn't be possible to come across a null visual parent here because the visual should be attached to the visual tree, but according to #6930, it is. Because we don't have a repro here, defensively return a null value instead of throwing if we hit this case. --- src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs index cb916293ac..63c22efc3f 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs @@ -126,7 +126,12 @@ namespace Avalonia.Rendering.SceneGraph while (node == null && visual.IsVisible) { - visual = visual.VisualParent!; + var parent = visual.VisualParent; + + if (parent is null) + return null; + + visual = parent; node = scene.FindNode(visual); } From 32bf6160dfcb3c0a0550e627a51325b2f935625d Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 3 Jan 2022 18:23:40 +0000 Subject: [PATCH 128/260] use IControlledApplicationLifetime --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 5015c92e46..57a8701202 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -131,7 +131,7 @@ namespace Avalonia.Native }; quitItem.Click += (sender, args) => { - (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown(); + (Application.Current.ApplicationLifetime as IControlledApplicationLifetime)?.Shutdown(); }; result.Add(quitItem); } From dfd0523e36e3f5227305a6da73cd61d6a0943b93 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 3 Jan 2022 22:12:51 +0300 Subject: [PATCH 129/260] Optimize Pen's subscriptions to IAffects render --- src/Avalonia.Visuals/ApiCompatBaseline.txt | 5 +- src/Avalonia.Visuals/Media/Pen.cs | 110 ++++++++------------- 2 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/Avalonia.Visuals/ApiCompatBaseline.txt b/src/Avalonia.Visuals/ApiCompatBaseline.txt index dcb3246a63..68e3673cfe 100644 --- a/src/Avalonia.Visuals/ApiCompatBaseline.txt +++ b/src/Avalonia.Visuals/ApiCompatBaseline.txt @@ -7,6 +7,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract. +CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. @@ -83,4 +86,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avaloni InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract. -Total Issues: 84 +Total Issues: 87 diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index 7c966a35cf..f4ae58eff3 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -7,7 +7,7 @@ namespace Avalonia.Media /// /// Describes how a stroke is drawn. /// - public class Pen : AvaloniaObject, IPen + public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber { /// /// Defines the property. @@ -45,6 +45,9 @@ namespace Avalonia.Media public static readonly StyledProperty MiterLimitProperty = AvaloniaProperty.Register(nameof(MiterLimit), 10.0); + private EventHandler? _invalidated; + private IAffectsRender? _subscribedTo; + /// /// Initializes a new instance of the class. /// @@ -96,17 +99,6 @@ namespace Avalonia.Media DashStyle = dashStyle; } - static Pen() - { - AffectsRender( - BrushProperty, - ThicknessProperty, - DashStyleProperty, - LineCapProperty, - LineJoinProperty, - MiterLimitProperty); - } - /// /// Gets or sets the brush used to draw the stroke. /// @@ -116,6 +108,11 @@ namespace Avalonia.Media set => SetValue(BrushProperty, value); } + private static readonly WeakEvent InvalidatedWeakEvent = + WeakEvent.Register( + (s, h) => s.Invalidated += h, + (s, h) => s.Invalidated -= h); + /// /// Gets or sets the stroke thickness. /// @@ -165,7 +162,19 @@ namespace Avalonia.Media /// /// Raised when the pen changes. /// - public event EventHandler? Invalidated; + public event EventHandler? Invalidated + { + add + { + _invalidated += value; + UpdateBrushSubscription(); + } + remove + { + _invalidated -= value; + UpdateBrushSubscription(); + } + } /// /// Creates an immutable clone of the brush. @@ -182,68 +191,33 @@ namespace Avalonia.Media MiterLimit); } - /// - /// Marks a property as affecting the pen's visual representation. - /// - /// The properties. - /// - /// After a call to this method in a pen's static constructor, any change to the - /// property will cause the event to be raised on the pen. - /// - protected static void AffectsRender(params AvaloniaProperty[] properties) - where T : Pen + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { - static void Invalidate(AvaloniaPropertyChangedEventArgs e) - { - if (e.Sender is T sender) - { - sender.RaiseInvalidated(EventArgs.Empty); - } - } + _invalidated?.Invoke(this, EventArgs.Empty); + if(change.Property == BrushProperty) + UpdateBrushSubscription(); + base.OnPropertyChanged(change); + } - static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e) + void UpdateBrushSubscription() + { + if ((_invalidated == null || _subscribedTo != Brush) && _subscribedTo != null) { - if (e.Sender is T sender) - { - if (e.OldValue is IAffectsRender oldValue) - { - WeakEventHandlerManager.Unsubscribe( - oldValue, - nameof(oldValue.Invalidated), - sender.AffectsRenderInvalidated); - } - - if (e.NewValue is IAffectsRender newValue) - { - WeakEventHandlerManager.Subscribe( - newValue, - nameof(newValue.Invalidated), - sender.AffectsRenderInvalidated); - } - - sender.RaiseInvalidated(EventArgs.Empty); - } + InvalidatedWeakEvent.Unsubscribe(_subscribedTo, this); + _subscribedTo = null; } - foreach (var property in properties) + if (_invalidated != null && _subscribedTo != Brush && Brush is IAffectsRender affectsRender) { - if (property.CanValueAffectRender()) - { - property.Changed.Subscribe(e => InvalidateAndSubscribe(e)); - } - else - { - property.Changed.Subscribe(e => Invalidate(e)); - } + InvalidatedWeakEvent.Subscribe(affectsRender, this); + _subscribedTo = affectsRender; } } - - /// - /// Raises the event. - /// - /// The event args. - protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); - - private void AffectsRenderInvalidated(object? sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); + + void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) + { + if (ev == InvalidatedWeakEvent) + _invalidated?.Invoke(this, EventArgs.Empty); + } } } From 7e45ee1e537e5f0b67c23cba159638dfcf5241a7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Jan 2022 01:19:14 +0300 Subject: [PATCH 130/260] Subscribe to DashStyle too --- src/Avalonia.Visuals/Media/Pen.cs | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Visuals/Media/Pen.cs b/src/Avalonia.Visuals/Media/Pen.cs index f4ae58eff3..65ba851100 100644 --- a/src/Avalonia.Visuals/Media/Pen.cs +++ b/src/Avalonia.Visuals/Media/Pen.cs @@ -46,7 +46,8 @@ namespace Avalonia.Media AvaloniaProperty.Register(nameof(MiterLimit), 10.0); private EventHandler? _invalidated; - private IAffectsRender? _subscribedTo; + private IAffectsRender? _subscribedToBrush; + private IAffectsRender? _subscribedToDashes; /// /// Initializes a new instance of the class. @@ -167,12 +168,12 @@ namespace Avalonia.Media add { _invalidated += value; - UpdateBrushSubscription(); + UpdateSubscriptions(); } remove { _invalidated -= value; - UpdateBrushSubscription(); + UpdateSubscriptions(); } } @@ -195,24 +196,33 @@ namespace Avalonia.Media { _invalidated?.Invoke(this, EventArgs.Empty); if(change.Property == BrushProperty) - UpdateBrushSubscription(); + UpdateSubscription(ref _subscribedToBrush, Brush); + if(change.Property == DashStyleProperty) + UpdateSubscription(ref _subscribedToDashes, DashStyle); base.OnPropertyChanged(change); } - void UpdateBrushSubscription() + + void UpdateSubscription(ref IAffectsRender? field, object? value) { - if ((_invalidated == null || _subscribedTo != Brush) && _subscribedTo != null) + if ((_invalidated == null || field != value) && field != null) { - InvalidatedWeakEvent.Unsubscribe(_subscribedTo, this); - _subscribedTo = null; + InvalidatedWeakEvent.Unsubscribe(field, this); + field = null; } - if (_invalidated != null && _subscribedTo != Brush && Brush is IAffectsRender affectsRender) + if (_invalidated != null && field != value && value is IAffectsRender affectsRender) { InvalidatedWeakEvent.Subscribe(affectsRender, this); - _subscribedTo = affectsRender; + field = affectsRender; } } + + void UpdateSubscriptions() + { + UpdateSubscription(ref _subscribedToBrush, Brush); + UpdateSubscription(ref _subscribedToDashes, DashStyle); + } void IWeakEventSubscriber.OnEvent(object? sender, WeakEvent ev, EventArgs e) { From a08c4888e0e2e1112275d228b42a0a3899e2a5f2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 4 Jan 2022 18:01:32 +0300 Subject: [PATCH 131/260] WeakEvent: cache Compact() delegate --- src/Avalonia.Base/Utilities/WeakEvent.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Utilities/WeakEvent.cs b/src/Avalonia.Base/Utilities/WeakEvent.cs index c353e11263..0b32015a8a 100644 --- a/src/Avalonia.Base/Utilities/WeakEvent.cs +++ b/src/Avalonia.Base/Utilities/WeakEvent.cs @@ -49,6 +49,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event { private readonly WeakEvent _ev; private readonly TSender _target; + private readonly Action _compact; private WeakReference>?[] _data = new WeakReference>[16]; @@ -60,7 +61,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event { _ev = ev; _target = target; - + _compact = Compact; _unsubscribe = ev._subscribe(target, OnEvent); } @@ -110,7 +111,7 @@ public class WeakEvent : WeakEvent where TEventArgs : Event if(_compactScheduled) return; _compactScheduled = true; - Dispatcher.UIThread.Post(Compact, DispatcherPriority.Background); + Dispatcher.UIThread.Post(_compact, DispatcherPriority.Background); } void Compact() From c73a6c86c0d0fba2b969a81dd9459ce0d0d02125 Mon Sep 17 00:00:00 2001 From: odalet Date: Tue, 4 Jan 2022 17:35:59 +0100 Subject: [PATCH 132/260] Fixes #7309 - Each time we retrieve a null *PlatformOptions from AvaloniaLocator, return a default instance --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 8 ++++---- src/Avalonia.Native/AvaloniaNativePlatform.cs | 4 ++-- src/Avalonia.X11/Glx/GlxDisplay.cs | 4 ++-- .../LinuxFramebufferPlatform.cs | 4 ++-- src/Windows/Avalonia.Win32/Win32GlManager.cs | 9 ++++----- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 57a8701202..40ffc31728 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -80,8 +80,8 @@ namespace Avalonia.Native }; result.Add(aboutItem); - var macOpts = AvaloniaLocator.Current.GetService(); - if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems) + var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); + if (!macOpts.DisableDefaultApplicationMenuItems) { result.Add(new NativeMenuItemSeparator()); @@ -142,9 +142,9 @@ namespace Avalonia.Native private void DoLayoutReset(bool forceUpdate = false) { - var macOpts = AvaloniaLocator.Current.GetService(); + var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); - if (macOpts != null && macOpts.DisableNativeMenus) + if (macOpts.DisableNativeMenus) { return; } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 5fa50f0e7f..1eadf70b13 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -96,9 +96,9 @@ namespace Avalonia.Native _factory.Initialize(new GCHandleDeallocator(), applicationPlatform); if (_factory.MacOptions != null) { - var macOpts = AvaloniaLocator.Current.GetService(); + var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); - _factory.MacOptions.SetShowInDock(macOpts?.ShowInDock != false ? 1 : 0); + _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); } AvaloniaLocator.CurrentMutable diff --git a/src/Avalonia.X11/Glx/GlxDisplay.cs b/src/Avalonia.X11/Glx/GlxDisplay.cs index fa8c866c09..fcdc10e999 100644 --- a/src/Avalonia.X11/Glx/GlxDisplay.cs +++ b/src/Avalonia.X11/Glx/GlxDisplay.cs @@ -95,8 +95,8 @@ namespace Avalonia.X11.Glx if (Environment.GetEnvironmentVariable("AVALONIA_GLX_IGNORE_RENDERER_BLACKLIST") != "1") { - var blacklist = AvaloniaLocator.Current.GetService() - ?.GlxRendererBlacklist; + var opts = AvaloniaLocator.Current.GetService() ?? new X11PlatformOptions(); + var blacklist = opts.GlxRendererBlacklist; if (blacklist != null) foreach (var item in blacklist) if (glInterface.Renderer.Contains(item)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index f4db6bf48a..4add4c423b 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -38,11 +38,11 @@ namespace Avalonia.LinuxFramebuffer if (_fb is IGlOutputBackend gl) AvaloniaLocator.CurrentMutable.Bind().ToConstant(gl.PlatformOpenGlInterface); - var opts = AvaloniaLocator.Current.GetService(); + var opts = AvaloniaLocator.Current.GetService() ?? new LinuxFramebufferPlatformOptions(); AvaloniaLocator.CurrentMutable .Bind().ToConstant(Threading) - .Bind().ToConstant(new DefaultRenderTimer(opts?.Fps ?? 60)) + .Bind().ToConstant(new DefaultRenderTimer(opts.Fps)) .Bind().ToConstant(new RenderLoop()) .Bind().ToTransient() .Bind().ToConstant(new KeyboardDevice()) diff --git a/src/Windows/Avalonia.Win32/Win32GlManager.cs b/src/Windows/Avalonia.Win32/Win32GlManager.cs index 289c100d51..0376a41f8c 100644 --- a/src/Windows/Avalonia.Win32/Win32GlManager.cs +++ b/src/Windows/Avalonia.Win32/Win32GlManager.cs @@ -13,19 +13,18 @@ namespace Avalonia.Win32 { AvaloniaLocator.CurrentMutable.Bind().ToLazy(() => { - var opts = AvaloniaLocator.Current.GetService(); - if (opts?.UseWgl == true) + var opts = AvaloniaLocator.Current.GetService() ?? new Win32PlatformOptions(); + if (opts.UseWgl) { var wgl = WglPlatformOpenGlInterface.TryCreate(); return wgl; } - if (opts?.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7) + if (opts.AllowEglInitialization ?? Win32Platform.WindowsVersion > PlatformConstants.Windows7) { var egl = EglPlatformOpenGlInterface.TryCreate(() => new AngleWin32EglDisplay()); - if (egl != null && - opts?.UseWindowsUIComposition == true) + if (egl != null && opts.UseWindowsUIComposition) { WinUICompositorConnection.TryCreateAndRegister(egl, opts.CompositionBackdropCornerRadius); } From 1604be478470c2dffd1c41055ea854fc89fa6b87 Mon Sep 17 00:00:00 2001 From: Tim U Date: Wed, 5 Jan 2022 08:00:03 +0100 Subject: [PATCH 133/260] fix Validation for SelectedDateProperty not working --- src/Avalonia.Controls/Calendar/CalendarDatePicker.cs | 11 +++++------ .../Controls/CalendarDatePicker.xaml | 6 +++++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs index a856ee071c..cd9c80d3e0 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDatePicker.cs @@ -185,7 +185,8 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect( nameof(SelectedDate), o => o.SelectedDate, - (o, v) => o.SelectedDate = v); + (o, v) => o.SelectedDate = v, + enableDataValidation: true); public static readonly StyledProperty SelectedDateFormatProperty = AvaloniaProperty.Register( @@ -533,13 +534,11 @@ namespace Avalonia.Controls } } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { - base.OnPropertyChanged(change); - - if (change.Property == SelectedDateProperty) + if (property == SelectedDateProperty) { - DataValidationErrors.SetError(this, change.NewValue.Error); + DataValidationErrors.SetError(this, value.Error); } } diff --git a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml index 6c4e94caf1..26c3bbc19f 100644 --- a/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml @@ -33,6 +33,7 @@ + @@ -107,7 +108,6 @@ Padding="{TemplateBinding Padding}" Watermark="{TemplateBinding Watermark}" UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}" - DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Grid.Column="0"/> @@ -136,8 +136,12 @@ DisplayDateEnd="{TemplateBinding DisplayDateEnd}" /> + + From fe21e298afbff5ed03fa0b08c95dad5e2352dd25 Mon Sep 17 00:00:00 2001 From: Tim U Date: Wed, 5 Jan 2022 08:00:43 +0100 Subject: [PATCH 134/260] Show validation in Demo App --- .../Pages/CalendarDatePickerPage.xaml | 5 +++++ .../ViewModels/MainWindowViewModel.cs | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml index 3e50bf8a08..2fe16ba8e3 100644 --- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml +++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml @@ -1,5 +1,7 @@ A control for selecting dates with a calendar drop-down @@ -39,6 +41,9 @@ + + + diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs index 4b3cfa9c9d..2b0c30f311 100644 --- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs +++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Controls.Notifications; using Avalonia.Dialogs; using Avalonia.Platform; using System; +using System.ComponentModel.DataAnnotations; using MiniMvvm; namespace ControlCatalog.ViewModels @@ -164,5 +165,17 @@ namespace ControlCatalog.ViewModels public MiniCommand ExitCommand { get; } public MiniCommand ToggleMenuItemCheckedCommand { get; } + + private DateTime? _validatedDateExample; + + /// + /// A required DateTime which should demonstrate validation for the DateTimePicker + /// + [Required] + public DateTime? ValidatedDateExample + { + get => _validatedDateExample; + set => this.RaiseAndSetIfChanged(ref _validatedDateExample, value); + } } } From 88db01532f70f209af9a1cb90f8bd13ad36fc6bf Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 16:34:41 +0100 Subject: [PATCH 135/260] Implement CellEditingTemplate --- .../DataGridTemplateColumn.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index 7e95dd100c..fbdad0a8ad 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -30,6 +30,20 @@ namespace Avalonia.Controls set { SetAndRaise(CellTemplateProperty, ref _cellTemplate, value); } } + private IDataTemplate _cellEditingCellTemplate; + + public static readonly DirectProperty CellEditingTemplateProperty = + AvaloniaProperty.RegisterDirect( + nameof(CellEditingTemplate), + o => o.CellEditingTemplate, + (o, v) => o.CellEditingTemplate = v); + + public IDataTemplate CellEditingTemplate + { + get => _cellEditingCellTemplate; + set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value); + } + private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) { var oldValue = (IDataTemplate)e.OldValue; @@ -38,7 +52,7 @@ namespace Avalonia.Controls public DataGridTemplateColumn() { - IsReadOnly = true; + // IsReadOnly = true; } protected override IControl GenerateElement(DataGridCell cell, object dataItem) @@ -60,7 +74,18 @@ namespace Avalonia.Controls protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding) { binding = null; - return GenerateElement(cell, dataItem); + if(CellEditingTemplate != null) + { + return CellEditingTemplate.Build(dataItem); + } + if (Design.IsDesignMode) + { + return null; + } + else + { + throw DataGridError.DataGridTemplateColumn.MissingTemplateForType(typeof(DataGridTemplateColumn)); + } } protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) @@ -70,7 +95,8 @@ namespace Avalonia.Controls protected internal override void RefreshCellContent(IControl element, string propertyName) { - if(propertyName == nameof(CellTemplate) && element.Parent is DataGridCell cell) + var cell = element.Parent as DataGridCell; + if(propertyName == nameof(CellTemplate) && cell is not null) { cell.Content = GenerateElement(cell, cell.DataContext); } From 66a02a37d09b226127b3d7636e18f0d84fbd0f02 Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 17:27:28 +0100 Subject: [PATCH 136/260] Handle IsReadOnly correct for DataGridTemplateColumn --- .../DataGridColumn.cs | 2 +- .../DataGridTemplateColumn.cs | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index 6b515503aa..8501ce3896 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -448,7 +448,7 @@ namespace Avalonia.Controls internal set; } - public bool IsReadOnly + public virtual bool IsReadOnly { get { diff --git a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs index fbdad0a8ad..e8ccb7df34 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls { public class DataGridTemplateColumn : DataGridColumn { - IDataTemplate _cellTemplate; + private IDataTemplate _cellTemplate; public static readonly DirectProperty CellTemplateProperty = AvaloniaProperty.RegisterDirect( @@ -54,7 +54,7 @@ namespace Avalonia.Controls { // IsReadOnly = true; } - + protected override IControl GenerateElement(DataGridCell cell, object dataItem) { if(CellTemplate != null) @@ -78,6 +78,10 @@ namespace Avalonia.Controls { return CellEditingTemplate.Build(dataItem); } + else if (CellTemplate != null) + { + return CellTemplate.Build(dataItem); + } if (Design.IsDesignMode) { return null; @@ -103,5 +107,22 @@ namespace Avalonia.Controls base.RefreshCellContent(element, propertyName); } + + public override bool IsReadOnly + { + get + { + if (CellEditingTemplate is null) + { + return true; + } + + return base.IsReadOnly; + } + set + { + base.IsReadOnly = value; + } + } } } From cf6c0991f8f52bcd683328dafa011550df184698 Mon Sep 17 00:00:00 2001 From: Tim U Date: Tue, 4 Jan 2022 17:27:57 +0100 Subject: [PATCH 137/260] Update Demo --- samples/ControlCatalog/Models/Person.cs | 15 +++++++++++++++ samples/ControlCatalog/Pages/DataGridPage.xaml | 12 ++++++++++++ samples/ControlCatalog/Pages/DataGridPage.xaml.cs | 6 +++--- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/samples/ControlCatalog/Models/Person.cs b/samples/ControlCatalog/Models/Person.cs index 47f41bc584..cd70fa3959 100644 --- a/samples/ControlCatalog/Models/Person.cs +++ b/samples/ControlCatalog/Models/Person.cs @@ -16,6 +16,7 @@ namespace ControlCatalog.Models string _firstName; string _lastName; bool _isBanned; + private int _age; public string FirstName { @@ -59,6 +60,20 @@ namespace ControlCatalog.Models } } + + /// + /// Gets or sets the age of the person + /// + public int Age + { + get => _age; + set + { + _age = value; + OnPropertyChanged(nameof(Age)); + } + } + Dictionary> _errorLookup = new Dictionary>(); void SetError(string propertyName, string error) diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 63e873d9b5..451a774cb4 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -64,6 +64,18 @@ + + + + + + + + + + + + public event EventHandler Click { - add { AddHandler(ClickEvent, value); } - remove { RemoveHandler(ClickEvent, value); } + add => AddHandler(ClickEvent, value); + remove => RemoveHandler(ClickEvent, value); } /// @@ -121,8 +127,8 @@ namespace Avalonia.Controls /// public ClickMode ClickMode { - get { return GetValue(ClickModeProperty); } - set { SetValue(ClickModeProperty, value); } + get => GetValue(ClickModeProperty); + set => SetValue(ClickModeProperty, value); } /// @@ -130,8 +136,8 @@ namespace Avalonia.Controls /// public ICommand Command { - get { return _command; } - set { SetAndRaise(CommandProperty, ref _command, value); } + get => _command; + set => SetAndRaise(CommandProperty, ref _command, value); } /// @@ -139,8 +145,8 @@ namespace Avalonia.Controls /// public KeyGesture HotKey { - get { return GetValue(HotKeyProperty); } - set { SetValue(HotKeyProperty, value); } + get => GetValue(HotKeyProperty); + set => SetValue(HotKeyProperty, value); } /// @@ -148,8 +154,8 @@ namespace Avalonia.Controls /// public object CommandParameter { - get { return GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } + get => GetValue(CommandParameterProperty); + set => SetValue(CommandParameterProperty, value); } /// @@ -158,8 +164,8 @@ namespace Avalonia.Controls /// public bool IsDefault { - get { return GetValue(IsDefaultProperty); } - set { SetValue(IsDefaultProperty, value); } + get => GetValue(IsDefaultProperty); + set => SetValue(IsDefaultProperty, value); } /// @@ -168,18 +174,21 @@ namespace Avalonia.Controls /// public bool IsCancel { - get { return GetValue(IsCancelProperty); } - set { SetValue(IsCancelProperty, value); } + get => GetValue(IsCancelProperty); + set => SetValue(IsCancelProperty, value); } + /// + /// Gets or sets a value indicating whether the button is currently pressed. + /// public bool IsPressed { - get { return GetValue(IsPressedProperty); } - private set { SetValue(IsPressedProperty, value); } + get => GetValue(IsPressedProperty); + private set => SetValue(IsPressedProperty, value); } /// - /// Gets or sets the Flyout that should be shown with this button + /// Gets or sets the Flyout that should be shown with this button. /// public FlyoutBase Flyout { @@ -187,7 +196,8 @@ namespace Avalonia.Controls set => SetValue(FlyoutProperty, value); } - protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; + /// + protected override bool IsEnabledCore => base.IsEnabledCore && _commandCanExecute; /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) @@ -224,6 +234,7 @@ namespace Avalonia.Controls } } + /// protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { if (_hotkey != null) // Control attached again, set Hotkey to create a hotkey manager for this control @@ -240,6 +251,7 @@ namespace Avalonia.Controls } } + /// protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { // This will cause the hotkey manager to dispose the observer and the reference to this control @@ -358,12 +370,14 @@ namespace Avalonia.Controls } } } - + + /// protected override void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { IsPressed = false; } + /// protected override void OnLostFocus(RoutedEventArgs e) { base.OnLostFocus(e); @@ -371,6 +385,7 @@ namespace Avalonia.Controls IsPressed = false; } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -391,6 +406,7 @@ namespace Avalonia.Controls } } + /// protected override void UpdateDataValidation(AvaloniaProperty property, BindingValue value) { base.UpdateDataValidation(property, value); @@ -493,7 +509,7 @@ namespace Avalonia.Controls /// /// The event sender. /// The event args. - private void CanExecuteChanged(object sender, EventArgs e) + public void CanExecuteChanged(object sender, EventArgs e) { var canExecute = Command == null || Command.CanExecute(CommandParameter); @@ -566,11 +582,12 @@ namespace Avalonia.Controls } } + /// + /// Updates the visual state of the control by applying latest PseudoClasses. + /// private void UpdatePseudoClasses(bool isPressed) { PseudoClasses.Set(":pressed", isPressed); } - - void ICommandSource.CanExecuteChanged(object sender, EventArgs e) => this.CanExecuteChanged(sender, e); } } From d8e071039bf342781ed9ed1c08d1a849588f8371 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 25 Jan 2022 20:02:40 -0500 Subject: [PATCH 209/260] Move more Button property changed handling into OnPropertyChanged override --- src/Avalonia.Controls/Button.cs | 142 ++-- .../SplitButton/SplitButton.cs | 735 ++++++++++++++++++ 2 files changed, 790 insertions(+), 87 deletions(-) create mode 100644 src/Avalonia.Controls/SplitButton/SplitButton.cs diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index 770eb63266..3735e6c010 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -59,13 +59,13 @@ namespace Avalonia.Controls AvaloniaProperty.Register(nameof(CommandParameter)); /// - /// Defines the property. + /// Defines the property. /// public static readonly StyledProperty IsDefaultProperty = AvaloniaProperty.Register(nameof(IsDefault)); /// - /// Defines the property. + /// Defines the property. /// public static readonly StyledProperty IsCancelProperty = AvaloniaProperty.Register(nameof(IsCancel)); @@ -98,10 +98,6 @@ namespace Avalonia.Controls static Button() { FocusableProperty.OverrideDefaultValue(typeof(Button), true); - CommandProperty.Changed.Subscribe(CommandChanged); - CommandParameterProperty.Changed.Subscribe(CommandParameterChanged); - IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); - IsCancelProperty.Changed.Subscribe(IsCancelChanged); AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler