Browse Source

Merge branch 'master' into feature/get-required-service

pull/7242/head
Steven Kirk 5 years ago
committed by GitHub
parent
commit
d841d76637
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      readme.md
  2. 18
      src/Avalonia.Input/Gestures.cs
  3. 9
      src/Avalonia.Input/PointerEventArgs.cs
  4. 44
      src/Avalonia.Input/TouchDevice.cs
  5. 7
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  6. 5
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  7. 1
      src/Avalonia.Styling/Avalonia.Styling.csproj
  8. 12
      src/Avalonia.Styling/Controls/ChildNameScope.cs
  9. 4
      src/Avalonia.Styling/Controls/INameScope.cs
  10. 30
      src/Avalonia.Styling/Controls/NameScope.cs
  11. 28
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  12. 8
      src/Avalonia.Styling/Controls/NameScopeLocator.cs
  13. 2
      src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs
  14. 2
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  15. 6
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  16. 2
      src/Avalonia.Styling/INamed.cs
  17. 16
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  18. 6
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  19. 26
      src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs
  20. 7
      src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
  21. 18
      src/Avalonia.Styling/StyledElement.cs
  22. 2
      src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs
  23. 2
      src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs
  24. 6
      src/Avalonia.Styling/Styling/ChildSelector.cs
  25. 2
      src/Avalonia.Styling/Styling/OrSelector.cs
  26. 2
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  27. 50
      src/Avalonia.Styling/Styling/Selectors.cs
  28. 14
      src/Avalonia.Styling/Styling/Styles.cs
  29. 6
      src/Avalonia.Styling/Styling/TemplateSelector.cs
  30. 2
      src/Avalonia.Visuals/Visual.cs
  31. 6
      src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs
  32. 272
      tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs

9
readme.md

@ -3,9 +3,6 @@
<br /> <br />
[![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) [![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 ## 📖 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. 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). 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 ### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)] Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]

18
src/Avalonia.Input/Gestures.cs

@ -6,6 +6,7 @@ namespace Avalonia.Input
{ {
public static class Gestures public static class Gestures
{ {
private static bool s_isDoubleTapped = false;
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>( public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>(
"Tapped", "Tapped",
RoutingStrategies.Bubble, RoutingStrategies.Bubble,
@ -81,20 +82,23 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
var visual = (IVisual)ev.Source; var visual = (IVisual)ev.Source;
#pragma warning disable CS0618 // Type or member is obsolete if (e.ClickCount <= 1)
var clickCount = e.ClickCount;
#pragma warning restore CS0618 // Type or member is obsolete
if (clickCount <= 1)
{ {
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source); 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) if (s_lastPress.TryGetTarget(out var target) && target == e.Source)
{ {
s_isDoubleTapped = true;
e.Source.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); 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)); 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)); e.Source.RaiseEvent(new TappedEventArgs(TappedEvent, e));
} }

9
src/Avalonia.Input/PointerEventArgs.cs

@ -114,7 +114,7 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs public class PointerPressedEventArgs : PointerEventArgs
{ {
private readonly int _obsoleteClickCount; private readonly int _clickCount;
public PointerPressedEventArgs( public PointerPressedEventArgs(
IInteractive source, IInteractive source,
@ -123,15 +123,14 @@ namespace Avalonia.Input
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers, KeyModifiers modifiers,
int obsoleteClickCount = 1) int clickCount = 1)
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, : base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition,
timestamp, properties, modifiers) timestamp, properties, modifiers)
{ {
_obsoleteClickCount = obsoleteClickCount; _clickCount = clickCount;
} }
[Obsolete("Use DoubleTapped event or Gestures.DoubleRightTapped attached event")] public int ClickCount => _clickCount;
public int ClickCount => _obsoleteClickCount;
[Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")] [Obsolete("Use PointerPressedEventArgs.GetCurrentPoint(this).Properties")]
public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton(); public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton();

44
src/Avalonia.Input/TouchDevice.cs

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.VisualTree; using Avalonia.Platform;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -16,7 +16,9 @@ namespace Avalonia.Input
{ {
private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>(); private readonly Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
private bool _disposed; private bool _disposed;
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) => KeyModifiers GetKeyModifiers(RawInputModifiers modifiers) =>
(KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask); (KeyModifiers)(modifiers & RawInputModifiers.KeyboardMask);
@ -27,10 +29,10 @@ namespace Avalonia.Input
rv |= RawInputModifiers.LeftMouseButton; rv |= RawInputModifiers.LeftMouseButton;
return rv; return rv;
} }
public void ProcessRawEvent(RawInputEventArgs ev) public void ProcessRawEvent(RawInputEventArgs ev)
{ {
if(_disposed) if (_disposed)
return; return;
var args = (RawTouchEventArgs)ev; var args = (RawTouchEventArgs)ev;
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer)) if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
@ -43,16 +45,40 @@ namespace Avalonia.Input
PointerType.Touch, _pointers.Count == 0); PointerType.Touch, _pointers.Count == 0);
pointer.Capture(hit); pointer.Capture(hit);
} }
var target = pointer.Captured ?? args.Root; var target = pointer.Captured ?? args.Root;
if (args.Type == RawPointerEventType.TouchBegin) if (args.Type == RawPointerEventType.TouchBegin)
{ {
if (_pointers.Count > 1)
{
_clickCount = 1;
_lastClickTime = 0;
_lastClickRect = new Rect();
}
else
{
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
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, target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
args.Root, args.Position, ev.Timestamp, args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), new PointerPointProperties(GetModifiers(args.InputModifiers, true),
PointerUpdateKind.LeftButtonPressed), PointerUpdateKind.LeftButtonPressed),
GetKeyModifiers(args.InputModifiers))); GetKeyModifiers(args.InputModifiers), _clickCount));
} }
if (args.Type == RawPointerEventType.TouchEnd) if (args.Type == RawPointerEventType.TouchEnd)
@ -84,12 +110,12 @@ namespace Avalonia.Input
GetKeyModifiers(args.InputModifiers))); GetKeyModifiers(args.InputModifiers)));
} }
} }
public void Dispose() public void Dispose()
{ {
if(_disposed) if (_disposed)
return; return;
var values = _pointers.Values.ToList(); var values = _pointers.Values.ToList();
_pointers.Clear(); _pointers.Clear();
@ -97,6 +123,6 @@ namespace Avalonia.Input
foreach (var p in values) foreach (var p in values)
p.Dispose(); p.Dispose();
} }
} }
} }

7
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -142,6 +142,13 @@ namespace Avalonia.Native
private void DoLayoutReset(bool forceUpdate = false) private void DoLayoutReset(bool forceUpdate = false)
{ {
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts != null && macOpts.DisableNativeMenus)
{
return;
}
if (_resetQueued || forceUpdate) if (_resetQueued || forceUpdate)
{ {
_resetQueued = false; _resetQueued = false;

5
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. /// You can prevent Avalonia from adding those items to the OSX Application Menu with this property. The default value is false.
/// </summary> /// </summary>
public bool DisableDefaultApplicationMenuItems { get; set; } public bool DisableDefaultApplicationMenuItems { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application.
/// </summary>
public bool DisableNativeMenus { get; set; }
} }
} }

1
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -9,4 +9,5 @@
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
</Project> </Project>

12
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 void Register(string name, object element) => _inner.Register(name, element);
public SynchronousCompletionAsyncResult<object> FindAsync(string name) public SynchronousCompletionAsyncResult<object?> FindAsync(string name)
{ {
var found = Find(name); var found = Find(name);
if (found != null) if (found != null)
return new SynchronousCompletionAsyncResult<object>(found); return new SynchronousCompletionAsyncResult<object?>(found);
// Not found and both current and parent scope are in completed state // Not found and both current and parent scope are in completed state
if(IsCompleted) if(IsCompleted)
return new SynchronousCompletionAsyncResult<object>(null); return new SynchronousCompletionAsyncResult<object?>(null);
return DoFindAsync(name); return DoFindAsync(name);
} }
public SynchronousCompletionAsyncResult<object> DoFindAsync(string name) public SynchronousCompletionAsyncResult<object?> DoFindAsync(string name)
{ {
var src = new SynchronousCompletionAsyncResultSource<object>(); var src = new SynchronousCompletionAsyncResultSource<object?>();
void ParentSearch() void ParentSearch()
{ {
@ -56,7 +56,7 @@ namespace Avalonia.Controls
return src.AsyncResult; return src.AsyncResult;
} }
public object Find(string name) public object? Find(string name)
{ {
var found = _inner.Find(name); var found = _inner.Find(name);
if (found != null) if (found != null)

4
src/Avalonia.Styling/Controls/INameScope.cs

@ -22,14 +22,14 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns> /// <returns>The element, or null if the name was not found.</returns>
SynchronousCompletionAsyncResult<object> FindAsync(string name); SynchronousCompletionAsyncResult<object?> FindAsync(string name);
/// <summary> /// <summary>
/// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack /// Finds a named element in the name scope, returns immediately, doesn't traverse the name scope stack
/// </summary> /// </summary>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The element, or null if the name was not found.</returns> /// <returns>The element, or null if the name was not found.</returns>
object Find(string name); object? Find(string name);
/// <summary> /// <summary>
/// Marks the name scope as completed, no further registrations will be allowed /// Marks the name scope as completed, no further registrations will be allowed

30
src/Avalonia.Styling/Controls/NameScope.cs

@ -22,8 +22,8 @@ namespace Avalonia.Controls
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>(); private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object>> _pendingSearches = private readonly Dictionary<string, SynchronousCompletionAsyncResultSource<object?>> _pendingSearches =
new Dictionary<string, SynchronousCompletionAsyncResultSource<object>>(); new Dictionary<string, SynchronousCompletionAsyncResultSource<object?>>();
/// <summary> /// <summary>
/// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element. /// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
@ -32,7 +32,7 @@ namespace Avalonia.Controls
/// <returns>The value of the NameScope attached property.</returns> /// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(StyledElement styled) public static INameScope GetNameScope(StyledElement styled)
{ {
Contract.Requires<ArgumentNullException>(styled != null); _ = styled ?? throw new ArgumentNullException(nameof(styled));
return styled.GetValue(NameScopeProperty); return styled.GetValue(NameScopeProperty);
} }
@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// <param name="value">The value to set.</param> /// <param name="value">The value to set.</param>
public static void SetNameScope(StyledElement styled, INameScope value) public static void SetNameScope(StyledElement styled, INameScope value)
{ {
Contract.Requires<ArgumentNullException>(styled != null); _ = styled ?? throw new ArgumentNullException(nameof(styled));
styled.SetValue(NameScopeProperty, value); styled.SetValue(NameScopeProperty, value);
} }
@ -54,12 +54,11 @@ namespace Avalonia.Controls
{ {
if (IsCompleted) if (IsCompleted)
throw new InvalidOperationException("NameScope is completed, no further registrations are allowed"); throw new InvalidOperationException("NameScope is completed, no further registrations are allowed");
Contract.Requires<ArgumentNullException>(name != null);
Contract.Requires<ArgumentNullException>(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) if (existing != element)
{ {
@ -77,27 +76,26 @@ namespace Avalonia.Controls
} }
} }
public SynchronousCompletionAsyncResult<object> FindAsync(string name) public SynchronousCompletionAsyncResult<object?> FindAsync(string name)
{ {
var found = Find(name); var found = Find(name);
if (found != null) if (found != null)
return new SynchronousCompletionAsyncResult<object>(found); return new SynchronousCompletionAsyncResult<object?>(found);
if (IsCompleted) if (IsCompleted)
return new SynchronousCompletionAsyncResult<object>((object)null); return new SynchronousCompletionAsyncResult<object?>(null);
if (!_pendingSearches.TryGetValue(name, out var tcs)) if (!_pendingSearches.TryGetValue(name, out var tcs))
// We are intentionally running continuations synchronously here // We are intentionally running continuations synchronously here
_pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object>(); _pendingSearches[name] = tcs = new SynchronousCompletionAsyncResultSource<object?>();
return tcs.AsyncResult; return tcs.AsyncResult;
} }
/// <inheritdoc /> /// <inheritdoc />
public object Find(string name) public object? Find(string name)
{ {
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
object result; _inner.TryGetValue(name, out var result);
_inner.TryGetValue(name, out result);
return result; return result;
} }

28
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@ -17,11 +17,11 @@ namespace Avalonia.Controls
/// <param name="nameScope">The name scope.</param> /// <param name="nameScope">The name scope.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns> /// <returns>The named element or null if not found.</returns>
public static T Find<T>(this INameScope nameScope, string name) public static T? Find<T>(this INameScope nameScope, string name)
where T : class where T : class
{ {
Contract.Requires<ArgumentNullException>(nameScope != null); _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope));
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
var result = nameScope.Find(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()}'."); $"Expected control '{name}' to be '{typeof(T)} but it was '{result.GetType()}'.");
} }
return (T)result; return (T?)result;
} }
/// <summary> /// <summary>
@ -41,11 +41,11 @@ namespace Avalonia.Controls
/// <param name="anchor">The control to take the name scope from.</param> /// <param name="anchor">The control to take the name scope from.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The named element or null if not found.</returns> /// <returns>The named element or null if not found.</returns>
public static T Find<T>(this ILogical anchor, string name) public static T? Find<T>(this ILogical anchor, string name)
where T : class where T : class
{ {
Contract.Requires<ArgumentNullException>(anchor != null); _ = anchor ?? throw new ArgumentNullException(nameof(anchor));
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
var styledAnchor = anchor as StyledElement; var styledAnchor = anchor as StyledElement;
if (styledAnchor == null) if (styledAnchor == null)
return null; return null;
@ -64,8 +64,8 @@ namespace Avalonia.Controls
public static T Get<T>(this INameScope nameScope, string name) public static T Get<T>(this INameScope nameScope, string name)
where T : class where T : class
{ {
Contract.Requires<ArgumentNullException>(nameScope != null); _ = nameScope ?? throw new ArgumentNullException(nameof(nameScope));
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
var result = nameScope.Find(name); var result = nameScope.Find(name);
@ -94,9 +94,9 @@ namespace Avalonia.Controls
public static T Get<T>(this ILogical anchor, string name) public static T Get<T>(this ILogical anchor, string name)
where T : class where T : class
{ {
Contract.Requires<ArgumentNullException>(anchor != null); _ = anchor ?? throw new ArgumentNullException(nameof(anchor));
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor); var nameScope = (anchor as INameScope) ?? NameScope.GetNameScope((StyledElement)anchor);
if (nameScope == null) if (nameScope == null)
throw new InvalidOperationException( throw new InvalidOperationException(
@ -105,9 +105,9 @@ namespace Avalonia.Controls
return nameScope.Get<T>(name); return nameScope.Get<T>(name);
} }
public static INameScope FindNameScope(this ILogical control) public static INameScope? FindNameScope(this ILogical control)
{ {
Contract.Requires<ArgumentNullException>(control != null); _ = control ?? throw new ArgumentNullException(nameof(control));
var scope = control.GetSelfAndLogicalAncestors() var scope = control.GetSelfAndLogicalAncestors()
.OfType<StyledElement>() .OfType<StyledElement>()

8
src/Avalonia.Styling/Controls/NameScopeLocator.cs

@ -11,9 +11,9 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="scope">The scope relative from which the object should be resolved.</param> /// <param name="scope">The scope relative from which the object should be resolved.</param>
/// <param name="name">The name of the object to find.</param> /// <param name="name">The name of the object to find.</param>
public static IObservable<object> Track(INameScope scope, string name) public static IObservable<object?> Track(INameScope scope, string name)
{ {
return new NeverEndingSynchronousCompletionAsyncResultObservable<object>(scope.FindAsync(name)); return new NeverEndingSynchronousCompletionAsyncResultObservable<object?>(scope.FindAsync(name));
} }
// This class is implemented in such weird way because for some reason // This class is implemented in such weird way because for some reason
@ -22,7 +22,7 @@ namespace Avalonia.Controls
private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T> private class NeverEndingSynchronousCompletionAsyncResultObservable<T> : IObservable<T>
{ {
private T _value; private T? _value;
private SynchronousCompletionAsyncResult<T>? _asyncResult; private SynchronousCompletionAsyncResult<T>? _asyncResult;
public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task) public NeverEndingSynchronousCompletionAsyncResultObservable(SynchronousCompletionAsyncResult<T> task)
@ -47,7 +47,7 @@ namespace Avalonia.Controls
observer.OnNext(_asyncResult.Value.GetResult()); observer.OnNext(_asyncResult.Value.GetResult());
}); });
else else
observer.OnNext(_value); observer.OnNext(_value!);
return Disposable.Empty; return Disposable.Empty;
} }

2
src/Avalonia.Styling/Controls/PseudoClassesExtensions.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls
/// <param name="value">True to add the pseudoclass or false to remove.</param> /// <param name="value">True to add the pseudoclass or false to remove.</param>
public static void Set(this IPseudoClasses classes, string name, bool value) public static void Set(this IPseudoClasses classes, string name, bool value)
{ {
Contract.Requires<ArgumentNullException>(classes != null); _ = classes ?? throw new ArgumentNullException(nameof(classes));
if (value) if (value)
{ {

2
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); Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
} }

6
src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@ -109,7 +109,7 @@ namespace Avalonia.Controls
observer.OnNext(Convert(_target.FindResource(_key))); 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))); 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) if (_owner is object)
{ {
@ -176,7 +176,7 @@ namespace Avalonia.Controls
PublishNext(); PublishNext();
} }
private void ResourcesChanged(object sender, ResourcesChangedEventArgs e) private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e)
{ {
PublishNext(); PublishNext();
} }

2
src/Avalonia.Styling/INamed.cs

@ -8,6 +8,6 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the element name. /// Gets the element name.
/// </summary> /// </summary>
string Name { get; } string? Name { get; }
} }
} }

16
src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@ -9,19 +9,19 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
public static class ControlLocator public static class ControlLocator
{ {
public static IObservable<ILogical> Track(ILogical relativeTo, int ancestorLevel, Type ancestorType = null) public static IObservable<ILogical?> Track(ILogical relativeTo, int ancestorLevel, Type? ancestorType = null)
{ {
return new ControlTracker(relativeTo, ancestorLevel, ancestorType); return new ControlTracker(relativeTo, ancestorLevel, ancestorType);
} }
private class ControlTracker : LightweightObservableBase<ILogical> private class ControlTracker : LightweightObservableBase<ILogical?>
{ {
private readonly ILogical _relativeTo; private readonly ILogical _relativeTo;
private readonly int _ancestorLevel; private readonly int _ancestorLevel;
private readonly Type _ancestorType; private readonly Type? _ancestorType;
private ILogical _value; private ILogical? _value;
public ControlTracker(ILogical relativeTo, int ancestorLevel, Type ancestorType) public ControlTracker(ILogical relativeTo, int ancestorLevel, Type? ancestorType)
{ {
_relativeTo = relativeTo; _relativeTo = relativeTo;
_ancestorLevel = ancestorLevel; _ancestorLevel = ancestorLevel;
@ -43,18 +43,18 @@ namespace Avalonia.LogicalTree
_value = null; _value = null;
} }
protected override void Subscribed(IObserver<ILogical> observer, bool first) protected override void Subscribed(IObserver<ILogical?> observer, bool first)
{ {
observer.OnNext(_value); observer.OnNext(_value);
} }
private void Attached(object sender, LogicalTreeAttachmentEventArgs e) private void Attached(object? sender, LogicalTreeAttachmentEventArgs e)
{ {
Update(); Update();
PublishNext(_value); PublishNext(_value);
} }
private void Detached(object sender, LogicalTreeAttachmentEventArgs e) private void Detached(object? sender, LogicalTreeAttachmentEventArgs e)
{ {
_value = null; _value = null;
PublishNext(null); PublishNext(null);

6
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -12,12 +12,12 @@ namespace Avalonia.LogicalTree
/// <summary> /// <summary>
/// Raised when the control is attached to a rooted logical tree. /// Raised when the control is attached to a rooted logical tree.
/// </summary> /// </summary>
event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree; event EventHandler<LogicalTreeAttachmentEventArgs>? AttachedToLogicalTree;
/// <summary> /// <summary>
/// Raised when the control is detached from a rooted logical tree. /// Raised when the control is detached from a rooted logical tree.
/// </summary> /// </summary>
event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree; event EventHandler<LogicalTreeAttachmentEventArgs>? DetachedFromLogicalTree;
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
@ -27,7 +27,7 @@ namespace Avalonia.LogicalTree
/// <summary> /// <summary>
/// Gets the logical parent. /// Gets the logical parent.
/// </summary> /// </summary>
ILogical LogicalParent { get; } ILogical? LogicalParent { get; }
/// <summary> /// <summary>
/// Gets the logical children. /// Gets the logical children.

26
src/Avalonia.Styling/LogicalTree/LogicalExtensions.cs

@ -15,14 +15,14 @@ namespace Avalonia.LogicalTree
/// <returns>The logical's ancestors.</returns> /// <returns>The logical's ancestors.</returns>
public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical) public static IEnumerable<ILogical> GetLogicalAncestors(this ILogical logical)
{ {
Contract.Requires<ArgumentNullException>(logical != null); _ = logical ?? throw new ArgumentNullException(nameof(logical));
logical = logical.LogicalParent; ILogical? l = logical.LogicalParent;
while (logical != null) while (l != null)
{ {
yield return logical; yield return l;
logical = logical.LogicalParent; l = l.LogicalParent;
} }
} }
@ -48,14 +48,14 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First ancestor of given type.</returns> /// <returns>First ancestor of given type.</returns>
public static T FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalAncestorOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
return null; return null;
} }
ILogical parent = includeSelf ? logical : logical.LogicalParent; var parent = includeSelf ? logical : logical.LogicalParent;
while (parent != null) while (parent != null)
{ {
@ -120,7 +120,7 @@ namespace Avalonia.LogicalTree
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <param name="includeSelf">If given logical should be included in search.</param> /// <param name="includeSelf">If given logical should be included in search.</param>
/// <returns>First descendant of given type.</returns> /// <returns>First descendant of given type.</returns>
public static T FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class public static T? FindLogicalDescendantOfType<T>(this ILogical logical, bool includeSelf = false) where T : class
{ {
if (logical is null) if (logical is null)
{ {
@ -140,7 +140,7 @@ namespace Avalonia.LogicalTree
/// </summary> /// </summary>
/// <param name="logical">The logical.</param> /// <param name="logical">The logical.</param>
/// <returns>The parent, or null if the logical is unparented.</returns> /// <returns>The parent, or null if the logical is unparented.</returns>
public static ILogical GetLogicalParent(this ILogical logical) public static ILogical? GetLogicalParent(this ILogical logical)
{ {
return logical.LogicalParent; return logical.LogicalParent;
} }
@ -153,7 +153,7 @@ namespace Avalonia.LogicalTree
/// <returns> /// <returns>
/// The parent, or null if the logical is unparented or its parent is not of type <typeparamref name="T"/>. /// The parent, or null if the logical is unparented or its parent is not of type <typeparamref name="T"/>.
/// </returns> /// </returns>
public static T GetLogicalParent<T>(this ILogical logical) where T : class public static T? GetLogicalParent<T>(this ILogical logical) where T : class
{ {
return logical.LogicalParent as T; return logical.LogicalParent as T;
} }
@ -165,7 +165,7 @@ namespace Avalonia.LogicalTree
/// <returns>The logical siblings.</returns> /// <returns>The logical siblings.</returns>
public static IEnumerable<ILogical> GetLogicalSiblings(this ILogical logical) public static IEnumerable<ILogical> GetLogicalSiblings(this ILogical logical)
{ {
ILogical parent = logical.LogicalParent; var parent = logical.LogicalParent;
if (parent != null) if (parent != null)
{ {
@ -187,7 +187,7 @@ namespace Avalonia.LogicalTree
/// </returns> /// </returns>
public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target) public static bool IsLogicalAncestorOf(this ILogical logical, ILogical target)
{ {
ILogical current = target?.LogicalParent; var current = target?.LogicalParent;
while (current != null) while (current != null)
{ {
@ -202,7 +202,7 @@ namespace Avalonia.LogicalTree
return false; return false;
} }
private static T FindDescendantOfTypeCore<T>(ILogical logical) where T : class private static T? FindDescendantOfTypeCore<T>(ILogical logical) where T : class
{ {
var logicalChildren = logical.LogicalChildren; var logicalChildren = logical.LogicalChildren;
var logicalChildrenCount = logicalChildren.Count; var logicalChildrenCount = logicalChildren.Count;

7
src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs

@ -19,11 +19,8 @@ namespace Avalonia.LogicalTree
ILogical source, ILogical source,
ILogical parent) ILogical parent)
{ {
Contract.Requires<ArgumentNullException>(root != null); Root = root ?? throw new ArgumentNullException(nameof(root));
Contract.Requires<ArgumentNullException>(source != null); Source = source ?? throw new ArgumentNullException(nameof(source));
Root = root;
Source = source;
Parent = parent; Parent = parent;
} }

18
src/Avalonia.Styling/StyledElement.cs

@ -427,7 +427,7 @@ namespace Avalonia
if (_logicalRoot != null) if (_logicalRoot != null)
{ {
var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old); var e = new LogicalTreeAttachmentEventArgs(_logicalRoot, this, old!);
OnDetachedFromLogicalTreeCore(e); OnDetachedFromLogicalTreeCore(e);
} }
@ -435,7 +435,7 @@ namespace Avalonia
if (newRoot is object) if (newRoot is object)
{ {
var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent); var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent!);
OnAttachedToLogicalTreeCore(e); OnAttachedToLogicalTreeCore(e);
} }
else if (parent is null) else if (parent is null)
@ -495,21 +495,21 @@ namespace Avalonia
DetachStylesFromThisAndDescendents(allStyles); DetachStylesFromThisAndDescendents(allStyles);
} }
protected virtual void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) protected virtual void LogicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
SetLogicalParent(e.NewItems); SetLogicalParent(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
ClearLogicalParent(e.OldItems); ClearLogicalParent(e.OldItems!);
break; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
ClearLogicalParent(e.OldItems); ClearLogicalParent(e.OldItems!);
SetLogicalParent(e.NewItems); SetLogicalParent(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
@ -729,7 +729,7 @@ namespace Avalonia
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var logical = (ILogical) children[i]; var logical = (ILogical) children[i]!;
if (logical.LogicalParent is null) if (logical.LogicalParent is null)
{ {
@ -744,7 +744,7 @@ namespace Avalonia
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
var logical = (ILogical) children[i]; var logical = (ILogical) children[i]!;
if (logical.LogicalParent == this) if (logical.LogicalParent == this)
{ {

2
src/Avalonia.Styling/Styling/Activators/NthChildActivator.cs

@ -37,7 +37,7 @@ namespace Avalonia.Styling.Activators
_provider.ChildIndexChanged -= ChildIndexChanged; _provider.ChildIndexChanged -= ChildIndexChanged;
} }
private void ChildIndexChanged(object sender, ChildIndexChangedEventArgs e) private void ChildIndexChanged(object? sender, ChildIndexChangedEventArgs e)
{ {
// Run matching again if: // Run matching again if:
// 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index. // 1. Selector is reversed, so other item insertion/deletion might affect total count without changing subscribed item index.

2
src/Avalonia.Styling/Styling/Activators/StyleClassActivator.cs

@ -66,7 +66,7 @@ namespace Avalonia.Styling.Activators
_classes.CollectionChanged -= ClassesChangedHandler; _classes.CollectionChanged -= ClassesChangedHandler;
} }
private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e) private void ClassesChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
if (e.Action != NotifyCollectionChangedAction.Move) if (e.Action != NotifyCollectionChangedAction.Move)
{ {

6
src/Avalonia.Styling/Styling/ChildSelector.cs

@ -6,7 +6,7 @@ namespace Avalonia.Styling
internal class ChildSelector : Selector internal class ChildSelector : Selector
{ {
private readonly Selector _parent; private readonly Selector _parent;
private string _selectorString; private string? _selectorString;
public ChildSelector(Selector parent) public ChildSelector(Selector parent)
{ {
@ -25,7 +25,7 @@ namespace Avalonia.Styling
public override bool IsCombinator => true; public override bool IsCombinator => true;
/// <inheritdoc/> /// <inheritdoc/>
public override Type TargetType => null; public override Type? TargetType => null;
public override string ToString() public override string ToString()
{ {
@ -64,6 +64,6 @@ namespace Avalonia.Styling
} }
} }
protected override Selector MovePrevious() => null; protected override Selector? MovePrevious() => null;
} }
} }

2
src/Avalonia.Styling/Styling/OrSelector.cs

@ -120,7 +120,7 @@ namespace Avalonia.Styling
} }
else else
{ {
while (!result.IsAssignableFrom(selector.TargetType)) while (result is not null && !result.IsAssignableFrom(selector.TargetType))
{ {
result = result.BaseType; result = result.BaseType;
} }

2
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@ -109,7 +109,7 @@ namespace Avalonia.Styling
var converter = TypeDescriptor.GetConverter(propertyType); var converter = TypeDescriptor.GetConverter(propertyType);
if (converter?.CanConvertFrom(valueType) == true) 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; return false;

50
src/Avalonia.Styling/Styling/Selectors.cs

@ -25,10 +25,14 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="name">The name of the style class.</param> /// <param name="name">The name of the style class.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Class(this Selector previous, string name) public static Selector Class(this Selector? previous, string name)
{ {
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name may not be empty", nameof(name));
}
var tac = previous as TypeNameAndClassSelector; var tac = previous as TypeNameAndClassSelector;
@ -48,7 +52,7 @@ namespace Avalonia.Styling
/// </summary> /// </summary>
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Descendant(this Selector previous) public static Selector Descendant(this Selector? previous)
{ {
return new DescendantSelector(previous); return new DescendantSelector(previous);
} }
@ -59,9 +63,9 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Is(this Selector previous, Type type) public static Selector Is(this Selector? previous, Type type)
{ {
Contract.Requires<ArgumentNullException>(type != null); _ = type ?? throw new ArgumentNullException(nameof(type));
return TypeNameAndClassSelector.Is(previous, type); return TypeNameAndClassSelector.Is(previous, type);
} }
@ -72,7 +76,7 @@ namespace Avalonia.Styling
/// <typeparam name="T">The type.</typeparam> /// <typeparam name="T">The type.</typeparam>
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Is<T>(this Selector previous) where T : IStyleable public static Selector Is<T>(this Selector? previous) where T : IStyleable
{ {
return previous.Is(typeof(T)); return previous.Is(typeof(T));
} }
@ -83,10 +87,14 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="name">The name.</param> /// <param name="name">The name.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Name(this Selector previous, string name) public static Selector Name(this Selector? previous, string name)
{ {
Contract.Requires<ArgumentNullException>(name != null); _ = name ?? throw new ArgumentNullException(nameof(name));
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(name));
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Name may not be empty", nameof(name));
}
var tac = previous as TypeNameAndClassSelector; var tac = previous as TypeNameAndClassSelector;
@ -107,7 +115,7 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param> /// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Func<Selector, Selector> argument) public static Selector Not(this Selector? previous, Func<Selector?, Selector> argument)
{ {
return new NotSelector(previous, argument(null)); return new NotSelector(previous, argument(null));
} }
@ -118,7 +126,7 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param> /// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Selector argument) public static Selector Not(this Selector? previous, Selector argument)
{ {
return new NotSelector(previous, argument); return new NotSelector(previous, argument);
} }
@ -126,7 +134,7 @@ namespace Avalonia.Styling
/// <inheritdoc cref="NthChildSelector"/> /// <inheritdoc cref="NthChildSelector"/>
/// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/> /// <inheritdoc cref="NthChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
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); return new NthChildSelector(previous, step, offset);
} }
@ -134,7 +142,7 @@ namespace Avalonia.Styling
/// <inheritdoc cref="NthLastChildSelector"/> /// <inheritdoc cref="NthLastChildSelector"/>
/// <inheritdoc cref="NthLastChildSelector(Selector?, int, int)"/> /// <inheritdoc cref="NthLastChildSelector(Selector?, int, int)"/>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
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); return new NthLastChildSelector(previous, step, offset);
} }
@ -145,9 +153,9 @@ namespace Avalonia.Styling
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <param name="type">The type.</param> /// <param name="type">The type.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector OfType(this Selector previous, Type type) public static Selector OfType(this Selector? previous, Type type)
{ {
Contract.Requires<ArgumentNullException>(type != null); _ = type ?? throw new ArgumentNullException(nameof(type));
return TypeNameAndClassSelector.OfType(previous, type); return TypeNameAndClassSelector.OfType(previous, type);
} }
@ -158,7 +166,7 @@ namespace Avalonia.Styling
/// <typeparam name="T">The type.</typeparam> /// <typeparam name="T">The type.</typeparam>
/// <param name="previous">The previous selector.</param> /// <param name="previous">The previous selector.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector OfType<T>(this Selector previous) where T : IStyleable public static Selector OfType<T>(this Selector? previous) where T : IStyleable
{ {
return previous.OfType(typeof(T)); return previous.OfType(typeof(T));
} }
@ -191,9 +199,9 @@ namespace Avalonia.Styling
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The property value.</param> /// <param name="value">The property value.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector PropertyEquals<T>(this Selector previous, AvaloniaProperty<T> property, object value) public static Selector PropertyEquals<T>(this Selector? previous, AvaloniaProperty<T> property, object? value)
{ {
Contract.Requires<ArgumentNullException>(property != null); _ = property ?? throw new ArgumentNullException(nameof(property));
return new PropertyEqualsSelector(previous, property, value); return new PropertyEqualsSelector(previous, property, value);
} }
@ -205,9 +213,9 @@ namespace Avalonia.Styling
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <param name="value">The property value.</param> /// <param name="value">The property value.</param>
/// <returns>The selector.</returns> /// <returns>The selector.</returns>
public static Selector PropertyEquals(this Selector previous, AvaloniaProperty property, object value) public static Selector PropertyEquals(this Selector? previous, AvaloniaProperty property, object? value)
{ {
Contract.Requires<ArgumentNullException>(property != null); _ = property ?? throw new ArgumentNullException(nameof(property));
return new PropertyEqualsSelector(previous, property, value); return new PropertyEqualsSelector(previous, property, value);
} }

14
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<T> ToReadOnlyList<T>(IList list) static IReadOnlyList<T> ToReadOnlyList<T>(IList list)
{ {
@ -282,7 +282,7 @@ namespace Avalonia.Styling
{ {
for (var i = 0; i < items.Count; ++i) 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) if (Owner is object && style is IResourceProvider resourceProvider)
{ {
@ -299,7 +299,7 @@ namespace Avalonia.Styling
{ {
for (var i = 0; i < items.Count; ++i) 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) if (Owner is object && style is IResourceProvider resourceProvider)
{ {
@ -315,14 +315,14 @@ namespace Avalonia.Styling
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
Add(e.NewItems); Add(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
Remove(e.OldItems); Remove(e.OldItems!);
break; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
Remove(e.OldItems); Remove(e.OldItems!);
Add(e.NewItems); Add(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
throw new InvalidOperationException("Reset should not be called on Styles."); throw new InvalidOperationException("Reset should not be called on Styles.");

6
src/Avalonia.Styling/Styling/TemplateSelector.cs

@ -5,7 +5,7 @@ namespace Avalonia.Styling
internal class TemplateSelector : Selector internal class TemplateSelector : Selector
{ {
private readonly Selector _parent; private readonly Selector _parent;
private string _selectorString; private string? _selectorString;
public TemplateSelector(Selector parent) public TemplateSelector(Selector parent)
{ {
@ -24,7 +24,7 @@ namespace Avalonia.Styling
public override bool IsCombinator => true; public override bool IsCombinator => true;
/// <inheritdoc/> /// <inheritdoc/>
public override Type TargetType => null; public override Type? TargetType => null;
public override string ToString() public override string ToString()
{ {
@ -48,6 +48,6 @@ namespace Avalonia.Styling
return _parent.Match(templatedParent, subscribe); return _parent.Match(templatedParent, subscribe);
} }
protected override Selector MovePrevious() => null; protected override Selector? MovePrevious() => null;
} }
} }

2
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); base.LogicalChildrenCollectionChanged(sender, e);
VisualRoot?.Renderer?.RecalculateChildren(this); VisualRoot?.Renderer?.RecalculateChildren(this);

6
src/Markup/Avalonia.Markup/Markup/Parsers/SelectorParser.cs

@ -138,16 +138,16 @@ namespace Avalonia.Markup.Parsers
break; break;
case SelectorGrammar.ChildSyntax child: case SelectorGrammar.ChildSyntax child:
result = result.Child(); result = result!.Child();
break; break;
case SelectorGrammar.DescendantSyntax descendant: case SelectorGrammar.DescendantSyntax descendant:
result = result.Descendant(); result = result.Descendant();
break; break;
case SelectorGrammar.TemplateSyntax template: case SelectorGrammar.TemplateSyntax template:
result = result.Template(); result = result!.Template();
break; break;
case SelectorGrammar.NotSyntax not: case SelectorGrammar.NotSyntax not:
result = result.Not(x => Create(not.Argument)); result = result.Not(x => Create(not.Argument)!);
break; break;
case SelectorGrammar.NthChildSyntax nth: case SelectorGrammar.NthChildSyntax nth:
result = result.NthChild(nth.Step, nth.Offset); result = result.NthChild(nth.Step, nth.Offset);

272
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<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().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<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().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<IPlatformSettings>();
platformSettingsMock.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(0, 0, 0, 0, 20));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().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<IPlatformSettings>();
tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().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<IPlatformSettings>();
tmp.Setup(x => x.DoubleClickTime).Returns(new TimeSpan(200));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().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));
}
}
}
Loading…
Cancel
Save