Browse Source

Merge branch 'master' into fixes/Warnings/CS0436

pull/11582/head
workgroupengineering 3 years ago
committed by GitHub
parent
commit
522641e8a6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/Avalonia/Avalonia.csproj
  2. 1
      packages/Avalonia/Avalonia.targets
  3. 26
      packages/Avalonia/AvaloniaPrivateApis.targets
  4. 5
      samples/RenderDemo/Pages/CustomStringAnimator.cs
  5. 94
      src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs
  6. 59
      src/Avalonia.Base/Animation/Animation.cs
  7. 30
      src/Avalonia.Base/Animation/ICustomAnimator.cs
  8. 28
      src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs
  9. 7
      src/Avalonia.Base/CornerRadius.cs
  10. 37
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs
  11. 73
      src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs
  12. 23
      src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs
  13. 30
      src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs
  14. 31
      src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs
  15. 38
      src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
  16. 10
      src/Avalonia.Base/Input/Gestures.cs
  17. 11
      src/Avalonia.Base/Input/IInputRoot.cs
  18. 2
      src/Avalonia.Base/Input/IKeyboardDevice.cs
  19. 2
      src/Avalonia.Base/Input/IMouseDevice.cs
  20. 5
      src/Avalonia.Base/Input/IPenDevice.cs
  21. 1
      src/Avalonia.Base/Input/IPointer.cs
  22. 2
      src/Avalonia.Base/Input/IPointerDevice.cs
  23. 46
      src/Avalonia.Base/Input/InputElement.cs
  24. 2
      src/Avalonia.Base/Input/KeyEventArgs.cs
  25. 2
      src/Avalonia.Base/Input/KeyboardDevice.cs
  26. 33
      src/Avalonia.Base/Input/MouseDevice.cs
  27. 43
      src/Avalonia.Base/Input/PenDevice.cs
  28. 30
      src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs
  29. 31
      src/Avalonia.Base/Input/Pointer.cs
  30. 10
      src/Avalonia.Base/Input/PointerEventArgs.cs
  31. 2
      src/Avalonia.Base/Input/Raw/IDragDropDevice.cs
  32. 4
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  33. 66
      src/Avalonia.Base/Input/TouchDevice.cs
  34. 4
      src/Avalonia.Base/Layout/ILayoutManager.cs
  35. 5
      src/Avalonia.Base/Layout/LayoutManager.cs
  36. 6
      src/Avalonia.Base/Media/BoxShadow.cs
  37. 6
      src/Avalonia.Base/Media/BoxShadows.cs
  38. 5
      src/Avalonia.Base/Media/Brush.cs
  39. 7
      src/Avalonia.Base/Media/Color.cs
  40. 2
      src/Avalonia.Base/Media/Effects/EffectAnimator.cs
  41. 37
      src/Avalonia.Base/Media/MediaContext.Clock.cs
  42. 3
      src/Avalonia.Base/Media/MediaContext.Compositor.cs
  43. 12
      src/Avalonia.Base/Media/MediaContext.cs
  44. 6
      src/Avalonia.Base/Media/Transform.cs
  45. 9
      src/Avalonia.Base/Platform/DefaultPlatformSettings.cs
  46. 12
      src/Avalonia.Base/Platform/IPlatformSettings.cs
  47. 3
      src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
  48. 7
      src/Avalonia.Base/Point.cs
  49. 5
      src/Avalonia.Base/Rect.cs
  50. 7
      src/Avalonia.Base/RelativePoint.cs
  51. 4
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  52. 5
      src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs
  53. 10
      src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs
  54. 4
      src/Avalonia.Base/Rendering/IRenderRoot.cs
  55. 6
      src/Avalonia.Base/Rendering/IRenderer.cs
  56. 4
      src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
  57. 7
      src/Avalonia.Base/Size.cs
  58. 7
      src/Avalonia.Base/Thickness.cs
  59. 24
      src/Avalonia.Base/Utilities/ThrowHelper.cs
  60. 7
      src/Avalonia.Base/Vector.cs
  61. 19
      src/Avalonia.Base/VisualTree/VisualExtensions.cs
  62. 30
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  63. 12
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  64. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  65. 16
      src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs
  66. 23
      src/Avalonia.Controls/Application.cs
  67. 2
      src/Avalonia.Controls/ContextMenu.cs
  68. 2
      src/Avalonia.Controls/Control.cs
  69. 19
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  70. 3
      src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs
  71. 5
      src/Avalonia.Controls/ListBox.cs
  72. 2
      src/Avalonia.Controls/ListBoxItem.cs
  73. 3
      src/Avalonia.Controls/MaskedTextBox.cs
  74. 12
      src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs
  75. 8
      src/Avalonia.Controls/Primitives/OverlayPopupHost.cs
  76. 2
      src/Avalonia.Controls/Primitives/Thumb.cs
  77. 3
      src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs
  78. 116
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs
  79. 365
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  80. 2
      src/Avalonia.Controls/SelectableTextBlock.cs
  81. 61
      src/Avalonia.Controls/TextBox.cs
  82. 47
      src/Avalonia.Controls/ToggleSwitch.cs
  83. 17
      src/Avalonia.Controls/TopLevel.cs
  84. 5
      src/Avalonia.Controls/TreeView.cs
  85. 8
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  86. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  87. 8
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  88. 18
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  89. 34
      src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs
  90. 20
      src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
  91. 13
      src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml
  92. 82
      tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs
  93. 76
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  94. 24
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
  95. 14
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  96. 1
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  97. 10
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  98. 6
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  99. 1
      tests/Avalonia.UnitTests/TestRoot.cs
  100. 20
      tests/Avalonia.UnitTests/TouchTestHelper.cs

2
packages/Avalonia/Avalonia.csproj

@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.BuildServices" Version="0.0.16" />
<PackageReference Include="Avalonia.BuildServices" Version="0.0.19" />
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets>

1
packages/Avalonia/Avalonia.targets

@ -1,3 +1,4 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.targets"/>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaPrivateApis.targets" />
</Project>

26
packages/Avalonia/AvaloniaPrivateApis.targets

@ -0,0 +1,26 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="RemoveAvaloniaRefAssemblies" AfterTargets="ResolveTargetingPackAssets" Condition="'$(AvaloniaAccessUnstablePrivateApis.ToLowerInvariant())'=='true'">
<ItemGroup>
<ReferencesToRemove Include="@(Reference)" Condition="'%(Reference.NuGetPackageId)' == 'Avalonia'" />
</ItemGroup>
<ItemGroup>
<Reference Remove="@(ReferencesToRemove)" />
</ItemGroup>
</Target>
<Target Name="PreventPackingAvaloniaUnstableApis" BeforeTargets="Pack">
<Error Condition="'$(IsPackable.ToLowerInvariant())' == 'true' and '$(AvaloniaAccessUnstablePrivateApis.ToLowerInvariant())' == 'true' and '$(Avalonia_I_Want_To_Use_Private_Apis_In_Nuget_Package_And_Promise_To_Pin_The_Exact_Avalonia_Version_In_Package_Dependency)' != 'true'"
Text="It seems that you are using private APIs in a nuget package, please follow this guide https://github.com/AvaloniaUI/Avalonia/wiki/Using-private-apis-in-nuget-packages" />
</Target>
<Target Name="AddReferencePathsToRealAvaloniaAssemblies" BeforeTargets="CoreCompile" Condition="'$(AvaloniaAccessUnstablePrivateApis.ToLowerInvariant())'=='true'">
<PropertyGroup>
<AvaloniaUnstableApiFrameworkToUse>net6.0</AvaloniaUnstableApiFrameworkToUse>
<AvaloniaUnstableApiFrameworkToUse Condition="$(TargetFramework.StartsWith('net4')) == 'true' or $(TargetFramework.StartsWith('net5')) == 'true' or $(TargetFramework.StartsWith('netsta')) == 'true' or $(TargetFramework.StartsWith('netcore')) == 'true'">netstandard2.0</AvaloniaUnstableApiFrameworkToUse>
</PropertyGroup>
<ItemGroup>
<Reference Include="$(MSBuildThisFileDirectory)/../lib/$(AvaloniaUnstableApiFrameworkToUse)/*.dll"/>
<ReferencePath Include="$(MSBuildThisFileDirectory)/../lib/$(AvaloniaUnstableApiFrameworkToUse)/*.dll"/>
<ReferencePathWithRefAssemblies Include="$(MSBuildThisFileDirectory)/../lib/$(AvaloniaUnstableApiFrameworkToUse)/*.dll"/>
</ItemGroup>
<Warning Text="AvaloniaAccessUnstablePrivateApis is Enabled: This means you are using unstable internal APIs, and your code may be depending on APIs which may change or be removed in future versions of Avalonia. Set AvaloniaAccessUnstablePrivateApis to 'False' to disable this warning." />
</Target>
</Project>

5
samples/RenderDemo/Pages/CustomStringAnimator.cs

@ -1,9 +1,10 @@
using Avalonia.Animation;
using System;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
namespace RenderDemo.Pages
{
public class CustomStringAnimator : CustomAnimatorBase<string>
public class CustomStringAnimator : InterpolatingAnimator<string>
{
public override string Interpolate(double progress, string oldValue, string newValue)
{

94
src/Avalonia.Base/Animation/Animation.AnimatorRegistry.cs

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using Avalonia.Animation.Animators;
using Avalonia.Media;
namespace Avalonia.Animation;
partial class Animation
{
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
[Obsolete("CustomAnimatorBase will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, ICustomAnimator value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)>
Animators = new()
{
(prop =>(typeof(double).IsAssignableFrom(prop.PropertyType) && typeof(Transform).IsAssignableFrom(prop.OwnerType)),
typeof(TransformAnimator), () => new TransformAnimator()),
(prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator()),
(prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator()),
(prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator()),
(prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator()),
(prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator()),
(prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator()),
(prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator()),
(prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator()),
(prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator()),
(prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator()),
(prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator()),
};
static Animation()
{
RegisterAnimator<IEffect?, EffectAnimator>();
RegisterAnimator<BoxShadow, BoxShadowAnimator>();
RegisterAnimator<BoxShadows, BoxShadowsAnimator>();
RegisterAnimator<IBrush?, BaseBrushAnimator>();
RegisterAnimator<CornerRadius, CornerRadiusAnimator>();
RegisterAnimator<Color, ColorAnimator>();
RegisterAnimator<Vector, VectorAnimator>();
RegisterAnimator<Point, PointAnimator>();
RegisterAnimator<Rect, RectAnimator>();
RegisterAnimator<RelativePoint, RelativePointAnimator>();
RegisterAnimator<Size, SizeAnimator>();
RegisterAnimator<Thickness, ThicknessAnimator>();
}
/// <summary>
/// Registers a <see cref="Animator{T}"/> that can handle
/// a value type that matches the specified condition.
/// </summary>
static void RegisterAnimator<T, TAnimator>()
where TAnimator : Animator<T>, new()
{
Animators.Insert(0,
(prop => typeof(T).IsAssignableFrom(prop.PropertyType), typeof(TAnimator), () => new TAnimator()));
}
public static void RegisterCustomAnimator<T, TAnimator>() where TAnimator : InterpolatingAnimator<T>, new()
{
Animators.Insert(0, (prop => typeof(T).IsAssignableFrom(prop.PropertyType),
typeof(InterpolatingAnimator<T>.AnimatorWrapper), () => new TAnimator().CreateWrapper()));
}
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
return (type, factory);
}
}
return null;
}
}

59
src/Avalonia.Base/Animation/Animation.cs

@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Reactive;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Data;
using Avalonia.Metadata;
@ -16,7 +13,7 @@ namespace Avalonia.Animation
/// <summary>
/// Tracks the progress of an animation.
/// </summary>
public sealed class Animation : AvaloniaObject, IAnimation
public sealed partial class Animation : AvaloniaObject, IAnimation
{
/// <summary>
/// Defines the <see cref="Duration"/> property.
@ -195,60 +192,6 @@ namespace Avalonia.Animation
return null;
}
/// <summary>
/// Sets the value of the Animator attached property for a setter.
/// </summary>
/// <param name="setter">The animation setter.</param>
/// <param name="value">The property animator value.</param>
public static void SetAnimator(IAnimationSetter setter, CustomAnimatorBase value)
{
s_animators[setter] = (value.WrapperType, value.CreateWrapper);
}
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator, Func<IAnimator> Factory)> Animators = new()
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
};
/// <summary>
/// Registers a <see cref="Animator{T}"/> that can handle
/// a value type that matches the specified condition.
/// </summary>
/// <param name="condition">
/// The condition to which the <see cref="Animator{T}"/>
/// is to be activated and used.
/// </param>
/// <typeparam name="TAnimator">
/// The type of the animator to instantiate.
/// </typeparam>
internal static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
where TAnimator : IAnimator, new()
{
Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
private static (Type Type, Func<IAnimator> Factory)? GetAnimatorType(AvaloniaProperty property)
{
foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
return (type, factory);
}
}
return null;
}
private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{
var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>();

30
src/Avalonia.Base/Animation/ICustomAnimator.cs

@ -1,14 +1,15 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation;
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase
{
internal abstract IAnimator CreateWrapper();
internal abstract Type WrapperType { get; }
}
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
@ -25,6 +26,33 @@ public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}
public interface ICustomAnimator
{
internal IAnimator CreateWrapper();
internal Type WrapperType { get; }
}
public abstract class InterpolatingAnimator<T> : ICustomAnimator
{
public abstract T Interpolate(double progress, T oldValue, T newValue);
Type ICustomAnimator.WrapperType => typeof(AnimatorWrapper);
IAnimator ICustomAnimator.CreateWrapper() => new AnimatorWrapper(this);
internal IAnimator CreateWrapper() => new AnimatorWrapper(this);
internal class AnimatorWrapper : Animator<T>
{
private readonly InterpolatingAnimator<T> _parent;
public AnimatorWrapper(InterpolatingAnimator<T> parent)
{
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) => _parent.Interpolate(progress, oldValue, newValue);
}
}

28
src/Avalonia.Base/Animation/InterpolatingTransitionBase.cs

@ -0,0 +1,28 @@
using System;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation;
/// <summary>
/// The base class for user-defined transition that are doing simple value interpolation
/// </summary>
public abstract class InterpolatingTransitionBase<T> : Transition<T>
{
class Animator : Animator<T>
{
private readonly InterpolatingTransitionBase<T> _parent;
public Animator(InterpolatingTransitionBase<T> parent)
{
_parent = parent;
}
public override T Interpolate(double progress, T oldValue, T newValue) =>
_parent.Interpolate(progress, oldValue, newValue);
}
protected abstract T Interpolate(double progress, T from, T to);
internal override IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue) =>
new AnimatorTransitionObservable<T, Animator>(new Animator(this), progress, Easing, oldValue, newValue);
}

7
src/Avalonia.Base/CornerRadius.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct CornerRadius : IEquatable<CornerRadius>
{
static CornerRadius()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<CornerRadiusAnimator>(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
#endif
}
public CornerRadius(double uniformRadius)
{
TopLeft = TopRight = BottomLeft = BottomRight = uniformRadius;

37
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs

@ -0,0 +1,37 @@
namespace Avalonia.Input.GestureRecognizers
{
public abstract class GestureRecognizer : StyledElement
{
protected internal IInputElement? Target { get; internal set; }
protected abstract void PointerPressed(PointerPressedEventArgs e);
protected abstract void PointerReleased(PointerReleasedEventArgs e);
protected abstract void PointerMoved(PointerEventArgs e);
protected abstract void PointerCaptureLost(IPointer pointer);
internal void PointerPressedInternal(PointerPressedEventArgs e)
{
PointerPressed(e);
}
internal void PointerReleasedInternal(PointerReleasedEventArgs e)
{
PointerReleased(e);
}
internal void PointerMovedInternal(PointerEventArgs e)
{
PointerMoved(e);
}
internal void PointerCaptureLostInternal(IPointer pointer)
{
PointerCaptureLost(pointer);
}
protected void Capture(IPointer pointer)
{
(pointer as Pointer)?.CaptureGestureRecognizer(this);
}
}
}

73
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@ -6,29 +6,26 @@ using Avalonia.Reactive;
namespace Avalonia.Input.GestureRecognizers
{
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher
public class GestureRecognizerCollection : IReadOnlyCollection<GestureRecognizer>
{
private readonly IInputElement _inputElement;
private List<IGestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
private List<GestureRecognizer>? _recognizers;
public GestureRecognizerCollection(IInputElement inputElement)
{
_inputElement = inputElement;
}
public void Add(IGestureRecognizer recognizer)
public void Add(GestureRecognizer recognizer)
{
if (_recognizers == null)
{
// We initialize the collection when the first recognizer is added
_recognizers = new List<IGestureRecognizer>();
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>();
_recognizers = new List<GestureRecognizer>();
}
_recognizers.Add(recognizer);
recognizer.Initialize(_inputElement, this);
recognizer.Target = _inputElement;
// Hacks to make bindings work
@ -41,25 +38,22 @@ namespace Avalonia.Input.GestureRecognizers
}
}
static readonly List<IGestureRecognizer> s_Empty = new List<IGestureRecognizer>();
static readonly List<GestureRecognizer> s_Empty = new List<GestureRecognizer>();
public IEnumerator<IGestureRecognizer> GetEnumerator()
public IEnumerator<GestureRecognizer> GetEnumerator()
=> _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => _recognizers?.Count ?? 0;
internal bool HandlePointerPressed(PointerPressedEventArgs e)
{
if (_recognizers == null)
return false;
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerPressed(e);
r.PointerPressedInternal(e);
}
return e.Handled;
@ -69,17 +63,15 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
var pointer = e.Pointer as Pointer;
foreach (var r in _recognizers)
{
capture.PointerReleased(e);
if (pointer?.CapturedGestureRecognizer != null)
break;
r.PointerReleasedInternal(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerReleased(e);
}
return e.Handled;
}
@ -87,41 +79,16 @@ namespace Avalonia.Input.GestureRecognizers
{
if (_recognizers == null)
return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
{
capture.PointerMoved(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerMoved(e);
}
return e.Handled;
}
var pointer = e.Pointer as Pointer;
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (_recognizers == null)
return;
_pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers)
{
r.PointerCaptureLost(e.Pointer);
}
}
if (pointer?.CapturedGestureRecognizer != null)
break;
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
{
pointer.Capture(_inputElement);
_pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers!)
{
if (r != recognizer)
r.PointerCaptureLost(pointer);
r.PointerMovedInternal(e);
}
return e.Handled;
}
}
}

23
src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs

@ -1,23 +0,0 @@
namespace Avalonia.Input.GestureRecognizers
{
public interface IGestureRecognizer
{
void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions);
void PointerPressed(PointerPressedEventArgs e);
void PointerReleased(PointerReleasedEventArgs e);
void PointerMoved(PointerEventArgs e);
void PointerCaptureLost(IPointer pointer);
}
public interface IGestureRecognizerActionsDispatcher
{
void Capture(IPointer pointer, IGestureRecognizer recognizer);
}
public enum GestureRecognizerResult
{
None,
Capture,
ReleaseCapture
}
}

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

@ -2,10 +2,8 @@
namespace Avalonia.Input
{
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
public class PinchGestureRecognizer : GestureRecognizer
{
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance;
private IPointer? _firstContact;
private Point _firstPoint;
@ -13,12 +11,6 @@ namespace Avalonia.Input
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);
@ -29,14 +21,14 @@ namespace Avalonia.Input
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
RemoveContact(pointer);
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (_target != null && _target is Visual visual)
if (Target != null && Target is Visual visual)
{
if(_firstContact == e.Pointer)
{
@ -58,16 +50,16 @@ namespace Avalonia.Input
var scale = distance / _initialDistance;
var pinchEventArgs = new PinchEventArgs(scale, _origin);
_target?.RaiseEvent(pinchEventArgs);
Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled;
}
}
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
if (_firstContact == null)
{
@ -92,13 +84,13 @@ namespace Avalonia.Input
_origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_actions!.Capture(_firstContact, this);
_actions!.Capture(_secondContact, this);
Capture(_firstContact);
Capture(_secondContact);
}
}
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
RemoveContact(e.Pointer);
}
@ -118,7 +110,7 @@ namespace Avalonia.Input
_secondContact = null;
}
_target?.RaiseEvent(new PinchEndedEventArgs());
Target?.RaiseEvent(new PinchEndedEventArgs());
}
}

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

@ -1,15 +1,11 @@
using System;
using System.Diagnostics;
using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input
{
public class PullGestureRecognizer : StyledElement, IGestureRecognizer
public class PullGestureRecognizer : GestureRecognizer
{
internal static int MinPullDetectionSize = 50;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition;
private int _gestureId;
private IPointer? _tracking;
@ -34,13 +30,7 @@ namespace Avalonia.Input
public PullGestureRecognizer() { }
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
@ -48,12 +38,13 @@ namespace Avalonia.Input
}
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (_tracking == e.Pointer && _target is Visual visual)
if (_tracking == e.Pointer && Target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
_actions!.Capture(e.Pointer, this);
Capture(e.Pointer);
e.PreventGestureRecognition();
Vector delta = default;
switch (PullDirection)
@ -86,15 +77,15 @@ namespace Avalonia.Input
_pullInProgress = true;
var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
_target?.RaiseEvent(pullEventArgs);
Target?.RaiseEvent(pullEventArgs);
e.Handled = pullEventArgs.Handled;
}
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
var position = e.GetPosition(visual);
@ -127,7 +118,7 @@ namespace Avalonia.Input
}
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer && _pullInProgress)
{
@ -141,7 +132,7 @@ namespace Avalonia.Input
_initialPosition = default;
_pullInProgress = false;
_target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection));
}
}
}

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

@ -4,7 +4,7 @@ using Avalonia.Threading;
namespace Avalonia.Input.GestureRecognizers
{
public class ScrollGestureRecognizer : AvaloniaObject, IGestureRecognizer
public class ScrollGestureRecognizer : GestureRecognizer
{
// Pixels per second speed that is considered to be the stop of inertial scroll
internal const double InertialScrollSpeedEnd = 5;
@ -18,8 +18,6 @@ namespace Avalonia.Input.GestureRecognizers
private bool _scrolling;
private Point _trackedRootPoint;
private IPointer? _tracking;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private int _gestureId;
private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker;
@ -91,15 +89,9 @@ namespace Avalonia.Input.GestureRecognizers
{
get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
public void PointerPressed(PointerPressedEventArgs e)
protected override void PointerPressed(PointerPressedEventArgs e)
{
if (e.Pointer.IsPrimary &&
(e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
@ -107,15 +99,15 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture();
_tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target);
_trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)Target);
}
}
public void PointerMoved(PointerEventArgs e)
protected override void PointerMoved(PointerEventArgs e)
{
if (e.Pointer == _tracking)
{
var rootPoint = e.GetPosition((Visual?)_target);
var rootPoint = e.GetPosition((Visual?)Target);
if (!_scrolling)
{
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance)
@ -131,7 +123,9 @@ namespace Avalonia.Input.GestureRecognizers
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
_actions!.Capture(e.Pointer, this);
Capture(e.Pointer);
e.PreventGestureRecognition();
}
}
@ -143,13 +137,13 @@ namespace Avalonia.Input.GestureRecognizers
_lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true;
}
}
}
public void PointerCaptureLost(IPointer pointer)
protected override void PointerCaptureLost(IPointer pointer)
{
if (pointer == _tracking) EndGesture();
}
@ -161,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers
{
_inertia = default;
_scrolling = false;
_target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0;
_lastMoveTimestamp = null;
}
@ -169,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers
}
public void PointerReleased(PointerReleasedEventArgs e)
protected override void PointerReleased(PointerReleasedEventArgs e)
{
if (e.Pointer == _tracking && _scrolling)
{
@ -188,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers
var savedGestureId = _gestureId;
var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero;
_target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
DispatcherTimer.Run(() =>
{
// Another gesture has started, finish the current one
@ -203,7 +197,7 @@ namespace Avalonia.Input.GestureRecognizers
var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds;
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
_target!.RaiseEvent(scrollGestureEventArgs);
Target!.RaiseEvent(scrollGestureEventArgs);
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture)
{

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

@ -182,7 +182,7 @@ namespace Avalonia.Input
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
if (settings != null)
{
@ -221,7 +221,7 @@ namespace Avalonia.Input
e.Source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
@ -260,10 +260,10 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target))
{
if (e.Pointer == s_lastPointer)
if (e.Pointer == s_lastPointer && ev.Source is Interactive i)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
@ -273,7 +273,7 @@ namespace Avalonia.Input
return;
}
if (s_isHolding && ev.Source is Interactive i)
if (s_isHolding)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type));
}

11
src/Avalonia.Base/Input/IInputRoot.cs

@ -1,4 +1,5 @@
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Input
{
@ -17,10 +18,18 @@ namespace Avalonia.Input
/// Gets focus manager of the root.
/// </summary>
/// <remarks>
/// Focus manager can be null only if application wasn't initialized yet.
/// Focus manager can be null only if window wasn't initialized yet.
/// </remarks>
IFocusManager? FocusManager { get; }
/// <summary>
/// Represents a contract for accessing top-level platform-specific settings.
/// </summary>
/// <remarks>
/// PlatformSettings can be null only if window wasn't initialized yet.
/// </remarks>
IPlatformSettings? PlatformSettings { get; }
/// <summary>
/// Gets or sets the input element that the pointer is currently over.
/// </summary>

2
src/Avalonia.Base/Input/IKeyboardDevice.cs

@ -43,7 +43,7 @@ namespace Avalonia.Input
PenBarrelButton = 2048
}
[NotClientImplementable]
[PrivateApi]
public interface IKeyboardDevice : IInputDevice
{
}

2
src/Avalonia.Base/Input/IMouseDevice.cs

@ -5,7 +5,7 @@ namespace Avalonia.Input
/// <summary>
/// Represents a mouse device.
/// </summary>
[NotClientImplementable]
[PrivateApi]
public interface IMouseDevice : IPointerDevice
{
}

5
src/Avalonia.Base/Input/IPenDevice.cs

@ -1,8 +1,11 @@
namespace Avalonia.Input
using Avalonia.Metadata;
namespace Avalonia.Input
{
/// <summary>
/// Represents a pen/stylus device.
/// </summary>
[PrivateApi]
public interface IPenDevice : IPointerDevice
{

1
src/Avalonia.Base/Input/IPointer.cs

@ -1,3 +1,4 @@
using Avalonia.Input.GestureRecognizers;
using Avalonia.Metadata;
namespace Avalonia.Input

2
src/Avalonia.Base/Input/IPointerDevice.cs

@ -3,7 +3,7 @@ using Avalonia.Metadata;
namespace Avalonia.Input
{
[NotClientImplementable]
[PrivateApi]
public interface IPointerDevice : IInputDevice
{
/// <summary>

46
src/Avalonia.Base/Input/InputElement.cs

@ -225,6 +225,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
// Gesture only handlers
PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
}
public InputElement()
@ -583,10 +588,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerMoved(PointerEventArgs e)
{
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -595,10 +596,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerPressed(PointerPressedEventArgs e)
{
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -607,10 +604,33 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
{
if (_gestureRecognizers?.HandlePointerReleased(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerReleased(PointerReleasedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerReleased(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerPressed(PointerPressedEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
}
private void OnGesturePointerMoved(PointerEventArgs e)
{
if (!e.IsGestureRecognitionSkipped)
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
}
/// <summary>
@ -619,7 +639,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param>
protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{
_gestureRecognizers?.HandlePointerCaptureLost(e);
}
/// <summary>

2
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -5,8 +5,6 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
public IKeyboardDevice? Device { get; init; }
public Key Key { get; init; }
public KeyModifiers KeyModifiers { get; init; }

2
src/Avalonia.Base/Input/KeyboardDevice.cs

@ -192,7 +192,6 @@ namespace Avalonia.Input
KeyEventArgs ev = new KeyEventArgs
{
RoutedEvent = routedEvent,
Device = this,
Key = keyInput.Key,
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element,
@ -241,7 +240,6 @@ namespace Avalonia.Input
{
var ev = new TextInputEventArgs()
{
Device = this,
Text = text.Text,
Source = element,
RoutedEvent = InputElement.TextInputEvent

33
src/Avalonia.Base/Input/MouseDevice.cs

@ -2,9 +2,12 @@ using System;
using System.Collections.Generic;
using Avalonia.Reactive;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using Avalonia.Input.GestureRecognizers;
#pragma warning disable CS0618
namespace Avalonia.Input
@ -126,9 +129,10 @@ namespace Avalonia.Input
if (source != null)
{
_pointer.Capture(source);
if (source != null)
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null)
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse);
@ -141,11 +145,12 @@ namespace Avalonia.Input
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
}
return false;
@ -158,17 +163,21 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest;
var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is object)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerMovedInternal(e);
else
source.RaiseEvent(e);
return e.Handled;
}
return false;
}
@ -178,15 +187,19 @@ namespace Avalonia.Input
device = device ?? throw new ArgumentNullException(nameof(device));
root = root ?? throw new ArgumentNullException(nameof(root));
var source = _pointer.Captured ?? hitTest;
var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
_lastMouseDownButton);
source?.RaiseEvent(e);
if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerReleasedInternal(e);
else
source?.RaiseEvent(e);
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}

43
src/Avalonia.Base/Input/PenDevice.cs

@ -2,9 +2,13 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
#pragma warning disable CS0618
namespace Avalonia.Input
@ -80,19 +84,23 @@ namespace Avalonia.Input
if (source != null)
{
pointer.Capture(source);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds ?? 500;
var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Pen) ?? new Size(4, 4);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null)
{
_clickCount = 0;
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Pen).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Pen);
if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
++_clickCount;
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
_lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton();
var e = new PointerPressedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
@ -107,14 +115,17 @@ namespace Avalonia.Input
KeyModifiers inputModifiers, IInputElement? hitTest,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints)
{
var source = pointer.Captured ?? hitTest;
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
p, timestamp, properties, inputModifiers, intermediatePoints);
source.RaiseEvent(e);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerMovedInternal(e);
else
source.RaiseEvent(e);
return e.Handled;
}
@ -125,15 +136,19 @@ namespace Avalonia.Input
IInputElement root, Point p, PointerPointProperties properties,
KeyModifiers inputModifiers, IInputElement? hitTest)
{
var source = pointer.Captured ?? hitTest;
var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest;
if (source is not null)
{
var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
_lastMouseDownButton);
source.RaiseEvent(e);
if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer)
gestureRecognizer.PointerReleasedInternal(e);
else
source.RaiseEvent(e);
pointer.Capture(null);
pointer.CaptureGestureRecognizer(null);
_lastMouseDownButton = default;
return e.Handled;
}

30
src/Avalonia.Base/Input/Platform/PlatformHotkeyConfiguration.cs

@ -1,16 +1,20 @@
using System.Collections.Generic;
#nullable enable
using Avalonia.Metadata;
namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
/// <summary>
/// The PlatformHotkeyConfiguration class represents a configuration for platform-specific hotkeys in an Avalonia application.
/// </summary>
public sealed class PlatformHotkeyConfiguration
{
[PrivateApi]
public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
[PrivateApi]
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
@ -85,6 +89,22 @@ namespace Avalonia.Input.Platform
{
new KeyGesture(Key.Left, KeyModifiers.Alt)
};
PageLeft = new List<KeyGesture>
{
new KeyGesture(Key.PageUp, KeyModifiers.Shift)
};
PageRight = new List<KeyGesture>
{
new KeyGesture(Key.PageDown, KeyModifiers.Shift)
};
PageUp = new List<KeyGesture>
{
new KeyGesture(Key.PageUp)
};
PageDown = new List<KeyGesture>
{
new KeyGesture(Key.PageDown)
};
}
public KeyModifiers CommandModifiers { get; set; }
@ -106,5 +126,9 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List<KeyGesture> OpenContextMenu { get; set; }
public List<KeyGesture> Back { get; set; }
public List<KeyGesture> PageUp { get; set; }
public List<KeyGesture> PageDown { get; set; }
public List<KeyGesture> PageRight { get; set; }
public List<KeyGesture> PageLeft { get; set; }
}
}

31
src/Avalonia.Base/Input/Pointer.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.GestureRecognizers;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -52,6 +53,9 @@ namespace Avalonia.Input
if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
CaptureGestureRecognizer(null);
}
static IInputElement? GetNextCapture(Visual parent)
@ -69,6 +73,31 @@ namespace Avalonia.Input
public PointerType Type { get; }
public bool IsPrimary { get; }
public void Dispose() => Capture(null);
/// <summary>
/// Gets the gesture recognizer that is currently capturing by the pointer, if any.
/// </summary>
internal GestureRecognizer? CapturedGestureRecognizer { get; private set; }
public void Dispose()
{
Capture(null);
}
/// <summary>
/// Captures pointer input to the specified gesture recognizer.
/// </summary>
/// <param name="gestureRecognizer">The gesture recognizer.</param>
/// </remarks>
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
CapturedGestureRecognizer?.PointerCaptureLostInternal(this);
if (gestureRecognizer != null)
Capture(null);
CapturedGestureRecognizer = gestureRecognizer;
}
}
}

10
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -58,6 +58,8 @@ namespace Avalonia.Input
/// </summary>
public ulong Timestamp { get; }
internal bool IsGestureRecognitionSkipped { get; private set; }
/// <summary>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary>
@ -121,6 +123,14 @@ namespace Avalonia.Input
return points;
}
/// <summary>
/// Prevents this event from being handled by other gesture recognizers in the route
/// </summary>
public void PreventGestureRecognition()
{
IsGestureRecognitionSkipped = true;
}
/// <summary>
/// Returns the current pointer point properties
/// </summary>

2
src/Avalonia.Base/Input/Raw/IDragDropDevice.cs

@ -2,7 +2,7 @@
namespace Avalonia.Input.Raw
{
[NotClientImplementable]
[PrivateApi]
public interface IDragDropDevice : IInputDevice
{
}

4
src/Avalonia.Base/Input/TextInputEventArgs.cs

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

66
src/Avalonia.Base/Input/TouchDevice.cs

@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.VisualTree;
#pragma warning disable CS0618
namespace Avalonia.Input
@ -49,6 +52,7 @@ namespace Avalonia.Input
}
var target = pointer.Captured ?? args.Root;
var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers();
@ -62,19 +66,23 @@ namespace Avalonia.Input
}
else
{
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > doubleClickTime)
var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null)
{
_clickCount = 0;
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds;
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > doubleClickTime)
{
_clickCount = 0;
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
++_clickCount;
_lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(doubleClickSize.Width / 2, doubleClickSize.Height / 2));
}
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
@ -88,10 +96,19 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId);
using (pointer)
{
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
(Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left));
target = gestureTarget ?? target;
var e = new PointerReleasedEventArgs(target, pointer,
(Visual)args.Root, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
keyModifier, MouseButton.Left);
if (gestureTarget != null)
{
pointer?.CapturedGestureRecognizer?.PointerReleasedInternal(e);
}
else
{
target.RaiseEvent(e);
}
}
}
@ -99,15 +116,28 @@ namespace Avalonia.Input
{
_pointers.Remove(args.RawPointerId);
using (pointer)
pointer.Capture(null);
{
pointer?.Capture(null);
pointer?.CaptureGestureRecognizer(null);
}
}
if (args.Type == RawPointerEventType.TouchUpdate)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
target = gestureTarget ?? target;
var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer!, (Visual)args.Root,
args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
keyModifier, args.IntermediatePoints));
keyModifier, args.IntermediatePoints);
if (gestureTarget != null)
{
pointer?.CapturedGestureRecognizer?.PointerMovedInternal(e);
}
else
{
target.RaiseEvent(e);
}
}
}

4
src/Avalonia.Base/Layout/ILayoutManager.cs

@ -6,8 +6,8 @@ namespace Avalonia.Layout
/// <summary>
/// Manages measuring and arranging of controls.
/// </summary>
[NotClientImplementable]
internal interface ILayoutManager : IDisposable
[PrivateApi]
public interface ILayoutManager : IDisposable
{
/// <summary>
/// Raised when the layout manager completes a layout pass.

5
src/Avalonia.Base/Layout/LayoutManager.cs

@ -2,9 +2,9 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.Utilities;
@ -16,7 +16,8 @@ namespace Avalonia.Layout
/// <summary>
/// Manages measuring and arranging of controls.
/// </summary>
internal class LayoutManager : ILayoutManager, IDisposable
[PrivateApi]
public class LayoutManager : ILayoutManager, IDisposable
{
private const int MaxPasses = 10;
private readonly Layoutable _owner;

6
src/Avalonia.Base/Media/BoxShadow.cs

@ -16,12 +16,6 @@ namespace Avalonia.Media
public Color Color { get; set; }
public bool IsInset { get; set; }
static BoxShadow()
{
Animation.Animation.RegisterAnimator<BoxShadowAnimator>(prop =>
typeof(BoxShadow).IsAssignableFrom(prop.PropertyType));
}
public bool Equals(in BoxShadow other)
{
return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color);

6
src/Avalonia.Base/Media/BoxShadows.cs

@ -10,12 +10,6 @@ namespace Avalonia.Media
private readonly BoxShadow _first;
private readonly BoxShadow[]? _list;
public int Count { get; }
static BoxShadows()
{
Animation.Animation.RegisterAnimator<BoxShadowsAnimator>(prop =>
typeof(BoxShadows).IsAssignableFrom(prop.PropertyType));
}
public BoxShadows(BoxShadow shadow)
{

5
src/Avalonia.Base/Media/Brush.cs

@ -34,11 +34,6 @@ namespace Avalonia.Media
/// </summary>
public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
AvaloniaProperty.Register<Brush, RelativePoint>(nameof(TransformOrigin));
static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// Gets or sets the opacity of the brush.

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

@ -25,13 +25,6 @@ namespace Avalonia.Media
{
private const double byteToDouble = 1.0 / 255;
static Color()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<ColorAnimator>(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// Gets the Alpha component of the color.
/// </summary>

2
src/Avalonia.Base/Media/Effects/EffectAnimator.cs

@ -63,8 +63,6 @@ internal class EffectAnimator : Animator<IEffect?>
if(s_Registered)
return;
s_Registered = true;
Animation.RegisterAnimator<EffectAnimator>(prop =>
typeof(IEffect).IsAssignableFrom(prop.PropertyType));
}
}

37
src/Avalonia.Base/Media/MediaContext.Clock.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using Avalonia.Animation;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Media;
@ -17,8 +18,12 @@ internal partial class MediaContext
{
private readonly MediaContext _parent;
private List<IObserver<TimeSpan>> _observers = new();
public bool HasNewSubscriptions { get; set; }
public bool HasSubscriptions => _observers.Count > 0;
private List<IObserver<TimeSpan>> _newObservers = new();
private Queue<Action<TimeSpan>> _queuedAnimationFrames = new();
private Queue<Action<TimeSpan>> _queuedAnimationFramesNext = new();
private TimeSpan _currentAnimationTimestamp;
public bool HasNewSubscriptions => _newObservers.Count > 0;
public bool HasSubscriptions => _observers.Count > 0 || _queuedAnimationFrames.Count > 0;
public MediaContextClock(MediaContext parent)
{
@ -29,19 +34,41 @@ internal partial class MediaContext
{
_parent.ScheduleRender(false);
Dispatcher.UIThread.VerifyAccess();
HasNewSubscriptions = true;
_observers.Add(observer);
_newObservers.Add(observer);
return Disposable.Create(() =>
{
Dispatcher.UIThread.VerifyAccess();
_observers.Remove(observer);
});
}
public void RequestAnimationFrame(Action<TimeSpan> action)
{
_parent.ScheduleRender(false);
_queuedAnimationFrames.Enqueue(action);
}
public void Pulse(TimeSpan now)
{
_newObservers.Clear();
_currentAnimationTimestamp = now;
// We are swapping the queues before enumeration
(_queuedAnimationFrames, _queuedAnimationFramesNext) = (_queuedAnimationFramesNext, _queuedAnimationFrames);
var animationFrames = _queuedAnimationFramesNext;
while (animationFrames.TryDequeue(out var callback))
callback(now);
foreach (var observer in _observers.ToArray())
observer.OnNext(now);
observer.OnNext(_currentAnimationTimestamp);
}
public void PulseNewSubscriptions()
{
foreach (var observer in _newObservers.ToArray())
observer.OnNext(_currentAnimationTimestamp);
_newObservers.Clear();
}
public PlayState PlayState
@ -50,4 +77,6 @@ internal partial class MediaContext
set => throw new InvalidOperationException();
}
}
public void RequestAnimationFrame(Action<TimeSpan> action) => _clock.RequestAnimationFrame(action);
}

3
src/Avalonia.Base/Media/MediaContext.Compositor.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform;
using Avalonia.Rendering.Composition;
@ -78,7 +79,7 @@ partial class MediaContext
// Nothing to do, and there are no pending commits
return false;
foreach (var c in _requestedCommits)
foreach (var c in _requestedCommits.ToArray())
CommitCompositor(c);
_requestedCommits.Clear();

12
src/Avalonia.Base/Media/MediaContext.cs

@ -131,12 +131,11 @@ internal partial class MediaContext : ICompositorScheduler
// We are doing several iterations when it happens
for (var c = 0; c < 10; c++)
{
_clock.HasNewSubscriptions = false;
FireInvokeOnRenderCallbacks();
if (_clock.HasNewSubscriptions)
{
_clock.Pulse(now);
_clock.PulseNewSubscriptions();
continue;
}
@ -212,7 +211,12 @@ internal partial class MediaContext : ICompositorScheduler
}
while (count > 0);
}
/// <summary>
/// Executes the <param name="callback">callback</param> in the next iteration of the current UI-thread
/// render loop / layout pass that.
/// </summary>
/// <param name="callback">Code to execute.</param>
public void BeginInvokeOnRender(Action callback)
{
if (_invokeOnRenderCallbacks == null)
@ -224,4 +228,4 @@ internal partial class MediaContext : ICompositorScheduler
if (!_isRendering)
ScheduleRender(true);
}
}
}

6
src/Avalonia.Base/Media/Transform.cs

@ -15,12 +15,6 @@ namespace Avalonia.Media
/// </summary>
public abstract class Transform : Animatable, IMutableTransform, ICompositionRenderResource<ITransform>, ICompositorSerializable
{
static Transform()
{
Animation.Animation.RegisterAnimator<TransformAnimator>(prop =>
typeof(ITransform).IsAssignableFrom(prop.OwnerType));
}
internal Transform()
{

9
src/Avalonia.Base/Platform/DefaultPlatformSettings.cs

@ -1,12 +1,16 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.VisualTree;
namespace Avalonia.Platform
{
/// <summary>
/// A default implementation of <see cref="IPlatformSettings"/> for platforms.
/// </summary>
[PrivateApi]
public class DefaultPlatformSettings : IPlatformSettings
{
public virtual Size GetTapSize(PointerType type)
@ -28,7 +32,10 @@ namespace Avalonia.Platform
public virtual TimeSpan GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500);
public virtual TimeSpan HoldWaitDuration => TimeSpan.FromMilliseconds(300);
public PlatformHotkeyConfiguration HotkeyConfiguration =>
AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
public virtual PlatformColorValues GetColorValues()
{
return new PlatformColorValues

12
src/Avalonia.Base/Platform/IPlatformSettings.cs

@ -1,10 +1,15 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Metadata;
namespace Avalonia.Platform
{
[Unstable]
/// <summary>
/// The <see cref="IPlatformSettings"/> interface represents a contract for accessing platform-specific settings and information.
/// Some of these settings might be changed by used globally in the OS in runtime.
/// </summary>
[NotClientImplementable]
public interface IPlatformSettings
{
/// <summary>
@ -33,6 +38,11 @@ namespace Avalonia.Platform
/// </summary>
TimeSpan HoldWaitDuration { get; }
/// <summary>
/// Get a configuration for platform-specific hotkeys in an Avalonia application.
/// </summary>
PlatformHotkeyConfiguration HotkeyConfiguration { get; }
/// <summary>
/// Gets current system color values including dark mode and accent colors.
/// </summary>

3
src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs

@ -64,7 +64,8 @@ internal class BclStorageFile : IStorageBookmarkFile
public Task<Stream> OpenWriteAsync()
{
return Task.FromResult<Stream>(FileInfo.OpenWrite());
var stream = new FileStream(FileInfo.FullName, FileMode.Create, FileAccess.Write, FileShare.Write);
return Task.FromResult<Stream>(stream);
}
public virtual Task<string?> SaveBookmarkAsync()

7
src/Avalonia.Base/Point.cs

@ -16,13 +16,6 @@ namespace Avalonia
#endif
readonly struct Point : IEquatable<Point>
{
static Point()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<PointAnimator>(prop => typeof(Point).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The X position.
/// </summary>

5
src/Avalonia.Base/Rect.cs

@ -11,11 +11,6 @@ namespace Avalonia
/// </summary>
public readonly struct Rect : IEquatable<Rect>
{
static Rect()
{
Animation.Animation.RegisterAnimator<RectAnimator>(prop => typeof(Rect).IsAssignableFrom(prop.PropertyType));
}
/// <summary>
/// The X position.
/// </summary>

7
src/Avalonia.Base/RelativePoint.cs

@ -54,13 +54,6 @@ namespace Avalonia
private readonly RelativeUnit _unit;
static RelativePoint()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<RelativePointAnimator>(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// Initializes a new instance of the <see cref="RelativePoint"/> struct.
/// </summary>

4
src/Avalonia.Base/Rendering/Composition/Compositor.cs

@ -130,7 +130,7 @@ namespace Avalonia.Rendering.Composition
Dispatcher.UIThread.VerifyAccess();
using var noPump = NonPumpingLockHelper.Use();
_nextCommit ??= new();
var commit = _nextCommit ??= new();
(_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead);
while (_invokeBeforeCommitRead.Count > 0)
@ -188,7 +188,7 @@ namespace Avalonia.Rendering.Composition
}, TaskContinuationOptions.ExecuteSynchronously);
_nextCommit = null;
return _pendingBatch;
return commit;
}
}

5
src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionDrawListVisual.cs

@ -14,7 +14,7 @@ namespace Avalonia.Rendering.Composition.Server;
/// <summary>
/// Server-side counterpart of <see cref="CompositionDrawListVisual"/>
/// </summary>
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual, IServerRenderResourceObserver
{
#if DEBUG
// This is needed for debugging purposes so we could see inspect the associated visual from debugger
@ -37,6 +37,7 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
{
_renderCommands?.Dispose();
_renderCommands = reader.ReadObject<ServerCompositionRenderData?>();
_renderCommands?.AddObserver(this);
}
base.DeserializeChangesCore(reader, committedAt);
}
@ -50,6 +51,8 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.RenderCore(canvas, currentTransformedClip);
}
public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();
#if DEBUG
public override string ToString()
{

10
src/Avalonia.Base/Rendering/Composition/Server/ServerRenderResource.cs

@ -13,8 +13,8 @@ internal interface IServerRenderResourceObserver
internal interface IServerRenderResource : IServerRenderResourceObserver
{
void AddObserver(IServerRenderResource observer);
void RemoveObserver(IServerRenderResource observer);
void AddObserver(IServerRenderResourceObserver observer);
void RemoveObserver(IServerRenderResourceObserver observer);
void QueuedInvalidate();
}
@ -23,7 +23,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
private bool _pendingInvalidation;
private bool _disposed;
public bool IsDisposed => _disposed;
private RefCountingSmallDictionary<IServerRenderResource> _observers;
private RefCountingSmallDictionary<IServerRenderResourceObserver> _observers;
public SimpleServerRenderResource(ServerCompositor compositor) : base(compositor)
{
@ -97,7 +97,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
}
public void AddObserver(IServerRenderResource observer)
public void AddObserver(IServerRenderResourceObserver observer)
{
Debug.Assert(!_disposed);
if(_disposed)
@ -105,7 +105,7 @@ internal class SimpleServerRenderResource : SimpleServerObject, IServerRenderRes
_observers.Add(observer);
}
public void RemoveObserver(IServerRenderResource observer)
public void RemoveObserver(IServerRenderResourceObserver observer)
{
if (_disposed)
return;

4
src/Avalonia.Base/Rendering/IRenderRoot.cs

@ -16,9 +16,9 @@ namespace Avalonia.Rendering
/// <summary>
/// Gets the renderer for the window.
/// </summary>
internal IRenderer Renderer { get; }
public IRenderer Renderer { get; }
internal IHitTester HitTester { get; }
public IHitTester HitTester { get; }
/// <summary>
/// The scaling factor to use in rendering.

6
src/Avalonia.Base/Rendering/IRenderer.cs

@ -9,7 +9,8 @@ namespace Avalonia.Rendering
/// <summary>
/// Defines the interface for a renderer.
/// </summary>
internal interface IRenderer : IDisposable
[PrivateApi]
public interface IRenderer : IDisposable
{
/// <summary>
/// Gets a value indicating whether the renderer should draw specific diagnostics.
@ -73,7 +74,8 @@ namespace Avalonia.Rendering
Compositor Compositor { get; }
}
internal interface IHitTester
[PrivateApi]
public interface IHitTester
{
/// <summary>
/// Hit tests a location to find the visuals at the specified point.

4
src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs

@ -1,11 +1,13 @@
using System;
using Avalonia.Metadata;
namespace Avalonia.Rendering
{
/// <summary>
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
/// </summary>
internal class SceneInvalidatedEventArgs : EventArgs
[PrivateApi]
public class SceneInvalidatedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.

7
src/Avalonia.Base/Size.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Size : IEquatable<Size>
{
static Size()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<SizeAnimator>(prop => typeof(Size).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// A size representing infinity.
/// </summary>

7
src/Avalonia.Base/Thickness.cs

@ -15,13 +15,6 @@ namespace Avalonia
#endif
readonly struct Thickness : IEquatable<Thickness>
{
static Thickness()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<ThicknessAnimator>(prop => typeof(Thickness).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The thickness on the left.
/// </summary>

24
src/Avalonia.Base/Utilities/ThrowHelper.cs

@ -0,0 +1,24 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace Avalonia.Utilities;
/// <summary>
/// Helper method to help inlining methods that do a throw check.
/// Equivalent of .NET6+ ArgumentNullException.ThrowIfNull() for netstandard2.0+
/// </summary>
internal class ThrowHelper
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ThrowIfNull([NotNull] object? argument, string paramName)
{
if (argument is null)
{
ThrowArgumentNullException(paramName);
}
}
[DoesNotReturn]
private static void ThrowArgumentNullException(string paramName) => throw new ArgumentNullException(paramName);
}

7
src/Avalonia.Base/Vector.cs

@ -17,13 +17,6 @@ namespace Avalonia
#endif
readonly struct Vector : IEquatable<Vector>
{
static Vector()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<VectorAnimator>(prop => typeof(Vector).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary>
/// The X component.
/// </summary>

19
src/Avalonia.Base/VisualTree/VisualExtensions.cs

@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Rendering;
using Avalonia.Utilities;
namespace Avalonia.VisualTree
{
@ -125,9 +128,9 @@ namespace Avalonia.VisualTree
/// <returns>The visual's ancestors.</returns>
public static IEnumerable<Visual> GetVisualAncestors(this Visual visual)
{
Visual? v = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
v = v.VisualParent;
var v = visual.VisualParent;
while (v != null)
{
@ -194,7 +197,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual and its ancestors.</returns>
public static IEnumerable<Visual> GetSelfAndVisualAncestors(this Visual visual)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
yield return visual;
@ -275,7 +278,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual at the requested point.</returns>
public static Visual? GetVisualAt(this Visual visual, Point p)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualAt(p, x => x.IsVisible);
}
@ -292,7 +295,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual at the requested point.</returns>
public static Visual? GetVisualAt(this Visual visual, Point p, Func<Visual, bool> filter)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
@ -321,7 +324,7 @@ namespace Avalonia.VisualTree
this Visual visual,
Point p)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualsAt(p, x => x.IsVisible);
}
@ -341,7 +344,7 @@ namespace Avalonia.VisualTree
Point p,
Func<Visual, bool> filter)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot();
@ -435,7 +438,7 @@ namespace Avalonia.VisualTree
/// </returns>
public static IRenderRoot? GetVisualRoot(this Visual visual)
{
_ = visual ?? throw new ArgumentNullException(nameof(visual));
ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual as IRenderRoot ?? visual.VisualRoot;
}

30
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -2660,25 +2660,25 @@ namespace Avalonia.Controls
internal bool ProcessDownKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessDownKeyInternal(shift, ctrl);
}
internal bool ProcessEndKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEndKey(shift, ctrl);
}
internal bool ProcessEnterKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessEnterKey(shift, ctrl);
}
internal bool ProcessHomeKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessHomeKey(shift, ctrl);
}
@ -2718,25 +2718,25 @@ namespace Avalonia.Controls
internal bool ProcessLeftKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessLeftKey(shift, ctrl);
}
internal bool ProcessNextKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessNextKey(shift, ctrl);
}
internal bool ProcessPriorKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessPriorKey(shift, ctrl);
}
internal bool ProcessRightKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessRightKey(shift, ctrl);
}
@ -2854,7 +2854,7 @@ namespace Avalonia.Controls
internal bool ProcessUpKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessUpKey(shift, ctrl);
}
@ -3124,13 +3124,13 @@ namespace Avalonia.Controls
//TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
@ -4654,7 +4654,7 @@ namespace Avalonia.Controls
private bool ProcessAKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift, out bool alt);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift, out bool alt);
if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended)
{
@ -4923,7 +4923,7 @@ namespace Avalonia.Controls
private bool ProcessF2Key(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
if (!shift && !ctrl &&
_editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) &&
@ -5280,7 +5280,7 @@ namespace Avalonia.Controls
private bool ProcessTabKey(KeyEventArgs e)
{
KeyboardHelper.GetMetaKeyState(e.KeyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, e.KeyModifiers, out bool ctrl, out bool shift);
return ProcessTabKey(e, shift, ctrl);
}
@ -6099,7 +6099,7 @@ namespace Avalonia.Controls
/// <returns>Whether or not the DataGrid handled the key press.</returns>
private bool ProcessCopyKey(KeyModifiers modifiers)
{
KeyboardHelper.GetMetaKeyState(modifiers, out bool ctrl, out bool shift, out bool alt);
KeyboardHelper.GetMetaKeyState(this, modifiers, out bool ctrl, out bool shift, out bool alt);
if (ctrl && !shift && !alt && ClipboardCopyMode != DataGridClipboardCopyMode.None && SelectedItems.Count > 0)
{

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

@ -703,7 +703,7 @@ namespace Avalonia.Controls
public void ClearSort()
{
//InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier());
_headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier(OwningGrid));
}
/// <summary>
@ -1103,6 +1103,16 @@ namespace Avalonia.Controls
get;
set;
}
/// <summary>
/// Gets or sets an object associated with this column.
/// </summary>
public object Tag
{
get;
set;
}
/// <summary>
/// Holds a Comparer to use for sorting, if not using the default.
/// </summary>

2
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -241,7 +241,7 @@ namespace Avalonia.Controls
DataGrid owningGrid = OwningGrid;
DataGridSortDescription newSort;
KeyboardHelper.GetMetaKeyState(keyModifiers, out bool ctrl, out bool shift);
KeyboardHelper.GetMetaKeyState(this, keyModifiers, out bool ctrl, out bool shift);
DataGridSortDescription sort = OwningColumn.GetSortDescription();
IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView;

16
src/Avalonia.Controls.DataGrid/Utils/KeyboardHelper.cs

@ -10,23 +10,23 @@ namespace Avalonia.Controls.Utils
{
internal static class KeyboardHelper
{
public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift)
public static void GetMetaKeyState(Control target, KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift)
{
ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier());
ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier(target));
shift = modifiers.HasFlag(KeyModifiers.Shift);
}
public static void GetMetaKeyState(KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift, out bool alt)
public static void GetMetaKeyState(Control target, KeyModifiers modifiers, out bool ctrlOrCmd, out bool shift, out bool alt)
{
ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier());
ctrlOrCmd = modifiers.HasFlag(GetPlatformCtrlOrCmdKeyModifier(target));
shift = modifiers.HasFlag(KeyModifiers.Shift);
alt = modifiers.HasFlag(KeyModifiers.Alt);
}
public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier()
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
return keymap?.CommandModifiers ?? KeyModifiers.Control;
public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier(Control target)
{
var keymap = TopLevel.GetTopLevel(target)!.PlatformSettings!.HotkeyConfiguration;
return keymap.CommandModifiers;
}
}
}

23
src/Avalonia.Controls/Application.cs

@ -180,6 +180,17 @@ namespace Avalonia
/// </summary>
public IApplicationLifetime? ApplicationLifetime { get; set; }
/// <summary>
/// Represents a contract for accessing global platform-specific settings.
/// </summary>
/// <remarks>
/// PlatformSettings can be null only if application wasn't initialized yet.
/// <see cref="TopLevel"/>'s <see cref="TopLevel.PlatformSettings"/> is an equivalent API
/// which should always be preferred over a global one,
/// as specific top levels might have different settings set-up.
/// </remarks>
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
{
add => _stylesAdded += value;
@ -229,10 +240,12 @@ namespace Avalonia
var focusManager = new FocusManager();
InputManager = new InputManager();
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
settings.ColorValuesChanged += OnColorValuesChanged;
OnColorValuesChanged(settings, settings.GetColorValues());
if (PlatformSettings is { } settings)
{
settings.ColorValuesChanged += OnColorValuesChanged;
OnColorValuesChanged(settings, settings.GetColorValues());
}
AvaloniaLocator.CurrentMutable
.Bind<IAccessKeyHandler>().ToTransient<AccessKeyHandler>()
.Bind<IGlobalDataTemplates>().ToConstant(this)
@ -242,7 +255,7 @@ namespace Avalonia
.Bind<IInputManager>().ToConstant(InputManager)
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance);
// TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x
if (AvaloniaLocator.Current.GetService<IPlatformDragSource>() == null)
AvaloniaLocator.CurrentMutable

2
src/Avalonia.Controls/ContextMenu.cs

@ -405,7 +405,7 @@ namespace Avalonia.Controls
{
if (IsOpen)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true
&& !CancelClosing())

2
src/Avalonia.Controls/Control.cs

@ -480,7 +480,7 @@ namespace Avalonia.Controls
if (e.Source == this
&& !e.Handled)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.OpenContextMenu;
var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration.OpenContextMenu;
if (keymap is null)
{

19
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -1,14 +1,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls.Embedding.Offscreen
{
@ -17,7 +13,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
{
private double _scaling = 1;
private Size _clientSize;
private ManualRenderTimer _manualRenderTimer = new();
public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; }
@ -27,21 +22,10 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true;
}
class ManualRenderTimer : IRenderTimer
{
static Stopwatch St = Stopwatch.StartNew();
public event Action<TimeSpan>? Tick;
public bool RunsInBackground => false;
public void TriggerTick() => Tick?.Invoke(St.Elapsed);
}
public Compositor Compositor { get; }
public OffscreenTopLevelImplBase()
{
Compositor = new Compositor(new RenderLoop(_manualRenderTimer), null, false,
MediaContext.Instance, false);
}
=> Compositor = new Compositor(null);
public abstract IEnumerable<object> Surfaces { get; }
@ -76,7 +60,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
/// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;

3
src/Avalonia.Controls/Flyouts/PopupFlyoutBase.cs

@ -9,6 +9,7 @@ using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives
{
@ -392,7 +393,7 @@ namespace Avalonia.Controls.Primitives
&& IsOpen
&& Target?.ContextFlyout == this)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true)
{

5
src/Avalonia.Controls/ListBox.cs

@ -9,6 +9,7 @@ using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -127,7 +128,7 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e)
{
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl &&
@ -165,7 +166,7 @@ namespace Avalonia.Controls
internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e)
{
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var hotkeys = Application.Current!.PlatformSettings?.HotkeyConfiguration;
var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
return UpdateSelectionFromEventSource(

2
src/Avalonia.Controls/ListBoxItem.cs

@ -90,7 +90,7 @@ namespace Avalonia.Controls
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
{
var point = e.GetCurrentPoint(this);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var settings = TopLevel.GetTopLevel(e.Source as Visual)?.PlatformSettings;
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(_pointerDownPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));

3
src/Avalonia.Controls/MaskedTextBox.cs

@ -7,6 +7,7 @@ using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -204,7 +205,7 @@ namespace Avalonia.Controls
return;
}
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));

12
src/Avalonia.Controls/Platform/ManagedDispatcherImpl.cs

@ -99,9 +99,15 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
continue;
}
if (_nextTimer != null)
TimeSpan? nextTimer;
lock (_lock)
{
nextTimer = _nextTimer;
}
if (nextTimer != null)
{
var waitFor = _clock.Elapsed - _nextTimer.Value;
var waitFor = nextTimer.Value - _clock.Elapsed;
if (waitFor.TotalMilliseconds < 1)
continue;
_wakeup.WaitOne(waitFor);
@ -112,4 +118,4 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
registration.Dispose();
}
}
}

8
src/Avalonia.Controls/Primitives/OverlayPopupHost.cs

@ -120,11 +120,11 @@ namespace Avalonia.Controls.Primitives
void IManagedPopupPositionerPopup.MoveAndResize(Point devicePoint, Size virtualSize)
{
_lastRequestedPosition = devicePoint;
Dispatcher.UIThread.Post(() =>
MediaContext.Instance.BeginInvokeOnRender(() =>
{
OverlayLayer.SetLeft(this, _lastRequestedPosition.X);
OverlayLayer.SetTop(this, _lastRequestedPosition.Y);
}, DispatcherPriority.Render);
Canvas.SetLeft(this, _lastRequestedPosition.X);
Canvas.SetTop(this, _lastRequestedPosition.Y);
});
}
double IManagedPopupPositionerPopup.Scaling => 1;

2
src/Avalonia.Controls/Primitives/Thumb.cs

@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives
PseudoClasses.Add(":pressed");
e.PreventGestureRecognition();
RaiseEvent(ev);
}

3
src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs

@ -238,7 +238,6 @@ namespace Avalonia.Controls
visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, 0, 0) :
new Vector3(0, visualizerVisual.Offset.Y, 0);
visual.Offset = default;
_content.InvalidateMeasure();
break;
case RefreshVisualizerState.Interacting:
@ -452,8 +451,6 @@ namespace Avalonia.Controls
_interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
.Subscribe(InteractionRatioObserver);
var visual = RefreshInfoProvider.Visual;
_executingRatio = RefreshInfoProvider.ExecutionRatio;
}
else

116
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.Framebuffer.cs

@ -0,0 +1,116 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
using Avalonia.Remote.Protocol.Viewport;
using PlatformPixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
namespace Avalonia.Controls.Remote.Server
{
internal partial class RemoteServerTopLevelImpl
{
private enum FrameStatus
{
NotRendered,
Rendered,
CopiedToMessage
}
private sealed class Framebuffer
{
public static Framebuffer Empty { get; } = new(ProtocolPixelFormat.Rgba8888, default, 1.0);
private readonly double _dpi;
private readonly PixelSize _frameSize;
private readonly object _dataLock = new();
private readonly byte[] _data; // for rendering only
private readonly byte[] _dataCopy; // for messages only
private FrameStatus _status = FrameStatus.NotRendered;
public Framebuffer(ProtocolPixelFormat format, Size clientSize, double renderScaling)
{
var frameSize = PixelSize.FromSize(clientSize, renderScaling);
if (frameSize.Width <= 0 || frameSize.Height <= 0)
frameSize = PixelSize.Empty;
var bpp = format == ProtocolPixelFormat.Rgb565 ? 2 : 4;
var stride = frameSize.Width * bpp;
var dataLength = Math.Max(0, stride * frameSize.Height);
_dpi = renderScaling * 96.0;
_frameSize = frameSize;
Format = format;
ClientSize = clientSize;
RenderScaling = renderScaling;
(Stride, _data, _dataCopy) = dataLength > 0 ?
(stride, new byte[dataLength], new byte[dataLength]) :
(0, Array.Empty<byte>(), Array.Empty<byte>());
}
public ProtocolPixelFormat Format { get; }
public Size ClientSize { get; }
public double RenderScaling { get; }
public int Stride { get; }
public FrameStatus GetStatus()
{
lock (_dataLock)
return _status;
}
public ILockedFramebuffer Lock(Action onUnlocked)
{
var handle = GCHandle.Alloc(_data, GCHandleType.Pinned);
Monitor.Enter(_dataLock);
try
{
return new LockedFramebuffer(
handle.AddrOfPinnedObject(),
_frameSize,
Stride,
new Vector(_dpi, _dpi),
new PlatformPixelFormat((PixelFormatEnum)Format),
() =>
{
handle.Free();
Array.Copy(_data, _dataCopy, _data.Length);
_status = FrameStatus.Rendered;
Monitor.Exit(_dataLock);
onUnlocked();
});
}
catch
{
handle.Free();
Monitor.Exit(_dataLock);
throw;
}
}
/// <remarks>The returned message must NOT be kept around, as it contains a shared buffer.</remarks>
public FrameMessage ToMessage(long sequenceId)
{
lock (_dataLock)
_status = FrameStatus.CopiedToMessage;
return new FrameMessage
{
SequenceId = sequenceId,
Data = _dataCopy,
Format = Format,
Width = _frameSize.Width,
Height = _frameSize.Height,
Stride = Stride,
DpiX = _dpi,
DpiY = _dpi
};
}
}
}
}

365
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@ -11,7 +10,6 @@ using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Rendering;
using Avalonia.Threading;
using Key = Avalonia.Input.Key;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
@ -20,28 +18,28 @@ using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
namespace Avalonia.Controls.Remote.Server
{
[Unstable]
internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
internal partial class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
{
private readonly IAvaloniaRemoteTransportConnection _transport;
private LockedFramebuffer? _framebuffer;
private readonly object _lock = new();
private readonly Action _sendLastFrameIfNeeded;
private readonly Action _renderAndSendFrameIfNeeded;
private Framebuffer _framebuffer = Framebuffer.Empty;
private long _lastSentFrame = -1;
private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1;
private ClientViewportAllocatedMessage? _pendingAllocation;
private bool _queuedNextRender;
private bool _inRender;
private Vector _dpi = new Vector(96, 96);
private ProtocolPixelFormat[]? _supportedFormats;
private ProtocolPixelFormat? _format;
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport)
{
_sendLastFrameIfNeeded = SendLastFrameIfNeeded;
_renderAndSendFrameIfNeeded = RenderAndSendFrameIfNeeded;
_transport = transport;
_transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetRequiredService<IKeyboardDevice>();
QueueNextRender();
Compositor.AfterCommit += QueueNextRender;
}
private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed)
@ -112,45 +110,41 @@ namespace Avalonia.Controls.Remote.Server
{
lock (_lock)
{
if (obj is FrameReceivedMessage lastFrame)
switch (obj)
{
lock (_lock)
{
case FrameReceivedMessage lastFrame:
_lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame);
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if(obj is ClientRenderInfoMessage renderInfo)
{
lock(_lock)
{
_dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
_queuedNextRender = true;
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
{
lock (_lock)
_supportedFormats = supportedFormats.Formats;
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if (obj is MeasureViewportMessage measure)
Dispatcher.UIThread.Post(() =>
{
var m = Measure(new Size(measure.Width, measure.Height));
_transport.Send(new MeasureViewportMessage
Dispatcher.UIThread.Post(_sendLastFrameIfNeeded);
break;
case ClientRenderInfoMessage renderInfo:
Dispatcher.UIThread.Post(() =>
{
Width = m.Width,
Height = m.Height
RenderScaling = renderInfo.DpiX / 96.0;
RenderAndSendFrameIfNeeded();
});
});
if (obj is ClientViewportAllocatedMessage allocated)
{
lock (_lock)
{
break;
case ClientSupportedPixelFormatsMessage supportedFormats:
_format = TryGetValidPixelFormat(supportedFormats.Formats);
Dispatcher.UIThread.Post(_renderAndSendFrameIfNeeded);
break;
case MeasureViewportMessage measure:
Dispatcher.UIThread.Post(() =>
{
var m = Measure(new Size(measure.Width, measure.Height));
_transport.Send(new MeasureViewportMessage
{
Width = m.Width,
Height = m.Height
});
});
break;
case ClientViewportAllocatedMessage allocated:
if (_pendingAllocation == null)
{
Dispatcher.UIThread.Post(() =>
{
ClientViewportAllocatedMessage allocation;
@ -159,101 +153,111 @@ namespace Avalonia.Controls.Remote.Server
allocation = _pendingAllocation!;
_pendingAllocation = null;
}
_dpi = new Vector(allocation.DpiX, allocation.DpiY);
RenderScaling = allocation.DpiX / 96.0;
ClientSize = new Size(allocation.Width, allocation.Height);
RenderIfNeeded();
RenderAndSendFrameIfNeeded();
});
}
_pendingAllocation = allocated;
}
}
if(obj is PointerMovedEventMessage pointer)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
RawPointerEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaRawInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is PointerPressedEventMessage pressed)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
GetAvaloniaEventType(pressed.Button, true),
new Point(pressed.X, pressed.Y),
GetAvaloniaRawInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input);
}
if (obj is PointerReleasedEventMessage released)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
GetAvaloniaEventType(released.Button, false),
new Point(released.X, released.Y),
GetAvaloniaRawInputModifiers(released.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is ScrollEventMessage scroll)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseWheelEventArgs(
MouseDevice,
0,
InputRoot!,
new Point(scroll.X, scroll.Y),
new Vector(scroll.DeltaX, scroll.DeltaY),
GetAvaloniaRawInputModifiers(scroll.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is KeyEventMessage key)
{
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice,
0,
InputRoot!,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaRawInputModifiers(key.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is TextInputEventMessage text)
{
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice,
0,
InputRoot!,
text.Text));
}, DispatcherPriority.Input);
break;
case PointerMovedEventMessage pointer:
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
RawPointerEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaRawInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
break;
case PointerPressedEventMessage pressed:
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
GetAvaloniaEventType(pressed.Button, true),
new Point(pressed.X, pressed.Y),
GetAvaloniaRawInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input);
break;
case PointerReleasedEventMessage released:
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot!,
GetAvaloniaEventType(released.Button, false),
new Point(released.X, released.Y),
GetAvaloniaRawInputModifiers(released.Modifiers)));
}, DispatcherPriority.Input);
break;
case ScrollEventMessage scroll:
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseWheelEventArgs(
MouseDevice,
0,
InputRoot!,
new Point(scroll.X, scroll.Y),
new Vector(scroll.DeltaX, scroll.DeltaY),
GetAvaloniaRawInputModifiers(scroll.Modifiers)));
}, DispatcherPriority.Input);
break;
case KeyEventMessage key:
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice,
0,
InputRoot!,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaRawInputModifiers(key.Modifiers)));
}, DispatcherPriority.Input);
break;
case TextInputEventMessage text:
Dispatcher.UIThread.Post(() =>
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice,
0,
InputRoot!,
text.Text));
}, DispatcherPriority.Input);
break;
}
}
}
protected void SetDpi(Vector dpi)
private static ProtocolPixelFormat? TryGetValidPixelFormat(ProtocolPixelFormat[]? formats)
{
_dpi = dpi;
RenderIfNeeded();
if (formats is not null)
{
foreach (var format in formats)
{
if (format is >= 0 and <= ProtocolPixelFormat.MaxValue)
return format;
}
}
return null;
}
protected virtual Size Measure(Size constraint)
@ -265,88 +269,63 @@ namespace Avalonia.Controls.Remote.Server
public override IEnumerable<object> Surfaces => new[] { this };
private FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
private Framebuffer GetOrCreateFramebuffer()
{
var scalingX = _dpi.X / 96.0;
var scalingY = _dpi.Y / 96.0;
width = (int)(width * scalingX);
height = (int)(height * scalingY);
var fmt = format ?? ProtocolPixelFormat.Rgba8888;
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
var data = new byte[width * height * bpp];
var handle = GCHandle.Alloc(data, GCHandleType.Pinned);
try
{
if (width > 0 && height > 0)
{
_framebuffer = new LockedFramebuffer(handle.AddrOfPinnedObject(), new PixelSize(width, height), width * bpp, _dpi, new((PixelFormatEnum)fmt),
null);
Paint?.Invoke(new Rect(0, 0, width, height));
}
}
finally
lock (_lock)
{
_framebuffer = null;
handle.Free();
if (_format is not { } format)
_framebuffer = Framebuffer.Empty;
else if (_framebuffer.Format != format || _framebuffer.ClientSize != ClientSize || _framebuffer.RenderScaling != RenderScaling)
_framebuffer = new Framebuffer(format, ClientSize, RenderScaling);
return _framebuffer;
}
return new FrameMessage
{
Data = data,
Format = fmt,
Width = width,
Height = height,
Stride = width * bpp,
DpiX = _dpi.X,
DpiY = _dpi.Y
};
}
public ILockedFramebuffer Lock()
{
if (_framebuffer == null)
throw new InvalidOperationException("Paint was not requested, wait for Paint event");
return _framebuffer;
}
=> GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded);
protected void RenderIfNeeded()
private void SendLastFrameIfNeeded()
{
lock (_lock)
{
if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null)
return;
if (IsDisposed)
return;
}
Framebuffer framebuffer;
long sequenceId;
var format = ProtocolPixelFormat.Rgba8888;
foreach(var fmt in _supportedFormats)
if (fmt <= ProtocolPixelFormat.MaxValue)
{
format = fmt;
break;
}
_inRender = true;
var frame = RenderFrame((int) ClientSize.Width, (int) ClientSize.Height, format);
lock (_lock)
{
// Ideally we should only send a frame if its status is Rendered: since the renderer might not be
// initialized at the start, we're sending black frames in this case. However, this was the historical
// behavior and some external programs are depending on receiving a frame asap.
if (_lastReceivedFrame != _lastSentFrame || _framebuffer.GetStatus() == FrameStatus.CopiedToMessage)
return;
framebuffer = _framebuffer;
_lastSentFrame = _nextFrameNumber++;
frame.SequenceId = _lastSentFrame;
_queuedNextRender = false;
sequenceId = _lastSentFrame;
}
_inRender = false;
_transport.Send(frame);
_transport.Send(framebuffer.ToMessage(sequenceId));
}
private void QueueNextRender()
protected void RenderAndSendFrameIfNeeded()
{
if (!_inRender && !IsDisposed)
if (IsDisposed)
return;
lock (_lock)
{
_queuedNextRender = true;
DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background);
if (_lastReceivedFrame != _lastSentFrame || _format is null)
return;
}
var framebuffer = GetOrCreateFramebuffer();
if (framebuffer.Stride > 0)
Paint?.Invoke(new Rect(framebuffer.ClientSize));
SendLastFrameIfNeeded();
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();

2
src/Avalonia.Controls/SelectableTextBlock.cs

@ -198,7 +198,7 @@ namespace Avalonia.Controls
var handled = false;
var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));

61
src/Avalonia.Controls/TextBox.cs

@ -15,7 +15,6 @@ using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.Controls.Metadata;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers;
using Avalonia.Threading;
@ -32,20 +31,17 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Cut action
/// </summary>
public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
public static KeyGesture? CutGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Cut.FirstOrDefault();
/// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Copy action
/// </summary>
public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault();
public static KeyGesture? CopyGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Copy.FirstOrDefault();
/// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Paste action
/// </summary>
public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
public static KeyGesture? PasteGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Paste.FirstOrDefault();
/// <summary>
/// Defines the <see cref="AcceptsReturn"/> property
@ -1103,7 +1099,7 @@ namespace Avalonia.Controls
var handled = false;
var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers);
@ -1217,6 +1213,34 @@ namespace Avalonia.Controls
selection = true;
handled = true;
}
else if (Match(keymap.PageLeft))
{
MovePageLeft();
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.PageRight))
{
MovePageRight();
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.PageUp))
{
MovePageUp();
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.PageDown))
{
MovePageDown();
movement = true;
selection = false;
handled = true;
}
else
{
bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers);
@ -1407,8 +1431,6 @@ namespace Avalonia.Controls
{
var point = e.GetPosition(_presenter);
var oldIndex = CaretIndex;
_presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
@ -1741,6 +1763,25 @@ namespace Avalonia.Controls
}
}
private void MovePageRight()
{
_scrollViewer?.PageRight();
}
private void MovePageLeft()
{
_scrollViewer?.PageLeft();
}
private void MovePageUp()
{
_scrollViewer?.PageUp();
}
private void MovePageDown()
{
_scrollViewer?.PageDown();
}
/// <summary>
/// Select all text in the TextBox
/// </summary>

47
src/Avalonia.Controls/ToggleSwitch.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.Metadata;
using Avalonia.Animation;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -42,6 +43,10 @@ namespace Avalonia.Controls
x.UpdateKnobPos(x.IsChecked.Value);
}
});
KnobTransitionsProperty.Changed.AddClassHandler<ToggleSwitch>((x, e) =>
{
x.UpdateKnobTransitions();
});
}
/// <summary>
@ -68,6 +73,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IDataTemplate?> OnContentTemplateProperty =
AvaloniaProperty.Register<ToggleSwitch, IDataTemplate?>(nameof(OnContentTemplate));
/// <summary>
/// Defines the <see cref="KnobTransitions"/> property.
/// </summary>
public static readonly StyledProperty<Transitions> KnobTransitionsProperty =
AvaloniaProperty.Register<ToggleSwitch, Transitions>(nameof(KnobTransitions));
/// <summary>
/// Gets or Sets the Content that is displayed when in the On State.
/// </summary>
@ -116,6 +127,17 @@ namespace Avalonia.Controls
set { SetValue(OnContentTemplateProperty, value); }
}
/// <summary>
/// Gets or Sets the <see cref="Transitions"/> of switching knob.
/// </summary>
public Transitions KnobTransitions
{
get { return GetValue(KnobTransitionsProperty); }
set { SetValue(KnobTransitionsProperty, value); }
}
private void OffContentChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
@ -177,7 +199,21 @@ namespace Avalonia.Controls
UpdateKnobPos(IsChecked.Value);
}
}
protected override void OnLoaded()
{
base.OnLoaded();
UpdateKnobTransitions();
}
private void UpdateKnobTransitions()
{
if (_knobsPanel != null)
{
_knobsPanel.Transitions = KnobTransitions;
}
}
private void KnobsPanel_PointerPressed(object? sender, Input.PointerPressedEventArgs e)
{
_switchStartPoint = e.GetPosition(_switchKnob);
@ -194,7 +230,7 @@ namespace Avalonia.Controls
_knobsPanel!.ClearValue(Canvas.LeftProperty);
PseudoClasses.Set(":dragging", false);
if (shouldBecomeChecked == IsChecked)
{
UpdateKnobPos(shouldBecomeChecked);
@ -203,6 +239,7 @@ namespace Avalonia.Controls
{
SetCurrentValue(IsCheckedProperty, shouldBecomeChecked);
}
UpdateKnobTransitions();
}
else
{
@ -218,6 +255,10 @@ namespace Avalonia.Controls
{
if (_knobsPanelPressed)
{
if(_knobsPanel != null)
{
_knobsPanel.Transitions = null;
}
var difference = e.GetPosition(_switchKnob) - _switchStartPoint;
if ((!_isDragging) && (System.Math.Abs(difference.X) > 3))

17
src/Avalonia.Controls/TopLevel.cs

@ -25,6 +25,7 @@ using System.Linq;
using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls
{
@ -250,7 +251,7 @@ namespace Avalonia.Controls
if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.Back;
var keymap = PlatformSettings?.HotkeyConfiguration.Back;
if (keymap != null)
{
@ -487,6 +488,9 @@ namespace Avalonia.Controls
/// <inheritdoc />
public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
/// <inheritdoc />
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
/// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p)
{
@ -532,7 +536,16 @@ namespace Avalonia.Controls
return Disposable.Create(() => { });
}
}
/// <summary>
/// Enqueues a callback to be called on the next animation tick
/// </summary>
public void RequestAnimationFrame(Action<TimeSpan> action)
{
Dispatcher.UIThread.VerifyAccess();
MediaContext.Instance.RequestAnimationFrame(action);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

5
src/Avalonia.Controls/TreeView.cs

@ -571,7 +571,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
bool Match(List<KeyGesture>? gestures) => gestures?.Any(g => g.Matches(e)) ?? false;
if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll))
@ -652,11 +652,12 @@ namespace Avalonia.Controls
if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
{
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
e.Handled = UpdateSelectionFromEventSource(
e.Source,
true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().CommandModifiers),
e.KeyModifiers.HasAllFlags(keymap.CommandModifiers),
point.Properties.IsRightButtonPressed);
}
}

8
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -53,7 +53,11 @@ namespace Avalonia.DesignerSupport.Remote
// In previewer mode we completely ignore client-side viewport size
if (obj is ClientViewportAllocatedMessage alloc)
{
Dispatcher.UIThread.Post(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY)));
Dispatcher.UIThread.Post(() =>
{
RenderScaling = alloc.DpiX / 96.0;
RenderAndSendFrameIfNeeded();
});
return;
}
base.OnMessage(transport, obj);
@ -67,7 +71,7 @@ namespace Avalonia.DesignerSupport.Remote
Height = clientSize.Height
});
ClientSize = clientSize;
RenderIfNeeded();
RenderAndSendFrameIfNeeded();
}
public void Move(PixelPoint point)

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -52,7 +52,7 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(null))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderTimer>().ToConstant(new UiThreadRenderTimer(60))
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();

8
src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -60,9 +60,9 @@ namespace Avalonia.FreeDesktop
{
try
{
_serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item2));
_serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item1, x.Item3));
var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher");
OnNameChange(nameOwner);
OnNameChange("org.kde.StatusNotifierWatcher", nameOwner);
}
catch
{
@ -72,9 +72,9 @@ namespace Avalonia.FreeDesktop
}
}
private void OnNameChange(string? newOwner)
private void OnNameChange(string name, string? newOwner)
{
if (_isDisposed || _connection is null)
if (_isDisposed || _connection is null || name != "org.kde.StatusNotifierWatcher")
return;
if (!_serviceConnected & newOwner is not null)

18
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -108,20 +108,18 @@ namespace Avalonia.Native
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
.Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()));
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
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));
}
var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt);
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));
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(hotkeys);
if (_options.UseGpu)
{

34
src/Avalonia.Themes.Fluent/Accents/SystemAccentColors.cs

@ -16,18 +16,12 @@ internal class SystemAccentColors : IResourceProvider
public const string AccentLight2Key = "SystemAccentColorLight2";
public const string AccentLight3Key = "SystemAccentColorLight3";
private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215);
private readonly IPlatformSettings? _platformSettings;
private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215);
private bool _invalidateColors = true;
private Color _systemAccentColor;
private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3;
private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3;
public SystemAccentColors()
{
_platformSettings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
}
public bool HasResources => true;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{
@ -96,10 +90,12 @@ internal class SystemAccentColors : IResourceProvider
Owner = owner;
OwnerChanged?.Invoke(this, EventArgs.Empty);
if (_platformSettings is not null)
if (GetFromOwner(owner) is { } platformSettings)
{
_platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
platformSettings.ColorValuesChanged += PlatformSettingsOnColorValuesChanged;
}
_invalidateColors = true;
}
}
@ -110,10 +106,12 @@ internal class SystemAccentColors : IResourceProvider
Owner = null;
OwnerChanged?.Invoke(this, EventArgs.Empty);
if (_platformSettings is not null)
if (GetFromOwner(owner) is { } platformSettings)
{
_platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
platformSettings.ColorValuesChanged -= PlatformSettingsOnColorValuesChanged;
}
_invalidateColors = true;
}
}
@ -122,13 +120,25 @@ internal class SystemAccentColors : IResourceProvider
if (_invalidateColors)
{
_invalidateColors = false;
var platformSettings = GetFromOwner(Owner);
_systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor;
_systemAccentColor = platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor;
(_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3,
_systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor);
}
}
private static IPlatformSettings? GetFromOwner(IResourceHost? owner)
{
return owner switch
{
Application app => app.PlatformSettings,
Visual visual => TopLevel.GetTopLevel(visual)?.PlatformSettings,
_ => null
};
}
public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor)
{
// dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255

20
src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml

@ -28,6 +28,14 @@
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="KnobTransitions">
<Transitions>
<DoubleTransition
Easing="CubicEaseOut"
Property="Canvas.Left"
Duration="0:0:0.2" />
</Transitions>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Grid Background="{TemplateBinding Background}" RowDefinitions="Auto,*">
@ -134,18 +142,6 @@
<Setter Property="Margin" Value="0" />
</Style>
<!-- NormalState -->
<Style Selector="^:not(:dragging) /template/ Grid#PART_MovingKnobs">
<Setter Property="Transitions">
<Transitions>
<DoubleTransition
Easing="CubicEaseOut"
Property="Canvas.Left"
Duration="0:0:0.2" />
</Transitions>
</Setter>
</Style>
<!-- PointerOverState -->
<Style Selector="^:pointerover /template/ Border#OuterBorder">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffPointerOver}" />

13
src/Avalonia.Themes.Simple/Controls/ToggleSwitch.xaml

@ -46,6 +46,14 @@
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
<Setter Property="KnobTransitions">
<Transitions>
<DoubleTransition
Easing="CubicEaseOut"
Property="Canvas.Left"
Duration="0:0:0.2" />
</Transitions>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Grid Background="{TemplateBinding Background}"
@ -123,11 +131,6 @@
<Grid x:Name="PART_MovingKnobs"
Width="20" Height="20">
<Grid.Transitions>
<Transitions>
<DoubleTransition Property="Canvas.Left" Duration="0:0:0.2" Easing="CubicEaseOut" />
</Transitions>
</Grid.Transitions>
<Ellipse x:Name="SwitchKnobOn"
Fill="{DynamicResource HighlightForegroundColor}"

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

@ -24,13 +24,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Follow_Pointer_Pressed_Released()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var result = new List<string>();
AddHandlers(decorator, border, result, false);
AddHandlers(root, border, result, false);
_mouse.Click(border);
@ -41,13 +41,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var result = new List<string>();
AddHandlers(decorator, border, result, true);
AddHandlers(root, border, result, true);
_mouse.Click(border);
@ -58,13 +58,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Not_Be_Raised_For_Middle_Button()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle);
@ -75,13 +75,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Not_Be_Raised_For_Right_Button()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
@ -92,13 +92,13 @@ namespace Avalonia.Base.UnitTests.Input
public void RightTapped_Should_Be_Raised_For_Right_Button()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true);
root.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
@ -109,13 +109,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var result = new List<string>();
AddHandlers(decorator, border, result, false);
AddHandlers(root, border, result, false);
_mouse.Click(border);
_mouse.Down(border, clickCount: 2);
@ -127,13 +127,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var result = new List<string>();
AddHandlers(decorator, border, result, true);
AddHandlers(root, border, result, true);
_mouse.Click(border);
_mouse.Down(border, clickCount: 2);
@ -145,13 +145,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle);
_mouse.Down(border, MouseButton.Middle, clickCount: 2);
@ -163,13 +163,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button()
{
Border border = new Border();
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right);
_mouse.Down(border, MouseButton.Right, clickCount: 2);
@ -191,13 +191,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
HoldingState holding = HoldingState.Cancelled;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState);
root.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState);
_mouse.Down(border);
Assert.False(holding != HoldingState.Cancelled);
@ -227,13 +227,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Started);
root.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Started);
_mouse.Down(border);
Assert.False(raised);
@ -262,13 +262,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
root.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
_mouse.Down(border);
Assert.False(raised);
@ -297,13 +297,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var cancelled = false;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
root.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
_mouse.Down(border);
Assert.False(cancelled);
@ -333,13 +333,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var root = new TestRoot()
{
Child = border
};
var cancelled = false;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
root.AddHandler(Gestures.HoldingEvent, (_, e) => cancelled = e.HoldingState == HoldingState.Cancelled);
_mouse.Down(border);
@ -369,13 +369,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator
var testRoot = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
testRoot.AddHandler(Gestures.HoldingEvent, (_, e) => raised = e.HoldingState == HoldingState.Completed);
var secondMouse = new MouseTestHelper();
@ -392,12 +392,12 @@ namespace Avalonia.Base.UnitTests.Input
}
private static void AddHandlers(
Decorator decorator,
TestRoot root,
Border border,
IList<string> result,
bool markHandled)
{
decorator.AddHandler(InputElement.PointerPressedEvent, (_, e) =>
root.AddHandler(InputElement.PointerPressedEvent, (_, e) =>
{
result.Add("dp");
@ -407,7 +407,7 @@ namespace Avalonia.Base.UnitTests.Input
}
});
decorator.AddHandler(InputElement.PointerReleasedEvent, (_, e) =>
root.AddHandler(InputElement.PointerReleasedEvent, (_, e) =>
{
result.Add("dr");
@ -420,8 +420,8 @@ namespace Avalonia.Base.UnitTests.Input
border.AddHandler(InputElement.PointerPressedEvent, (_, _) => result.Add("bp"));
border.AddHandler(InputElement.PointerReleasedEvent, (_, _) => result.Add("br"));
decorator.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt"));
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt"));
root.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt"));
root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt"));
border.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("bdt"));
}
@ -438,13 +438,13 @@ namespace Avalonia.Base.UnitTests.Input
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
@ -466,13 +466,13 @@ namespace Avalonia.Base.UnitTests.Input
Background = new SolidColorBrush(Colors.Red)
};
border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10);
@ -502,13 +502,13 @@ namespace Avalonia.Base.UnitTests.Input
CanVerticallyScroll = true,
ScrollStartDistance = 50
});
var decorator = new Decorator
var root = new TestRoot
{
Child = border
};
var raised = false;
decorator.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true);
root.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true);
var firstTouch = new TouchTestHelper();

76
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -3,9 +3,11 @@ using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
@ -14,7 +16,7 @@ using MouseButton = Avalonia.Input.MouseButton;
namespace Avalonia.Controls.UnitTests
{
public class ButtonTests
public class ButtonTests : ScopedTestBase
{
private MouseTestHelper _helper = new MouseTestHelper();
@ -139,11 +141,19 @@ namespace Avalonia.Controls.UnitTests
renderer.Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<Visual>(), It.IsAny<Func<Visual, bool>>()))
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton(renderer.Object)
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var root = new Window() { HitTesterOverride = renderer.Object };
var target = new Button()
{
Bounds = new Rect(0, 0, 100, 100)
Width = 100,
Height = 100,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left
};
root.Content = target;
root.Show();
bool clicked = false;
@ -165,16 +175,13 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
var renderer = new Mock<IHitTester>();
renderer.Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<Visual>(), It.IsAny<Func<Visual, bool>>()))
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]);
var target = new TestButton(renderer.Object)
var root = new TestRoot();
var target = new Button()
{
Bounds = new Rect(0, 0, 100, 100)
Width = 100,
Height = 100
};
root.Child = target;
bool clicked = false;
@ -203,12 +210,20 @@ namespace Avalonia.Controls.UnitTests
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]);
var target = new TestButton(renderer.Object)
using var _ = UnitTestApplication.Start(TestServices.StyledWindow);
var root = new Window() { HitTesterOverride = renderer.Object };
var target = new Button()
{
Bounds = new Rect(0, 0, 100, 100),
Width = 100,
Height = 100,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Left,
RenderTransform = new TranslateTransform { X = 100, Y = 0 }
};
root.Content = target;
root.Show();
//actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
//so mouse with x=150 coordinates should trigger click
@ -381,37 +396,6 @@ namespace Avalonia.Controls.UnitTests
return new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = key, Source = source };
}
private class TestButton : Button, IRenderRoot
{
public TestButton(IHitTester hit)
{
IsVisible = true;
HitTester = hit;
Renderer = new NullRenderer();
}
public new Rect Bounds
{
get => base.Bounds;
set => base.Bounds = value;
}
public Size ClientSize => throw new NotImplementedException();
public IRenderer Renderer { get; }
public IHitTester HitTester { get; }
public double RenderScaling => throw new NotImplementedException();
public IRenderTarget CreateRenderTarget() => throw new NotImplementedException();
public void Invalidate(Rect rect) => throw new NotImplementedException();
public Point PointToClient(PixelPoint p) => throw new NotImplementedException();
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
{
_helper.Down(button, mouseButton, position, clickCount: clickCount);

24
tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs

@ -34,7 +34,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift);
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
@ -60,7 +60,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
SelectionChangedEventArgs receivedArgs = null;
@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[1]);
_helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
_helper.Click(target.Presenter.Panel.Children[3], modifiers: KeyModifiers.Control);
@ -152,7 +152,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[1]);
_helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
@ -183,7 +183,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[3]);
_helper.Click(target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control);
@ -211,7 +211,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[3]);
_helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[0]);
_helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
@ -266,7 +266,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
SelectionChangedEventArgs receivedArgs = null;
@ -319,7 +319,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(new[] { "Foo" }, target.SelectedItems);
@ -414,7 +414,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[0]);
_helper.Click(target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift);
@ -444,7 +444,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[0]);
_helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift);
@ -471,7 +471,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
_helper.Click(target.Presenter.Panel.Children[0]);
_helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control);

14
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls.UnitTests
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
@ -79,7 +79,7 @@ namespace Avalonia.Controls.UnitTests
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
_mouse.Click(target.Presenter.Panel.Children[0]);
@ -97,7 +97,7 @@ namespace Avalonia.Controls.UnitTests
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
target.SelectedIndex = 0;
@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
_mouse.Click(target.Presenter.Panel.Children[0]);
@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests
SelectionMode = SelectionMode.Toggle,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
target.SelectedIndex = 0;
@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
target.SelectedIndex = 0;
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target);
target.SelectedIndex = 1;

1
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -2,6 +2,7 @@
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Moq;

10
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs

@ -1234,7 +1234,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
ItemsSource = new[] { "Foo", "Bar", "Baz " },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
Prepare(target);
var container = target.ContainerFromIndex(1)!;
@ -1258,7 +1258,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
ItemsSource = items,
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
Prepare(target);
_helper.Down(target.Presenter.Panel.Children[1]);
@ -1355,7 +1355,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
@ -1373,7 +1373,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
Template = Template(),
ItemsSource = new[] { "Foo", "Bar", "Baz", "Foo", "Bar", "Baz" },
};
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object);
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
Prepare(target);
_helper.Down((Interactive)target.Presenter.Panel.Children[3]);
@ -2141,7 +2141,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.RaiseEvent(new TextInputEventArgs
{
RoutedEvent = InputElement.TextInputEvent,
Device = KeyboardDevice.Instance,
Text = "Foo"
});
@ -2152,7 +2151,6 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.RaiseEvent(new TextInputEventArgs
{
RoutedEvent = InputElement.TextInputEvent,
Device = KeyboardDevice.Instance,
Text = "Foo"
});

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

@ -1045,7 +1045,7 @@ namespace Avalonia.Controls.UnitTests
var data = CreateTestTreeData();
var target = CreateTarget(data: data, multiSelect: true);
var rootNode = data[0];
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
@ -1075,7 +1075,7 @@ namespace Avalonia.Controls.UnitTests
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
@ -1105,7 +1105,7 @@ namespace Avalonia.Controls.UnitTests
ClickContainer(fromContainer, KeyModifiers.None);
ClickContainer(toContainer, KeyModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>();
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs

1
tests/Avalonia.UnitTests/TestRoot.cs

@ -67,6 +67,7 @@ namespace Avalonia.UnitTests
public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
public IInputElement PointerOverElement { get; set; }

20
tests/Avalonia.UnitTests/TouchTestHelper.cs

@ -27,8 +27,13 @@ namespace Avalonia.UnitTests
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));
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
Timestamp(), PointerPointProperties.None, modifiers);
if (_pointer.CapturedGestureRecognizer != null)
_pointer.CapturedGestureRecognizer.PointerMovedInternal(e);
else
target.RaiseEvent(e);
}
public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default)
@ -36,9 +41,16 @@ namespace Avalonia.UnitTests
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));
var e = new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None,
modifiers, MouseButton.None);
if (_pointer.CapturedGestureRecognizer != null)
_pointer.CapturedGestureRecognizer.PointerReleasedInternal(e);
else
source.RaiseEvent(e);
_pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
}
public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save