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> </PropertyGroup>
<ItemGroup> <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.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj"> <ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

1
packages/Avalonia/Avalonia.targets

@ -1,3 +1,4 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.targets"/> <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.targets"/>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaPrivateApis.targets" />
</Project> </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; using Avalonia.Animation.Animators;
namespace RenderDemo.Pages namespace RenderDemo.Pages
{ {
public class CustomStringAnimator : CustomAnimatorBase<string> public class CustomStringAnimator : InterpolatingAnimator<string>
{ {
public override string Interpolate(double progress, string oldValue, string newValue) 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Avalonia.Reactive; using Avalonia.Reactive;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -16,7 +13,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Tracks the progress of an animation. /// Tracks the progress of an animation.
/// </summary> /// </summary>
public sealed class Animation : AvaloniaObject, IAnimation public sealed partial class Animation : AvaloniaObject, IAnimation
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Duration"/> property. /// Defines the <see cref="Duration"/> property.
@ -195,60 +192,6 @@ namespace Avalonia.Animation
return null; 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) private (IList<IAnimator> Animators, IList<IDisposable> subscriptions) InterpretKeyframes(Animatable control)
{ {
var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>(); var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func<IAnimator>>();

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

@ -1,14 +1,15 @@
using System; using System;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
namespace Avalonia.Animation; namespace Avalonia.Animation;
[Obsolete("This class will be removed before 11.0, use InterpolatingAnimator<T>", true)]
public abstract class CustomAnimatorBase public abstract class CustomAnimatorBase
{ {
internal abstract IAnimator CreateWrapper(); internal abstract IAnimator CreateWrapper();
internal abstract Type WrapperType { get; } 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 class CustomAnimatorBase<T> : CustomAnimatorBase
{ {
public abstract T Interpolate(double progress, T oldValue, T newValue); public abstract T Interpolate(double progress, T oldValue, T newValue);
@ -25,6 +26,33 @@ public abstract class CustomAnimatorBase<T> : CustomAnimatorBase
_parent = parent; _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); 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 #endif
readonly struct CornerRadius : IEquatable<CornerRadius> readonly struct CornerRadius : IEquatable<CornerRadius>
{ {
static CornerRadius()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<CornerRadiusAnimator>(prop => typeof(CornerRadius).IsAssignableFrom(prop.PropertyType));
#endif
}
public CornerRadius(double uniformRadius) public CornerRadius(double uniformRadius)
{ {
TopLeft = TopRight = BottomLeft = BottomRight = 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 namespace Avalonia.Input.GestureRecognizers
{ {
public class GestureRecognizerCollection : IReadOnlyCollection<IGestureRecognizer>, IGestureRecognizerActionsDispatcher public class GestureRecognizerCollection : IReadOnlyCollection<GestureRecognizer>
{ {
private readonly IInputElement _inputElement; private readonly IInputElement _inputElement;
private List<IGestureRecognizer>? _recognizers; private List<GestureRecognizer>? _recognizers;
private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
public GestureRecognizerCollection(IInputElement inputElement) public GestureRecognizerCollection(IInputElement inputElement)
{ {
_inputElement = inputElement; _inputElement = inputElement;
} }
public void Add(IGestureRecognizer recognizer) public void Add(GestureRecognizer recognizer)
{ {
if (_recognizers == null) if (_recognizers == null)
{ {
// We initialize the collection when the first recognizer is added // We initialize the collection when the first recognizer is added
_recognizers = new List<IGestureRecognizer>(); _recognizers = new List<GestureRecognizer>();
_pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>();
} }
_recognizers.Add(recognizer); _recognizers.Add(recognizer);
recognizer.Initialize(_inputElement, this); recognizer.Target = _inputElement;
// Hacks to make bindings work // 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(); => _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public int Count => _recognizers?.Count ?? 0; public int Count => _recognizers?.Count ?? 0;
internal bool HandlePointerPressed(PointerPressedEventArgs e) internal bool HandlePointerPressed(PointerPressedEventArgs e)
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
if (e.Handled) r.PointerPressedInternal(e);
break;
r.PointerPressed(e);
} }
return e.Handled; return e.Handled;
@ -69,17 +63,15 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; 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; return e.Handled;
} }
@ -87,41 +79,16 @@ namespace Avalonia.Input.GestureRecognizers
{ {
if (_recognizers == null) if (_recognizers == null)
return false; return false;
if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture)) var pointer = e.Pointer as Pointer;
{
capture.PointerMoved(e);
}
else
foreach (var r in _recognizers)
{
if (e.Handled)
break;
r.PointerMoved(e);
}
return e.Handled;
}
internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
{
if (_recognizers == null)
return;
_pointerGrabs!.Remove(e.Pointer);
foreach (var r in _recognizers) foreach (var r in _recognizers)
{ {
r.PointerCaptureLost(e.Pointer); if (pointer?.CapturedGestureRecognizer != null)
} break;
}
void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) r.PointerMovedInternal(e);
{
pointer.Capture(_inputElement);
_pointerGrabs![pointer] = recognizer;
foreach (var r in _recognizers!)
{
if (r != recognizer)
r.PointerCaptureLost(pointer);
} }
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 namespace Avalonia.Input
{ {
public class PinchGestureRecognizer : StyledElement, IGestureRecognizer public class PinchGestureRecognizer : GestureRecognizer
{ {
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private float _initialDistance; private float _initialDistance;
private IPointer? _firstContact; private IPointer? _firstContact;
private Point _firstPoint; private Point _firstPoint;
@ -13,12 +11,6 @@ namespace Avalonia.Input
private Point _secondPoint; private Point _secondPoint;
private Point _origin; private Point _origin;
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e) private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{ {
PointerPressed(e); PointerPressed(e);
@ -29,14 +21,14 @@ namespace Avalonia.Input
PointerReleased(e); PointerReleased(e);
} }
public void PointerCaptureLost(IPointer pointer) protected override void PointerCaptureLost(IPointer pointer)
{ {
RemoveContact(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) if(_firstContact == e.Pointer)
{ {
@ -58,16 +50,16 @@ namespace Avalonia.Input
var scale = distance / _initialDistance; var scale = distance / _initialDistance;
var pinchEventArgs = new PinchEventArgs(scale, _origin); var pinchEventArgs = new PinchEventArgs(scale, _origin);
_target?.RaiseEvent(pinchEventArgs); Target?.RaiseEvent(pinchEventArgs);
e.Handled = pinchEventArgs.Handled; 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) 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); _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f);
_actions!.Capture(_firstContact, this); Capture(_firstContact);
_actions!.Capture(_secondContact, this); Capture(_secondContact);
} }
} }
} }
public void PointerReleased(PointerReleasedEventArgs e) protected override void PointerReleased(PointerReleasedEventArgs e)
{ {
RemoveContact(e.Pointer); RemoveContact(e.Pointer);
} }
@ -118,7 +110,7 @@ namespace Avalonia.Input
_secondContact = null; _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;
using System.Diagnostics;
using Avalonia.Input.GestureRecognizers; using Avalonia.Input.GestureRecognizers;
namespace Avalonia.Input namespace Avalonia.Input
{ {
public class PullGestureRecognizer : StyledElement, IGestureRecognizer public class PullGestureRecognizer : GestureRecognizer
{ {
internal static int MinPullDetectionSize = 50; internal static int MinPullDetectionSize = 50;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private Point _initialPosition; private Point _initialPosition;
private int _gestureId; private int _gestureId;
private IPointer? _tracking; private IPointer? _tracking;
@ -34,13 +30,7 @@ namespace Avalonia.Input
public PullGestureRecognizer() { } public PullGestureRecognizer() { }
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) protected override void PointerCaptureLost(IPointer pointer)
{
_target = target;
_actions = actions;
}
public void PointerCaptureLost(IPointer pointer)
{ {
if (_tracking == 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); var currentPosition = e.GetPosition(visual);
_actions!.Capture(e.Pointer, this); Capture(e.Pointer);
e.PreventGestureRecognition();
Vector delta = default; Vector delta = default;
switch (PullDirection) switch (PullDirection)
@ -86,15 +77,15 @@ namespace Avalonia.Input
_pullInProgress = true; _pullInProgress = true;
var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection); var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection);
_target?.RaiseEvent(pullEventArgs); Target?.RaiseEvent(pullEventArgs);
e.Handled = pullEventArgs.Handled; 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); 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) if (_tracking == e.Pointer && _pullInProgress)
{ {
@ -141,7 +132,7 @@ namespace Avalonia.Input
_initialPosition = default; _initialPosition = default;
_pullInProgress = false; _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 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 // Pixels per second speed that is considered to be the stop of inertial scroll
internal const double InertialScrollSpeedEnd = 5; internal const double InertialScrollSpeedEnd = 5;
@ -18,8 +18,6 @@ namespace Avalonia.Input.GestureRecognizers
private bool _scrolling; private bool _scrolling;
private Point _trackedRootPoint; private Point _trackedRootPoint;
private IPointer? _tracking; private IPointer? _tracking;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private int _gestureId; private int _gestureId;
private Point _pointerPressedPoint; private Point _pointerPressedPoint;
private VelocityTracker? _velocityTracker; private VelocityTracker? _velocityTracker;
@ -91,15 +89,9 @@ namespace Avalonia.Input.GestureRecognizers
{ {
get => _scrollStartDistance; get => _scrollStartDistance;
set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); 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 && if (e.Pointer.IsPrimary &&
(e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
@ -107,15 +99,15 @@ namespace Avalonia.Input.GestureRecognizers
EndGesture(); EndGesture();
_tracking = e.Pointer; _tracking = e.Pointer;
_gestureId = ScrollGestureEventArgs.GetNextFreeId(); _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) if (e.Pointer == _tracking)
{ {
var rootPoint = e.GetPosition((Visual?)_target); var rootPoint = e.GetPosition((Visual?)Target);
if (!_scrolling) if (!_scrolling)
{ {
if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) 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.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance),
_trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? 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; _lastMoveTimestamp = e.Timestamp;
_trackedRootPoint = rootPoint; _trackedRootPoint = rootPoint;
_target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector));
e.Handled = true; e.Handled = true;
} }
} }
} }
public void PointerCaptureLost(IPointer pointer) protected override void PointerCaptureLost(IPointer pointer)
{ {
if (pointer == _tracking) EndGesture(); if (pointer == _tracking) EndGesture();
} }
@ -161,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers
{ {
_inertia = default; _inertia = default;
_scrolling = false; _scrolling = false;
_target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId));
_gestureId = 0; _gestureId = 0;
_lastMoveTimestamp = null; _lastMoveTimestamp = null;
} }
@ -169,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers
} }
public void PointerReleased(PointerReleasedEventArgs e) protected override void PointerReleased(PointerReleasedEventArgs e)
{ {
if (e.Pointer == _tracking && _scrolling) if (e.Pointer == _tracking && _scrolling)
{ {
@ -188,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers
var savedGestureId = _gestureId; var savedGestureId = _gestureId;
var st = Stopwatch.StartNew(); var st = Stopwatch.StartNew();
var lastTime = TimeSpan.Zero; var lastTime = TimeSpan.Zero;
_target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia));
DispatcherTimer.Run(() => DispatcherTimer.Run(() =>
{ {
// Another gesture has started, finish the current one // 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 speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds);
var distance = speed * elapsedSinceLastTick.TotalSeconds; var distance = speed * elapsedSinceLastTick.TotalSeconds;
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance);
_target!.RaiseEvent(scrollGestureEventArgs); Target!.RaiseEvent(scrollGestureEventArgs);
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) 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_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource(); s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token; var token = s_holdCancellationToken.Token;
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>(); var settings = ((IInputRoot?)visual.GetVisualRoot())?.PlatformSettings;
if (settings != null) if (settings != null)
{ {
@ -221,7 +221,7 @@ namespace Avalonia.Input
e.Source is Interactive i) e.Source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); 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 tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size()) var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height)); .Inflate(new Thickness(tapSize.Width, tapSize.Height));
@ -260,10 +260,10 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev; var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target)) 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 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 tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size()) var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height)); .Inflate(new Thickness(tapSize.Width, tapSize.Height));
@ -273,7 +273,7 @@ namespace Avalonia.Input
return; return;
} }
if (s_isHolding && ev.Source is Interactive i) if (s_isHolding)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastPointer!.Type)); 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.Metadata;
using Avalonia.Platform;
namespace Avalonia.Input namespace Avalonia.Input
{ {
@ -17,10 +18,18 @@ namespace Avalonia.Input
/// Gets focus manager of the root. /// Gets focus manager of the root.
/// </summary> /// </summary>
/// <remarks> /// <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> /// </remarks>
IFocusManager? FocusManager { get; } 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> /// <summary>
/// Gets or sets the input element that the pointer is currently over. /// Gets or sets the input element that the pointer is currently over.
/// </summary> /// </summary>

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

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

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

@ -5,7 +5,7 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Represents a mouse device. /// Represents a mouse device.
/// </summary> /// </summary>
[NotClientImplementable] [PrivateApi]
public interface IMouseDevice : IPointerDevice 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> /// <summary>
/// Represents a pen/stylus device. /// Represents a pen/stylus device.
/// </summary> /// </summary>
[PrivateApi]
public interface IPenDevice : IPointerDevice public interface IPenDevice : IPointerDevice
{ {

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

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

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

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

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

@ -225,6 +225,11 @@ namespace Avalonia.Input
PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e)); PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e)); PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(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() public InputElement()
@ -583,10 +588,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerMoved(PointerEventArgs e) protected virtual void OnPointerMoved(PointerEventArgs e)
{ {
if (_gestureRecognizers?.HandlePointerMoved(e) == true)
{
e.Handled = true;
}
} }
/// <summary> /// <summary>
@ -595,10 +596,6 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerPressed(PointerPressedEventArgs e) protected virtual void OnPointerPressed(PointerPressedEventArgs e)
{ {
if (_gestureRecognizers?.HandlePointerPressed(e) == true)
{
e.Handled = true;
}
} }
/// <summary> /// <summary>
@ -607,10 +604,33 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerReleased(PointerReleasedEventArgs e) 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> /// <summary>
@ -619,7 +639,7 @@ namespace Avalonia.Input
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
{ {
_gestureRecognizers?.HandlePointerCaptureLost(e);
} }
/// <summary> /// <summary>

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

@ -5,8 +5,6 @@ namespace Avalonia.Input
{ {
public class KeyEventArgs : RoutedEventArgs public class KeyEventArgs : RoutedEventArgs
{ {
public IKeyboardDevice? Device { get; init; }
public Key Key { get; init; } public Key Key { get; init; }
public KeyModifiers KeyModifiers { 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 KeyEventArgs ev = new KeyEventArgs
{ {
RoutedEvent = routedEvent, RoutedEvent = routedEvent,
Device = this,
Key = keyInput.Key, Key = keyInput.Key,
KeyModifiers = keyInput.Modifiers.ToKeyModifiers(), KeyModifiers = keyInput.Modifiers.ToKeyModifiers(),
Source = element, Source = element,
@ -241,7 +240,6 @@ namespace Avalonia.Input
{ {
var ev = new TextInputEventArgs() var ev = new TextInputEventArgs()
{ {
Device = this,
Text = text.Text, Text = text.Text,
Source = element, Source = element,
RoutedEvent = InputElement.TextInputEvent RoutedEvent = InputElement.TextInputEvent

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

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

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

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

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

@ -1,16 +1,20 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Metadata;
#nullable enable
namespace Avalonia.Input.Platform 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) public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{ {
} }
[PrivateApi]
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers, public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift, KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control) KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
@ -85,6 +89,22 @@ namespace Avalonia.Input.Platform
{ {
new KeyGesture(Key.Left, KeyModifiers.Alt) 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; } public KeyModifiers CommandModifiers { get; set; }
@ -106,5 +126,9 @@ namespace Avalonia.Input.Platform
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; } public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
public List<KeyGesture> OpenContextMenu { get; set; } public List<KeyGesture> OpenContextMenu { get; set; }
public List<KeyGesture> Back { 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Input.GestureRecognizers;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Input namespace Avalonia.Input
@ -52,6 +53,9 @@ namespace Avalonia.Input
if (Captured is Visual v3) if (Captured is Visual v3)
v3.DetachedFromVisualTree += OnCaptureDetached; v3.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
CaptureGestureRecognizer(null);
} }
static IInputElement? GetNextCapture(Visual parent) static IInputElement? GetNextCapture(Visual parent)
@ -69,6 +73,31 @@ namespace Avalonia.Input
public PointerType Type { get; } public PointerType Type { get; }
public bool IsPrimary { 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> /// </summary>
public ulong Timestamp { get; } public ulong Timestamp { get; }
internal bool IsGestureRecognitionSkipped { get; private set; }
/// <summary> /// <summary>
/// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated. /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
/// </summary> /// </summary>
@ -121,6 +123,14 @@ namespace Avalonia.Input
return points; return points;
} }
/// <summary>
/// Prevents this event from being handled by other gesture recognizers in the route
/// </summary>
public void PreventGestureRecognition()
{
IsGestureRecognitionSkipped = true;
}
/// <summary> /// <summary>
/// Returns the current pointer point properties /// Returns the current pointer point properties
/// </summary> /// </summary>

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

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

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

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

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

@ -3,8 +3,11 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree;
#pragma warning disable CS0618 #pragma warning disable CS0618
namespace Avalonia.Input namespace Avalonia.Input
@ -49,6 +52,7 @@ namespace Avalonia.Input
} }
var target = pointer.Captured ?? args.Root; var target = pointer.Captured ?? args.Root;
var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
var updateKind = args.Type.ToUpdateKind(); var updateKind = args.Type.ToUpdateKind();
var keyModifier = args.InputModifiers.ToKeyModifiers(); var keyModifier = args.InputModifiers.ToKeyModifiers();
@ -62,19 +66,23 @@ namespace Avalonia.Input
} }
else else
{ {
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>(); var settings = ((IInputRoot?)(target as Interactive)?.GetVisualRoot())?.PlatformSettings;
var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; if (settings is not null)
var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch);
if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > doubleClickTime)
{ {
_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, target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
@ -88,10 +96,19 @@ namespace Avalonia.Input
_pointers.Remove(args.RawPointerId); _pointers.Remove(args.RawPointerId);
using (pointer) using (pointer)
{ {
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer, target = gestureTarget ?? target;
(Visual)args.Root, args.Position, ev.Timestamp, var e = new PointerReleasedEventArgs(target, pointer,
new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind), (Visual)args.Root, args.Position, ev.Timestamp,
keyModifier, MouseButton.Left)); 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); _pointers.Remove(args.RawPointerId);
using (pointer) using (pointer)
pointer.Capture(null); {
pointer?.Capture(null);
pointer?.CaptureGestureRecognizer(null);
}
} }
if (args.Type == RawPointerEventType.TouchUpdate) 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, args.Position, ev.Timestamp,
new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind), 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> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
[NotClientImplementable] [PrivateApi]
internal interface ILayoutManager : IDisposable public interface ILayoutManager : IDisposable
{ {
/// <summary> /// <summary>
/// Raised when the layout manager completes a layout pass. /// 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.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -16,7 +16,8 @@ namespace Avalonia.Layout
/// <summary> /// <summary>
/// Manages measuring and arranging of controls. /// Manages measuring and arranging of controls.
/// </summary> /// </summary>
internal class LayoutManager : ILayoutManager, IDisposable [PrivateApi]
public class LayoutManager : ILayoutManager, IDisposable
{ {
private const int MaxPasses = 10; private const int MaxPasses = 10;
private readonly Layoutable _owner; private readonly Layoutable _owner;

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

@ -16,12 +16,6 @@ namespace Avalonia.Media
public Color Color { get; set; } public Color Color { get; set; }
public bool IsInset { 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) 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); 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 _first;
private readonly BoxShadow[]? _list; private readonly BoxShadow[]? _list;
public int Count { get; } public int Count { get; }
static BoxShadows()
{
Animation.Animation.RegisterAnimator<BoxShadowsAnimator>(prop =>
typeof(BoxShadows).IsAssignableFrom(prop.PropertyType));
}
public BoxShadows(BoxShadow shadow) public BoxShadows(BoxShadow shadow)
{ {

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

@ -34,11 +34,6 @@ namespace Avalonia.Media
/// </summary> /// </summary>
public static readonly StyledProperty<RelativePoint> TransformOriginProperty = public static readonly StyledProperty<RelativePoint> TransformOriginProperty =
AvaloniaProperty.Register<Brush, RelativePoint>(nameof(TransformOrigin)); AvaloniaProperty.Register<Brush, RelativePoint>(nameof(TransformOrigin));
static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// Gets or sets the opacity of the brush. /// 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; private const double byteToDouble = 1.0 / 255;
static Color()
{
#if !BUILDTASK
Animation.Animation.RegisterAnimator<ColorAnimator>(prop => typeof(Color).IsAssignableFrom(prop.PropertyType));
#endif
}
/// <summary> /// <summary>
/// Gets the Alpha component of the color. /// Gets the Alpha component of the color.
/// </summary> /// </summary>

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

@ -63,8 +63,6 @@ internal class EffectAnimator : Animator<IEffect?>
if(s_Registered) if(s_Registered)
return; return;
s_Registered = true; 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.Animation;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Media; namespace Avalonia.Media;
@ -17,8 +18,12 @@ internal partial class MediaContext
{ {
private readonly MediaContext _parent; private readonly MediaContext _parent;
private List<IObserver<TimeSpan>> _observers = new(); private List<IObserver<TimeSpan>> _observers = new();
public bool HasNewSubscriptions { get; set; } private List<IObserver<TimeSpan>> _newObservers = new();
public bool HasSubscriptions => _observers.Count > 0; 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) public MediaContextClock(MediaContext parent)
{ {
@ -29,19 +34,41 @@ internal partial class MediaContext
{ {
_parent.ScheduleRender(false); _parent.ScheduleRender(false);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
HasNewSubscriptions = true;
_observers.Add(observer); _observers.Add(observer);
_newObservers.Add(observer);
return Disposable.Create(() => return Disposable.Create(() =>
{ {
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_observers.Remove(observer); _observers.Remove(observer);
}); });
} }
public void RequestAnimationFrame(Action<TimeSpan> action)
{
_parent.ScheduleRender(false);
_queuedAnimationFrames.Enqueue(action);
}
public void Pulse(TimeSpan now) 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()) 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 public PlayState PlayState
@ -50,4 +77,6 @@ internal partial class MediaContext
set => throw new InvalidOperationException(); 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;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
@ -78,7 +79,7 @@ partial class MediaContext
// Nothing to do, and there are no pending commits // Nothing to do, and there are no pending commits
return false; return false;
foreach (var c in _requestedCommits) foreach (var c in _requestedCommits.ToArray())
CommitCompositor(c); CommitCompositor(c);
_requestedCommits.Clear(); _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 // We are doing several iterations when it happens
for (var c = 0; c < 10; c++) for (var c = 0; c < 10; c++)
{ {
_clock.HasNewSubscriptions = false;
FireInvokeOnRenderCallbacks(); FireInvokeOnRenderCallbacks();
if (_clock.HasNewSubscriptions) if (_clock.HasNewSubscriptions)
{ {
_clock.Pulse(now); _clock.PulseNewSubscriptions();
continue; continue;
} }
@ -212,7 +211,12 @@ internal partial class MediaContext : ICompositorScheduler
} }
while (count > 0); 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) public void BeginInvokeOnRender(Action callback)
{ {
if (_invokeOnRenderCallbacks == null) if (_invokeOnRenderCallbacks == null)
@ -224,4 +228,4 @@ internal partial class MediaContext : ICompositorScheduler
if (!_isRendering) if (!_isRendering)
ScheduleRender(true); ScheduleRender(true);
} }
} }

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

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

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

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

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

@ -1,10 +1,15 @@
using System; using System;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Metadata; using Avalonia.Metadata;
namespace Avalonia.Platform 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 public interface IPlatformSettings
{ {
/// <summary> /// <summary>
@ -33,6 +38,11 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
TimeSpan HoldWaitDuration { get; } TimeSpan HoldWaitDuration { get; }
/// <summary>
/// Get a configuration for platform-specific hotkeys in an Avalonia application.
/// </summary>
PlatformHotkeyConfiguration HotkeyConfiguration { get; }
/// <summary> /// <summary>
/// Gets current system color values including dark mode and accent colors. /// Gets current system color values including dark mode and accent colors.
/// </summary> /// </summary>

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

@ -64,7 +64,8 @@ internal class BclStorageFile : IStorageBookmarkFile
public Task<Stream> OpenWriteAsync() 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() public virtual Task<string?> SaveBookmarkAsync()

7
src/Avalonia.Base/Point.cs

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

5
src/Avalonia.Base/Rect.cs

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

7
src/Avalonia.Base/RelativePoint.cs

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

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

@ -130,7 +130,7 @@ namespace Avalonia.Rendering.Composition
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
using var noPump = NonPumpingLockHelper.Use(); using var noPump = NonPumpingLockHelper.Use();
_nextCommit ??= new(); var commit = _nextCommit ??= new();
(_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead); (_invokeBeforeCommitRead, _invokeBeforeCommitWrite) = (_invokeBeforeCommitWrite, _invokeBeforeCommitRead);
while (_invokeBeforeCommitRead.Count > 0) while (_invokeBeforeCommitRead.Count > 0)
@ -188,7 +188,7 @@ namespace Avalonia.Rendering.Composition
}, TaskContinuationOptions.ExecuteSynchronously); }, TaskContinuationOptions.ExecuteSynchronously);
_nextCommit = null; _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> /// <summary>
/// Server-side counterpart of <see cref="CompositionDrawListVisual"/> /// Server-side counterpart of <see cref="CompositionDrawListVisual"/>
/// </summary> /// </summary>
internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisual, IServerRenderResourceObserver
{ {
#if DEBUG #if DEBUG
// This is needed for debugging purposes so we could see inspect the associated visual from debugger // 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?.Dispose();
_renderCommands = reader.ReadObject<ServerCompositionRenderData?>(); _renderCommands = reader.ReadObject<ServerCompositionRenderData?>();
_renderCommands?.AddObserver(this);
} }
base.DeserializeChangesCore(reader, committedAt); base.DeserializeChangesCore(reader, committedAt);
} }
@ -50,6 +51,8 @@ internal class ServerCompositionDrawListVisual : ServerCompositionContainerVisua
base.RenderCore(canvas, currentTransformedClip); base.RenderCore(canvas, currentTransformedClip);
} }
public void DependencyQueuedInvalidate(IServerRenderResource sender) => ValuesInvalidated();
#if DEBUG #if DEBUG
public override string ToString() public override string ToString()
{ {

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

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

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

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

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

@ -9,7 +9,8 @@ namespace Avalonia.Rendering
/// <summary> /// <summary>
/// Defines the interface for a renderer. /// Defines the interface for a renderer.
/// </summary> /// </summary>
internal interface IRenderer : IDisposable [PrivateApi]
public interface IRenderer : IDisposable
{ {
/// <summary> /// <summary>
/// Gets a value indicating whether the renderer should draw specific diagnostics. /// Gets a value indicating whether the renderer should draw specific diagnostics.
@ -73,7 +74,8 @@ namespace Avalonia.Rendering
Compositor Compositor { get; } Compositor Compositor { get; }
} }
internal interface IHitTester [PrivateApi]
public interface IHitTester
{ {
/// <summary> /// <summary>
/// Hit tests a location to find the visuals at the specified point. /// 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 System;
using Avalonia.Metadata;
namespace Avalonia.Rendering namespace Avalonia.Rendering
{ {
/// <summary> /// <summary>
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event. /// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
/// </summary> /// </summary>
internal class SceneInvalidatedEventArgs : EventArgs [PrivateApi]
public class SceneInvalidatedEventArgs : EventArgs
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class. /// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.

7
src/Avalonia.Base/Size.cs

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

7
src/Avalonia.Base/Thickness.cs

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

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

@ -1,7 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Utilities;
namespace Avalonia.VisualTree namespace Avalonia.VisualTree
{ {
@ -125,9 +128,9 @@ namespace Avalonia.VisualTree
/// <returns>The visual's ancestors.</returns> /// <returns>The visual's ancestors.</returns>
public static IEnumerable<Visual> GetVisualAncestors(this Visual visual) 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) while (v != null)
{ {
@ -194,7 +197,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual and its ancestors.</returns> /// <returns>The visual and its ancestors.</returns>
public static IEnumerable<Visual> GetSelfAndVisualAncestors(this Visual visual) public static IEnumerable<Visual> GetSelfAndVisualAncestors(this Visual visual)
{ {
_ = visual ?? throw new ArgumentNullException(nameof(visual)); ThrowHelper.ThrowIfNull(visual, nameof(visual));
yield return visual; yield return visual;
@ -275,7 +278,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual at the requested point.</returns> /// <returns>The visual at the requested point.</returns>
public static Visual? GetVisualAt(this Visual visual, Point p) 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); return visual.GetVisualAt(p, x => x.IsVisible);
} }
@ -292,7 +295,7 @@ namespace Avalonia.VisualTree
/// <returns>The visual at the requested point.</returns> /// <returns>The visual at the requested point.</returns>
public static Visual? GetVisualAt(this Visual visual, Point p, Func<Visual, bool> filter) 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(); var root = visual.GetVisualRoot();
@ -321,7 +324,7 @@ namespace Avalonia.VisualTree
this Visual visual, this Visual visual,
Point p) Point p)
{ {
_ = visual ?? throw new ArgumentNullException(nameof(visual)); ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual.GetVisualsAt(p, x => x.IsVisible); return visual.GetVisualsAt(p, x => x.IsVisible);
} }
@ -341,7 +344,7 @@ namespace Avalonia.VisualTree
Point p, Point p,
Func<Visual, bool> filter) Func<Visual, bool> filter)
{ {
_ = visual ?? throw new ArgumentNullException(nameof(visual)); ThrowHelper.ThrowIfNull(visual, nameof(visual));
var root = visual.GetVisualRoot(); var root = visual.GetVisualRoot();
@ -435,7 +438,7 @@ namespace Avalonia.VisualTree
/// </returns> /// </returns>
public static IRenderRoot? GetVisualRoot(this Visual visual) public static IRenderRoot? GetVisualRoot(this Visual visual)
{ {
_ = visual ?? throw new ArgumentNullException(nameof(visual)); ThrowHelper.ThrowIfNull(visual, nameof(visual));
return visual as IRenderRoot ?? visual.VisualRoot; 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) 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); return ProcessDownKeyInternal(shift, ctrl);
} }
internal bool ProcessEndKey(KeyEventArgs e) 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); return ProcessEndKey(shift, ctrl);
} }
internal bool ProcessEnterKey(KeyEventArgs e) 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); return ProcessEnterKey(shift, ctrl);
} }
internal bool ProcessHomeKey(KeyEventArgs e) 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); return ProcessHomeKey(shift, ctrl);
} }
@ -2718,25 +2718,25 @@ namespace Avalonia.Controls
internal bool ProcessLeftKey(KeyEventArgs e) 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); return ProcessLeftKey(shift, ctrl);
} }
internal bool ProcessNextKey(KeyEventArgs e) 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); return ProcessNextKey(shift, ctrl);
} }
internal bool ProcessPriorKey(KeyEventArgs e) 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); return ProcessPriorKey(shift, ctrl);
} }
internal bool ProcessRightKey(KeyEventArgs e) 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); return ProcessRightKey(shift, ctrl);
} }
@ -2854,7 +2854,7 @@ namespace Avalonia.Controls
internal bool ProcessUpKey(KeyEventArgs e) 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); return ProcessUpKey(shift, ctrl);
} }
@ -3124,13 +3124,13 @@ namespace Avalonia.Controls
//TODO: Ensure right button is checked for //TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) 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); return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
} }
//TODO: Ensure left button is checked for //TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit) 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); return UpdateStateOnMouseLeftButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
} }
@ -4654,7 +4654,7 @@ namespace Avalonia.Controls
private bool ProcessAKey(KeyEventArgs e) 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) if (ctrl && !shift && !alt && SelectionMode == DataGridSelectionMode.Extended)
{ {
@ -4923,7 +4923,7 @@ namespace Avalonia.Controls
private bool ProcessF2Key(KeyEventArgs e) 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 && if (!shift && !ctrl &&
_editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) && _editingColumnIndex == -1 && CurrentColumnIndex != -1 && GetRowSelection(CurrentSlot) &&
@ -5280,7 +5280,7 @@ namespace Avalonia.Controls
private bool ProcessTabKey(KeyEventArgs e) 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); return ProcessTabKey(e, shift, ctrl);
} }
@ -6099,7 +6099,7 @@ namespace Avalonia.Controls
/// <returns>Whether or not the DataGrid handled the key press.</returns> /// <returns>Whether or not the DataGrid handled the key press.</returns>
private bool ProcessCopyKey(KeyModifiers modifiers) 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) 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() public void ClearSort()
{ {
//InvokeProcessSort is already validating if sorting is possible //InvokeProcessSort is already validating if sorting is possible
_headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier()); _headerCell?.InvokeProcessSort(KeyboardHelper.GetPlatformCtrlOrCmdKeyModifier(OwningGrid));
} }
/// <summary> /// <summary>
@ -1103,6 +1103,16 @@ namespace Avalonia.Controls
get; get;
set; set;
} }
/// <summary>
/// Gets or sets an object associated with this column.
/// </summary>
public object Tag
{
get;
set;
}
/// <summary> /// <summary>
/// Holds a Comparer to use for sorting, if not using the default. /// Holds a Comparer to use for sorting, if not using the default.
/// </summary> /// </summary>

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

@ -241,7 +241,7 @@ namespace Avalonia.Controls
DataGrid owningGrid = OwningGrid; DataGrid owningGrid = OwningGrid;
DataGridSortDescription newSort; 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(); DataGridSortDescription sort = OwningColumn.GetSortDescription();
IDataGridCollectionView collectionView = owningGrid.DataConnection.CollectionView; 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 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); 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); shift = modifiers.HasFlag(KeyModifiers.Shift);
alt = modifiers.HasFlag(KeyModifiers.Alt); alt = modifiers.HasFlag(KeyModifiers.Alt);
} }
public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier() public static KeyModifiers GetPlatformCtrlOrCmdKeyModifier(Control target)
{ {
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var keymap = TopLevel.GetTopLevel(target)!.PlatformSettings!.HotkeyConfiguration;
return keymap?.CommandModifiers ?? KeyModifiers.Control; return keymap.CommandModifiers;
} }
} }
} }

23
src/Avalonia.Controls/Application.cs

@ -180,6 +180,17 @@ namespace Avalonia
/// </summary> /// </summary>
public IApplicationLifetime? ApplicationLifetime { get; set; } 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 event Action<IReadOnlyList<IStyle>>? IGlobalStyles.GlobalStylesAdded
{ {
add => _stylesAdded += value; add => _stylesAdded += value;
@ -229,10 +240,12 @@ namespace Avalonia
var focusManager = new FocusManager(); var focusManager = new FocusManager();
InputManager = new InputManager(); InputManager = new InputManager();
var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>(); if (PlatformSettings is { } settings)
settings.ColorValuesChanged += OnColorValuesChanged; {
OnColorValuesChanged(settings, settings.GetColorValues()); settings.ColorValuesChanged += OnColorValuesChanged;
OnColorValuesChanged(settings, settings.GetColorValues());
}
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable
.Bind<IAccessKeyHandler>().ToTransient<AccessKeyHandler>() .Bind<IAccessKeyHandler>().ToTransient<AccessKeyHandler>()
.Bind<IGlobalDataTemplates>().ToConstant(this) .Bind<IGlobalDataTemplates>().ToConstant(this)
@ -242,7 +255,7 @@ namespace Avalonia
.Bind<IInputManager>().ToConstant(InputManager) .Bind<IInputManager>().ToConstant(InputManager)
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>() .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance); .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 // 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) if (AvaloniaLocator.Current.GetService<IPlatformDragSource>() == null)
AvaloniaLocator.CurrentMutable AvaloniaLocator.CurrentMutable

2
src/Avalonia.Controls/ContextMenu.cs

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

2
src/Avalonia.Controls/Control.cs

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

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

@ -1,14 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls.Embedding.Offscreen namespace Avalonia.Controls.Embedding.Offscreen
{ {
@ -17,7 +13,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
{ {
private double _scaling = 1; private double _scaling = 1;
private Size _clientSize; private Size _clientSize;
private ManualRenderTimer _manualRenderTimer = new();
public IInputRoot? InputRoot { get; private set; } public IInputRoot? InputRoot { get; private set; }
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
@ -27,21 +22,10 @@ namespace Avalonia.Controls.Embedding.Offscreen
IsDisposed = true; 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 Compositor Compositor { get; }
public OffscreenTopLevelImplBase() public OffscreenTopLevelImplBase()
{ => Compositor = new Compositor(null);
Compositor = new Compositor(new RenderLoop(_manualRenderTimer), null, false,
MediaContext.Instance, false);
}
public abstract IEnumerable<object> Surfaces { get; } public abstract IEnumerable<object> Surfaces { get; }
@ -76,7 +60,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }
/// <inheritdoc/>
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1); public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 1, 1);
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; 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.Layout;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -392,7 +393,7 @@ namespace Avalonia.Controls.Primitives
&& IsOpen && IsOpen
&& Target?.ContextFlyout == this) && Target?.ContextFlyout == this)
{ {
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
if (keymap?.OpenContextMenu.Any(k => k.Matches(e)) == true) 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.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -127,7 +128,7 @@ namespace Avalonia.Controls
protected override void OnKeyDown(KeyEventArgs e) 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); var ctrl = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
if (!ctrl && if (!ctrl &&
@ -165,7 +166,7 @@ namespace Avalonia.Controls
internal bool UpdateSelectionFromPointerEvent(Control source, PointerEventArgs e) 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); var toggle = hotkeys is not null && e.KeyModifiers.HasAllFlags(hotkeys.CommandModifiers);
return UpdateSelectionFromEventSource( return UpdateSelectionFromEventSource(

2
src/Avalonia.Controls/ListBoxItem.cs

@ -90,7 +90,7 @@ namespace Avalonia.Controls
e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right) e.InitialPressMouseButton is MouseButton.Left or MouseButton.Right)
{ {
var point = e.GetCurrentPoint(this); 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 tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(_pointerDownPoint, new Size()) var tapRect = new Rect(_pointerDownPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height)); .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.Input.Platform;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -204,7 +205,7 @@ namespace Avalonia.Controls
return; return;
} }
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var keymap = Application.Current!.PlatformSettings?.HotkeyConfiguration;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e)); 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; 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) if (waitFor.TotalMilliseconds < 1)
continue; continue;
_wakeup.WaitOne(waitFor); _wakeup.WaitOne(waitFor);
@ -112,4 +118,4 @@ public class ManagedDispatcherImpl : IControlledDispatcherImpl
registration.Dispose(); registration.Dispose();
} }
} }

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

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

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

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

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

@ -238,7 +238,6 @@ namespace Avalonia.Controls
visualizerVisual.Offset = IsPullDirectionVertical ? visualizerVisual.Offset = IsPullDirectionVertical ?
new Vector3(visualizerVisual.Offset.X, 0, 0) : new Vector3(visualizerVisual.Offset.X, 0, 0) :
new Vector3(0, visualizerVisual.Offset.Y, 0); new Vector3(0, visualizerVisual.Offset.Y, 0);
visual.Offset = default;
_content.InvalidateMeasure(); _content.InvalidateMeasure();
break; break;
case RefreshVisualizerState.Interacting: case RefreshVisualizerState.Interacting:
@ -452,8 +451,6 @@ namespace Avalonia.Controls
_interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty) _interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty)
.Subscribe(InteractionRatioObserver); .Subscribe(InteractionRatioObserver);
var visual = RefreshInfoProvider.Visual;
_executingRatio = RefreshInfoProvider.ExecutionRatio; _executingRatio = RefreshInfoProvider.ExecutionRatio;
} }
else 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using Avalonia.Controls.Embedding.Offscreen; using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces; using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input; using Avalonia.Input;
@ -11,7 +10,6 @@ using Avalonia.Platform;
using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input; using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport; using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Rendering;
using Avalonia.Threading; using Avalonia.Threading;
using Key = Avalonia.Input.Key; using Key = Avalonia.Input.Key;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat; using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
@ -20,28 +18,28 @@ using ProtocolMouseButton = Avalonia.Remote.Protocol.Input.MouseButton;
namespace Avalonia.Controls.Remote.Server namespace Avalonia.Controls.Remote.Server
{ {
[Unstable] [Unstable]
internal class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl internal partial class RemoteServerTopLevelImpl : OffscreenTopLevelImplBase, IFramebufferPlatformSurface, ITopLevelImpl
{ {
private readonly IAvaloniaRemoteTransportConnection _transport; private readonly IAvaloniaRemoteTransportConnection _transport;
private LockedFramebuffer? _framebuffer;
private readonly object _lock = new(); private readonly object _lock = new();
private readonly Action _sendLastFrameIfNeeded;
private readonly Action _renderAndSendFrameIfNeeded;
private Framebuffer _framebuffer = Framebuffer.Empty;
private long _lastSentFrame = -1; private long _lastSentFrame = -1;
private long _lastReceivedFrame = -1; private long _lastReceivedFrame = -1;
private long _nextFrameNumber = 1; private long _nextFrameNumber = 1;
private ClientViewportAllocatedMessage? _pendingAllocation; private ClientViewportAllocatedMessage? _pendingAllocation;
private bool _queuedNextRender; private ProtocolPixelFormat? _format;
private bool _inRender;
private Vector _dpi = new Vector(96, 96);
private ProtocolPixelFormat[]? _supportedFormats;
public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport) public RemoteServerTopLevelImpl(IAvaloniaRemoteTransportConnection transport)
{ {
_sendLastFrameIfNeeded = SendLastFrameIfNeeded;
_renderAndSendFrameIfNeeded = RenderAndSendFrameIfNeeded;
_transport = transport; _transport = transport;
_transport.OnMessage += OnMessage; _transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetRequiredService<IKeyboardDevice>(); KeyboardDevice = AvaloniaLocator.Current.GetRequiredService<IKeyboardDevice>();
QueueNextRender();
Compositor.AfterCommit += QueueNextRender;
} }
private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed) private static RawPointerEventType GetAvaloniaEventType(ProtocolMouseButton button, bool pressed)
@ -112,45 +110,41 @@ namespace Avalonia.Controls.Remote.Server
{ {
lock (_lock) lock (_lock)
{ {
if (obj is FrameReceivedMessage lastFrame) switch (obj)
{ {
lock (_lock) case FrameReceivedMessage lastFrame:
{
_lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame); _lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame);
} Dispatcher.UIThread.Post(_sendLastFrameIfNeeded);
Dispatcher.UIThread.Post(RenderIfNeeded); break;
}
if(obj is ClientRenderInfoMessage renderInfo) case ClientRenderInfoMessage renderInfo:
{ Dispatcher.UIThread.Post(() =>
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
{ {
Width = m.Width, RenderScaling = renderInfo.DpiX / 96.0;
Height = m.Height RenderAndSendFrameIfNeeded();
}); });
}); break;
if (obj is ClientViewportAllocatedMessage allocated)
{ case ClientSupportedPixelFormatsMessage supportedFormats:
lock (_lock) _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) if (_pendingAllocation == null)
{
Dispatcher.UIThread.Post(() => Dispatcher.UIThread.Post(() =>
{ {
ClientViewportAllocatedMessage allocation; ClientViewportAllocatedMessage allocation;
@ -159,101 +153,111 @@ namespace Avalonia.Controls.Remote.Server
allocation = _pendingAllocation!; allocation = _pendingAllocation!;
_pendingAllocation = null; _pendingAllocation = null;
} }
_dpi = new Vector(allocation.DpiX, allocation.DpiY);
RenderScaling = allocation.DpiX / 96.0;
ClientSize = new Size(allocation.Width, allocation.Height); ClientSize = new Size(allocation.Width, allocation.Height);
RenderIfNeeded(); RenderAndSendFrameIfNeeded();
}); });
}
_pendingAllocation = allocated; _pendingAllocation = allocated;
} break;
}
if(obj is PointerMovedEventMessage pointer) case PointerMovedEventMessage pointer:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Input?.Invoke(new RawPointerEventArgs(
Input?.Invoke(new RawPointerEventArgs( MouseDevice,
MouseDevice, 0,
0, InputRoot!,
InputRoot!, RawPointerEventType.Move,
RawPointerEventType.Move, new Point(pointer.X, pointer.Y),
new Point(pointer.X, pointer.Y), GetAvaloniaRawInputModifiers(pointer.Modifiers)));
GetAvaloniaRawInputModifiers(pointer.Modifiers))); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
}
if(obj is PointerPressedEventMessage pressed) case PointerPressedEventMessage pressed:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Input?.Invoke(new RawPointerEventArgs(
Input?.Invoke(new RawPointerEventArgs( MouseDevice,
MouseDevice, 0,
0, InputRoot!,
InputRoot!, GetAvaloniaEventType(pressed.Button, true),
GetAvaloniaEventType(pressed.Button, true), new Point(pressed.X, pressed.Y),
new Point(pressed.X, pressed.Y), GetAvaloniaRawInputModifiers(pressed.Modifiers)));
GetAvaloniaRawInputModifiers(pressed.Modifiers))); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
}
if (obj is PointerReleasedEventMessage released) case PointerReleasedEventMessage released:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Input?.Invoke(new RawPointerEventArgs(
Input?.Invoke(new RawPointerEventArgs( MouseDevice,
MouseDevice, 0,
0, InputRoot!,
InputRoot!, GetAvaloniaEventType(released.Button, false),
GetAvaloniaEventType(released.Button, false), new Point(released.X, released.Y),
new Point(released.X, released.Y), GetAvaloniaRawInputModifiers(released.Modifiers)));
GetAvaloniaRawInputModifiers(released.Modifiers))); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
}
if(obj is ScrollEventMessage scroll) case ScrollEventMessage scroll:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Input?.Invoke(new RawMouseWheelEventArgs(
Input?.Invoke(new RawMouseWheelEventArgs( MouseDevice,
MouseDevice, 0,
0, InputRoot!,
InputRoot!, new Point(scroll.X, scroll.Y),
new Point(scroll.X, scroll.Y), new Vector(scroll.DeltaX, scroll.DeltaY),
new Vector(scroll.DeltaX, scroll.DeltaY), GetAvaloniaRawInputModifiers(scroll.Modifiers)));
GetAvaloniaRawInputModifiers(scroll.Modifiers))); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
}
if(obj is KeyEventMessage key) case KeyEventMessage key:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawKeyEventArgs(
Input?.Invoke(new RawKeyEventArgs( KeyboardDevice,
KeyboardDevice, 0,
0, InputRoot!,
InputRoot!, key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, (Key)key.Key,
(Key)key.Key, GetAvaloniaRawInputModifiers(key.Modifiers)));
GetAvaloniaRawInputModifiers(key.Modifiers))); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
}
if(obj is TextInputEventMessage text) case TextInputEventMessage text:
{ Dispatcher.UIThread.Post(() =>
Dispatcher.UIThread.Post(() => {
{ Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Input?.Invoke(new RawTextInputEventArgs(
Input?.Invoke(new RawTextInputEventArgs( KeyboardDevice,
KeyboardDevice, 0,
0, InputRoot!,
InputRoot!, text.Text));
text.Text)); }, DispatcherPriority.Input);
}, DispatcherPriority.Input); break;
} }
} }
} }
protected void SetDpi(Vector dpi) private static ProtocolPixelFormat? TryGetValidPixelFormat(ProtocolPixelFormat[]? formats)
{ {
_dpi = dpi; if (formats is not null)
RenderIfNeeded(); {
foreach (var format in formats)
{
if (format is >= 0 and <= ProtocolPixelFormat.MaxValue)
return format;
}
}
return null;
} }
protected virtual Size Measure(Size constraint) protected virtual Size Measure(Size constraint)
@ -265,88 +269,63 @@ namespace Avalonia.Controls.Remote.Server
public override IEnumerable<object> Surfaces => new[] { this }; 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; lock (_lock)
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
{ {
_framebuffer = null; if (_format is not { } format)
handle.Free(); _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() public ILockedFramebuffer Lock()
{ => GetOrCreateFramebuffer().Lock(_sendLastFrameIfNeeded);
if (_framebuffer == null)
throw new InvalidOperationException("Paint was not requested, wait for Paint event");
return _framebuffer;
}
protected void RenderIfNeeded() private void SendLastFrameIfNeeded()
{ {
lock (_lock) if (IsDisposed)
{ return;
if (_lastReceivedFrame != _lastSentFrame || !_queuedNextRender || _supportedFormats == null)
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) 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++; _lastSentFrame = _nextFrameNumber++;
frame.SequenceId = _lastSentFrame; sequenceId = _lastSentFrame;
_queuedNextRender = false;
} }
_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; if (_lastReceivedFrame != _lastSentFrame || _format is null)
DispatcherTimer.RunOnce(RenderIfNeeded, TimeSpan.FromMilliseconds(2), DispatcherPriority.Background); return;
} }
var framebuffer = GetOrCreateFramebuffer();
if (framebuffer.Stride > 0)
Paint?.Invoke(new Rect(framebuffer.ClientSize));
SendLastFrameIfNeeded();
} }
public override IMouseDevice MouseDevice { get; } = new MouseDevice(); public override IMouseDevice MouseDevice { get; } = new MouseDevice();

2
src/Avalonia.Controls/SelectableTextBlock.cs

@ -198,7 +198,7 @@ namespace Avalonia.Controls
var handled = false; var handled = false;
var modifiers = e.KeyModifiers; 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 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.Utilities;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Media.TextFormatting; using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers; using Avalonia.Automation.Peers;
using Avalonia.Threading; using Avalonia.Threading;
@ -32,20 +31,17 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Cut action /// Gets a platform-specific <see cref="KeyGesture"/> for the Cut action
/// </summary> /// </summary>
public static KeyGesture? CutGesture { get; } = AvaloniaLocator.Current public static KeyGesture? CutGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Cut.FirstOrDefault();
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
/// <summary> /// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Copy action /// Gets a platform-specific <see cref="KeyGesture"/> for the Copy action
/// </summary> /// </summary>
public static KeyGesture? CopyGesture { get; } = AvaloniaLocator.Current public static KeyGesture? CopyGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Copy.FirstOrDefault();
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault();
/// <summary> /// <summary>
/// Gets a platform-specific <see cref="KeyGesture"/> for the Paste action /// Gets a platform-specific <see cref="KeyGesture"/> for the Paste action
/// </summary> /// </summary>
public static KeyGesture? PasteGesture { get; } = AvaloniaLocator.Current public static KeyGesture? PasteGesture => Application.Current?.PlatformSettings?.HotkeyConfiguration.Paste.FirstOrDefault();
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
/// <summary> /// <summary>
/// Defines the <see cref="AcceptsReturn"/> property /// Defines the <see cref="AcceptsReturn"/> property
@ -1103,7 +1099,7 @@ namespace Avalonia.Controls
var handled = false; var handled = false;
var modifiers = e.KeyModifiers; 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 Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers); bool DetectSelection() => e.KeyModifiers.HasAllFlags(keymap.SelectionModifiers);
@ -1217,6 +1213,34 @@ namespace Avalonia.Controls
selection = true; selection = true;
handled = 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 else
{ {
bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers); bool hasWholeWordModifiers = modifiers.HasAllFlags(keymap.WholeWordTextActionModifiers);
@ -1407,8 +1431,6 @@ namespace Avalonia.Controls
{ {
var point = e.GetPosition(_presenter); var point = e.GetPosition(_presenter);
var oldIndex = CaretIndex;
_presenter.MoveCaretToPoint(point); _presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex; 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> /// <summary>
/// Select all text in the TextBox /// Select all text in the TextBox
/// </summary> /// </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.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -42,6 +43,10 @@ namespace Avalonia.Controls
x.UpdateKnobPos(x.IsChecked.Value); x.UpdateKnobPos(x.IsChecked.Value);
} }
}); });
KnobTransitionsProperty.Changed.AddClassHandler<ToggleSwitch>((x, e) =>
{
x.UpdateKnobTransitions();
});
} }
/// <summary> /// <summary>
@ -68,6 +73,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IDataTemplate?> OnContentTemplateProperty = public static readonly StyledProperty<IDataTemplate?> OnContentTemplateProperty =
AvaloniaProperty.Register<ToggleSwitch, IDataTemplate?>(nameof(OnContentTemplate)); 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> /// <summary>
/// Gets or Sets the Content that is displayed when in the On State. /// Gets or Sets the Content that is displayed when in the On State.
/// </summary> /// </summary>
@ -116,6 +127,17 @@ namespace Avalonia.Controls
set { SetValue(OnContentTemplateProperty, value); } 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) private void OffContentChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (e.OldValue is ILogical oldChild) if (e.OldValue is ILogical oldChild)
@ -177,7 +199,21 @@ namespace Avalonia.Controls
UpdateKnobPos(IsChecked.Value); 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) private void KnobsPanel_PointerPressed(object? sender, Input.PointerPressedEventArgs e)
{ {
_switchStartPoint = e.GetPosition(_switchKnob); _switchStartPoint = e.GetPosition(_switchKnob);
@ -194,7 +230,7 @@ namespace Avalonia.Controls
_knobsPanel!.ClearValue(Canvas.LeftProperty); _knobsPanel!.ClearValue(Canvas.LeftProperty);
PseudoClasses.Set(":dragging", false); PseudoClasses.Set(":dragging", false);
if (shouldBecomeChecked == IsChecked) if (shouldBecomeChecked == IsChecked)
{ {
UpdateKnobPos(shouldBecomeChecked); UpdateKnobPos(shouldBecomeChecked);
@ -203,6 +239,7 @@ namespace Avalonia.Controls
{ {
SetCurrentValue(IsCheckedProperty, shouldBecomeChecked); SetCurrentValue(IsCheckedProperty, shouldBecomeChecked);
} }
UpdateKnobTransitions();
} }
else else
{ {
@ -218,6 +255,10 @@ namespace Avalonia.Controls
{ {
if (_knobsPanelPressed) if (_knobsPanelPressed)
{ {
if(_knobsPanel != null)
{
_knobsPanel.Transitions = null;
}
var difference = e.GetPosition(_switchKnob) - _switchStartPoint; var difference = e.GetPosition(_switchKnob) - _switchStartPoint;
if ((!_isDragging) && (System.Math.Abs(difference.X) > 3)) 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 System.Threading.Tasks;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -250,7 +251,7 @@ namespace Avalonia.Controls
if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown)
{ {
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>()?.Back; var keymap = PlatformSettings?.HotkeyConfiguration.Back;
if (keymap != null) if (keymap != null)
{ {
@ -487,6 +488,9 @@ namespace Avalonia.Controls
/// <inheritdoc /> /// <inheritdoc />
public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>(); public IFocusManager? FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
/// <inheritdoc />
public IPlatformSettings? PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
/// <inheritdoc/> /// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p) Point IRenderRoot.PointToClient(PixelPoint p)
{ {
@ -532,7 +536,16 @@ namespace Avalonia.Controls
return Disposable.Create(() => { }); 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) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);

5
src/Avalonia.Controls/TreeView.cs

@ -571,7 +571,7 @@ namespace Avalonia.Controls
if (!e.Handled) 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; bool Match(List<KeyGesture>? gestures) => gestures?.Any(g => g.Matches(e)) ?? false;
if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll)) if (this.SelectionMode == SelectionMode.Multiple && Match(keymap?.SelectAll))
@ -652,11 +652,12 @@ namespace Avalonia.Controls
if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed) if (point.Properties.IsLeftButtonPressed || point.Properties.IsRightButtonPressed)
{ {
var keymap = Application.Current!.PlatformSettings!.HotkeyConfiguration;
e.Handled = UpdateSelectionFromEventSource( e.Handled = UpdateSelectionFromEventSource(
e.Source, e.Source,
true, true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift), e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService<PlatformHotkeyConfiguration>().CommandModifiers), e.KeyModifiers.HasAllFlags(keymap.CommandModifiers),
point.Properties.IsRightButtonPressed); 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 // In previewer mode we completely ignore client-side viewport size
if (obj is ClientViewportAllocatedMessage alloc) 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; return;
} }
base.OnMessage(transport, obj); base.OnMessage(transport, obj);
@ -67,7 +71,7 @@ namespace Avalonia.DesignerSupport.Remote
Height = clientSize.Height Height = clientSize.Height
}); });
ClientSize = clientSize; ClientSize = clientSize;
RenderIfNeeded(); RenderAndSendFrameIfNeeded();
} }
public void Move(PixelPoint point) 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<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>() .Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
.Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(null)) .Bind<IDispatcherImpl>().ToConstant(new ManagedDispatcherImpl(null))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new UiThreadRenderTimer(60))
.Bind<IWindowingPlatform>().ToConstant(instance) .Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>() .Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>(); .Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();

8
src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -60,9 +60,9 @@ namespace Avalonia.FreeDesktop
{ {
try 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"); var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher");
OnNameChange(nameOwner); OnNameChange("org.kde.StatusNotifierWatcher", nameOwner);
} }
catch 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; return;
if (!_serviceConnected & newOwner is not null) if (!_serviceConnected & newOwner is not null)

18
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -108,20 +108,18 @@ namespace Avalonia.Native
.Bind<IWindowingPlatform>().ToConstant(this) .Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60)) .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)) .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform) .Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform)
.Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands())); .Bind<INativeApplicationCommands>().ToConstant(new MacOSNativeMenuCommands(_factory.CreateApplicationCommands()));
var hotkeys = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>(); var hotkeys = new PlatformHotkeyConfiguration(KeyModifiers.Meta, wholeWordTextActionModifiers: KeyModifiers.Alt);
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.MoveCursorToTheStartOfLine.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, 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) 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 AccentLight2Key = "SystemAccentColorLight2";
public const string AccentLight3Key = "SystemAccentColorLight3"; public const string AccentLight3Key = "SystemAccentColorLight3";
private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215); private static readonly Color s_defaultSystemAccentColor = Color.FromRgb(0, 120, 215);
private readonly IPlatformSettings? _platformSettings;
private bool _invalidateColors = true; private bool _invalidateColors = true;
private Color _systemAccentColor; private Color _systemAccentColor;
private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3; private Color _systemAccentColorDark1, _systemAccentColorDark2, _systemAccentColorDark3;
private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3; private Color _systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3;
public SystemAccentColors()
{
_platformSettings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
}
public bool HasResources => true; public bool HasResources => true;
public bool TryGetResource(object key, ThemeVariant? theme, out object? value) public bool TryGetResource(object key, ThemeVariant? theme, out object? value)
{ {
@ -96,10 +90,12 @@ internal class SystemAccentColors : IResourceProvider
Owner = owner; Owner = owner;
OwnerChanged?.Invoke(this, EventArgs.Empty); 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; Owner = null;
OwnerChanged?.Invoke(this, EventArgs.Empty); 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) if (_invalidateColors)
{ {
_invalidateColors = false; _invalidateColors = false;
var platformSettings = GetFromOwner(Owner);
_systemAccentColor = _platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor; _systemAccentColor = platformSettings?.GetColorValues().AccentColor1 ?? s_defaultSystemAccentColor;
(_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3, (_systemAccentColorDark1,_systemAccentColorDark2, _systemAccentColorDark3,
_systemAccentColorLight1, _systemAccentColorLight2, _systemAccentColorLight3) = CalculateAccentShades(_systemAccentColor); _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) public static (Color d1, Color d2, Color d3, Color l1, Color l2, Color l3) CalculateAccentShades(Color accentColor)
{ {
// dark1step = (hslAccent.L - SystemAccentColorDark1.L) * 255 // 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="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <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"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid Background="{TemplateBinding Background}" RowDefinitions="Auto,*"> <Grid Background="{TemplateBinding Background}" RowDefinitions="Auto,*">
@ -134,18 +142,6 @@
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
</Style> </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 --> <!-- PointerOverState -->
<Style Selector="^:pointerover /template/ Border#OuterBorder"> <Style Selector="^:pointerover /template/ Border#OuterBorder">
<Setter Property="BorderBrush" Value="{DynamicResource ToggleSwitchStrokeOffPointerOver}" /> <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="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Center" /> <Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" /> <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"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid Background="{TemplateBinding Background}" <Grid Background="{TemplateBinding Background}"
@ -123,11 +131,6 @@
<Grid x:Name="PART_MovingKnobs" <Grid x:Name="PART_MovingKnobs"
Width="20" Height="20"> 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" <Ellipse x:Name="SwitchKnobOn"
Fill="{DynamicResource HighlightForegroundColor}" 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() public void Tapped_Should_Follow_Pointer_Pressed_Released()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var result = new List<string>(); var result = new List<string>();
AddHandlers(decorator, border, result, false); AddHandlers(root, border, result, false);
_mouse.Click(border); _mouse.Click(border);
@ -41,13 +41,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled() public void Tapped_Should_Be_Raised_Even_When_Pressed_Released_Handled()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var result = new List<string>(); var result = new List<string>();
AddHandlers(decorator, border, result, true); AddHandlers(root, border, result, true);
_mouse.Click(border); _mouse.Click(border);
@ -58,13 +58,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Not_Be_Raised_For_Middle_Button() public void Tapped_Should_Not_Be_Raised_For_Middle_Button()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle); _mouse.Click(border, MouseButton.Middle);
@ -75,13 +75,13 @@ namespace Avalonia.Base.UnitTests.Input
public void Tapped_Should_Not_Be_Raised_For_Right_Button() public void Tapped_Should_Not_Be_Raised_For_Right_Button()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.TappedEvent, (_, _) => raised = true); root.AddHandler(Gestures.TappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right); _mouse.Click(border, MouseButton.Right);
@ -92,13 +92,13 @@ namespace Avalonia.Base.UnitTests.Input
public void RightTapped_Should_Be_Raised_For_Right_Button() public void RightTapped_Should_Be_Raised_For_Right_Button()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true); root.AddHandler(Gestures.RightTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right); _mouse.Click(border, MouseButton.Right);
@ -109,13 +109,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed() public void DoubleTapped_Should_Follow_Pointer_Pressed_Released_Pressed()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var result = new List<string>(); var result = new List<string>();
AddHandlers(decorator, border, result, false); AddHandlers(root, border, result, false);
_mouse.Click(border); _mouse.Click(border);
_mouse.Down(border, clickCount: 2); _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() public void DoubleTapped_Should_Be_Raised_Even_When_Pressed_Released_Handled()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var result = new List<string>(); var result = new List<string>();
AddHandlers(decorator, border, result, true); AddHandlers(root, border, result, true);
_mouse.Click(border); _mouse.Click(border);
_mouse.Down(border, clickCount: 2); _mouse.Down(border, clickCount: 2);
@ -145,13 +145,13 @@ namespace Avalonia.Base.UnitTests.Input
public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button() public void DoubleTapped_Should_Not_Be_Raised_For_Middle_Button()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Middle); _mouse.Click(border, MouseButton.Middle);
_mouse.Down(border, MouseButton.Middle, clickCount: 2); _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() public void DoubleTapped_Should_Not_Be_Raised_For_Right_Button()
{ {
Border border = new Border(); Border border = new Border();
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true); root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => raised = true);
_mouse.Click(border, MouseButton.Right); _mouse.Click(border, MouseButton.Right);
_mouse.Down(border, MouseButton.Right, clickCount: 2); _mouse.Down(border, MouseButton.Right, clickCount: 2);
@ -191,13 +191,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
HoldingState holding = HoldingState.Cancelled; HoldingState holding = HoldingState.Cancelled;
decorator.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState); root.AddHandler(Gestures.HoldingEvent, (_, e) => holding = e.HoldingState);
_mouse.Down(border); _mouse.Down(border);
Assert.False(holding != HoldingState.Cancelled); Assert.False(holding != HoldingState.Cancelled);
@ -227,13 +227,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; 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); _mouse.Down(border);
Assert.False(raised); Assert.False(raised);
@ -262,13 +262,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; 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); _mouse.Down(border);
Assert.False(raised); Assert.False(raised);
@ -297,13 +297,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var cancelled = false; 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); _mouse.Down(border);
Assert.False(cancelled); Assert.False(cancelled);
@ -333,13 +333,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var root = new TestRoot()
{ {
Child = border Child = border
}; };
var cancelled = false; 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); _mouse.Down(border);
@ -369,13 +369,13 @@ namespace Avalonia.Base.UnitTests.Input
Border border = new Border(); Border border = new Border();
Gestures.SetIsHoldWithMouseEnabled(border, true); Gestures.SetIsHoldWithMouseEnabled(border, true);
var decorator = new Decorator var testRoot = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; 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(); var secondMouse = new MouseTestHelper();
@ -392,12 +392,12 @@ namespace Avalonia.Base.UnitTests.Input
} }
private static void AddHandlers( private static void AddHandlers(
Decorator decorator, TestRoot root,
Border border, Border border,
IList<string> result, IList<string> result,
bool markHandled) bool markHandled)
{ {
decorator.AddHandler(InputElement.PointerPressedEvent, (_, e) => root.AddHandler(InputElement.PointerPressedEvent, (_, e) =>
{ {
result.Add("dp"); 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"); result.Add("dr");
@ -420,8 +420,8 @@ namespace Avalonia.Base.UnitTests.Input
border.AddHandler(InputElement.PointerPressedEvent, (_, _) => result.Add("bp")); border.AddHandler(InputElement.PointerPressedEvent, (_, _) => result.Add("bp"));
border.AddHandler(InputElement.PointerReleasedEvent, (_, _) => result.Add("br")); border.AddHandler(InputElement.PointerReleasedEvent, (_, _) => result.Add("br"));
decorator.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt")); root.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("dt"));
decorator.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt")); root.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("ddt"));
border.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("bt")); border.AddHandler(Gestures.TappedEvent, (_, _) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("bdt")); border.AddHandler(Gestures.DoubleTappedEvent, (_, _) => result.Add("bdt"));
} }
@ -438,13 +438,13 @@ namespace Avalonia.Base.UnitTests.Input
Background = new SolidColorBrush(Colors.Red) Background = new SolidColorBrush(Colors.Red)
}; };
border.GestureRecognizers.Add(new PinchGestureRecognizer()); border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5); var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10); var secondPoint = new Point(10, 10);
@ -466,13 +466,13 @@ namespace Avalonia.Base.UnitTests.Input
Background = new SolidColorBrush(Colors.Red) Background = new SolidColorBrush(Colors.Red)
}; };
border.GestureRecognizers.Add(new PinchGestureRecognizer()); border.GestureRecognizers.Add(new PinchGestureRecognizer());
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true);
var firstPoint = new Point(5, 5); var firstPoint = new Point(5, 5);
var secondPoint = new Point(10, 10); var secondPoint = new Point(10, 10);
@ -502,13 +502,13 @@ namespace Avalonia.Base.UnitTests.Input
CanVerticallyScroll = true, CanVerticallyScroll = true,
ScrollStartDistance = 50 ScrollStartDistance = 50
}); });
var decorator = new Decorator var root = new TestRoot
{ {
Child = border Child = border
}; };
var raised = false; var raised = false;
decorator.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true); root.AddHandler(Gestures.ScrollGestureEvent, (_, _) => raised = true);
var firstTouch = new TouchTestHelper(); var firstTouch = new TouchTestHelper();

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

@ -3,9 +3,11 @@ using System.Windows.Input;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Moq; using Moq;
@ -14,7 +16,7 @@ using MouseButton = Avalonia.Input.MouseButton;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
public class ButtonTests public class ButtonTests : ScopedTestBase
{ {
private MouseTestHelper _helper = new MouseTestHelper(); 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>>())) 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) => .Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new Visual[] { r } : new Visual[0]); 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; bool clicked = false;
@ -165,16 +175,13 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside() public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{ {
var renderer = new Mock<IHitTester>(); var root = new TestRoot();
var target = new Button()
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)
{ {
Bounds = new Rect(0, 0, 100, 100) Width = 100,
Height = 100
}; };
root.Child = target;
bool clicked = false; bool clicked = false;
@ -203,12 +210,20 @@ namespace Avalonia.Controls.UnitTests
.Returns<Point, Visual, Func<Visual, bool>>((p, r, f) => .Returns<Point, Visual, Func<Visual, bool>>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ? r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
new Visual[] { r } : new Visual[0]); 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 } 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 //actual bounds of button should be 100,0,100,100 x -> translated 100 pixels
//so mouse with x=150 coordinates should trigger click //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 }; 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) private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
{ {
_helper.Down(button, mouseButton, position, clickCount: clickCount); _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); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Shift);
Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems); Assert.Equal(new[] { "Foo", "Bar", "Baz" }, target.SelectedItems);
@ -60,7 +60,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
SelectionChangedEventArgs receivedArgs = null; SelectionChangedEventArgs receivedArgs = null;
@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[1]);
_helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
_helper.Click(target.Presenter.Panel.Children[3], 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); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[1]);
_helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control); _helper.Click(target.Presenter.Panel.Children[2], modifiers: KeyModifiers.Control);
@ -183,7 +183,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[3]);
_helper.Click(target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control); _helper.Click(target.Presenter.Panel.Children[4], modifiers: KeyModifiers.Control);
@ -211,7 +211,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[3]);
_helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); _helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
@ -239,7 +239,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[0]);
_helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift); _helper.Click(target.Presenter.Panel.Children[5], modifiers: KeyModifiers.Shift);
@ -266,7 +266,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); root.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
SelectionChangedEventArgs receivedArgs = null; SelectionChangedEventArgs receivedArgs = null;
@ -319,7 +319,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[0]);
Assert.Equal(new[] { "Foo" }, target.SelectedItems); Assert.Equal(new[] { "Foo" }, target.SelectedItems);
@ -414,7 +414,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[0]);
_helper.Click(target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift); _helper.Click(target.Presenter.Panel.Children[1], modifiers: KeyModifiers.Shift);
@ -444,7 +444,7 @@ namespace Avalonia.Controls.UnitTests
var root = new TestRoot(target); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[0]);
_helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Shift); _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); var root = new TestRoot(target);
root.LayoutManager.ExecuteInitialLayoutPass(); 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[0]);
_helper.Click(target.Presenter.Panel.Children[2], MouseButton.Right, modifiers: KeyModifiers.Control); _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), Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
@ -79,7 +79,7 @@ namespace Avalonia.Controls.UnitTests
Template = new FuncControlTemplate(CreateListBoxTemplate), Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
@ -97,7 +97,7 @@ namespace Avalonia.Controls.UnitTests
Template = new FuncControlTemplate(CreateListBoxTemplate), Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
target.SelectedIndex = 0; target.SelectedIndex = 0;
@ -118,7 +118,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle, SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
_mouse.Click(target.Presenter.Panel.Children[0]); _mouse.Click(target.Presenter.Panel.Children[0]);
@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests
SelectionMode = SelectionMode.Toggle, SelectionMode = SelectionMode.Toggle,
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
target.SelectedIndex = 0; target.SelectedIndex = 0;
@ -160,7 +160,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected, SelectionMode = SelectionMode.Toggle | SelectionMode.AlwaysSelected,
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
target.SelectedIndex = 0; target.SelectedIndex = 0;
@ -181,7 +181,7 @@ namespace Avalonia.Controls.UnitTests
ItemsSource = new[] { "Foo", "Bar", "Baz " }, ItemsSource = new[] { "Foo", "Bar", "Baz " },
SelectionMode = SelectionMode.Single | SelectionMode.Toggle, SelectionMode = SelectionMode.Single | SelectionMode.Toggle,
}; };
AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new Mock<PlatformHotkeyConfiguration>().Object); AvaloniaLocator.CurrentMutable.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration());
ApplyTemplate(target); ApplyTemplate(target);
target.SelectedIndex = 1; target.SelectedIndex = 1;

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

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

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

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

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

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

1
tests/Avalonia.UnitTests/TestRoot.cs

@ -67,6 +67,7 @@ namespace Avalonia.UnitTests
public IKeyboardNavigationHandler KeyboardNavigationHandler => null; public IKeyboardNavigationHandler KeyboardNavigationHandler => null;
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>(); public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
public IPlatformSettings PlatformSettings => AvaloniaLocator.Current.GetService<IPlatformSettings>();
public IInputElement PointerOverElement { get; set; } 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) public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default)
{ {
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position,
Timestamp(), PointerPointProperties.None, modifiers)); 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) 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) 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, var e = new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None,
modifiers, MouseButton.None)); modifiers, MouseButton.None);
if (_pointer.CapturedGestureRecognizer != null)
_pointer.CapturedGestureRecognizer.PointerReleasedInternal(e);
else
source.RaiseEvent(e);
_pointer.Capture(null); _pointer.Capture(null);
_pointer.CaptureGestureRecognizer(null);
} }
public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default) 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