Browse Source

Merge branch 'master' into fixes/TypefaceLeak

pull/4627/head
Benedikt Stebner 6 years ago
committed by GitHub
parent
commit
70d55e4908
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      azure-pipelines.yml
  2. 10
      src/Avalonia.Controls/ColumnDefinitions.cs
  3. 24
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  4. 51
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs
  5. 22
      src/Avalonia.Input/AccessKeyHandler.cs
  6. 2
      src/Avalonia.Input/Avalonia.Input.csproj
  7. 6
      src/Avalonia.Input/DataObject.cs
  8. 6
      src/Avalonia.Input/DragDropDevice.cs
  9. 47
      src/Avalonia.Input/FocusManager.cs
  10. 16
      src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs
  11. 15
      src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs
  12. 9
      src/Avalonia.Input/Gestures.cs
  13. 2
      src/Avalonia.Input/IAccessKeyHandler.cs
  14. 6
      src/Avalonia.Input/IDataObject.cs
  15. 6
      src/Avalonia.Input/IFocusManager.cs
  16. 2
      src/Avalonia.Input/IInputElement.cs
  17. 4
      src/Avalonia.Input/IInputRoot.cs
  18. 4
      src/Avalonia.Input/IKeyboardDevice.cs
  19. 4
      src/Avalonia.Input/IPointer.cs
  20. 4
      src/Avalonia.Input/IPointerDevice.cs
  21. 8
      src/Avalonia.Input/InputElement.cs
  22. 2
      src/Avalonia.Input/KeyEventArgs.cs
  23. 8
      src/Avalonia.Input/KeyboardDevice.cs
  24. 13
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  25. 73
      src/Avalonia.Input/MouseDevice.cs
  26. 42
      src/Avalonia.Input/Navigation/TabNavigation.cs
  27. 9
      src/Avalonia.Input/Pointer.cs
  28. 14
      src/Avalonia.Input/PointerEventArgs.cs
  29. 2
      src/Avalonia.Input/Raw/RawDragEventType.cs
  30. 2
      src/Avalonia.Input/Raw/RawInputEventArgs.cs
  31. 4
      src/Avalonia.Input/TextInputEventArgs.cs
  32. 4
      src/Avalonia.Layout/Layoutable.cs
  33. 5
      src/Avalonia.OpenGL/AngleOptions.cs
  34. 3
      src/Avalonia.OpenGL/EglConsts.cs
  35. 6
      src/Avalonia.OpenGL/EglDisplay.cs
  36. 6
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  37. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  38. 31
      tests/Avalonia.LeakTests/ControlTests.cs

7
azure-pipelines.yml

@ -56,13 +56,6 @@ jobs:
xcodeVersion: '10' # Options: 8, 9, default, specifyPath xcodeVersion: '10' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2
displayName: 'Install CastXML'
inputs:
script: |
brew update
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Install Nuke'
inputs: inputs:

10
src/Avalonia.Controls/ColumnDefinitions.cs

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text;
using Avalonia.Collections; using Avalonia.Collections;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -13,7 +14,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ColumnDefinitions"/> class. /// Initializes a new instance of the <see cref="ColumnDefinitions"/> class.
/// </summary> /// </summary>
public ColumnDefinitions() : base () public ColumnDefinitions()
{ {
} }
@ -27,6 +28,11 @@ namespace Avalonia.Controls
AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x))); AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
} }
public override string ToString()
{
return string.Join(",", this.Select(x => x.Width));
}
/// <summary> /// <summary>
/// Parses a string representation of column definitions collection. /// Parses a string representation of column definitions collection.
/// </summary> /// </summary>
@ -34,4 +40,4 @@ namespace Avalonia.Controls
/// <returns>The <see cref="ColumnDefinitions"/>.</returns> /// <returns>The <see cref="ColumnDefinitions"/>.</returns>
public static ColumnDefinitions Parse(string s) => new ColumnDefinitions(s); public static ColumnDefinitions Parse(string s) => new ColumnDefinitions(s);
} }
} }

24
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -7,10 +7,10 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -681,8 +681,15 @@ namespace Avalonia.Controls
if (oldValue != null) if (oldValue != null)
{ {
oldValue.UninitializeForContext(LayoutContext); oldValue.UninitializeForContext(LayoutContext);
oldValue.MeasureInvalidated -= InvalidateMeasureForLayout;
oldValue.ArrangeInvalidated -= InvalidateArrangeForLayout; WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
oldValue,
nameof(AttachedLayout.MeasureInvalidated),
InvalidateMeasureForLayout);
WeakEventHandlerManager.Unsubscribe<EventArgs, ItemsRepeater>(
oldValue,
nameof(AttachedLayout.ArrangeInvalidated),
InvalidateArrangeForLayout);
// Walk through all the elements and make sure they are cleared // Walk through all the elements and make sure they are cleared
foreach (var element in Children) foreach (var element in Children)
@ -699,8 +706,15 @@ namespace Avalonia.Controls
if (newValue != null) if (newValue != null)
{ {
newValue.InitializeForContext(LayoutContext); newValue.InitializeForContext(LayoutContext);
newValue.MeasureInvalidated += InvalidateMeasureForLayout;
newValue.ArrangeInvalidated += InvalidateArrangeForLayout; WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
newValue,
nameof(AttachedLayout.MeasureInvalidated),
InvalidateMeasureForLayout);
WeakEventHandlerManager.Subscribe<AttachedLayout, EventArgs, ItemsRepeater>(
newValue,
nameof(AttachedLayout.ArrangeInvalidated),
InvalidateArrangeForLayout);
} }
bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout; bool isVirtualizingLayout = newValue != null && newValue is VirtualizingLayout;

51
src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs

@ -8,8 +8,8 @@ namespace Avalonia.Diagnostics.ViewModels
internal abstract class PropertyViewModel : ViewModelBase internal abstract class PropertyViewModel : ViewModelBase
{ {
private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static;
private static readonly Type[] StringParameter = new[] { typeof(string) }; private static readonly Type[] StringParameter = { typeof(string) };
private static readonly Type[] StringIFormatProviderParameters = new[] { typeof(string), typeof(IFormatProvider) }; private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) };
public abstract object Key { get; } public abstract object Key { get; }
public abstract string Name { get; } public abstract string Name { get; }
@ -26,35 +26,46 @@ namespace Avalonia.Diagnostics.ViewModels
} }
var converter = TypeDescriptor.GetConverter(value); var converter = TypeDescriptor.GetConverter(value);
return converter?.ConvertToString(value) ?? value.ToString();
//CollectionConverter does not deliver any important information. It just displays "(Collection)".
if (!converter.CanConvertTo(typeof(string)) ||
converter.GetType() == typeof(CollectionConverter))
{
return value.ToString();
}
return converter.ConvertToString(value);
} }
protected static object ConvertFromString(string s, Type targetType) private static object InvokeParse(string s, Type targetType)
{ {
var converter = TypeDescriptor.GetConverter(targetType); var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null);
if (converter != null && converter.CanConvertFrom(typeof(string))) if (method != null)
{ {
return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s); return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture });
} }
else
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null);
if (method != null)
{ {
var method = targetType.GetMethod("Parse", PublicStatic, null, StringIFormatProviderParameters, null); return method.Invoke(null, new object[] { s });
}
if (method != null) throw new InvalidCastException("Unable to convert value.");
{ }
return method.Invoke(null, new object[] { s, CultureInfo.InvariantCulture });
}
method = targetType.GetMethod("Parse", PublicStatic, null, StringParameter, null); protected static object ConvertFromString(string s, Type targetType)
{
var converter = TypeDescriptor.GetConverter(targetType);
if (method != null) if (converter.CanConvertFrom(typeof(string)))
{ {
return method.Invoke(null, new object[] { s }); return converter.ConvertFrom(null, CultureInfo.InvariantCulture, s);
}
} }
throw new InvalidCastException("Unable to convert value."); return InvokeParse(s, targetType);
} }
} }
} }

22
src/Avalonia.Input/AccessKeyHandler.cs

@ -28,7 +28,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot _owner; private IInputRoot? _owner;
/// <summary> /// <summary>
/// Whether access keys are currently being shown; /// Whether access keys are currently being shown;
@ -48,17 +48,17 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Element to restore following AltKey taking focus. /// Element to restore following AltKey taking focus.
/// </summary> /// </summary>
private IInputElement _restoreFocusElement; private IInputElement? _restoreFocusElement;
/// <summary> /// <summary>
/// The window's main menu. /// The window's main menu.
/// </summary> /// </summary>
private IMainMenu _mainMenu; private IMainMenu? _mainMenu;
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
public IMainMenu MainMenu public IMainMenu? MainMenu
{ {
get => _mainMenu; get => _mainMenu;
set set
@ -86,14 +86,12 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetOwner(IInputRoot owner) public void SetOwner(IInputRoot owner)
{ {
Contract.Requires<ArgumentNullException>(owner != null);
if (_owner != null) if (_owner != null)
{ {
throw new InvalidOperationException("AccessKeyHandler owner has already been set."); throw new InvalidOperationException("AccessKeyHandler owner has already been set.");
} }
_owner = owner; _owner = owner ?? throw new ArgumentNullException(nameof(owner));
_owner.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel); _owner.AddHandler(InputElement.KeyDownEvent, OnPreviewKeyDown, RoutingStrategies.Tunnel);
_owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Bubble); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown, RoutingStrategies.Bubble);
@ -149,7 +147,7 @@ namespace Avalonia.Input
// When Alt is pressed without a main menu, or with a closed main menu, show // When Alt is pressed without a main menu, or with a closed main menu, show
// access key markers in the window (i.e. "_File"). // access key markers in the window (i.e. "_File").
_owner.ShowAccessKeys = _showingAccessKeys = true; _owner!.ShowAccessKeys = _showingAccessKeys = true;
} }
else else
{ {
@ -241,7 +239,7 @@ namespace Avalonia.Input
{ {
if (_showingAccessKeys) if (_showingAccessKeys)
{ {
_owner.ShowAccessKeys = false; _owner!.ShowAccessKeys = false;
} }
} }
@ -250,13 +248,13 @@ namespace Avalonia.Input
/// </summary> /// </summary>
private void CloseMenu() private void CloseMenu()
{ {
MainMenu.Close(); MainMenu!.Close();
_owner.ShowAccessKeys = _showingAccessKeys = false; _owner!.ShowAccessKeys = _showingAccessKeys = false;
} }
private void MainMenuClosed(object sender, EventArgs e) private void MainMenuClosed(object sender, EventArgs e)
{ {
_owner.ShowAccessKeys = false; _owner!.ShowAccessKeys = false;
} }
} }
} }

2
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,6 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Nullable>Enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" /> <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

6
src/Avalonia.Input/DataObject.cs

@ -11,7 +11,7 @@ namespace Avalonia.Input
return _items.ContainsKey(dataFormat); return _items.ContainsKey(dataFormat);
} }
public object Get(string dataFormat) public object? Get(string dataFormat)
{ {
if (_items.ContainsKey(dataFormat)) if (_items.ContainsKey(dataFormat))
return _items[dataFormat]; return _items[dataFormat];
@ -23,12 +23,12 @@ namespace Avalonia.Input
return _items.Keys; return _items.Keys;
} }
public IEnumerable<string> GetFileNames() public IEnumerable<string>? GetFileNames()
{ {
return Get(DataFormats.FileNames) as IEnumerable<string>; return Get(DataFormats.FileNames) as IEnumerable<string>;
} }
public string GetText() public string? GetText()
{ {
return Get(DataFormats.Text) as string; return Get(DataFormats.Text) as string;
} }

6
src/Avalonia.Input/DragDropDevice.cs

@ -9,9 +9,9 @@ namespace Avalonia.Input
{ {
public static readonly DragDropDevice Instance = new DragDropDevice(); public static readonly DragDropDevice Instance = new DragDropDevice();
private Interactive _lastTarget = null; private Interactive? _lastTarget = null;
private Interactive GetTarget(IInputRoot root, Point local) private Interactive? GetTarget(IInputRoot root, Point local)
{ {
var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault(); var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target)) if (target != null && DragDrop.GetAllowDrop(target))
@ -19,7 +19,7 @@ namespace Avalonia.Input
return null; return null;
} }
private DragDropEffects RaiseDragEvent(Interactive target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers) private DragDropEffects RaiseDragEvent(Interactive? target, IInputRoot inputRoot, Point point, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data, KeyModifiers modifiers)
{ {
if (target == null) if (target == null)
return DragDropEffects.None; return DragDropEffects.None;

47
src/Avalonia.Input/FocusManager.cs

@ -15,8 +15,8 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The focus scopes in which the focus is currently defined. /// The focus scopes in which the focus is currently defined.
/// </summary> /// </summary>
private readonly ConditionalWeakTable<IFocusScope, IInputElement> _focusScopes = private readonly ConditionalWeakTable<IFocusScope, IInputElement?> _focusScopes =
new ConditionalWeakTable<IFocusScope, IInputElement>(); new ConditionalWeakTable<IFocusScope, IInputElement?>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="FocusManager"/> class. /// Initializes a new instance of the <see cref="FocusManager"/> class.
@ -37,12 +37,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
public IInputElement Current => KeyboardDevice.Instance?.FocusedElement; public IInputElement? Current => KeyboardDevice.Instance?.FocusedElement;
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
/// </summary> /// </summary>
public IFocusScope Scope public IFocusScope? Scope
{ {
get; get;
private set; private set;
@ -55,7 +55,7 @@ namespace Avalonia.Input
/// <param name="method">The method by which focus was changed.</param> /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
public void Focus( public void Focus(
IInputElement control, IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
@ -75,17 +75,18 @@ namespace Avalonia.Input
// If control is null, set focus to the topmost focus scope. // If control is null, set focus to the topmost focus scope.
foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList()) foreach (var scope in GetFocusScopeAncestors(Current).Reverse().ToList())
{ {
IInputElement element; if (_focusScopes.TryGetValue(scope, out var element) && element != null)
if (_focusScopes.TryGetValue(scope, out element) && element != null)
{ {
Focus(element, method); Focus(element, method);
return; return;
} }
} }
// Couldn't find a focus scope, clear focus. if (Scope is object)
SetFocusedElement(Scope, null); {
// Couldn't find a focus scope, clear focus.
SetFocusedElement(Scope, null);
}
} }
} }
@ -102,13 +103,13 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetFocusedElement( public void SetFocusedElement(
IFocusScope scope, IFocusScope scope,
IInputElement element, IInputElement? element,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
Contract.Requires<ArgumentNullException>(scope != null); scope = scope ?? throw new ArgumentNullException(nameof(scope));
if (_focusScopes.TryGetValue(scope, out IInputElement existingElement)) if (_focusScopes.TryGetValue(scope, out var existingElement))
{ {
if (element != existingElement) if (element != existingElement)
{ {
@ -133,11 +134,9 @@ namespace Avalonia.Input
/// <param name="scope">The new focus scope.</param> /// <param name="scope">The new focus scope.</param>
public void SetFocusScope(IFocusScope scope) public void SetFocusScope(IFocusScope scope)
{ {
Contract.Requires<ArgumentNullException>(scope != null); scope = scope ?? throw new ArgumentNullException(nameof(scope));
IInputElement e; if (!_focusScopes.TryGetValue(scope, out var e))
if (!_focusScopes.TryGetValue(scope, out e))
{ {
// TODO: Make this do something useful, i.e. select the first focusable // TODO: Make this do something useful, i.e. select the first focusable
// control, select a control that the user has specified to have default // control, select a control that the user has specified to have default
@ -164,17 +163,19 @@ namespace Avalonia.Input
/// <returns>The focus scopes.</returns> /// <returns>The focus scopes.</returns>
private static IEnumerable<IFocusScope> GetFocusScopeAncestors(IInputElement control) private static IEnumerable<IFocusScope> GetFocusScopeAncestors(IInputElement control)
{ {
while (control != null) IInputElement? c = control;
while (c != null)
{ {
var scope = control as IFocusScope; var scope = c as IFocusScope;
if (scope != null && control.VisualRoot?.IsVisible == true) if (scope != null && c.VisualRoot?.IsVisible == true)
{ {
yield return scope; yield return scope;
} }
control = control.GetVisualParent<IInputElement>() ?? c = c.GetVisualParent<IInputElement>() ??
((control as IHostedVisualTreeRoot)?.Host as IInputElement); ((c as IHostedVisualTreeRoot)?.Host as IInputElement);
} }
} }
@ -190,7 +191,7 @@ namespace Avalonia.Input
if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) if (sender == e.Source && ev.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {
IVisual element = ev.Pointer?.Captured ?? e.Source as IInputElement; IVisual? element = ev.Pointer?.Captured ?? e.Source as IInputElement;
while (element != null) while (element != null)
{ {

16
src/Avalonia.Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -1,8 +1,6 @@
using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Styling; using Avalonia.Styling;
@ -11,8 +9,8 @@ namespace Avalonia.Input.GestureRecognizers
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher
{ {
private readonly IInputElement _inputElement; private readonly IInputElement _inputElement;
private List<IGestureRecognizer> _recognizers; private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer> _pointerGrabs; private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
public GestureRecognizerCollection(IInputElement inputElement) public GestureRecognizerCollection(IInputElement inputElement)
@ -72,7 +70,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{ {
capture.PointerReleased(e); capture.PointerReleased(e);
} }
@ -90,7 +88,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs.TryGetValue(e.Pointer, out var capture)) if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{ {
capture.PointerMoved(e); capture.PointerMoved(e);
} }
@ -108,7 +106,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return; return;
_pointerGrabs.Remove(e.Pointer); _pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
r.PointerCaptureLost(e.Pointer); r.PointerCaptureLost(e.Pointer);
@ -118,8 +116,8 @@ namespace Avalonia.Input.GestureRecognizers
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
{ {
pointer.Capture(_inputElement); pointer.Capture(_inputElement);
_pointerGrabs[pointer] = recognizer; _pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers) foreach (var r in _recognizers!)
{ {
if (r != recognizer) if (r != recognizer)
r.PointerCaptureLost(pointer); r.PointerCaptureLost(pointer);

15
src/Avalonia.Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -1,6 +1,5 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Interactivity;
using Avalonia.Threading; using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers namespace Avalonia.Input.GestureRecognizers
@ -11,9 +10,9 @@ namespace Avalonia.Input.GestureRecognizers
{ {
private bool _scrolling; private bool _scrolling;
private Point _trackedRootPoint; private Point _trackedRootPoint;
private IPointer _tracking; private IPointer? _tracking;
private IInputElement _target; private IInputElement? _target;
private IGestureRecognizerActionsDispatcher _actions; private IGestureRecognizerActionsDispatcher? _actions;
private bool _canHorizontallyScroll; private bool _canHorizontallyScroll;
private bool _canVerticallyScroll; private bool _canVerticallyScroll;
private int _gestureId; private int _gestureId;
@ -95,7 +94,7 @@ namespace Avalonia.Input.GestureRecognizers
_scrolling = true; _scrolling = true;
if (_scrolling) if (_scrolling)
{ {
_actions.Capture(e.Pointer, this); _actions!.Capture(e.Pointer, this);
} }
} }
@ -110,7 +109,7 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint = rootPoint; _trackedRootPoint = rootPoint;
if (elapsed.TotalSeconds > 0) if (elapsed.TotalSeconds > 0)
_inertia = vector / elapsed.TotalSeconds; _inertia = vector / elapsed.TotalSeconds;
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true; e.Handled = true;
} }
} }
@ -128,7 +127,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
_inertia = default; _inertia = default;
_scrolling = false; _scrolling = false;
_target.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0; _gestureId = 0;
_lastMoveTimestamp = null; _lastMoveTimestamp = null;
} }
@ -165,7 +164,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds); var speed = _inertia * Math.Pow(0.15, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds; var distance = speed * elapsedSinceLastTick.TotalSeconds;
_target.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance)); _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, distance));

9
src/Avalonia.Input/Gestures.cs

@ -29,7 +29,9 @@ namespace Avalonia.Input
RoutedEvent.Register<ScrollGestureEventArgs>( RoutedEvent.Register<ScrollGestureEventArgs>(
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures)); "ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null); private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
static Gestures() static Gestures()
{ {
@ -69,6 +71,11 @@ namespace Avalonia.Input
private static void PointerPressed(RoutedEventArgs ev) private static void PointerPressed(RoutedEventArgs ev)
{ {
if (ev.Source is null)
{
return;
}
if (ev.Route == RoutingStrategies.Bubble) if (ev.Route == RoutingStrategies.Bubble)
{ {
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
@ -76,7 +83,7 @@ namespace Avalonia.Input
if (e.ClickCount <= 1) if (e.ClickCount <= 1)
{ {
s_lastPress = new WeakReference<IInteractive>(e.Source); s_lastPress = new WeakReference<IInteractive>(ev.Source);
} }
else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed) else if (s_lastPress != null && e.ClickCount == 2 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{ {

2
src/Avalonia.Input/IAccessKeyHandler.cs

@ -8,7 +8,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the window's main menu. /// Gets or sets the window's main menu.
/// </summary> /// </summary>
IMainMenu MainMenu { get; set; } IMainMenu? MainMenu { get; set; }
/// <summary> /// <summary>
/// Sets the owner of the access key handler. /// Sets the owner of the access key handler.

6
src/Avalonia.Input/IDataObject.cs

@ -23,17 +23,17 @@ namespace Avalonia.Input
/// Returns the dragged text if the DataObject contains any text. /// Returns the dragged text if the DataObject contains any text.
/// <seealso cref="DataFormats.Text"/> /// <seealso cref="DataFormats.Text"/>
/// </summary> /// </summary>
string GetText(); string? GetText();
/// <summary> /// <summary>
/// Returns a list of filenames if the DataObject contains filenames. /// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/> /// <seealso cref="DataFormats.FileNames"/>
/// </summary> /// </summary>
IEnumerable<string> GetFileNames(); IEnumerable<string>? GetFileNames();
/// <summary> /// <summary>
/// Tries to get the data of the given DataFormat. /// Tries to get the data of the given DataFormat.
/// </summary> /// </summary>
object Get(string dataFormat); object? Get(string dataFormat);
} }
} }

6
src/Avalonia.Input/IFocusManager.cs

@ -8,12 +8,12 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets the currently focused <see cref="IInputElement"/>. /// Gets the currently focused <see cref="IInputElement"/>.
/// </summary> /// </summary>
IInputElement Current { get; } IInputElement? Current { get; }
/// <summary> /// <summary>
/// Gets the current focus scope. /// Gets the current focus scope.
/// </summary> /// </summary>
IFocusScope Scope { get; } IFocusScope? Scope { get; }
/// <summary> /// <summary>
/// Focuses a control. /// Focuses a control.
@ -22,7 +22,7 @@ namespace Avalonia.Input
/// <param name="method">The method by which focus was changed.</param> /// <param name="method">The method by which focus was changed.</param>
/// <param name="keyModifiers">Any key modifiers active at the time of focus.</param> /// <param name="keyModifiers">Any key modifiers active at the time of focus.</param>
void Focus( void Focus(
IInputElement control, IInputElement? control,
NavigationMethod method = NavigationMethod.Unspecified, NavigationMethod method = NavigationMethod.Unspecified,
KeyModifiers keyModifiers = KeyModifiers.None); KeyModifiers keyModifiers = KeyModifiers.None);

2
src/Avalonia.Input/IInputElement.cs

@ -78,7 +78,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the associated mouse cursor. /// Gets or sets the associated mouse cursor.
/// </summary> /// </summary>
Cursor Cursor { get; } Cursor? Cursor { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether this control and all its parents are enabled. /// Gets a value indicating whether this control and all its parents are enabled.

4
src/Avalonia.Input/IInputRoot.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets the input element that the pointer is currently over. /// Gets or sets the input element that the pointer is currently over.
/// </summary> /// </summary>
IInputElement PointerOverElement { get; set; } IInputElement? PointerOverElement { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether access keys are shown in the window. /// Gets or sets a value indicating whether access keys are shown in the window.
@ -31,6 +31,6 @@ namespace Avalonia.Input
/// Gets associated mouse device /// Gets associated mouse device
/// </summary> /// </summary>
[CanBeNull] [CanBeNull]
IMouseDevice MouseDevice { get; } IMouseDevice? MouseDevice { get; }
} }
} }

4
src/Avalonia.Input/IKeyboardDevice.cs

@ -58,10 +58,10 @@ namespace Avalonia.Input
public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged public interface IKeyboardDevice : IInputDevice, INotifyPropertyChanged
{ {
IInputElement FocusedElement { get; } IInputElement? FocusedElement { get; }
void SetFocusedElement( void SetFocusedElement(
IInputElement element, IInputElement? element,
NavigationMethod method, NavigationMethod method,
KeyModifiers modifiers); KeyModifiers modifiers);
} }

4
src/Avalonia.Input/IPointer.cs

@ -3,8 +3,8 @@ namespace Avalonia.Input
public interface IPointer public interface IPointer
{ {
int Id { get; } int Id { get; }
void Capture(IInputElement control); void Capture(IInputElement? control);
IInputElement Captured { get; } IInputElement? Captured { get; }
PointerType Type { get; } PointerType Type { get; }
bool IsPrimary { get; } bool IsPrimary { get; }

4
src/Avalonia.Input/IPointerDevice.cs

@ -6,10 +6,10 @@ namespace Avalonia.Input
public interface IPointerDevice : IInputDevice public interface IPointerDevice : IInputDevice
{ {
[Obsolete("Use IPointer")] [Obsolete("Use IPointer")]
IInputElement Captured { get; } IInputElement? Captured { get; }
[Obsolete("Use IPointer")] [Obsolete("Use IPointer")]
void Capture(IInputElement control); void Capture(IInputElement? control);
[Obsolete("Use PointerEventArgs.GetPosition")] [Obsolete("Use PointerEventArgs.GetPosition")]
Point GetPosition(IVisual relativeTo); Point GetPosition(IVisual relativeTo);

8
src/Avalonia.Input/InputElement.cs

@ -37,8 +37,8 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
/// </summary> /// </summary>
public static readonly StyledProperty<Cursor> CursorProperty = public static readonly StyledProperty<Cursor?> CursorProperty =
AvaloniaProperty.Register<InputElement, Cursor>(nameof(Cursor), null, true); AvaloniaProperty.Register<InputElement, Cursor?>(nameof(Cursor), null, true);
/// <summary> /// <summary>
/// Defines the <see cref="IsFocused"/> property. /// Defines the <see cref="IsFocused"/> property.
@ -160,7 +160,7 @@ namespace Avalonia.Input
private bool _isFocused; private bool _isFocused;
private bool _isFocusVisible; private bool _isFocusVisible;
private bool _isPointerOver; private bool _isPointerOver;
private GestureRecognizerCollection _gestureRecognizers; private GestureRecognizerCollection? _gestureRecognizers;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="InputElement"/> class. /// Initializes static members of the <see cref="InputElement"/> class.
@ -336,7 +336,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Gets or sets associated mouse cursor. /// Gets or sets associated mouse cursor.
/// </summary> /// </summary>
public Cursor Cursor public Cursor? Cursor
{ {
get { return GetValue(CursorProperty); } get { return GetValue(CursorProperty); }
set { SetValue(CursorProperty, value); } set { SetValue(CursorProperty, value); }

2
src/Avalonia.Input/KeyEventArgs.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
{ {
public class KeyEventArgs : RoutedEventArgs public class KeyEventArgs : RoutedEventArgs
{ {
public IKeyboardDevice Device { get; set; } public IKeyboardDevice? Device { get; set; }
public Key Key { get; set; } public Key Key { get; set; }

8
src/Avalonia.Input/KeyboardDevice.cs

@ -8,9 +8,9 @@ namespace Avalonia.Input
{ {
public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged public class KeyboardDevice : IKeyboardDevice, INotifyPropertyChanged
{ {
private IInputElement _focusedElement; private IInputElement? _focusedElement;
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;
public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>(); public static IKeyboardDevice Instance => AvaloniaLocator.Current.GetService<IKeyboardDevice>();
@ -18,7 +18,7 @@ namespace Avalonia.Input
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>(); public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IInputElement FocusedElement public IInputElement? FocusedElement
{ {
get get
{ {
@ -33,7 +33,7 @@ namespace Avalonia.Input
} }
public void SetFocusedElement( public void SetFocusedElement(
IInputElement element, IInputElement? element,
NavigationMethod method, NavigationMethod method,
KeyModifiers keyModifiers) KeyModifiers keyModifiers)
{ {

13
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// The window to which the handler belongs. /// The window to which the handler belongs.
/// </summary> /// </summary>
private IInputRoot _owner; private IInputRoot? _owner;
/// <summary> /// <summary>
/// Sets the owner of the keyboard navigation handler. /// Sets the owner of the keyboard navigation handler.
@ -24,15 +24,12 @@ namespace Avalonia.Input
/// </remarks> /// </remarks>
public void SetOwner(IInputRoot owner) public void SetOwner(IInputRoot owner)
{ {
Contract.Requires<ArgumentNullException>(owner != null);
if (_owner != null) if (_owner != null)
{ {
throw new InvalidOperationException("AccessKeyHandler owner has already been set."); throw new InvalidOperationException("AccessKeyHandler owner has already been set.");
} }
_owner = owner; _owner = owner ?? throw new ArgumentNullException(nameof(owner));
_owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown); _owner.AddHandler(InputElement.KeyDownEvent, OnKeyDown);
} }
@ -45,11 +42,11 @@ namespace Avalonia.Input
/// The next element in the specified direction, or null if <paramref name="element"/> /// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction. /// was the last in the requested direction.
/// </returns> /// </returns>
public static IInputElement GetNext( public static IInputElement? GetNext(
IInputElement element, IInputElement element,
NavigationDirection direction) NavigationDirection direction)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
var customHandler = element.GetSelfAndVisualAncestors() var customHandler = element.GetSelfAndVisualAncestors()
.OfType<ICustomKeyboardNavigation>() .OfType<ICustomKeyboardNavigation>()
@ -97,7 +94,7 @@ namespace Avalonia.Input
NavigationDirection direction, NavigationDirection direction,
KeyModifiers keyModifiers = KeyModifiers.None) KeyModifiers keyModifiers = KeyModifiers.None)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
var next = GetNext(element, direction); var next = GetNext(element, direction);

73
src/Avalonia.Input/MouseDevice.cs

@ -20,7 +20,7 @@ namespace Avalonia.Input
private readonly Pointer _pointer; private readonly Pointer _pointer;
private bool _disposed; private bool _disposed;
public MouseDevice(Pointer pointer = null) public MouseDevice(Pointer? pointer = null)
{ {
_pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true); _pointer = pointer ?? new Pointer(Pointer.GetNextFreeId(), PointerType.Mouse, true);
} }
@ -34,7 +34,7 @@ namespace Avalonia.Input
/// <see cref="Capture"/> method. /// <see cref="Capture"/> method.
/// </remarks> /// </remarks>
[Obsolete("Use IPointer instead")] [Obsolete("Use IPointer instead")]
public IInputElement Captured => _pointer.Captured; public IInputElement? Captured => _pointer.Captured;
/// <summary> /// <summary>
/// Gets the mouse position, in screen coordinates. /// Gets the mouse position, in screen coordinates.
@ -54,7 +54,7 @@ namespace Avalonia.Input
/// within the control's bounds or not. The current mouse capture control is exposed /// within the control's bounds or not. The current mouse capture control is exposed
/// by the <see cref="Captured"/> property. /// by the <see cref="Captured"/> property.
/// </remarks> /// </remarks>
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
_pointer.Capture(control); _pointer.Capture(control);
} }
@ -66,7 +66,7 @@ namespace Avalonia.Input
/// <returns>The mouse position in the control's coordinates.</returns> /// <returns>The mouse position in the control's coordinates.</returns>
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual relativeTo)
{ {
Contract.Requires<ArgumentNullException>(relativeTo != null); relativeTo = relativeTo ?? throw new ArgumentNullException(nameof(relativeTo));
if (relativeTo.VisualRoot == null) if (relativeTo.VisualRoot == null)
{ {
@ -75,7 +75,7 @@ namespace Avalonia.Input
var rootPoint = relativeTo.VisualRoot.PointToClient(Position); var rootPoint = relativeTo.VisualRoot.PointToClient(Position);
var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo); var transform = relativeTo.VisualRoot.TransformToVisual(relativeTo);
return rootPoint * transform.Value; return rootPoint * transform!.Value;
} }
public void ProcessRawEvent(RawInputEventArgs e) public void ProcessRawEvent(RawInputEventArgs e)
@ -126,7 +126,7 @@ namespace Avalonia.Input
private void ProcessRawEvent(RawPointerEventArgs e) private void ProcessRawEvent(RawPointerEventArgs e)
{ {
Contract.Requires<ArgumentNullException>(e != null); e = e ?? throw new ArgumentNullException(nameof(e));
var mouse = (MouseDevice)e.Device; var mouse = (MouseDevice)e.Device;
if(mouse._disposed) if(mouse._disposed)
@ -173,8 +173,8 @@ namespace Avalonia.Input
private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties, private void LeaveWindow(IMouseDevice device, ulong timestamp, IInputRoot root, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
ClearPointerOver(this, timestamp, root, properties, inputModifiers); ClearPointerOver(this, timestamp, root, properties, inputModifiers);
} }
@ -214,8 +214,8 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -250,10 +250,10 @@ namespace Avalonia.Input
private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties, private bool MouseMove(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
IInputElement source; IInputElement? source;
if (_pointer.Captured == null) if (_pointer.Captured == null)
{ {
@ -265,18 +265,23 @@ namespace Avalonia.Input
source = _pointer.Captured; source = _pointer.Captured;
} }
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root, if (source is object)
p, timestamp, properties, inputModifiers); {
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, root,
p, timestamp, properties, inputModifiers);
source?.RaiseEvent(e); source.RaiseEvent(e);
return e.Handled; return e.Handled;
}
return false;
} }
private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props, private bool MouseUp(IMouseDevice device, ulong timestamp, IInputRoot root, Point p, PointerPointProperties props,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -298,8 +303,8 @@ namespace Avalonia.Input
PointerPointProperties props, PointerPointProperties props,
Vector delta, KeyModifiers inputModifiers) Vector delta, KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var hit = HitTest(root, p); var hit = HitTest(root, p);
@ -317,21 +322,21 @@ namespace Avalonia.Input
private IInteractive GetSource(IVisual hit) private IInteractive GetSource(IVisual hit)
{ {
Contract.Requires<ArgumentNullException>(hit != null); hit = hit ?? throw new ArgumentNullException(nameof(hit));
return _pointer.Captured ?? return _pointer.Captured ??
(hit as IInteractive) ?? (hit as IInteractive) ??
hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault(); hit.GetSelfAndVisualAncestors().OfType<IInteractive>().FirstOrDefault();
} }
private IInputElement HitTest(IInputElement root, Point p) private IInputElement? HitTest(IInputElement root, Point p)
{ {
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
return _pointer.Captured ?? root.InputHitTest(p); return _pointer.Captured ?? root.InputHitTest(p);
} }
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive source, PointerEventArgs CreateSimpleEvent(RoutedEvent ev, ulong timestamp, IInteractive? source,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
@ -343,8 +348,8 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.PointerOverElement; var element = root.PointerOverElement;
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers); var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, timestamp, element, properties, inputModifiers);
@ -384,12 +389,12 @@ namespace Avalonia.Input
} }
} }
private IInputElement SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p, private IInputElement? SetPointerOver(IPointerDevice device, ulong timestamp, IInputRoot root, Point p,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
var element = root.InputHitTest(p); var element = root.InputHitTest(p);
@ -412,11 +417,11 @@ namespace Avalonia.Input
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers inputModifiers) KeyModifiers inputModifiers)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Contract.Requires<ArgumentNullException>(root != null); root = root ?? throw new ArgumentNullException(nameof(root));
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
IInputElement branch = null; IInputElement? branch = null;
var el = element; var el = element;

42
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -22,15 +22,17 @@ namespace Avalonia.Input.Navigation
/// The next element in the specified direction, or null if <paramref name="element"/> /// The next element in the specified direction, or null if <paramref name="element"/>
/// was the last in the requested direction. /// was the last in the requested direction.
/// </returns> /// </returns>
public static IInputElement GetNextInTabOrder( public static IInputElement? GetNextInTabOrder(
IInputElement element, IInputElement element,
NavigationDirection direction, NavigationDirection direction,
bool outsideElement = false) bool outsideElement = false)
{ {
Contract.Requires<ArgumentNullException>(element != null); element = element ?? throw new ArgumentNullException(nameof(element));
Contract.Requires<ArgumentException>(
direction == NavigationDirection.Next || if (direction != NavigationDirection.Next && direction != NavigationDirection.Previous)
direction == NavigationDirection.Previous); {
throw new ArgumentException("Invalid direction: must be Next or Previous.");
}
var container = element.GetVisualParent<IInputElement>(); var container = element.GetVisualParent<IInputElement>();
@ -110,7 +112,7 @@ namespace Avalonia.Input.Navigation
if (customNext.handled) if (customNext.handled)
{ {
yield return customNext.next; yield return customNext.next!;
} }
else else
{ {
@ -143,12 +145,14 @@ namespace Avalonia.Input.Navigation
/// If true will not descend into <paramref name="element"/> to find next control. /// If true will not descend into <paramref name="element"/> to find next control.
/// </param> /// </param>
/// <returns>The next element, or null if the element is the last.</returns> /// <returns>The next element, or null if the element is the last.</returns>
private static IInputElement GetNextInContainer( private static IInputElement? GetNextInContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction, NavigationDirection direction,
bool outsideElement) bool outsideElement)
{ {
IInputElement? e = element;
if (direction == NavigationDirection.Next && !outsideElement) if (direction == NavigationDirection.Next && !outsideElement)
{ {
var descendant = GetFocusableDescendants(element, direction).FirstOrDefault(); var descendant = GetFocusableDescendants(element, direction).FirstOrDefault();
@ -167,13 +171,13 @@ namespace Avalonia.Input.Navigation
// INavigableContainer. // INavigableContainer.
if (navigable != null) if (navigable != null)
{ {
while (element != null) while (e != null)
{ {
element = navigable.GetControl(direction, element, false); e = navigable.GetControl(direction, e, false);
if (element != null && if (e != null &&
element.CanFocus() && e.CanFocus() &&
KeyboardNavigation.GetIsTabStop((InputElement) element)) KeyboardNavigation.GetIsTabStop((InputElement)e))
{ {
break; break;
} }
@ -183,12 +187,12 @@ namespace Avalonia.Input.Navigation
{ {
// TODO: Do a spatial search here if the container doesn't implement // TODO: Do a spatial search here if the container doesn't implement
// INavigableContainer. // INavigableContainer.
element = null; e = null;
} }
if (element != null && direction == NavigationDirection.Previous) if (e != null && direction == NavigationDirection.Previous)
{ {
var descendant = GetFocusableDescendants(element, direction).LastOrDefault(); var descendant = GetFocusableDescendants(e, direction).LastOrDefault();
if (descendant != null) if (descendant != null)
{ {
@ -196,7 +200,7 @@ namespace Avalonia.Input.Navigation
} }
} }
return element; return e;
} }
return null; return null;
@ -209,13 +213,13 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The container.</param> /// <param name="container">The container.</param>
/// <param name="direction">The direction of the search.</param> /// <param name="direction">The direction of the search.</param>
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement? GetFirstInNextContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
NavigationDirection direction) NavigationDirection direction)
{ {
var parent = container.GetVisualParent<IInputElement>(); var parent = container.GetVisualParent<IInputElement>();
IInputElement next = null; IInputElement? next = null;
if (parent != null) if (parent != null)
{ {
@ -268,7 +272,7 @@ namespace Avalonia.Input.Navigation
return next; return next;
} }
private static (bool handled, IInputElement next) GetCustomNext(IInputElement element, private static (bool handled, IInputElement? next) GetCustomNext(IInputElement element,
NavigationDirection direction) NavigationDirection direction)
{ {
if (element is ICustomKeyboardNavigation custom) if (element is ICustomKeyboardNavigation custom)

9
src/Avalonia.Input/Pointer.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Interactivity;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
@ -20,7 +19,7 @@ namespace Avalonia.Input
public int Id { get; } public int Id { get; }
IInputElement FindCommonParent(IInputElement control1, IInputElement control2) IInputElement? FindCommonParent(IInputElement? control1, IInputElement? control2)
{ {
if (control1 == null || control2 == null) if (control1 == null || control2 == null)
return null; return null;
@ -28,12 +27,12 @@ namespace Avalonia.Input
return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains); return control2.GetSelfAndVisualAncestors().OfType<IInputElement>().FirstOrDefault(seen.Contains);
} }
protected virtual void PlatformCapture(IInputElement element) protected virtual void PlatformCapture(IInputElement? element)
{ {
} }
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
if (Captured != null) if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached; Captured.DetachedFromVisualTree -= OnCaptureDetached;
@ -66,7 +65,7 @@ namespace Avalonia.Input
} }
public IInputElement Captured { get; private set; } public IInputElement? Captured { get; private set; }
public PointerType Type { get; } public PointerType Type { get; }
public bool IsPrimary { get; } public bool IsPrimary { get; }

14
src/Avalonia.Input/PointerEventArgs.cs

@ -7,14 +7,14 @@ namespace Avalonia.Input
{ {
public class PointerEventArgs : RoutedEventArgs public class PointerEventArgs : RoutedEventArgs
{ {
private readonly IVisual _rootVisual; private readonly IVisual? _rootVisual;
private readonly Point _rootVisualPosition; private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties; private readonly PointerPointProperties _properties;
public PointerEventArgs(RoutedEvent routedEvent, public PointerEventArgs(RoutedEvent routedEvent,
IInteractive source, IInteractive? source,
IPointer pointer, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, IVisual? rootVisual, Point rootVisualPosition,
ulong timestamp, ulong timestamp,
PointerPointProperties properties, PointerPointProperties properties,
KeyModifiers modifiers) KeyModifiers modifiers)
@ -40,8 +40,8 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException(); public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement Captured => _ev.Pointer.Captured; public IInputElement? Captured => _ev.Pointer.Captured;
public void Capture(IInputElement control) public void Capture(IInputElement? control)
{ {
_ev.Pointer.Capture(control); _ev.Pointer.Capture(control);
} }
@ -52,7 +52,7 @@ namespace Avalonia.Input
public IPointer Pointer { get; } public IPointer Pointer { get; }
public ulong Timestamp { get; } public ulong Timestamp { get; }
private IPointerDevice _device; private IPointerDevice? _device;
[Obsolete("Use Pointer to get pointer-specific information")] [Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this)); public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
@ -76,7 +76,7 @@ namespace Avalonia.Input
public KeyModifiers KeyModifiers { get; } public KeyModifiers KeyModifiers { get; }
public Point GetPosition(IVisual relativeTo) public Point GetPosition(IVisual? relativeTo)
{ {
if (_rootVisual == null) if (_rootVisual == null)
return default; return default;

2
src/Avalonia.Input/Raw/RawDragEventType.cs

@ -7,4 +7,4 @@
DragLeave, DragLeave,
Drop Drop
} }
} }

2
src/Avalonia.Input/Raw/RawInputEventArgs.cs

@ -21,7 +21,7 @@ namespace Avalonia.Input.Raw
/// <param name="root">The root from which the event originates.</param> /// <param name="root">The root from which the event originates.</param>
public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root)
{ {
Contract.Requires<ArgumentNullException>(device != null); device = device ?? throw new ArgumentNullException(nameof(device));
Device = device; Device = device;
Timestamp = timestamp; Timestamp = timestamp;

4
src/Avalonia.Input/TextInputEventArgs.cs

@ -4,8 +4,8 @@ namespace Avalonia.Input
{ {
public class TextInputEventArgs : RoutedEventArgs public class TextInputEventArgs : RoutedEventArgs
{ {
public IKeyboardDevice Device { get; set; } public IKeyboardDevice? Device { get; set; }
public string Text { get; set; } public string? Text { get; set; }
} }
} }

4
src/Avalonia.Layout/Layoutable.cs

@ -758,8 +758,6 @@ namespace Avalonia.Layout
protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{ {
base.OnDetachedFromVisualTreeCore(e);
if (e.Root is ILayoutRoot r) if (e.Root is ILayoutRoot r)
{ {
if (_layoutUpdated is object) if (_layoutUpdated is object)
@ -772,6 +770,8 @@ namespace Avalonia.Layout
r.LayoutManager.UnregisterEffectiveViewportListener(this); r.LayoutManager.UnregisterEffectiveViewportListener(this);
} }
} }
base.OnDetachedFromVisualTreeCore(e);
} }
/// <summary> /// <summary>

5
src/Avalonia.OpenGL/AngleOptions.cs

@ -10,9 +10,6 @@ namespace Avalonia.OpenGL
DirectX11 DirectX11
} }
public List<PlatformApi> AllowedPlatformApis = new List<PlatformApi> public IList<PlatformApi> AllowedPlatformApis { get; set; } = null;
{
PlatformApi.DirectX9
};
} }
} }

3
src/Avalonia.OpenGL/EglConsts.cs

@ -186,6 +186,9 @@ namespace Avalonia.OpenGL
public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206; public const int EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A;
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_NULL_ANGLE = 0x345E;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D;
public const int EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E;
//EGL_ANGLE_platform_angle_d3d //EGL_ANGLE_platform_angle_d3d
public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209; public const int EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209;

6
src/Avalonia.OpenGL/EglDisplay.cs

@ -36,7 +36,11 @@ namespace Avalonia.OpenGL
throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll"); throw new OpenGlException("eglGetPlatformDisplayEXT is not supported by libegl.dll");
var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis var allowedApis = AvaloniaLocator.Current.GetService<AngleOptions>()?.AllowedPlatformApis
?? new List<AngleOptions.PlatformApi> {AngleOptions.PlatformApi.DirectX9}; ?? new []
{
AngleOptions.PlatformApi.DirectX11,
AngleOptions.PlatformApi.DirectX9
};
foreach (var platformApi in allowedApis) foreach (var platformApi in allowedApis)
{ {

6
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -16,6 +16,10 @@ namespace Avalonia
/// <summary> /// <summary>
/// The maximum number of bytes for video memory to store textures and resources. /// The maximum number of bytes for video memory to store textures and resources.
/// </summary> /// </summary>
public long? MaxGpuResourceSizeBytes { get; set; } /// <remarks>
/// This is set by default to the recommended value for Avalonia.
/// Setting this to null will give you the default Skia value.
/// </remarks>
public long? MaxGpuResourceSizeBytes { get; set; } = 1024 * 600 * 4 * 12; // ~28mb 12x 1024 x 600 textures.
} }
} }

2
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -1007,10 +1007,12 @@ namespace Avalonia.Win32
if (newProperties.IsResizable) if (newProperties.IsResizable)
{ {
style |= WindowStyles.WS_SIZEFRAME; style |= WindowStyles.WS_SIZEFRAME;
style |= WindowStyles.WS_MAXIMIZEBOX;
} }
else else
{ {
style &= ~WindowStyles.WS_SIZEFRAME; style &= ~WindowStyles.WS_SIZEFRAME;
style &= ~WindowStyles.WS_MAXIMIZEBOX;
} }
SetStyle(style); SetStyle(style);

31
tests/Avalonia.LeakTests/ControlTests.cs

@ -552,6 +552,37 @@ namespace Avalonia.LeakTests
} }
} }
[Fact]
public void ItemsRepeater_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window
{
Content = new ItemsRepeater(),
};
window.Show();
window.LayoutManager.ExecuteInitialLayoutPass();
Assert.IsType<ItemsRepeater>(window.Presenter.Child);
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ItemsRepeater>()).ObjectsCount));
}
}
private IDisposable Start() private IDisposable Start()
{ {
return UnitTestApplication.Start(TestServices.StyledWindow.With( return UnitTestApplication.Start(TestServices.StyledWindow.With(

Loading…
Cancel
Save