Browse Source

Merge remote-tracking branch 'origin/master' into remove-reactive-package

# Conflicts:
#	src/Avalonia.Base/Animation/AnimationInstance`1.cs
#	src/Avalonia.Base/Data/InstancedBinding.cs
pull/9749/head
Max Katz 4 years ago
parent
commit
a92bf0aaf1
  1. 1
      Avalonia.sln
  2. 5
      build/JetBrains.Annotations.props
  3. 1
      nukebuild/DotNetConfigHelper.cs
  4. 3
      samples/ControlCatalog/MainView.xaml
  5. 212
      samples/ControlCatalog/Pages/GesturePage.cs
  6. 117
      samples/ControlCatalog/Pages/GesturePage.xaml
  7. 1
      src/Avalonia.Base/Avalonia.Base.csproj
  8. 34
      src/Avalonia.Base/Contract.cs
  9. 128
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  10. 33
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  11. 22
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  12. 8
      src/Avalonia.Base/Input/Gestures.cs
  13. 24
      src/Avalonia.Base/Input/PinchEventArgs.cs
  14. 6
      src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs
  15. 6
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
  16. 7
      src/Avalonia.Base/Media/DrawingGroup.cs
  17. 3
      src/Avalonia.Base/Media/Typeface.cs
  18. 2
      src/Avalonia.Base/PixelVector.cs
  19. 5
      src/Avalonia.Base/Visual.cs
  20. 1
      src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj
  21. 1
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  22. 4
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  23. 1
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  24. 2
      src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs
  25. 1
      src/Avalonia.Controls/Avalonia.Controls.csproj
  26. 83
      src/Avalonia.Controls/ComboBox.cs
  27. 1
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  28. 3
      src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs
  29. 23
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  30. 1
      src/Avalonia.Native/Helpers.cs
  31. 1
      src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs
  32. 1
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  33. 1
      src/Avalonia.X11/X11Info.cs
  34. 1
      src/Avalonia.X11/X11Screens.cs
  35. 3
      src/Avalonia.X11/X11Window.Ime.cs
  36. 1
      src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
  37. 32
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  38. 3
      src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
  39. 4
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  40. 14
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs
  41. 6
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  42. 4
      src/Skia/Avalonia.Skia/GlyphRunImpl.cs
  43. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  44. 8
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  45. 5
      src/Windows/Avalonia.Win32/Win32Platform.cs
  46. 12
      src/Windows/Avalonia.Win32/WindowImpl.cs
  47. 1
      src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs
  48. 1
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  49. 98
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  50. 1
      tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs
  51. 2
      tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs
  52. 2
      tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs
  53. 1
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  54. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  55. 53
      tests/Avalonia.UnitTests/TouchTestHelper.cs

1
Avalonia.sln

@ -100,7 +100,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\EmbedXaml.props = build\EmbedXaml.props
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\ImageSharp.props = build\ImageSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props

5
build/JetBrains.Annotations.props

@ -1,5 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="10.3.0" />
</ItemGroup>
</Project>

1
nukebuild/DotNetConfigHelper.cs

@ -1,5 +1,4 @@
using System.Globalization;
using JetBrains.Annotations;
using Nuke.Common.Tools.DotNet;
// ReSharper disable ReturnValueOfPureMethodIsNotUsed

3
samples/ControlCatalog/MainView.xaml

@ -92,6 +92,9 @@
<TabItem Header="Flyouts">
<pages:FlyoutsPage />
</TabItem>
<TabItem Header="Gestures">
<pages:GesturePage />
</TabItem>
<TabItem Header="Image"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">

212
samples/ControlCatalog/Pages/GesturePage.cs

@ -0,0 +1,212 @@
using System;
using System.Numerics;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering.Composition;
namespace ControlCatalog.Pages
{
public class GesturePage : UserControl
{
private bool _isInit;
private float _currentScale;
public GesturePage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if(_isInit)
{
return;
}
_isInit = true;
SetPullHandlers(this.Find<Border>("TopPullZone"), false);
SetPullHandlers(this.Find<Border>("BottomPullZone"), true);
SetPullHandlers(this.Find<Border>("RightPullZone"), true);
SetPullHandlers(this.Find<Border>("LeftPullZone"), false);
var image = this.Find<Image>("PinchImage");
SetPinchHandlers(image);
var reset = this.Find<Button>("ResetButton");
reset!.Click += (s, e) =>
{
var compositionVisual = ElementComposition.GetElementVisual(image);
if(compositionVisual!= null)
{
_currentScale = 1;
compositionVisual.Scale = new Vector3(1,1,1);
image.InvalidateMeasure();
}
};
}
private void SetPinchHandlers(Control? control)
{
if (control == null)
{
return;
}
_currentScale = 1;
Vector3 currentOffset = default;
bool isZooming = false;
CompositionVisual? compositionVisual = null;
void InitComposition(Control visual)
{
if (compositionVisual != null)
{
return;
}
compositionVisual = ElementComposition.GetElementVisual(visual);
}
control.LayoutUpdated += (s, e) =>
{
InitComposition(control!);
if (compositionVisual != null)
{
compositionVisual.Scale = new(_currentScale, _currentScale, 1);
if(currentOffset == default)
{
currentOffset = compositionVisual.Offset;
}
}
};
control.AddHandler(Gestures.PinchEvent, (s, e) =>
{
InitComposition(control!);
isZooming = true;
if(compositionVisual != null)
{
var scale = _currentScale * (float)e.Scale;
compositionVisual.Scale = new(scale, scale, 1);
}
});
control.AddHandler(Gestures.PinchEndedEvent, (s, e) =>
{
InitComposition(control!);
isZooming = false;
if (compositionVisual != null)
{
_currentScale = compositionVisual.Scale.X;
}
});
control.AddHandler(Gestures.ScrollGestureEvent, (s, e) =>
{
InitComposition(control!);
if (compositionVisual != null && !isZooming)
{
currentOffset -= new Vector3((float)e.Delta.X, (float)e.Delta.Y, 0);
compositionVisual.Offset = currentOffset;
}
});
}
private void SetPullHandlers(Control? control, bool inverse)
{
if (control == null)
{
return;
}
var ball = control.FindLogicalDescendantOfType<Border>();
Vector3 defaultOffset = default;
CompositionVisual? ballCompositionVisual = null;
if (ball != null)
{
InitComposition(ball);
}
else
{
return;
}
control.LayoutUpdated += (s, e) =>
{
InitComposition(ball!);
if (ballCompositionVisual != null)
{
defaultOffset = ballCompositionVisual.Offset;
}
};
control.AddHandler(Gestures.PullGestureEvent, (s, e) =>
{
Vector3 center = new((float)control.Bounds.Center.X, (float)control.Bounds.Center.Y, 0);
InitComposition(ball!);
if (ballCompositionVisual != null)
{
ballCompositionVisual.Offset = defaultOffset + new System.Numerics.Vector3((float)e.Delta.X * 0.4f, (float)e.Delta.Y * 0.4f, 0) * (inverse ? -1 : 1);
}
});
control.AddHandler(Gestures.PullGestureEndedEvent, (s, e) =>
{
InitComposition(ball!);
if (ballCompositionVisual != null)
{
ballCompositionVisual.Offset = defaultOffset;
}
});
void InitComposition(Control control)
{
if (ballCompositionVisual != null)
{
return;
}
ballCompositionVisual = ElementComposition.GetElementVisual(ball);
if (ballCompositionVisual != null)
{
var offsetAnimation = ballCompositionVisual.Compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(100);
var implicitAnimations = ballCompositionVisual.Compositor.CreateImplicitAnimationCollection();
implicitAnimations["Offset"] = offsetAnimation;
ballCompositionVisual.ImplicitAnimations = implicitAnimations;
}
}
}
}
}

117
samples/ControlCatalog/Pages/GesturePage.xaml

@ -0,0 +1,117 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DesignHeight="800"
d:DesignWidth="400"
x:Class="ControlCatalog.Pages.GesturePage">
<StackPanel Orientation="Vertical"
Spacing="4">
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pull Gexture (Touch / Pen)</TextBlock>
<TextBlock Margin="5">Pull from colored rectangles</TextBlock>
<Border>
<DockPanel HorizontalAlignment="Stretch"
ClipToBounds="True"
Margin="5"
Height="200">
<Border DockPanel.Dock="Top"
Margin="2"
Name="TopPullZone"
Background="Transparent"
BorderBrush="Red"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="TopToBottom"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Name="TopBall"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Bottom"
BorderBrush="Green"
Margin="2"
Background="Transparent"
Name="BottomPullZone"
HorizontalAlignment="Stretch"
Height="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="BottomToTop"/>
</Border.GestureRecognizers>
<Border Width="10"
Name="BottomBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="10"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Right"
Margin="2"
Background="Transparent"
Name="RightPullZone"
BorderBrush="Blue"
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="RightToLeft"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="RightBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
<Border DockPanel.Dock="Left"
Margin="2"
Background="Transparent"
Name="LeftPullZone"
BorderBrush="Orange"
HorizontalAlignment="Left"
VerticalAlignment="Stretch"
Width="50"
BorderThickness="1">
<Border.GestureRecognizers>
<PullGestureRecognizer PullDirection="LeftToRight"/>
</Border.GestureRecognizers>
<Border Width="10"
Height="10"
Name="LeftBall"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="5"
Background="Green"/>
</Border>
</DockPanel>
</Border>
<TextBlock FontWeight="Bold"
FontSize="18"
Margin="5">Pinch/Zoom Gexture (Multi Touch)</TextBlock>
<Border ClipToBounds="True">
<Image Stretch="UniformToFill"
Margin="5"
Name="PinchImage"
Source="/Assets/delicate-arch-896885_640.jpg">
<Image.GestureRecognizers>
<PinchGestureRecognizer/>
<ScrollGestureRecognizer CanHorizontallyScroll="True" CanVerticallyScroll="True"/>
</Image.GestureRecognizers>
</Image>
</Border>
<Button HorizontalAlignment="Center" Name="ResetButton">Reset</Button>
</StackPanel>
</UserControl>

1
src/Avalonia.Base/Avalonia.Base.csproj

@ -14,7 +14,6 @@
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\System.Memory.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />

34
src/Avalonia.Base/Contract.cs

@ -1,34 +0,0 @@
using System;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia
{
/// <summary>
/// A stub of Code Contract's Contract class.
/// </summary>
/// <remarks>
/// It would be nice to use Code Contracts on Avalonia but last time I tried it slowed things
/// to a crawl and often crashed. Instead use the same signature for checking preconditions
/// in the hope that it might become usable at some point.
/// </remarks>
public static class Contract
{
/// <summary>
/// Specifies a precondition.
/// </summary>
/// <typeparam name="TException">
/// The exception to throw if <paramref name="condition"/> is false.
/// </typeparam>
/// <param name="condition">The precondition.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[ContractAnnotation("condition:false=>stop")]
public static void Requires<TException>(bool condition) where TException : Exception, new()
{
if (!condition)
{
throw new TException();
}
}
}
}

128
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@ -0,0 +1,128 @@
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
{
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance;
private IPointer? _firstContact;
private Point _firstPoint;
private IPointer? _secondContact;
private Point _secondPoint;
private Point _origin;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
{
RemoveContact(pointer);
}
public void PointerMoved(PointerEventArgs e)
{
if (_target != null && _target is Visual visual)
{
if(_firstContact == e.Pointer)
{
_firstPoint = e.GetPosition(visual);
}
else if (_secondContact == e.Pointer)
{
_secondPoint = e.GetPosition(visual);
}
else
{
return;
}
if (_firstContact != null && _secondContact != null)
{
var distance = GetDistance(_firstPoint, _secondPoint);
var scale = distance / _initialDistance;
_target?.RaiseEvent(new PinchEventArgs(scale, _origin));
}
}
}
public void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
if (_firstContact == null)
{
_firstContact = e.Pointer;
_firstPoint = e.GetPosition(visual);
return;
}
else if (_secondContact == null && _firstContact != e.Pointer)
{
_secondContact = e.Pointer;
_secondPoint = e.GetPosition(visual);
}
else
{
return;
}
if (_firstContact != null && _secondContact != null)
{
_initialDistance = GetDistance(_firstPoint, _secondPoint);
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_actions!.Capture(_firstContact, this);
_actions!.Capture(_secondContact, this);
}
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
}
private void RemoveContact(IPointer pointer)
{
if (_firstContact == pointer || _secondContact == pointer)
{
if (_secondContact == pointer)
{
_secondContact = null;
}
if (_firstContact == pointer)
{
_firstContact = _secondContact;
_secondContact = null;
}
_target?.RaiseEvent(new PinchEndedEventArgs());
}
}
private float GetDistance(Point a, Point b)
{
var length = _secondPoint - _firstPoint;
return (float)new Vector(length.X, length.Y).Length;
}
}
}

33
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@ -1,15 +1,19 @@
using Avalonia.Input.GestureRecognizers;
using System;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PullGestureRecognizer : StyledElement, IGestureRecognizer
{
internal static int MinPullDetectionSize = 50;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
private PullDirection _pullDirection;
private bool _pullInProgress;
/// <summary>
/// Defines the <see cref="PullDirection"/> property.
@ -31,23 +35,12 @@ namespace Avalonia.Input
PullDirection = pullDirection;
}
public PullGestureRecognizer() { }
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
_target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
_target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
@ -94,6 +87,7 @@ namespace Avalonia.Input
break;
}
_pullInProgress = true;
_target?.RaiseEvent(new PullGestureEventArgs(_gestureId, delta, PullDirection));
}
}
@ -111,16 +105,16 @@ namespace Avalonia.Input
switch (PullDirection)
{
case PullDirection.TopToBottom:
canPull = position.Y < bounds.Height * 0.1;
canPull = position.Y < Math.Max(MinPullDetectionSize, bounds.Height * 0.1);
break;
case PullDirection.BottomToTop:
canPull = position.Y > bounds.Height - (bounds.Height * 0.1);
canPull = position.Y > Math.Min(bounds.Height - MinPullDetectionSize, bounds.Height - (bounds.Height * 0.1));
break;
case PullDirection.LeftToRight:
canPull = position.X < bounds.Width * 0.1;
canPull = position.X < Math.Max(MinPullDetectionSize, bounds.Width * 0.1);
break;
case PullDirection.RightToLeft:
canPull = position.X > bounds.Width - (bounds.Width * 0.1);
canPull = position.X > Math.Min(bounds.Width - MinPullDetectionSize, bounds.Width - (bounds.Width * 0.1));
break;
}
@ -135,7 +129,7 @@ namespace Avalonia.Input
public void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer)
if (_tracking == e.Pointer && _pullInProgress)
{
EndPull();
}
@ -145,6 +139,7 @@ namespace Avalonia.Input
{
_tracking = null;
_initialPosition = default;
_pullInProgress = false;
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}

22
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@ -16,6 +16,7 @@ namespace Avalonia.Input.GestureRecognizers
private bool _canHorizontallyScroll;
private bool _canVerticallyScroll;
private int _gestureId;
private int _scrollStartDistance = 30;
// Movement per second
private Vector _inertia;
@ -38,6 +39,15 @@ namespace Avalonia.Input.GestureRecognizers
nameof(CanVerticallyScroll),
o => o.CanVerticallyScroll,
(o, v) => o.CanVerticallyScroll = v);
/// <summary>
/// Defines the <see cref="ScrollStartDistance"/> property.
/// </summary>
public static readonly DirectProperty<ScrollGestureRecognizer, int> ScrollStartDistanceProperty =
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, int>(
nameof(ScrollStartDistance),
o => o.ScrollStartDistance,
(o, v) => o.ScrollStartDistance = v);
/// <summary>
/// Gets or sets a value indicating whether the content can be scrolled horizontally.
@ -56,6 +66,15 @@ namespace Avalonia.Input.GestureRecognizers
get => _canVerticallyScroll;
set => SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value);
}
/// <summary>
/// Gets or sets a value indicating the distance the pointer moves before scrolling is started
/// </summary>
public int ScrollStartDistance
{
get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
@ -75,9 +94,6 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint = e.GetPosition((Visual?)_target);
}
}
// Arbitrary chosen value, probably need to move that to platform settings or something
private const double ScrollStartDistance = 30;
// Pixels per second speed that is considered to be the stop of inertial scroll
private const double InertialScrollSpeedEnd = 5;

8
src/Avalonia.Base/Input/Gestures.cs

@ -47,6 +47,14 @@ namespace Avalonia.Input
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>(
"PinchEvent", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PinchEndedEventArgs> PinchEndedEvent =
RoutedEvent.Register<PinchEndedEventArgs>(
"PinchEndedEvent", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PullGestureEventArgs> PullGestureEvent =
RoutedEvent.Register<PullGestureEventArgs>(
"PullGesture", RoutingStrategies.Bubble, typeof(Gestures));

24
src/Avalonia.Base/Input/PinchEventArgs.cs

@ -0,0 +1,24 @@
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class PinchEventArgs : RoutedEventArgs
{
public PinchEventArgs(double scale, Point scaleOrigin) : base(Gestures.PinchEvent)
{
Scale = scale;
ScaleOrigin = scaleOrigin;
}
public double Scale { get; } = 1;
public Point ScaleOrigin { get; }
}
public class PinchEndedEventArgs : RoutedEventArgs
{
public PinchEndedEventArgs() : base(Gestures.PinchEndedEvent)
{
}
}
}

6
src/Avalonia.Base/Input/Raw/RawInputEventArgs.cs

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

6
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@ -53,9 +53,6 @@ namespace Avalonia.Input.Raw
RawInputModifiers inputModifiers)
: base(device, timestamp, root)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Point = new RawPointerPoint();
Position = position;
Type = type;
@ -80,9 +77,6 @@ namespace Avalonia.Input.Raw
RawInputModifiers inputModifiers)
: base(device, timestamp, root)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
Point = point;
Type = type;
InputModifiers = inputModifiers;

7
src/Avalonia.Base/Media/DrawingGroup.cs

@ -461,9 +461,10 @@ namespace Avalonia.Media
if (_rootDrawing == null)
{
// When a DrawingGroup is set, it should be made the root if
// a root drawing didnt exist.
Contract.Requires<NotSupportedException>(_currentDrawingGroup == null);
if (_currentDrawingGroup != null)
{
throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist.");
}
// If this is the first Drawing being added, avoid creating a DrawingGroup
// and set this drawing as the root drawing. This optimizes the common

3
src/Avalonia.Base/Media/Typeface.cs

@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using JetBrains.Annotations;
namespace Avalonia.Media
{
@ -17,7 +16,7 @@ namespace Avalonia.Media
/// <param name="style">The font style.</param>
/// <param name="weight">The font weight.</param>
/// <param name="stretch">The font stretch.</param>
public Typeface([NotNull] FontFamily fontFamily,
public Typeface(FontFamily fontFamily,
FontStyle style = FontStyle.Normal,
FontWeight weight = FontWeight.Normal,
FontStretch stretch = FontStretch.Normal)

2
src/Avalonia.Base/PixelVector.cs

@ -1,7 +1,6 @@
using System;
using System.Globalization;
using Avalonia.Animation.Animators;
using JetBrains.Annotations;
namespace Avalonia
{
@ -135,7 +134,6 @@ namespace Avalonia
/// </summary>
/// <param name="other">The other vector.</param>
/// <returns>True if vectors are nearly equal.</returns>
[Pure]
public bool NearlyEquals(PixelVector other)
{
const float tolerance = float.Epsilon;

5
src/Avalonia.Base/Visual.cs

@ -368,7 +368,10 @@ namespace Avalonia
/// <param name="context">The drawing context.</param>
public virtual void Render(DrawingContext context)
{
Contract.Requires<ArgumentNullException>(context != null);
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
}
/// <summary>

1
src/Avalonia.Controls.ColorPicker/Avalonia.Controls.ColorPicker.csproj

@ -16,7 +16,6 @@
<EmbeddedResource Include="Themes\**\*.xaml" />
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<!--<Import Project="..\..\build\ApiDiff.props" />-->
<Import Project="..\..\build\NullableEnable.props" />

1
src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj

@ -13,7 +13,6 @@
<EmbeddedResource Include="Themes\**\*.xaml" />
</ItemGroup>
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\DevAnalyzers.props" />

4
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -641,7 +641,7 @@ namespace Avalonia.Controls
public Control GetCellContent(DataGridRow dataGridRow)
{
Contract.Requires<ArgumentNullException>(dataGridRow != null);
dataGridRow = dataGridRow ?? throw new ArgumentNullException(nameof(dataGridRow));
if (OwningGrid == null)
{
throw DataGridError.DataGrid.NoOwningGrid(GetType());
@ -659,7 +659,7 @@ namespace Avalonia.Controls
public Control GetCellContent(object dataItem)
{
Contract.Requires<ArgumentNullException>(dataItem != null);
dataItem = dataItem ?? throw new ArgumentNullException(nameof(dataItem));
if (OwningGrid == null)
{
throw DataGridError.DataGrid.NoOwningGrid(GetType());

1
src/Avalonia.Controls.DataGrid/DataGridRows.cs

@ -16,7 +16,6 @@ using System.Diagnostics;
using System.Linq;
using Avalonia.Data;
using Avalonia.Styling;
using JetBrains.Annotations;
namespace Avalonia.Controls
{

2
src/Avalonia.Controls.DataGrid/Primitives/DataGridFrozenGrid.cs

@ -26,7 +26,6 @@ namespace Avalonia.Controls.Primitives
/// <returns>true if the grid is frozen; otherwise, false. The default is true.</returns>
public static bool GetIsFrozen(Control element)
{
Contract.Requires<ArgumentNullException>(element != null);
return element.GetValue(IsFrozenProperty);
}
@ -38,7 +37,6 @@ namespace Avalonia.Controls.Primitives
/// <exception cref="T:System.ArgumentNullException"><paramref name="element" /> is null.</exception>
public static void SetIsFrozen(Control element, bool value)
{
Contract.Requires<ArgumentNullException>(element != null);
element.SetValue(IsFrozenProperty, value);
}
}

1
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -6,7 +6,6 @@
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj" />
</ItemGroup>
<Import Project="..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
<Import Project="..\..\build\TrimmingEnable.props" />

83
src/Avalonia.Controls/ComboBox.cs

@ -95,19 +95,26 @@ namespace Avalonia.Controls
{
ItemsPanelProperty.OverrideDefaultValue<ComboBox>(DefaultPanel);
FocusableProperty.OverrideDefaultValue<ComboBox>(true);
SelectedItemProperty.Changed.AddClassHandler<ComboBox>((x, e) => x.SelectedItemChanged(e));
KeyDownEvent.AddClassHandler<ComboBox>((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel);
IsTextSearchEnabledProperty.OverrideDefaultValue<ComboBox>(true);
IsDropDownOpenProperty.Changed.AddClassHandler<ComboBox>((x, e) => x.DropdownChanged(e));
}
/// <summary>
/// Occurs after the drop-down (popup) list of the <see cref="ComboBox"/> closes.
/// </summary>
public event EventHandler? DropDownClosed;
/// <summary>
/// Occurs after the drop-down (popup) list of the <see cref="ComboBox"/> opens.
/// </summary>
public event EventHandler? DropDownOpened;
/// <summary>
/// Gets or sets a value indicating whether the dropdown is currently open.
/// </summary>
public bool IsDropDownOpen
{
get { return _isDropDownOpen; }
set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
get => _isDropDownOpen;
set => SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value);
}
/// <summary>
@ -115,8 +122,8 @@ namespace Avalonia.Controls
/// </summary>
public double MaxDropDownHeight
{
get { return GetValue(MaxDropDownHeightProperty); }
set { SetValue(MaxDropDownHeightProperty, value); }
get => GetValue(MaxDropDownHeightProperty);
set => SetValue(MaxDropDownHeightProperty, value);
}
/// <summary>
@ -124,8 +131,8 @@ namespace Avalonia.Controls
/// </summary>
protected object? SelectionBoxItem
{
get { return _selectionBoxItem; }
set { SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); }
get => _selectionBoxItem;
set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value);
}
/// <summary>
@ -133,8 +140,8 @@ namespace Avalonia.Controls
/// </summary>
public string? PlaceholderText
{
get { return GetValue(PlaceholderTextProperty); }
set { SetValue(PlaceholderTextProperty, value); }
get => GetValue(PlaceholderTextProperty);
set => SetValue(PlaceholderTextProperty, value);
}
/// <summary>
@ -142,8 +149,8 @@ namespace Avalonia.Controls
/// </summary>
public IBrush? PlaceholderForeground
{
get { return GetValue(PlaceholderForegroundProperty); }
set { SetValue(PlaceholderForegroundProperty, value); }
get => GetValue(PlaceholderForegroundProperty);
set => SetValue(PlaceholderForegroundProperty, value);
}
/// <summary>
@ -151,8 +158,8 @@ namespace Avalonia.Controls
/// </summary>
public ItemVirtualizationMode VirtualizationMode
{
get { return GetValue(VirtualizationModeProperty); }
set { SetValue(VirtualizationModeProperty, value); }
get => GetValue(VirtualizationModeProperty);
set => SetValue(VirtualizationModeProperty, value);
}
/// <summary>
@ -160,8 +167,8 @@ namespace Avalonia.Controls
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get { return GetValue(HorizontalContentAlignmentProperty); }
set { SetValue(HorizontalContentAlignmentProperty, value); }
get => GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
/// <summary>
@ -169,8 +176,8 @@ namespace Avalonia.Controls
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get { return GetValue(VerticalContentAlignmentProperty); }
set { SetValue(VerticalContentAlignmentProperty, value); }
get => GetValue(VerticalContentAlignmentProperty);
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <inheritdoc/>
@ -182,6 +189,7 @@ namespace Avalonia.Controls
ComboBoxItem.ContentTemplateProperty);
}
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
@ -239,7 +247,7 @@ namespace Avalonia.Controls
}
// This part of code is needed just to acquire initial focus, subsequent focus navigation will be done by ItemsControl.
else if (IsDropDownOpen && SelectedIndex < 0 && ItemCount > 0 &&
(e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true)
(e.Key == Key.Up || e.Key == Key.Down) && IsFocused == true)
{
var firstChild = Presenter?.Panel?.Children.FirstOrDefault(c => CanFocus(c));
if (firstChild != null)
@ -309,12 +317,11 @@ namespace Avalonia.Controls
e.Handled = true;
}
}
PseudoClasses.Set(pcPressed, false);
base.OnPointerReleased(e);
}
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
@ -329,6 +336,22 @@ namespace Avalonia.Controls
_popup.Closed += PopupClosed;
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
if (change.Property == SelectedItemProperty)
{
UpdateSelectionBoxItem(change.NewValue);
TryFocusSelectedItem();
}
else if (change.Property == IsDropDownOpenProperty)
{
PseudoClasses.Set(pcDropdownOpen, change.GetNewValue<bool>());
}
base.OnPropertyChanged(change);
}
protected override AutomationPeer OnCreateAutomationPeer()
{
return new ComboBoxAutomationPeer(this);
@ -350,6 +373,8 @@ namespace Avalonia.Controls
{
Focus();
}
DropDownClosed?.Invoke(this, EventArgs.Empty);
}
private void PopupOpened(object? sender, EventArgs e)
@ -379,6 +404,8 @@ namespace Avalonia.Controls
}
UpdateFlowDirection();
DropDownOpened?.Invoke(this, EventArgs.Empty);
}
private void IsVisibleChanged(bool isVisible)
@ -389,12 +416,6 @@ namespace Avalonia.Controls
}
}
private void SelectedItemChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateSelectionBoxItem(e.NewValue);
TryFocusSelectedItem();
}
private void TryFocusSelectedItem()
{
var selectedIndex = SelectedIndex;
@ -494,11 +515,5 @@ namespace Avalonia.Controls
MoveSelection(NavigationDirection.Previous, WrapSelection);
}
}
private void DropdownChanged(AvaloniaPropertyChangedEventArgs e)
{
bool newValue = e.GetNewValue<bool>();
PseudoClasses.Set(pcDropdownOpen, newValue);
}
}
}

1
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -4,7 +4,6 @@ using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
using Avalonia.Utilities;
using JetBrains.Annotations;
namespace Avalonia.Controls.Utils
{

3
src/Avalonia.FreeDesktop/LinuxMountedVolumeInfoProvider.cs

@ -9,8 +9,7 @@ namespace Avalonia.FreeDesktop
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new LinuxMountedVolumeInfoListener(ref mountedDrives!);
return new LinuxMountedVolumeInfoListener(ref mountedDrives);
}
}
}

23
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -9,23 +9,23 @@ using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using JetBrains.Annotations;
using MicroCom.Runtime;
#nullable enable
namespace Avalonia.Native
{
class AvaloniaNativePlatform : IWindowingPlatform
{
private readonly IAvaloniaNativeFactory _factory;
private AvaloniaNativePlatformOptions _options;
private AvaloniaNativeGlPlatformGraphics _platformGl;
private AvaloniaNativePlatformOptions? _options;
private AvaloniaNativeGlPlatformGraphics? _platformGl;
[DllImport("libAvaloniaNative")]
static extern IntPtr CreateAvaloniaNative();
internal static readonly KeyboardDevice KeyboardDevice = new KeyboardDevice();
[CanBeNull] internal static Compositor Compositor { get; private set; }
[CanBeNull] internal static PlatformRenderInterfaceContextManager RenderInterface { get; private set; }
internal static Compositor? Compositor { get; private set; }
internal static PlatformRenderInterfaceContextManager? RenderInterface { get; private set; }
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{
@ -63,7 +63,7 @@ namespace Avalonia.Native
public void SetupApplicationName()
{
if (!string.IsNullOrWhiteSpace(Application.Current.Name))
if (!string.IsNullOrWhiteSpace(Application.Current!.Name))
{
_factory.MacOptions.SetApplicationTitle(Application.Current.Name);
}
@ -118,10 +118,13 @@ namespace Avalonia.Native
AvaloniaLocator.CurrentMutable.Bind<IRenderLoop>().ToConstant(renderLoop);
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
if (hotkeys is not null)
{
hotkeys.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
}
if (_options.UseGpu)
{

1
src/Avalonia.Native/Helpers.cs

@ -1,5 +1,4 @@
using Avalonia.Native.Interop;
using JetBrains.Annotations;
namespace Avalonia.Native
{

1
src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs

@ -69,7 +69,6 @@ namespace Avalonia.Native
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new MacOSMountedVolumeInfoListener(mountedDrives);
}
}

1
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -3,7 +3,6 @@ using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
// ReSharper disable IdentifierTypo
namespace Avalonia.X11.NativeDialogs

1
src/Avalonia.X11/X11Info.cs

@ -2,7 +2,6 @@ using System;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using JetBrains.Annotations;
using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Avalonia.X11

1
src/Avalonia.X11/X11Screens.cs

@ -5,7 +5,6 @@ using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.Platform;
using static Avalonia.X11.XLib;
using JetBrains.Annotations;
namespace Avalonia.X11
{

3
src/Avalonia.X11/X11Window.Ime.cs

@ -7,7 +7,6 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@ -206,7 +205,7 @@ namespace Avalonia.X11
// This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event
class RawKeyEventArgsWithText : RawKeyEventArgs
{
public RawKeyEventArgsWithText([NotNull] IKeyboardDevice device, ulong timestamp, [NotNull] IInputRoot root,
public RawKeyEventArgsWithText(IKeyboardDevice device, ulong timestamp, IInputRoot root,
RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) :
base(device, timestamp, root, type, key, modifiers)
{

1
src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs

@ -1,6 +1,5 @@
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Media;
using JetBrains.Annotations;
namespace Avalonia.LinuxFramebuffer
{

32
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -17,7 +17,7 @@ using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using JetBrains.Annotations;
#nullable enable
namespace Avalonia.LinuxFramebuffer
{
@ -26,9 +26,9 @@ namespace Avalonia.LinuxFramebuffer
IOutputBackend _fb;
private static readonly Stopwatch St = Stopwatch.StartNew();
internal static uint Timestamp => (uint)St.ElapsedTicks;
public static InternalPlatformThreadingInterface Threading;
public static InternalPlatformThreadingInterface? Threading;
internal static Compositor Compositor { get; private set; }
internal static Compositor? Compositor { get; private set; }
LinuxFramebufferPlatform(IOutputBackend backend)
@ -60,7 +60,7 @@ namespace Avalonia.LinuxFramebuffer
}
internal static LinuxFramebufferLifetime Initialize<T>(T builder, IOutputBackend outputBackend, IInputBackend inputBackend) where T : AppBuilderBase<T>, new()
internal static LinuxFramebufferLifetime Initialize<T>(T builder, IOutputBackend outputBackend, IInputBackend? inputBackend) where T : AppBuilderBase<T>, new()
{
var platform = new LinuxFramebufferPlatform(outputBackend);
builder.UseSkia().UseWindowingSubsystem(platform.Initialize, "fbdev");
@ -71,8 +71,8 @@ namespace Avalonia.LinuxFramebuffer
class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime
{
private readonly IOutputBackend _fb;
[CanBeNull] private readonly IInputBackend _inputBackend;
private TopLevel _topLevel;
private readonly IInputBackend? _inputBackend;
private TopLevel? _topLevel;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public CancellationToken Token => _cts.Token;
@ -81,15 +81,15 @@ namespace Avalonia.LinuxFramebuffer
_fb = fb;
}
public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend input)
public LinuxFramebufferLifetime(IOutputBackend fb, IInputBackend? input)
{
_fb = fb;
_inputBackend = input;
}
public Control MainView
public Control? MainView
{
get => (Control)_topLevel?.Content;
get => (Control?)_topLevel?.Content;
set
{
if (_topLevel == null)
@ -119,8 +119,8 @@ namespace Avalonia.LinuxFramebuffer
}
public int ExitCode { get; private set; }
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs>? Startup;
public event EventHandler<ControlledApplicationLifetimeExitEventArgs>? Exit;
public void Start(string[] args)
{
@ -140,19 +140,19 @@ namespace Avalonia.LinuxFramebuffer
public static class LinuxFramebufferPlatformExtensions
{
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev = null, double scaling = 1, IInputBackend inputBackend = default)
public static int StartLinuxFbDev<T>(this T builder, string[] args, string? fbdev = null, double scaling = 1, IInputBackend? inputBackend = default)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling }, inputBackend);
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend inputBackend = default)
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling, IInputBackend? inputBackend = default)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling }, inputBackend);
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, double scaling = 1, IInputBackend inputBackend = default)
public static int StartLinuxDrm<T>(this T builder, string[] args, string? card = null, double scaling = 1, IInputBackend? inputBackend = default)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card) { Scaling = scaling }, inputBackend);
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null, IInputBackend inputBackend = default)
public static int StartLinuxDrm<T>(this T builder, string[] args, string? card = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null, IInputBackend? inputBackend = default)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card, connectorsForceProbe, options), inputBackend);
public static int StartLinuxDirect<T>(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend inputBackend = default)
public static int StartLinuxDirect<T>(this T builder, string[] args, IOutputBackend outputBackend, IInputBackend? inputBackend = default)
where T : AppBuilderBase<T>, new()
{
var lifetime = LinuxFramebufferPlatform.Initialize(builder, outputBackend, inputBackend);

3
src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs

@ -8,7 +8,6 @@ using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using JetBrains.Annotations;
using static Avalonia.LinuxFramebuffer.NativeUnsafeMethods;
using static Avalonia.LinuxFramebuffer.Output.LibDrm;
using static Avalonia.LinuxFramebuffer.Output.LibDrm.GbmColorFormats;
@ -50,7 +49,7 @@ namespace Avalonia.LinuxFramebuffer.Output
_outputOptions = options;
Init(card, resources, connector, modeInfo);
}
public DrmOutput(string path = null, bool connectorsForceProbe = false, [CanBeNull] DrmOutputOptions options = null)
public DrmOutput(string path = null, bool connectorsForceProbe = false, DrmOutputOptions? options = null)
{
if(options != null)
_outputOptions = options;

4
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs

@ -44,9 +44,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public CommandAccessor(WeakReference<object> reference, Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
{
Contract.Requires<ArgumentNullException>(reference != null);
_reference = reference;
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_dependsOnProperties = dependsOnProperties;
_command = new Command(reference, execute, canExecute);

14
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/PropertyInfoAccessorFactory.cs

@ -29,11 +29,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public AvaloniaPropertyAccessor(WeakReference<AvaloniaObject> reference, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
_reference = reference;
_property = property;
_reference = reference ?? throw new ArgumentNullException(nameof(reference));;
_property = property ?? throw new ArgumentNullException(nameof(property));;
}
public AvaloniaObject Instance
@ -78,11 +75,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public InpcPropertyAccessor(WeakReference<object> reference, IPropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);
_reference = reference;
_property = property;
_reference = reference ?? throw new ArgumentNullException(nameof(reference));
_property = property ?? throw new ArgumentNullException(nameof(property));
}
public override Type PropertyType => _property.PropertyType;

6
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -10,7 +10,6 @@ using Avalonia.Rendering.SceneGraph;
using Avalonia.Rendering.Utilities;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using JetBrains.Annotations;
using SkiaSharp;
namespace Avalonia.Skia
@ -675,13 +674,14 @@ namespace Avalonia.Skia
}
}
[CanBeNull]
public object GetFeature(Type t)
#nullable enable
public object? GetFeature(Type t)
{
if (t == typeof(ISkiaSharpApiLeaseFeature))
return new SkiaLeaseFeature(this);
return null;
}
#nullable restore
/// <summary>
/// Configure paint wrapper for using gradient brush.

4
src/Skia/Avalonia.Skia/GlyphRunImpl.cs

@ -1,8 +1,8 @@
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
using JetBrains.Annotations;
using SkiaSharp;
#nullable enable
namespace Avalonia.Skia
{
@ -10,7 +10,7 @@ namespace Avalonia.Skia
[Unstable]
public class GlyphRunImpl : IGlyphRunImpl
{
public GlyphRunImpl([NotNull] SKTextBlob textBlob)
public GlyphRunImpl(SKTextBlob textBlob)
{
TextBlob = textBlob ?? throw new ArgumentNullException (nameof (textBlob));
}

1
src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj

@ -16,7 +16,6 @@
</ItemGroup>
<Import Project="..\..\..\build\SharpDX.props" />
<Import Project="..\..\..\build\HarfBuzzSharp.props" />
<Import Project="..\..\..\build\JetBrains.Annotations.props" />
<Import Project="..\..\..\build\DevAnalyzers.props" />
<Import Project="..\..\..\build\TrimmingEnable.props" />

8
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -820,6 +820,14 @@ namespace Avalonia.Win32.Interop
DWMWA_LAST
};
public enum DwmWindowCornerPreference : uint
{
DWMWCP_DEFAULT = 0,
DWMWCP_DONOTROUND,
DWMWCP_ROUND,
DWMWCP_ROUNDSMALL
}
public enum MapVirtualKeyMapTypes : uint
{
MAPVK_VK_TO_VSC = 0x00,

5
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -19,11 +19,11 @@ using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.Win32.Input;
using Avalonia.Win32.Interop;
using JetBrains.Annotations;
using static Avalonia.Win32.Interop.UnmanagedMethods;
namespace Avalonia
{
#nullable enable
public static class Win32ApplicationExtensions
{
public static T UseWin32<T>(
@ -106,9 +106,10 @@ namespace Avalonia
/// <summary>
/// Provides a way to use a custom-implemented graphics context such as a custom ISkiaGpu
/// </summary>
[CanBeNull] public IPlatformGraphics CustomPlatformGraphics { get; set; }
public IPlatformGraphics? CustomPlatformGraphics { get; set; }
}
}
#nullable restore
namespace Avalonia.Win32
{

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

@ -1029,6 +1029,12 @@ namespace Avalonia.Win32
{
var margins = UpdateExtendMargins();
DwmExtendFrameIntoClientArea(_hwnd, ref margins);
unsafe
{
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_ROUND;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int));
}
}
else
{
@ -1039,6 +1045,12 @@ namespace Avalonia.Win32
_extendedMargins = new Thickness();
Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout);
unsafe
{
int cornerPreference = (int)DwmWindowCornerPreference.DWMWCP_DEFAULT;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(int));
}
}
if (!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&

1
src/Windows/Avalonia.Win32/WindowsMountedVolumeInfoProvider.cs

@ -8,7 +8,6 @@ namespace Avalonia.Win32
{
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new WindowsMountedVolumeInfoListener(mountedDrives);
}
}

1
src/iOS/Avalonia.iOS/AvaloniaView.Text.cs

@ -1,6 +1,5 @@
#nullable enable
using Avalonia.Input.TextInput;
using JetBrains.Annotations;
using UIKit;
namespace Avalonia.iOS;

98
tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs

@ -1,6 +1,8 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Media;
using Avalonia.UnitTests;
using Xunit;
@ -201,5 +203,101 @@ namespace Avalonia.Base.UnitTests.Input
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
}
[Fact]
public void Pinched_Should_Not_Be_Raised_For_Same_Pointer()
{
var touch = new TouchTestHelper();
Border border = new Border()
{
Width = 100,
Height = 100,
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
touch.Down(border, position: firstPoint);
touch.Down(border, position: secondPoint);
touch.Down(border, position: new Point(20, 20));
Assert.False(raised);
}
[Fact]
public void Pinched_Should_Be_Raised_For_Two_Pointers_Moving()
{
Border border = new Border()
{
Width = 100,
Height = 100,
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (s, e) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
var firstTouch = new TouchTestHelper();
var secondTouch = new TouchTestHelper();
firstTouch.Down(border, position: firstPoint);
secondTouch.Down(border, position: secondPoint);
secondTouch.Move(border, position: new Point(20, 20));
Assert.True(raised);
}
[Fact]
public void Scrolling_Should_Start_After_Start_Distance_Is_Exceded()
{
Border border = new Border()
{
Width = 100,
Height = 100,
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new ScrollGestureRecognizer()
{
CanHorizontallyScroll = true,
CanVerticallyScroll = true,
ScrollStartDistance = 50
});
var decorator = new Decorator
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.ScrollGestureEvent, (s, e) => raised = true);
var firstTouch = new TouchTestHelper();
firstTouch.Down(border, position: new Point(5, 5));
firstTouch.Move(border, position: new Point(20, 20));
Assert.False(raised);
firstTouch.Move(border, position: new Point(70, 20));
Assert.True(raised);
}
}
}

1
tests/Avalonia.Base.UnitTests/Rendering/CompositorTestsBase.cs

@ -18,7 +18,6 @@ using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.Annotations;
using Xunit;
namespace Avalonia.Base.UnitTests.Rendering;

2
tests/Avalonia.Benchmarks/Data/AccessorTestObject.cs

@ -1,6 +1,5 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
namespace Avalonia.Benchmarks.Data
{
@ -38,7 +37,6 @@ namespace Avalonia.Benchmarks.Data
{
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

2
tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs

@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld<TValue>
internal class MockProperty : StyledProperty<int>
{
public MockProperty([JetBrains.Annotations.NotNull] string name) : base(name, typeof(object), new StyledPropertyMetadata<int>())
public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata<int>())
{
}
}

1
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -17,7 +17,6 @@ using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Styling;
using Avalonia.UnitTests;
using JetBrains.Annotations;
using Moq;
using Xunit;

2
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs

@ -15,7 +15,6 @@ using Avalonia.Styling;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.Annotations;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests
@ -389,7 +388,6 @@ namespace Avalonia.Markup.Xaml.UnitTests
public bool IsPressed { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

53
tests/Avalonia.UnitTests/TouchTestHelper.cs

@ -0,0 +1,53 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.UnitTests
{
public class TouchTestHelper
{
private readonly Pointer _pointer = new Pointer(Pointer.GetNextFreeId(), PointerType.Touch, true);
private ulong _nextStamp = 1;
private ulong Timestamp() => _nextStamp++;
public IInputElement Captured => _pointer.Captured;
public void Down(Interactive target, Point position = default, KeyModifiers modifiers = default)
{
Down(target, target, position, modifiers);
}
public void Down(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default)
{
_pointer.Capture((IInputElement)target);
source.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (Visual)source, position, Timestamp(), PointerPointProperties.None,
modifiers));
}
public void Move(Interactive target, in Point position, KeyModifiers modifiers = default) => Move(target, target, position, modifiers);
public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
Timestamp(), PointerPointProperties.None, modifiers));
}
public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default)
=> Up(target, target, position, modifiers);
public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default)
{
source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None,
modifiers, MouseButton.None));
_pointer.Capture(null);
}
public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default)
=> Tap(target, target, position, modifiers);
public void Tap(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default)
{
Down(target, source, position, modifiers);
Up(target, source, position, modifiers);
}
}
}
Loading…
Cancel
Save