From 4870fc0d4d86c8e9c3c2aaa58453042f91eae7b2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 22 Nov 2021 12:59:33 +0100 Subject: [PATCH 01/46] 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 02/46] 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 03/46] 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 04/46] 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 05/46] 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 06/46] 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 048cf388bcdb570f3fb176b5ea77e15919057948 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 13 Dec 2021 17:59:08 +0000 Subject: [PATCH 07/46] 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 08/46] 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 7624e14bcdd7c4ee7dea52aa12ac38f36727a1b6 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Fri, 17 Dec 2021 20:57:30 -0800 Subject: [PATCH 09/46] 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 10/46] 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 11/46] 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 12/46] 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 13/46] 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 14/46] 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 15/46] 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 16/46] 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 17/46] 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 18/46] 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 19/46] 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 20/46] 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 21/46] 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 2de57da8ff54c43ca49c85105cedeb2733d0ab2a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 19 Dec 2021 23:26:14 +0100 Subject: [PATCH 22/46] 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 23/46] 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 24/46] 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 25/46] 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 26/46] 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 27/46] 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 28/46] 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 29/46] 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 30/46] 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 31/46] 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 32/46] 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 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 33/46] 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 34/46] 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 35/46] 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 36/46] 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 37/46] 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 7fad72cca614198b8e294292236f12cffe3b0b3d Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Wed, 22 Dec 2021 19:32:40 +0200 Subject: [PATCH 38/46] 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 39/46] 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 40/46] 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 41/46] 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 240050dd8a7395bdab3c309d00b86fd3f0689ea6 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 10:42:05 +0300 Subject: [PATCH 42/46] 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 2ac4eafdcf0664f05746943eb107787f11697a19 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 23 Dec 2021 11:01:25 +0100 Subject: [PATCH 43/46] fixes: WarningsAsErrors in Visual Studio --- packages/Avalonia/Avalonia.csproj | 2 +- src/Avalonia.Animation/Avalonia.Animation.csproj | 2 +- src/Avalonia.Base/Avalonia.Base.csproj | 2 +- .../Avalonia.Controls.DataGrid.csproj | 2 +- src/Avalonia.Controls/Avalonia.Controls.csproj | 2 +- src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj | 2 +- src/Avalonia.Desktop/Avalonia.Desktop.csproj | 2 +- src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj | 2 +- src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj | 2 +- src/Avalonia.Dialogs/Avalonia.Dialogs.csproj | 2 +- src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj | 2 +- src/Avalonia.Headless.Vnc/Avalonia.Headless.Vnc.csproj | 2 +- src/Avalonia.Headless/Avalonia.Headless.csproj | 2 +- src/Avalonia.Input/Avalonia.Input.csproj | 2 +- src/Avalonia.Interactivity/Avalonia.Interactivity.csproj | 2 +- src/Avalonia.Layout/Avalonia.Layout.csproj | 2 +- src/Avalonia.Native/Avalonia.Native.csproj | 2 +- src/Avalonia.OpenGL/Avalonia.OpenGL.csproj | 2 +- src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj | 2 +- src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj | 2 +- src/Avalonia.Styling/Avalonia.Styling.csproj | 2 +- src/Avalonia.Themes.Default/Avalonia.Themes.Default.csproj | 2 +- src/Avalonia.Themes.Fluent/Avalonia.Themes.Fluent.csproj | 2 +- src/Avalonia.Visuals/Avalonia.Visuals.csproj | 2 +- src/Avalonia.X11/Avalonia.X11.csproj | 2 +- .../Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj | 2 +- .../Avalonia.Markup.Xaml.Loader.csproj | 2 +- src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj | 2 +- src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 2 +- src/Skia/Avalonia.Skia/Avalonia.Skia.csproj | 2 +- src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- 32 files changed, 32 insertions(+), 32 deletions(-) diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index af9ed71c5f..4b28527465 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net461;netcoreapp2.0;net6.0 + net6.0;netstandard2.0;net461;netcoreapp2.0 Avalonia diff --git a/src/Avalonia.Animation/Avalonia.Animation.csproj b/src/Avalonia.Animation/Avalonia.Animation.csproj index 85938ad958..465b922b72 100644 --- a/src/Avalonia.Animation/Avalonia.Animation.csproj +++ b/src/Avalonia.Animation/Avalonia.Animation.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index 07614f31db..376075df2a 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.Base Avalonia True diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index d50e8b1115..c89157dc0f 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 Avalonia.Controls.DataGrid diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 479bbb49b6..e2c6a714aa 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0 diff --git a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj index 9d8842f613..f8a7cdc690 100644 --- a/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj +++ b/src/Avalonia.DesignerSupport/Avalonia.DesignerSupport.csproj @@ -1,6 +1,6 @@  - netstandard2.0;net6.0 + net6.0;netstandard2.0