Browse Source

Merge branch 'master' into NoneCursor_Issue_#2378

pull/2551/head
danwalmsley 7 years ago
committed by GitHub
parent
commit
c7501c219c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/FUNDING.yml
  2. 2
      .gitignore
  3. 5
      dirs.proj
  4. 1
      packages/Avalonia/Avalonia.csproj
  5. 2
      samples/ControlCatalog.NetCore/Program.cs
  6. 1
      samples/ControlCatalog/MainView.xaml
  7. 99
      samples/ControlCatalog/Pages/PointersPage.cs
  8. 16
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  9. 3
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  10. 1
      src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj
  11. 15
      src/Avalonia.Controls/Application.cs
  12. 13
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  13. 13
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  14. 32
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  15. 18
      src/Avalonia.Controls/MenuItem.cs
  16. 4
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  17. 22
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  18. 4
      src/Avalonia.Controls/Primitives/Popup.cs
  19. 19
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  20. 2
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  21. 18
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  22. 18
      src/Avalonia.Controls/TextBox.cs
  23. 14
      src/Avalonia.Controls/Window.cs
  24. 1
      src/Avalonia.Desktop/Avalonia.Desktop.csproj
  25. 10
      src/Avalonia.Input/Gestures.cs
  26. 5
      src/Avalonia.Input/IMouseDevice.cs
  27. 18
      src/Avalonia.Input/IPointer.cs
  28. 8
      src/Avalonia.Input/IPointerDevice.cs
  29. 180
      src/Avalonia.Input/MouseDevice.cs
  30. 63
      src/Avalonia.Input/Pointer.cs
  31. 95
      src/Avalonia.Input/PointerEventArgs.cs
  32. 45
      src/Avalonia.Input/PointerPoint.cs
  33. 11
      src/Avalonia.Input/PointerWheelEventArgs.cs
  34. 4
      src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs
  35. 15
      src/Avalonia.Input/Raw/RawPointerEventArgs.cs
  36. 15
      src/Avalonia.Input/Raw/RawTouchEventArgs.cs
  37. 72
      src/Avalonia.Input/TouchDevice.cs
  38. 2
      src/Avalonia.Native/WindowImplBase.cs
  39. 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  40. 2
      src/Avalonia.Styling/Controls/IResourceProvider.cs
  41. 2
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  42. 10
      src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs
  43. 19
      src/Avalonia.Styling/StyledElement.cs
  44. 2
      src/Avalonia.Styling/Styling/Style.cs
  45. 2
      src/Avalonia.Styling/Styling/Styles.cs
  46. 10
      src/Avalonia.Themes.Default/Accents/BaseDark.xaml
  47. 10
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  48. 6
      src/Avalonia.Themes.Default/NotificationCard.xaml
  49. 5
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  50. 2
      src/Avalonia.X11/Avalonia.X11.csproj
  51. 4
      src/Avalonia.X11/X11Platform.cs
  52. 26
      src/Avalonia.X11/X11Window.cs
  53. 69
      src/Avalonia.X11/XI2Manager.cs
  54. 9
      src/Avalonia.X11/XIStructs.cs
  55. 1
      src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
  56. 18
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  57. 1
      src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj
  58. 18
      src/Linux/Avalonia.LinuxFramebuffer/Mice.cs
  59. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  60. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  61. 2
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  62. 24
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  63. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs
  64. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github
  65. 1
      src/Skia/Avalonia.Skia/Avalonia.Skia.csproj
  66. 1
      src/Windows/Avalonia.Direct2D1/Avalonia.Direct2D1.csproj
  67. 24
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  68. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  69. 72
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  70. 7
      src/Windows/Avalonia.Win32/Win32Platform.cs
  71. 78
      src/Windows/Avalonia.Win32/WindowImpl.cs
  72. 1
      src/iOS/Avalonia.iOS/Avalonia.iOS.csproj
  73. 16
      src/iOS/Avalonia.iOS/TopLevelImpl.cs
  74. 106
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  75. 14
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  76. 43
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  77. 9
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  78. 41
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  79. 116
      tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs
  80. 54
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  81. 42
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  82. 15
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  83. 208
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  84. 4
      tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs
  85. 2
      tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj
  86. 44
      tests/Avalonia.Interactivity.UnitTests/GestureTests.cs
  87. 3
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  88. 31
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs
  89. 6
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml
  90. 80
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs
  91. 7
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
  92. 23
      tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs
  93. 46
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

1
.github/FUNDING.yml

@ -0,0 +1 @@
open_collective: avalonia

2
.gitignore

@ -196,3 +196,5 @@ ModuleCache.noindex/
Build/Intermediates.noindex/
info.plist
build-intermediate
obj-Direct2D1/
obj-Skia/

5
dirs.proj

@ -8,10 +8,11 @@
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
</ItemGroup>
<!-- Disabled on CI because of ancient MSBuild project format -->
<ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\iOS') Or $([MSBuild]::IsOsPlatform('Windows')) ">
<ProjectReference Remove="src/iOS/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj" />
</ItemGroup>

1
packages/Avalonia/Avalonia.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net461;netcoreapp2.0</TargetFrameworks>
<PackageId>Avalonia</PackageId>
</PropertyGroup>
<ItemGroup>

2
samples/ControlCatalog.NetCore/Program.cs

@ -46,6 +46,8 @@ namespace ControlCatalog.NetCore
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.With(new X11PlatformOptions {EnableMultiTouch = true})
.With(new Win32PlatformOptions {EnableMultitouch = true})
.UseSkia()
.UseReactiveUI();

1
samples/ControlCatalog/MainView.xaml

@ -31,6 +31,7 @@
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>

99
samples/ControlCatalog/Pages/PointersPage.cs

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace ControlCatalog.Pages
{
public class PointersPage : Control
{
class PointerInfo
{
public Point Point { get; set; }
public Color Color { get; set; }
}
private static Color[] AllColors = new[]
{
Colors.Aqua,
Colors.Beige,
Colors.Chartreuse,
Colors.Coral,
Colors.Fuchsia,
Colors.Crimson,
Colors.Lavender,
Colors.Orange,
Colors.Orchid,
Colors.ForestGreen,
Colors.SteelBlue,
Colors.PapayaWhip,
Colors.PaleVioletRed,
Colors.Goldenrod,
Colors.Maroon,
Colors.Moccasin,
Colors.Navy,
Colors.Wheat,
Colors.Violet,
Colors.Sienna,
Colors.Indigo,
Colors.Honeydew
};
private Dictionary<IPointer, PointerInfo> _pointers = new Dictionary<IPointer, PointerInfo>();
public PointersPage()
{
ClipToBounds = true;
}
void UpdatePointer(PointerEventArgs e)
{
if (!_pointers.TryGetValue(e.Pointer, out var info))
{
if (e.RoutedEvent == PointerMovedEvent)
return;
var colors = AllColors.Except(_pointers.Values.Select(c => c.Color)).ToArray();
var color = colors[new Random().Next(0, colors.Length - 1)];
_pointers[e.Pointer] = info = new PointerInfo {Color = color};
}
info.Point = e.GetPosition(this);
InvalidateVisual();
}
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
UpdatePointer(e);
e.Pointer.Capture(this);
base.OnPointerPressed(e);
}
protected override void OnPointerMoved(PointerEventArgs e)
{
UpdatePointer(e);
base.OnPointerMoved(e);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
_pointers.Remove(e.Pointer);
InvalidateVisual();
}
public override void Render(DrawingContext context)
{
context.FillRectangle(Brushes.Transparent, new Rect(default, Bounds.Size));
foreach (var pt in _pointers.Values)
{
var brush = new ImmutableSolidColorBrush(pt.Color);
context.DrawGeometry(brush, null, new EllipseGeometry(new Rect(pt.Point.X - 75, pt.Point.Y - 75,
150, 150)));
}
}
}
}

16
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -33,7 +33,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return null;
}
RawMouseEventType? mouseEventType = null;
RawPointerEventType? mouseEventType = null;
var eventTime = DateTime.Now;
//Basic touch support
switch (e.Action)
@ -42,17 +42,17 @@ namespace Avalonia.Android.Platform.Specific.Helpers
//may be bot flood the evnt system with too many event especially on not so powerfull mobile devices
if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10)
{
mouseEventType = RawMouseEventType.Move;
mouseEventType = RawPointerEventType.Move;
}
break;
case MotionEventActions.Down:
mouseEventType = RawMouseEventType.LeftButtonDown;
mouseEventType = RawPointerEventType.LeftButtonDown;
break;
case MotionEventActions.Up:
mouseEventType = RawMouseEventType.LeftButtonUp;
mouseEventType = RawPointerEventType.LeftButtonUp;
break;
}
@ -75,14 +75,14 @@ namespace Avalonia.Android.Platform.Specific.Helpers
//we need to generate mouse move before first mouse down event
//as this is the way buttons are working every time
//otherwise there is a problem sometimes
if (mouseEventType == RawMouseEventType.LeftButtonDown)
if (mouseEventType == RawPointerEventType.LeftButtonDown)
{
var me = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
RawMouseEventType.Move, _point, InputModifiers.None);
var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
RawPointerEventType.Move, _point, InputModifiers.None);
_view.Input(me);
}
var mouseEvent = new RawMouseEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
mouseEventType.Value, _point, InputModifiers.LeftMouseButton);
_view.Input(mouseEvent);

3
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -150,7 +150,8 @@ namespace Avalonia.Build.Tasks
classType = typeSystem.TargetAssembly.FindType(tn.Text);
if (classType == null)
throw new XamlIlParseException($"Unable to find type `{tn.Text}`", classDirective);
initialRoot.Type = new XamlIlAstClrTypeReference(classDirective, classType, false);
compiler.OverrideRootType(parsed,
new XamlIlAstClrTypeReference(classDirective, classType, false));
initialRoot.Children.Remove(classDirective);
}

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

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.Controls.DataGrid</PackageId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />

15
src/Avalonia.Controls/Application.cs

@ -255,16 +255,13 @@ namespace Avalonia
if (MainWindow == null)
{
Dispatcher.UIThread.Post(() =>
if (!mainWindow.IsVisible)
{
if (!mainWindow.IsVisible)
{
mainWindow.Show();
}
mainWindow.Show();
}
MainWindow = mainWindow;
});
}
MainWindow = mainWindow;
}
return Run(new CancellationTokenSource());
}
@ -362,7 +359,7 @@ namespace Avalonia
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||

13
src/Avalonia.Controls/Calendar/CalendarButton.cs

@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarLeftMouseButtonUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

13
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives
if (e.MouseButton == MouseButton.Left)
CalendarDayButtonMouseUp?.Invoke(this, e);
}
/// <summary>
/// We need to simulate the MouseLeftButtonUp event for the
/// CalendarDayButton that stays in Pressed state after MouseCapture is
/// released since there is no actual MouseLeftButtonUp event for the
/// release.
/// </summary>
/// <param name="e">Event arguments.</param>
internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
{
e.Handled = false;
base.OnPointerReleased(e);
}
}
}

32
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -934,22 +934,6 @@ namespace Avalonia.Controls.Primitives
// The button is in Pressed state. Change the state to normal.
if (e.Device.Captured == b)
e.Device.Capture(null);
// null check is added for unit tests
if (_downEventArg != null)
{
var arg =
new PointerReleasedEventArgs()
{
Device = _downEventArg.Device,
MouseButton = _downEventArg.MouseButton,
Handled = _downEventArg.Handled,
InputModifiers = _downEventArg.InputModifiers,
Route = _downEventArg.Route,
Source = _downEventArg.Source
};
b.SendMouseLeftButtonUp(arg);
}
_lastCalendarDayButton = b;
}
}
@ -1221,21 +1205,7 @@ namespace Avalonia.Controls.Primitives
if (e.Device.Captured == b)
e.Device.Capture(null);
//b.ReleaseMouseCapture();
if (_downEventArgYearView != null)
{
var args =
new PointerReleasedEventArgs()
{
Device = _downEventArgYearView.Device,
MouseButton = _downEventArgYearView.MouseButton,
Handled = _downEventArgYearView.Handled,
InputModifiers = _downEventArgYearView.InputModifiers,
Route = _downEventArgYearView.Route,
Source = _downEventArgYearView.Source
};
b.SendMouseLeftButtonUp(args);
}
_lastCalendarButton = b;
}
}

18
src/Avalonia.Controls/MenuItem.cs

@ -337,12 +337,9 @@ namespace Avalonia.Controls
{
base.OnPointerEnter(e);
RaiseEvent(new PointerEventArgs
{
Device = e.Device,
RoutedEvent = PointerEnterItemEvent,
Source = this,
});
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
point.Properties, e.InputModifiers));
}
/// <inheritdoc/>
@ -350,12 +347,9 @@ namespace Avalonia.Controls
{
base.OnPointerLeave(e);
RaiseEvent(new PointerEventArgs
{
Device = e.Device,
RoutedEvent = PointerLeaveItemEvent,
Source = this,
});
var point = e.GetPointerPoint(null);
RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position,
point.Properties, e.InputModifiers));
}
/// <summary>

4
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -373,9 +373,9 @@ namespace Avalonia.Controls.Platform
protected internal virtual void RawInput(RawInputEventArgs e)
{
var mouse = e as RawMouseEventArgs;
var mouse = e as RawPointerEventArgs;
if (mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
Menu.Close();
}

22
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -43,7 +43,7 @@ namespace Avalonia.Platform
_lastPosition = default(Point);
_allowedEffects = allowedEffects;
using (_inputManager.PreProcess.OfType<RawMouseEventArgs>().Subscribe(ProcessMouseEvents))
using (_inputManager.PreProcess.OfType<RawPointerEventArgs>().Subscribe(ProcessMouseEvents))
{
using (_inputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(ProcessKeyEvents))
{
@ -153,7 +153,7 @@ namespace Avalonia.Platform
}
}
private void ProcessMouseEvents(RawMouseEventArgs e)
private void ProcessMouseEvents(RawPointerEventArgs e)
{
if (!_initialInputModifiers.HasValue)
_initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
@ -174,22 +174,22 @@ namespace Avalonia.Platform
switch (e.Type)
{
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
case RawMouseEventType.MiddleButtonDown:
case RawMouseEventType.NonClientLeftButtonDown:
case RawPointerEventType.LeftButtonDown:
case RawPointerEventType.RightButtonDown:
case RawPointerEventType.MiddleButtonDown:
case RawPointerEventType.NonClientLeftButtonDown:
CancelDragging();
e.Handled = true;
return;
case RawMouseEventType.LeaveWindow:
case RawPointerEventType.LeaveWindow:
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break;
case RawMouseEventType.LeftButtonUp:
case RawPointerEventType.LeftButtonUp:
CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
case RawMouseEventType.MiddleButtonUp:
case RawPointerEventType.MiddleButtonUp:
CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
case RawMouseEventType.RightButtonUp:
case RawPointerEventType.RightButtonUp:
CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
case RawMouseEventType.Move:
case RawPointerEventType.Move:
var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
if (_initialInputModifiers.Value != mods)
{

4
src/Avalonia.Controls/Primitives/Popup.cs

@ -421,9 +421,9 @@ namespace Avalonia.Controls.Primitives
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawMouseEventArgs;
var mouse = e as RawPointerEventArgs;
if (!StaysOpen && mouse?.Type == RawMouseEventType.NonClientLeftButtonDown)
if (!StaysOpen && mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
Close();
}

19
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -380,6 +380,7 @@ namespace Avalonia.Controls.Primitives
}
break;
case NotifyCollectionChangedAction.Move:
case NotifyCollectionChangedAction.Reset:
SelectedIndex = IndexOf(Items, SelectedItem);
break;
@ -644,20 +645,20 @@ namespace Avalonia.Controls.Primitives
/// <param name="desired">The desired items.</param>
internal static void SynchronizeItems(IList items, IEnumerable<object> desired)
{
int index = 0;
var index = 0;
foreach (var i in desired)
foreach (object item in desired)
{
if (index < items.Count)
int itemIndex = items.IndexOf(item);
if (itemIndex == -1)
{
if (items[index] != i)
{
items[index] = i;
}
items.Insert(index, item);
}
else
else if(itemIndex != index)
{
items.Add(i);
items.RemoveAt(itemIndex);
items.Insert(index, item);
}
++index;

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

@ -357,7 +357,7 @@ namespace Avalonia.Controls.Primitives
if (control.TemplatedParent == this)
{
foreach (IControl child in control.GetVisualChildren())
foreach (IControl child in control.GetLogicalChildren())
{
RegisterNames(child, nameScope);
}

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

@ -39,21 +39,21 @@ namespace Avalonia.Controls.Remote.Server
KeyboardDevice = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
private static RawPointerEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
{
switch (button)
{
case Avalonia.Remote.Protocol.Input.MouseButton.Left:
return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
return pressed ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Middle:
return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
return pressed ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Right:
return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
return pressed ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
default:
return RawMouseEventType.Move;
return RawPointerEventType.Move;
}
}
@ -166,11 +166,11 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,
RawMouseEventType.Move,
RawPointerEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
@ -179,7 +179,7 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,
@ -192,7 +192,7 @@ namespace Avalonia.Controls.Remote.Server
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
Input?.Invoke(new RawPointerEventArgs(
MouseDevice,
0,
InputRoot,

18
src/Avalonia.Controls/TextBox.cs

@ -214,9 +214,9 @@ namespace Avalonia.Controls
if (!_ignoreTextChanges)
{
var caretIndex = CaretIndex;
SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0);
SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0);
CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0);
SelectionStart = CoerceCaretIndex(SelectionStart, value);
SelectionEnd = CoerceCaretIndex(SelectionEnd, value);
CaretIndex = CoerceCaretIndex(caretIndex, value);
if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
{
@ -677,11 +677,15 @@ namespace Avalonia.Controls
}
}
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0);
private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
private int CoerceCaretIndex(int value, int length)
private int CoerceCaretIndex(int value, string text)
{
var text = Text;
if (text == null)
{
return 0;
}
var length = text.Length;
if (value < 0)
{
@ -691,7 +695,7 @@ namespace Avalonia.Controls
{
return length;
}
else if (value > 0 && text[value - 1] == '\r' && text[value] == '\n')
else if (value > 0 && text[value - 1] == '\r' && value < length && text[value] == '\n')
{
return value + 1;
}

14
src/Avalonia.Controls/Window.cs

@ -330,8 +330,7 @@ namespace Avalonia.Controls
protected virtual bool HandleClosing()
{
var args = new CancelEventArgs();
Closing?.Invoke(this, args);
OnClosing(args);
return args.Cancel;
}
@ -576,6 +575,17 @@ namespace Avalonia.Controls
base.HandleResized(clientSize);
}
/// <summary>
/// Raises the <see cref="Closing"/> event.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// A type that derives from <see cref="Window"/> may override <see cref="OnClosing"/>. The
/// overridden method must call <see cref="OnClosing"/> on the base class if the
/// <see cref="Closing"/> event needs to be raised.
/// </remarks>
protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e);
}
}

1
src/Avalonia.Desktop/Avalonia.Desktop.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.Desktop</PackageId>
</PropertyGroup>
<ItemGroup>

10
src/Avalonia.Input/Gestures.cs

@ -38,7 +38,10 @@ namespace Avalonia.Input
}
else if (s_lastPress?.IsAlive == true && e.ClickCount == 2 && s_lastPress.Target == e.Source)
{
e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
if (!ev.Handled)
{
e.Source.RaiseEvent(new RoutedEventArgs(DoubleTappedEvent));
}
}
}
}
@ -51,7 +54,10 @@ namespace Avalonia.Input
if (s_lastPress?.IsAlive == true && s_lastPress.Target == e.Source)
{
((IInteractive)s_lastPress.Target).RaiseEvent(new RoutedEventArgs(TappedEvent));
if (!ev.Handled)
{
((IInteractive)s_lastPress.Target).RaiseEvent(new RoutedEventArgs(TappedEvent));
}
}
}
}

5
src/Avalonia.Input/IMouseDevice.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Input
{
/// <summary>
@ -11,6 +13,9 @@ namespace Avalonia.Input
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
[Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

18
src/Avalonia.Input/IPointer.cs

@ -0,0 +1,18 @@
namespace Avalonia.Input
{
public interface IPointer
{
int Id { get; }
void Capture(IInputElement control);
IInputElement Captured { get; }
PointerType Type { get; }
bool IsPrimary { get; }
}
public enum PointerType
{
Mouse,
Touch
}
}

8
src/Avalonia.Input/IPointerDevice.cs

@ -1,18 +1,20 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public interface IPointerDevice : IInputDevice
{
[Obsolete("Use IPointer")]
IInputElement Captured { get; }
[Obsolete("Use IPointer")]
void Capture(IInputElement control);
[Obsolete("Use PointerEventArgs.GetPosition")]
Point GetPosition(IVisual relativeTo);
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

180
src/Avalonia.Input/MouseDevice.cs

@ -14,14 +14,18 @@ namespace Avalonia.Input
/// <summary>
/// Represents a mouse device.
/// </summary>
public class MouseDevice : IMouseDevice
public class MouseDevice : IMouseDevice, IPointer
{
private int _clickCount;
private Rect _lastClickRect;
private ulong _lastClickTime;
private IInputElement _captured;
private IDisposable _capturedSubscription;
PointerType IPointer.Type => PointerType.Mouse;
bool IPointer.IsPrimary => true;
int IPointer.Id { get; } = Pointer.GetNextFreeId();
/// <summary>
/// Gets the control that is currently capturing by the mouse, if any.
/// </summary>
@ -96,7 +100,7 @@ namespace Avalonia.Input
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawMouseEventArgs margs)
if (!e.Handled && e is RawPointerEventArgs margs)
ProcessRawEvent(margs);
}
@ -108,64 +112,98 @@ namespace Avalonia.Input
{
if (Captured == null)
{
SetPointerOver(this, root, clientPoint);
SetPointerOver(this, root, clientPoint, InputModifiers.None);
}
else
{
SetPointerOver(this, root, Captured);
SetPointerOver(this, root, Captured, InputModifiers.None);
}
}
}
private void ProcessRawEvent(RawMouseEventArgs e)
int ButtonCount(PointerPointProperties props)
{
var rv = 0;
if (props.IsLeftButtonPressed)
rv++;
if (props.IsMiddleButtonPressed)
rv++;
if (props.IsRightButtonPressed)
rv++;
return rv;
}
private void ProcessRawEvent(RawPointerEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
var mouse = (IMouseDevice)e.Device;
Position = e.Root.PointToScreen(e.Position);
var props = CreateProperties(e);
switch (e.Type)
{
case RawMouseEventType.LeaveWindow:
LeaveWindow(mouse, e.Root);
case RawPointerEventType.LeaveWindow:
LeaveWindow(mouse, e.Root, e.InputModifiers);
break;
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
case RawMouseEventType.MiddleButtonDown:
e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
e.Type == RawMouseEventType.LeftButtonDown
? MouseButton.Left
: e.Type == RawMouseEventType.RightButtonDown ? MouseButton.Right : MouseButton.Middle,
e.InputModifiers);
case RawPointerEventType.LeftButtonDown:
case RawPointerEventType.RightButtonDown:
case RawPointerEventType.MiddleButtonDown:
if (ButtonCount(props) > 1)
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
else
e.Handled = MouseDown(mouse, e.Timestamp, e.Root, e.Position,
props, e.InputModifiers);
break;
case RawMouseEventType.LeftButtonUp:
case RawMouseEventType.RightButtonUp:
case RawMouseEventType.MiddleButtonUp:
e.Handled = MouseUp(mouse, e.Root, e.Position,
e.Type == RawMouseEventType.LeftButtonUp
? MouseButton.Left
: e.Type == RawMouseEventType.RightButtonUp ? MouseButton.Right : MouseButton.Middle,
e.InputModifiers);
case RawPointerEventType.LeftButtonUp:
case RawPointerEventType.RightButtonUp:
case RawPointerEventType.MiddleButtonUp:
if (ButtonCount(props) != 0)
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
else
e.Handled = MouseUp(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
case RawMouseEventType.Move:
e.Handled = MouseMove(mouse, e.Root, e.Position, e.InputModifiers);
case RawPointerEventType.Move:
e.Handled = MouseMove(mouse, e.Root, e.Position, props, e.InputModifiers);
break;
case RawMouseEventType.Wheel:
e.Handled = MouseWheel(mouse, e.Root, e.Position, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
case RawPointerEventType.Wheel:
e.Handled = MouseWheel(mouse, e.Root, e.Position, props, ((RawMouseWheelEventArgs)e).Delta, e.InputModifiers);
break;
}
}
private void LeaveWindow(IMouseDevice device, IInputRoot root)
private void LeaveWindow(IMouseDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
ClearPointerOver(this, root);
ClearPointerOver(this, root, inputModifiers);
}
PointerPointProperties CreateProperties(RawPointerEventArgs args)
{
var rv = new PointerPointProperties(args.InputModifiers);
if (args.Type == RawPointerEventType.LeftButtonDown)
rv.IsLeftButtonPressed = true;
if (args.Type == RawPointerEventType.MiddleButtonDown)
rv.IsMiddleButtonPressed = true;
if (args.Type == RawPointerEventType.RightButtonDown)
rv.IsRightButtonPressed = true;
if (args.Type == RawPointerEventType.LeftButtonUp)
rv.IsLeftButtonPressed = false;
if (args.Type == RawPointerEventType.MiddleButtonUp)
rv.IsMiddleButtonPressed = false;
if (args.Type == RawPointerEventType.RightButtonDown)
rv.IsRightButtonPressed = false;
return rv;
}
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p, MouseButton button, InputModifiers inputModifiers)
private MouseButton _lastMouseDownButton;
private bool MouseDown(IMouseDevice device, ulong timestamp, IInputElement root, Point p,
PointerPointProperties properties,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -190,16 +228,8 @@ namespace Avalonia.Input
_lastClickTime = timestamp;
_lastClickRect = new Rect(p, new Size())
.Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2));
var e = new PointerPressedEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerPressedEvent,
Source = source,
ClickCount = _clickCount,
MouseButton = button,
InputModifiers = inputModifiers
};
_lastMouseDownButton = properties.GetObsoleteMouseButton();
var e = new PointerPressedEventArgs(source, this, root, p, properties, inputModifiers, _clickCount);
source.RaiseEvent(e);
return e.Handled;
@ -209,7 +239,8 @@ namespace Avalonia.Input
return false;
}
private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
private bool MouseMove(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties properties,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -218,27 +249,23 @@ namespace Avalonia.Input
if (Captured == null)
{
source = SetPointerOver(this, root, p);
source = SetPointerOver(this, root, p, inputModifiers);
}
else
{
SetPointerOver(this, root, Captured);
SetPointerOver(this, root, Captured, inputModifiers);
source = Captured;
}
var e = new PointerEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerMovedEvent,
Source = source,
InputModifiers = inputModifiers
};
var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, this, root,
p, properties, inputModifiers);
source?.RaiseEvent(e);
return e.Handled;
}
private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, MouseButton button, InputModifiers inputModifiers)
private bool MouseUp(IMouseDevice device, IInputRoot root, Point p, PointerPointProperties props,
InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -248,14 +275,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
var e = new PointerReleasedEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerReleasedEvent,
Source = source,
MouseButton = button,
InputModifiers = inputModifiers
};
var e = new PointerReleasedEventArgs(source, this, root, p, props, inputModifiers, _lastMouseDownButton);
source?.RaiseEvent(e);
return e.Handled;
@ -264,7 +284,9 @@ namespace Avalonia.Input
return false;
}
private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p, Vector delta, InputModifiers inputModifiers)
private bool MouseWheel(IMouseDevice device, IInputRoot root, Point p,
PointerPointProperties props,
Vector delta, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -274,14 +296,7 @@ namespace Avalonia.Input
if (hit != null)
{
var source = GetSource(hit);
var e = new PointerWheelEventArgs
{
Device = this,
RoutedEvent = InputElement.PointerWheelChangedEvent,
Source = source,
Delta = delta,
InputModifiers = inputModifiers
};
var e = new PointerWheelEventArgs(source, this, root, p, props, inputModifiers, delta);
source?.RaiseEvent(e);
return e.Handled;
@ -306,17 +321,19 @@ namespace Avalonia.Input
return Captured ?? root.InputHitTest(p);
}
private void ClearPointerOver(IPointerDevice device, IInputRoot root)
PointerEventArgs CreateSimpleEvent(RoutedEvent ev, IInteractive source, InputModifiers inputModifiers)
{
return new PointerEventArgs(ev, source, this, null, default,
new PointerPointProperties(inputModifiers), inputModifiers);
}
private void ClearPointerOver(IPointerDevice device, IInputRoot root, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
var element = root.PointerOverElement;
var e = new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Device = device,
};
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, element, inputModifiers);
if (element!=null && !element.IsAttachedToVisualTree)
{
@ -353,7 +370,7 @@ namespace Avalonia.Input
}
}
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p)
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -364,18 +381,18 @@ namespace Avalonia.Input
{
if (element != null)
{
SetPointerOver(device, root, element);
SetPointerOver(device, root, element, inputModifiers);
}
else
{
ClearPointerOver(device, root);
ClearPointerOver(device, root, inputModifiers);
}
}
return element;
}
private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element)
private void SetPointerOver(IPointerDevice device, IInputRoot root, IInputElement element, InputModifiers inputModifiers)
{
Contract.Requires<ArgumentNullException>(device != null);
Contract.Requires<ArgumentNullException>(root != null);
@ -383,7 +400,6 @@ namespace Avalonia.Input
IInputElement branch = null;
var e = new PointerEventArgs { Device = device, };
var el = element;
while (el != null)
@ -397,8 +413,8 @@ namespace Avalonia.Input
}
el = root.PointerOverElement;
e.RoutedEvent = InputElement.PointerLeaveEvent;
var e = CreateSimpleEvent(InputElement.PointerLeaveEvent, el, inputModifiers);
if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
{
ClearChildrenPointerOver(e,branch,false);

63
src/Avalonia.Input/Pointer.cs

@ -0,0 +1,63 @@
using System;
using System.Linq;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class Pointer : IPointer, IDisposable
{
private static int s_NextFreePointerId = 1000;
public static int GetNextFreeId() => s_NextFreePointerId++;
public Pointer(int id, PointerType type, bool isPrimary, IInputElement implicitlyCaptured)
{
Id = id;
Type = type;
IsPrimary = isPrimary;
ImplicitlyCaptured = implicitlyCaptured;
if (ImplicitlyCaptured != null)
ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached;
}
public int Id { get; }
public void Capture(IInputElement control)
{
if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached;
Captured = control;
if (Captured != null)
Captured.DetachedFromVisualTree += OnCaptureDetached;
}
IInputElement GetNextCapture(IVisual parent) =>
parent as IInputElement ?? parent.GetVisualAncestors().OfType<IInputElement>().FirstOrDefault();
private void OnCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
{
Capture(GetNextCapture(e.Parent));
}
private void OnImplicitCaptureDetached(object sender, VisualTreeAttachmentEventArgs e)
{
ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached;
ImplicitlyCaptured = GetNextCapture(e.Parent);
if (ImplicitlyCaptured != null)
ImplicitlyCaptured.DetachedFromVisualTree += OnImplicitCaptureDetached;
}
public IInputElement Captured { get; private set; }
public IInputElement ImplicitlyCaptured { get; private set; }
public IInputElement GetEffectiveCapture() => Captured ?? ImplicitlyCaptured;
public PointerType Type { get; }
public bool IsPrimary { get; }
public void Dispose()
{
if (ImplicitlyCaptured != null)
ImplicitlyCaptured.DetachedFromVisualTree -= OnImplicitCaptureDetached;
if (Captured != null)
Captured.DetachedFromVisualTree -= OnCaptureDetached;
}
}
}

95
src/Avalonia.Input/PointerEventArgs.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Input.Raw;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -8,25 +10,65 @@ namespace Avalonia.Input
{
public class PointerEventArgs : RoutedEventArgs
{
public PointerEventArgs()
{
private readonly IVisual _rootVisual;
private readonly Point _rootVisualPosition;
private readonly PointerPointProperties _properties;
public PointerEventArgs(RoutedEvent routedEvent,
IInteractive source,
IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
InputModifiers modifiers)
: base(routedEvent)
{
Source = source;
_rootVisual = rootVisual;
_rootVisualPosition = rootVisualPosition;
_properties = properties;
Pointer = pointer;
InputModifiers = modifiers;
}
public PointerEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
class EmulatedDevice : IPointerDevice
{
private readonly PointerEventArgs _ev;
public EmulatedDevice(PointerEventArgs ev)
{
_ev = ev;
}
public void ProcessRawEvent(RawInputEventArgs ev) => throw new NotSupportedException();
public IInputElement Captured => _ev.Pointer.Captured;
public void Capture(IInputElement control)
{
_ev.Pointer.Capture(control);
}
public Point GetPosition(IVisual relativeTo) => _ev.GetPosition(relativeTo);
}
public IPointerDevice Device { get; set; }
public IPointer Pointer { get; }
private IPointerDevice _device;
[Obsolete("Use Pointer to get pointer-specific information")]
public IPointerDevice Device => _device ?? (_device = new EmulatedDevice(this));
public InputModifiers InputModifiers { get; set; }
public InputModifiers InputModifiers { get; }
public Point GetPosition(IVisual relativeTo)
{
return Device.GetPosition(relativeTo);
if (_rootVisual == null)
return default;
if (relativeTo == null)
return _rootVisualPosition;
return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default;
}
public PointerPoint GetPointerPoint(IVisual relativeTo)
=> new PointerPoint(Pointer, GetPosition(relativeTo), _properties);
}
public enum MouseButton
@ -39,32 +81,39 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
public PointerPressedEventArgs()
: base(InputElement.PointerPressedEvent)
{
}
private readonly int _obsoleteClickCount;
public PointerPressedEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
public PointerPressedEventArgs(
IInteractive source,
IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties,
InputModifiers modifiers,
int obsoleteClickCount = 1)
: base(InputElement.PointerPressedEvent, source, pointer, rootVisual, rootVisualPosition, properties,
modifiers)
{
_obsoleteClickCount = obsoleteClickCount;
}
public int ClickCount { get; set; }
public MouseButton MouseButton { get; set; }
[Obsolete("Use DoubleTapped or DoubleRightTapped event instead")]
public int ClickCount => _obsoleteClickCount;
[Obsolete] public MouseButton MouseButton => GetPointerPoint(null).Properties.GetObsoleteMouseButton();
}
public class PointerReleasedEventArgs : PointerEventArgs
{
public PointerReleasedEventArgs()
: base(InputElement.PointerReleasedEvent)
{
}
public PointerReleasedEventArgs(RoutedEvent routedEvent)
: base(routedEvent)
public PointerReleasedEventArgs(
IInteractive source, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, PointerPointProperties properties, InputModifiers modifiers,
MouseButton obsoleteMouseButton)
: base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition,
properties, modifiers)
{
MouseButton = obsoleteMouseButton;
}
public MouseButton MouseButton { get; set; }
[Obsolete()]
public MouseButton MouseButton { get; private set; }
}
}

45
src/Avalonia.Input/PointerPoint.cs

@ -0,0 +1,45 @@
namespace Avalonia.Input
{
public sealed class PointerPoint
{
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{
Pointer = pointer;
Position = position;
Properties = properties;
}
public IPointer Pointer { get; }
public PointerPointProperties Properties { get; }
public Point Position { get; }
}
public sealed class PointerPointProperties
{
public bool IsLeftButtonPressed { get; set; }
public bool IsMiddleButtonPressed { get; set; }
public bool IsRightButtonPressed { get; set; }
public PointerPointProperties()
{
}
public PointerPointProperties(InputModifiers modifiers)
{
IsLeftButtonPressed = modifiers.HasFlag(InputModifiers.LeftMouseButton);
IsMiddleButtonPressed = modifiers.HasFlag(InputModifiers.MiddleMouseButton);
IsRightButtonPressed = modifiers.HasFlag(InputModifiers.RightMouseButton);
}
public MouseButton GetObsoleteMouseButton()
{
if (IsLeftButtonPressed)
return MouseButton.Left;
if (IsMiddleButtonPressed)
return MouseButton.Middle;
if (IsRightButtonPressed)
return MouseButton.Right;
return MouseButton.None;
}
}
}

11
src/Avalonia.Input/PointerWheelEventArgs.cs

@ -1,10 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
public class PointerWheelEventArgs : PointerEventArgs
{
public Vector Delta { get; set; }
public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
Point rootVisualPosition,
PointerPointProperties properties, InputModifiers modifiers, Vector delta)
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, properties, modifiers)
{
Delta = delta;
}
}
}

4
src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs

@ -4,7 +4,7 @@
namespace Avalonia.Input.Raw
{
public class RawMouseWheelEventArgs : RawMouseEventArgs
public class RawMouseWheelEventArgs : RawPointerEventArgs
{
public RawMouseWheelEventArgs(
IInputDevice device,
@ -12,7 +12,7 @@ namespace Avalonia.Input.Raw
IInputRoot root,
Point position,
Vector delta, InputModifiers inputModifiers)
: base(device, timestamp, root, RawMouseEventType.Wheel, position, inputModifiers)
: base(device, timestamp, root, RawPointerEventType.Wheel, position, inputModifiers)
{
Delta = delta;
}

15
src/Avalonia.Input/Raw/RawMouseEventArgs.cs → src/Avalonia.Input/Raw/RawPointerEventArgs.cs

@ -5,7 +5,7 @@ using System;
namespace Avalonia.Input.Raw
{
public enum RawMouseEventType
public enum RawPointerEventType
{
LeaveWindow,
LeftButtonDown,
@ -17,15 +17,18 @@ namespace Avalonia.Input.Raw
Move,
Wheel,
NonClientLeftButtonDown,
TouchBegin,
TouchUpdate,
TouchEnd
}
/// <summary>
/// A raw mouse event.
/// </summary>
public class RawMouseEventArgs : RawInputEventArgs
public class RawPointerEventArgs : RawInputEventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="RawMouseEventArgs"/> class.
/// Initializes a new instance of the <see cref="RawPointerEventArgs"/> class.
/// </summary>
/// <param name="device">The associated device.</param>
/// <param name="timestamp">The event timestamp.</param>
@ -33,11 +36,11 @@ namespace Avalonia.Input.Raw
/// <param name="type">The type of the event.</param>
/// <param name="position">The mouse position, in client DIPs.</param>
/// <param name="inputModifiers">The input modifiers.</param>
public RawMouseEventArgs(
public RawPointerEventArgs(
IInputDevice device,
ulong timestamp,
IInputRoot root,
RawMouseEventType type,
RawPointerEventType type,
Point position,
InputModifiers inputModifiers)
: base(device, timestamp)
@ -64,7 +67,7 @@ namespace Avalonia.Input.Raw
/// <summary>
/// Gets the type of the event.
/// </summary>
public RawMouseEventType Type { get; private set; }
public RawPointerEventType Type { get; private set; }
/// <summary>
/// Gets the input modifiers.

15
src/Avalonia.Input/Raw/RawTouchEventArgs.cs

@ -0,0 +1,15 @@
namespace Avalonia.Input.Raw
{
public class RawTouchEventArgs : RawPointerEventArgs
{
public RawTouchEventArgs(IInputDevice device, ulong timestamp, IInputRoot root,
RawPointerEventType type, Point position, InputModifiers inputModifiers,
long touchPointId)
: base(device, timestamp, root, type, position, inputModifiers)
{
TouchPointId = touchPointId;
}
public long TouchPointId { get; set; }
}
}

72
src/Avalonia.Input/TouchDevice.cs

@ -0,0 +1,72 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input.Raw;
using Avalonia.VisualTree;
namespace Avalonia.Input
{
/// <summary>
/// Handles raw touch events
/// <remarks>
/// This class is supposed to be used on per-toplevel basis, don't use a shared one
/// </remarks>
/// </summary>
public class TouchDevice : IInputDevice
{
Dictionary<long, Pointer> _pointers = new Dictionary<long, Pointer>();
static InputModifiers GetModifiers(InputModifiers modifiers, bool left)
{
var mask = (InputModifiers)0x7fffffff ^ InputModifiers.LeftMouseButton ^ InputModifiers.MiddleMouseButton ^
InputModifiers.RightMouseButton;
modifiers &= mask;
if (left)
modifiers |= InputModifiers.LeftMouseButton;
return modifiers;
}
public void ProcessRawEvent(RawInputEventArgs ev)
{
var args = (RawTouchEventArgs)ev;
if (!_pointers.TryGetValue(args.TouchPointId, out var pointer))
{
if (args.Type == RawPointerEventType.TouchEnd)
return;
var hit = args.Root.InputHitTest(args.Position);
_pointers[args.TouchPointId] = pointer = new Pointer(Pointer.GetNextFreeId(),
PointerType.Touch, _pointers.Count == 0, hit);
}
var target = pointer.GetEffectiveCapture() ?? args.Root;
if (args.Type == RawPointerEventType.TouchBegin)
{
var modifiers = GetModifiers(args.InputModifiers, false);
target.RaiseEvent(new PointerPressedEventArgs(target, pointer,
args.Root, args.Position, new PointerPointProperties(modifiers),
modifiers));
}
if (args.Type == RawPointerEventType.TouchEnd)
{
_pointers.Remove(args.TouchPointId);
var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary);
using (pointer)
{
target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
args.Root, args.Position, new PointerPointProperties(modifiers),
modifiers, pointer.IsPrimary ? MouseButton.Left : MouseButton.None));
}
}
if (args.Type == RawPointerEventType.TouchUpdate)
{
var modifiers = GetModifiers(args.InputModifiers, pointer.IsPrimary);
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, args.Root,
args.Position, new PointerPointProperties(modifiers), modifiers));
}
}
}
}

2
src/Avalonia.Native/WindowImplBase.cs

@ -226,7 +226,7 @@ namespace Avalonia.Native
break;
default:
Input?.Invoke(new RawMouseEventArgs(_mouse, timeStamp, _inputRoot, (RawMouseEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers));
Input?.Invoke(new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers));
break;
}
}

1
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.ReactiveUI</PackageId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />

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

@ -28,6 +28,6 @@ namespace Avalonia.Controls
/// <returns>
/// True if the resource if found, otherwise false.
/// </returns>
bool TryGetResource(string key, out object value);
bool TryGetResource(object key, out object value);
}
}

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

@ -69,7 +69,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
public bool TryGetResource(object key, out object value)
{
if (TryGetValue(key, out value))
{

10
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs

@ -11,7 +11,7 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
/// <param name="key">The resource key.</param>
/// <returns>The resource, or <see cref="AvaloniaProperty.UnsetValue"/> if not found.</returns>
public static object FindResource(this IResourceNode control, string key)
public static object FindResource(this IResourceNode control, object key)
{
if (control.TryFindResource(key, out var value))
{
@ -28,7 +28,7 @@ namespace Avalonia.Controls
/// <param name="key">The resource key.</param>
/// <param name="value">On return, contains the resource if found, otherwise null.</param>
/// <returns>True if the resource was found; otherwise false.</returns>
public static bool TryFindResource(this IResourceNode control, string key, out object value)
public static bool TryFindResource(this IResourceNode control, object key, out object value)
{
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(key != null);
@ -52,7 +52,7 @@ namespace Avalonia.Controls
return false;
}
public static IObservable<object> GetResourceObservable(this IResourceNode target, string key)
public static IObservable<object> GetResourceObservable(this IResourceNode target, object key)
{
return new ResourceObservable(target, key);
}
@ -60,9 +60,9 @@ namespace Avalonia.Controls
private class ResourceObservable : LightweightObservableBase<object>
{
private readonly IResourceNode _target;
private readonly string _key;
private readonly object _key;
public ResourceObservable(IResourceNode target, string key)
public ResourceObservable(IResourceNode target, object key)
{
_target = target;
_key = key;

19
src/Avalonia.Styling/StyledElement.cs

@ -415,7 +415,7 @@ namespace Avalonia
}
/// <inheritdoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
value = null;
return (_resources?.TryGetResource(key, out value) ?? false) ||
@ -677,23 +677,6 @@ namespace Avalonia
if (Name != null)
{
_nameScope?.Register(Name, this);
var visualParent = Parent as StyledElement;
if (this is INameScope && visualParent != null)
{
// If we have e.g. a named UserControl in a window then we want that control
// to be findable by name from the Window, so register with both name scopes.
// This differs from WPF's behavior in that XAML manually registers controls
// with name scopes based on the XAML file in which the name attribute appears,
// but we're trying to avoid XAML magic in Avalonia in order to made code-
// created UIs easy. This will cause problems if a UserControl declares a name
// in its XAML and that control is included multiple times in a parent control
// (as the name will be duplicated), however at the moment I'm fine with saying
// "don't do that".
var parentNameScope = NameScope.FindNameScope(visualParent);
parentNameScope?.Register(Name, this);
}
}
}

2
src/Avalonia.Styling/Styling/Style.cs

@ -171,7 +171,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object result)
public bool TryGetResource(object key, out object result)
{
result = null;
return _resources?.TryGetResource(key, out result) ?? false;

2
src/Avalonia.Styling/Styling/Styles.cs

@ -178,7 +178,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
public bool TryGetResource(object key, out object value)
{
if (_resources != null && _resources.TryGetValue(key, out value))
{

10
src/Avalonia.Themes.Default/Accents/BaseDark.xaml

@ -46,11 +46,11 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="#FDB328" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="#BD202C" Opacity="0.75"/>
<Thickness x:Key="ThemeBorderThickness">1,1,1,1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

10
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -46,11 +46,11 @@
<SolidColorBrush x:Key="ErrorBrush" Color="{DynamicResource ErrorColor}"></SolidColorBrush>
<SolidColorBrush x:Key="ErrorLowBrush" Color="{DynamicResource ErrorLowColor}"></SolidColorBrush>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="Teal"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="LimeGreen"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="Orange"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="OrangeRed"/>
<SolidColorBrush x:Key="NotificationCardBackgroundBrush" Color="#444444" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardInformationBackgroundBrush" Color="#007ACC" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardSuccessBackgroundBrush" Color="#1F9E45" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardWarningBackgroundBrush" Color="#FDB328" Opacity="0.75"/>
<SolidColorBrush x:Key="NotificationCardErrorBackgroundBrush" Color="#BD202C" Opacity="0.75"/>
<Thickness x:Key="ThemeBorderThickness">1</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>

6
src/Avalonia.Themes.Default/NotificationCard.xaml

@ -13,7 +13,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="8,8,0,0">
<ContentControl MinHeight="150" Content="{TemplateBinding Content}" />
<ContentControl Name="PART_Content" Content="{TemplateBinding Content}" />
</Border>
</LayoutTransformControl>
</ControlTemplate>
@ -40,6 +40,10 @@
</Style.Animations>
</Style>
<Style Selector="NotificationCard/template/ ContentControl#PART_Content">
<Setter Property="MinHeight" Value="150" />
</Style>
<Style Selector="NotificationCard[IsClosing=true] /template/ LayoutTransformControl#PART_LayoutTransformControl">
<Setter Property="RenderTransformOrigin" Value="50%,0%"/>
<Style.Animations>

5
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -528,6 +528,8 @@ namespace Avalonia.Rendering
oldScene?.Dispose();
}
_dirty.Clear();
if (SceneInvalidated != null)
{
var rect = new Rect();
@ -540,10 +542,9 @@ namespace Avalonia.Rendering
}
}
System.Diagnostics.Debug.WriteLine("Invalidated " + rect);
SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
_dirty.Clear();
}
else
{

2
src/Avalonia.X11/Avalonia.X11.csproj

@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.X11</PackageId>
</PropertyGroup>
<ItemGroup>

4
src/Avalonia.X11/X11Platform.cs

@ -28,6 +28,7 @@ namespace Avalonia.X11
public X11PlatformOptions Options { get; private set; }
public void Initialize(X11PlatformOptions options)
{
Options = options;
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
DeferredDisplay = XOpenDisplay(IntPtr.Zero);
@ -66,7 +67,7 @@ namespace Avalonia.X11
GlxGlPlatformFeature.TryInitialize(Info);
}
Options = options;
}
public IntPtr DeferredDisplay { get; set; }
@ -96,6 +97,7 @@ namespace Avalonia
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
public string WmClass { get; set; } = Assembly.GetEntryAssembly()?.GetName()?.Name ?? "AvaloniaApplication";
public bool? EnableMultiTouch { get; set; }
}
public static class AvaloniaX11PlatformExtensions
{

26
src/Avalonia.X11/X11Window.cs

@ -314,9 +314,9 @@ namespace Avalonia.X11
else if (ev.type == XEventName.FocusOut)
Deactivated?.Invoke();
else if (ev.type == XEventName.MotionNotify)
MouseEvent(RawMouseEventType.Move, ref ev, ev.MotionEvent.state);
MouseEvent(RawPointerEventType.Move, ref ev, ev.MotionEvent.state);
else if (ev.type == XEventName.LeaveNotify)
MouseEvent(RawMouseEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
MouseEvent(RawPointerEventType.LeaveWindow, ref ev, ev.CrossingEvent.state);
else if (ev.type == XEventName.PropertyNotify)
{
OnPropertyChange(ev.PropertyEvent.atom, ev.PropertyEvent.state == 0);
@ -326,9 +326,9 @@ namespace Avalonia.X11
if (ActivateTransientChildIfNeeded())
return;
if (ev.ButtonEvent.button < 4)
MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonDown
: ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonDown
: RawMouseEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonDown
: ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonDown
: RawPointerEventType.RightButtonDown, ref ev, ev.ButtonEvent.state);
else
{
var delta = ev.ButtonEvent.button == 4
@ -347,9 +347,9 @@ namespace Avalonia.X11
else if (ev.type == XEventName.ButtonRelease)
{
if (ev.ButtonEvent.button < 4)
MouseEvent(ev.ButtonEvent.button == 1 ? RawMouseEventType.LeftButtonUp
: ev.ButtonEvent.button == 2 ? RawMouseEventType.MiddleButtonUp
: RawMouseEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
MouseEvent(ev.ButtonEvent.button == 1 ? RawPointerEventType.LeftButtonUp
: ev.ButtonEvent.button == 2 ? RawPointerEventType.MiddleButtonUp
: RawPointerEventType.RightButtonUp, ref ev, ev.ButtonEvent.state);
}
else if (ev.type == XEventName.ConfigureNotify)
{
@ -577,7 +577,7 @@ namespace Avalonia.X11
public void ScheduleInput(RawInputEventArgs args)
{
if (args is RawMouseEventArgs mouse)
if (args is RawPointerEventArgs mouse)
mouse.Position = mouse.Position / Scaling;
if (args is RawDragEvent drag)
drag.Location = drag.Location / Scaling;
@ -598,13 +598,13 @@ namespace Avalonia.X11
}
}
void MouseEvent(RawMouseEventType type, ref XEvent ev, XModifierMask mods)
void MouseEvent(RawPointerEventType type, ref XEvent ev, XModifierMask mods)
{
var mev = new RawMouseEventArgs(
var mev = new RawPointerEventArgs(
_mouse, (ulong)ev.ButtonEvent.time.ToInt64(), _inputRoot,
type, new Point(ev.ButtonEvent.x, ev.ButtonEvent.y), TranslateModifiers(mods));
if(type == RawMouseEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawMouseEventArgs ma)
if (ma.Type == RawMouseEventType.Move)
if(type == RawPointerEventType.Move && _inputQueue.Count>0 && _lastEvent.Event is RawPointerEventArgs ma)
if (ma.Type == RawPointerEventType.Move)
{
_lastEvent.Event = mev;
return;

69
src/Avalonia.X11/XI2Manager.cs

@ -11,6 +11,7 @@ namespace Avalonia.X11
unsafe class XI2Manager
{
private X11Info _x11;
private bool _multitouch;
private Dictionary<IntPtr, IXI2Client> _clients = new Dictionary<IntPtr, IXI2Client>();
class DeviceInfo
{
@ -77,11 +78,14 @@ namespace Avalonia.X11
private PointerDeviceInfo _pointerDevice;
private AvaloniaX11Platform _platform;
private readonly TouchDevice _touchDevice = new TouchDevice();
public bool Init(AvaloniaX11Platform platform)
{
_platform = platform;
_x11 = platform.Info;
_multitouch = platform.Options?.EnableMultiTouch ?? false;
var devices =(XIDeviceInfo*) XIQueryDevice(_x11.Display,
(int)XiPredefinedDeviceId.XIAllMasterDevices, out int num);
for (var c = 0; c < num; c++)
@ -121,16 +125,23 @@ namespace Avalonia.X11
public XEventMask AddWindow(IntPtr xid, IXI2Client window)
{
_clients[xid] = window;
XiSelectEvents(_x11.Display, xid, new Dictionary<int, List<XiEventType>>
var events = new List<XiEventType>
{
[_pointerDevice.Id] = new List<XiEventType>()
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease
};
if (_multitouch)
events.AddRange(new[]
{
XiEventType.XI_Motion,
XiEventType.XI_ButtonPress,
XiEventType.XI_ButtonRelease,
}
});
XiEventType.XI_TouchBegin,
XiEventType.XI_TouchUpdate,
XiEventType.XI_TouchEnd
});
XiSelectEvents(_x11.Display, xid,
new Dictionary<int, List<XiEventType>> {[_pointerDevice.Id] = events});
// We are taking over mouse input handling from here
return XEventMask.PointerMotionMask
@ -154,8 +165,9 @@ namespace Avalonia.X11
_pointerDevice.Update(changed->Classes, changed->NumClasses);
}
//TODO: this should only be used for non-touch devices
if (xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
if ((xev->evtype >= XiEventType.XI_ButtonPress && xev->evtype <= XiEventType.XI_Motion)
|| (xev->evtype>=XiEventType.XI_TouchBegin&&xev->evtype<=XiEventType.XI_TouchEnd))
{
var dev = (XIDeviceEvent*)xev;
if (_clients.TryGetValue(dev->EventWindow, out var client))
@ -165,6 +177,23 @@ namespace Avalonia.X11
void OnDeviceEvent(IXI2Client client, ParsedDeviceEvent ev)
{
if (ev.Type == XiEventType.XI_TouchBegin
|| ev.Type == XiEventType.XI_TouchUpdate
|| ev.Type == XiEventType.XI_TouchEnd)
{
var type = ev.Type == XiEventType.XI_TouchBegin ?
RawPointerEventType.TouchBegin :
(ev.Type == XiEventType.XI_TouchUpdate ?
RawPointerEventType.TouchUpdate :
RawPointerEventType.TouchEnd);
client.ScheduleInput(new RawTouchEventArgs(_touchDevice,
ev.Timestamp, client.InputRoot, type, ev.Position, ev.Modifiers, ev.Detail));
return;
}
if (_multitouch && ev.Emulated)
return;
if (ev.Type == XiEventType.XI_Motion)
{
Vector scrollDelta = default;
@ -194,23 +223,23 @@ namespace Avalonia.X11
client.ScheduleInput(new RawMouseWheelEventArgs(_platform.MouseDevice, ev.Timestamp,
client.InputRoot, ev.Position, scrollDelta, ev.Modifiers));
if (_pointerDevice.HasMotion(ev))
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
RawMouseEventType.Move, ev.Position, ev.Modifiers));
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
RawPointerEventType.Move, ev.Position, ev.Modifiers));
}
if (ev.Type == XiEventType.XI_ButtonPress || ev.Type == XiEventType.XI_ButtonRelease)
{
var down = ev.Type == XiEventType.XI_ButtonPress;
var type =
ev.Button == 1 ? (down ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp)
: ev.Button == 2 ? (down ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp)
: ev.Button == 3 ? (down ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp)
: (RawMouseEventType?)null;
ev.Button == 1 ? (down ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp)
: ev.Button == 2 ? (down ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp)
: ev.Button == 3 ? (down ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp)
: (RawPointerEventType?)null;
if (type.HasValue)
client.ScheduleInput(new RawMouseEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
client.ScheduleInput(new RawPointerEventArgs(_platform.MouseDevice, ev.Timestamp, client.InputRoot,
type.Value, ev.Position, ev.Modifiers));
}
_pointerDevice.UpdateValuators(ev.Valuators);
}
}
@ -222,6 +251,8 @@ namespace Avalonia.X11
public ulong Timestamp { get; }
public Point Position { get; }
public int Button { get; set; }
public int Detail { get; set; }
public bool Emulated { get; set; }
public Dictionary<int, double> Valuators { get; }
public ParsedDeviceEvent(XIDeviceEvent* ev)
{
@ -258,6 +289,8 @@ namespace Avalonia.X11
Valuators[c] = *values++;
if (Type == XiEventType.XI_ButtonPress || Type == XiEventType.XI_ButtonRelease)
Button = ev->detail;
Detail = ev->detail;
Emulated = ev->flags.HasFlag(XiDeviceEventFlags.XIPointerEmulated);
}
}

9
src/Avalonia.X11/XIStructs.cs

@ -230,13 +230,20 @@ namespace Avalonia.X11
public double root_y;
public double event_x;
public double event_y;
public int flags;
public XiDeviceEventFlags flags;
public XIButtonState buttons;
public XIValuatorState valuators;
public XIModifierState mods;
public XIModifierState group;
}
[Flags]
public enum XiDeviceEventFlags : int
{
None = 0,
XIPointerEmulated = (1 << 16)
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct XIEvent
{

1
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@ -3,6 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);GTK3_PINVOKE</DefineConstants>
<PackageId>Avalonia.Gtk3</PackageId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

18
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -145,17 +145,17 @@ namespace Avalonia.Gtk3
private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata)
{
var evnt = (GdkEventButton*)ev;
var e = new RawMouseEventArgs(
var e = new RawPointerEventArgs(
Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
evnt->type == GdkEventType.ButtonRelease
? evnt->button == 1
? RawMouseEventType.LeftButtonUp
: evnt->button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp
? RawPointerEventType.LeftButtonUp
: evnt->button == 3 ? RawPointerEventType.RightButtonUp : RawPointerEventType.MiddleButtonUp
: evnt->button == 1
? RawMouseEventType.LeftButtonDown
: evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
? RawPointerEventType.LeftButtonDown
: evnt->button == 3 ? RawPointerEventType.RightButtonDown : RawPointerEventType.MiddleButtonDown,
new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state));
OnInput(e);
return true;
@ -179,11 +179,11 @@ namespace Avalonia.Gtk3
var evnt = (GdkEventMotion*)ev;
var position = new Point(evnt->x, evnt->y);
Native.GdkEventRequestMotions(ev);
var e = new RawMouseEventArgs(
var e = new RawPointerEventArgs(
Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
RawMouseEventType.Move,
RawPointerEventType.Move,
position, GetModifierKeys(evnt->state));
OnInput(e);
@ -237,10 +237,10 @@ namespace Avalonia.Gtk3
{
var evnt = (GdkEventCrossing*) pev;
var position = new Point(evnt->x, evnt->y);
OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse,
OnInput(new RawPointerEventArgs(Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
RawMouseEventType.Move,
RawPointerEventType.Move,
position, GetModifierKeys(evnt->state)));
return true;
}

1
src/Linux/Avalonia.LinuxFramebuffer/Avalonia.LinuxFramebuffer.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.LinuxFramebuffer</PackageId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

18
src/Linux/Avalonia.LinuxFramebuffer/Mice.cs

@ -76,9 +76,9 @@ namespace Avalonia.LinuxFramebuffer
_y = Math.Min(_height, Math.Max(0, _y + ev.value));
else
return;
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
InputModifiers.None));
}
if (ev.type ==(int) EvType.EV_ABS)
@ -89,24 +89,24 @@ namespace Avalonia.LinuxFramebuffer
_y = TranslateAxis(device.AbsY.Value, ev.value, _height);
else
return;
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
LinuxFramebufferPlatform.TopLevel.InputRoot, RawMouseEventType.Move, new Point(_x, _y),
LinuxFramebufferPlatform.TopLevel.InputRoot, RawPointerEventType.Move, new Point(_x, _y),
InputModifiers.None));
}
if (ev.type == (short) EvType.EV_KEY)
{
RawMouseEventType? type = null;
RawPointerEventType? type = null;
if (ev.code == (ushort) EvKey.BTN_LEFT)
type = ev.value == 1 ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
type = ev.value == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp;
if (ev.code == (ushort)EvKey.BTN_RIGHT)
type = ev.value == 1 ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
type = ev.value == 1 ? RawPointerEventType.RightButtonDown : RawPointerEventType.RightButtonUp;
if (ev.code == (ushort) EvKey.BTN_MIDDLE)
type = ev.value == 1 ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
type = ev.value == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp;
if (!type.HasValue)
return;
Event?.Invoke(new RawMouseEventArgs(LinuxFramebufferPlatform.MouseDevice,
Event?.Invoke(new RawPointerEventArgs(LinuxFramebufferPlatform.MouseDevice,
LinuxFramebufferPlatform.Timestamp,
LinuxFramebufferPlatform.TopLevel.InputRoot, type.Value, new Point(_x, _y), default(InputModifiers)));
}

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@ -26,7 +26,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ResourceKey = resourceKey;
}
public string ResourceKey { get; set; }
public object ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider) => ProvideTypedValue(serviceProvider);

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -47,7 +47,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inhertidoc/>
bool IResourceProvider.TryGetResource(string key, out object value)
bool IResourceProvider.TryGetResource(object key, out object value)
{
return Loaded.TryGetResource(key, out value);
}

2
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -86,7 +86,7 @@ namespace Avalonia.Markup.Xaml.Styling
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value);
public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)

24
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@ -112,13 +112,31 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
rootType = new XamlIlAstClrTypeReference(rootObject, overrideRootType, false);
}
rootObject.Type = rootType;
OverrideRootType(parsed, rootType);
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
}
public void OverrideRootType(XamlIlDocument doc, IXamlIlAstTypeReference newType)
{
var root = (XamlIlAstObjectNode)doc.Root;
var oldType = root.Type;
if (oldType.Equals(newType))
return;
root.Type = newType;
foreach (var child in root.Children.OfType<XamlIlAstXamlPropertyValueNode>())
{
if (child.Property is XamlIlAstNamePropertyReference prop)
{
if (prop.DeclaringType.Equals(oldType))
prop.DeclaringType = newType;
if (prop.TargetType.Equals(oldType))
prop.TargetType = newType;
}
}
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/XamlIl/Runtime/XamlIlRuntimeHelpers.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data;
using Portable.Xaml;
using Portable.Xaml.Markup;
// ReSharper disable UnusedMember.Global
// ReSharper disable UnusedParameter.Global
@ -17,19 +18,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
var resourceNodes = provider.GetService<IAvaloniaXamlIlParentStackProvider>().Parents
.OfType<IResourceNode>().ToList();
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes));
var rootObject = provider.GetService<IRootObjectProvider>().RootObject;
return sp => builder(new DeferredParentServiceProvider(sp, resourceNodes, rootObject));
}
class DeferredParentServiceProvider : IAvaloniaXamlIlParentStackProvider, IServiceProvider
class DeferredParentServiceProvider :
IAvaloniaXamlIlParentStackProvider,
IServiceProvider,
IRootObjectProvider
{
private readonly IServiceProvider _parentProvider;
private readonly List<IResourceNode> _parentResourceNodes;
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes)
public DeferredParentServiceProvider(IServiceProvider parentProvider, List<IResourceNode> parentResourceNodes,
object rootObject)
{
_parentProvider = parentProvider;
_parentResourceNodes = parentResourceNodes;
RootObject = rootObject;
}
public IEnumerable<object> Parents => GetParents();
@ -46,8 +52,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.Runtime
{
if (serviceType == typeof(IAvaloniaXamlIlParentStackProvider))
return this;
if (serviceType == typeof(IRootObjectProvider))
return this;
return _parentProvider?.GetService(serviceType);
}
public object RootObject { get; }
}

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github

@ -1 +1 @@
Subproject commit 3b3c1f93a566080d417b9782f9cc4ea67cd62344
Subproject commit a73c5234831267b23160e01a9fbc83be633f69fc

1
src/Skia/Avalonia.Skia/Avalonia.Skia.csproj

@ -3,6 +3,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>Avalonia.Skia</RootNamespace>
<AssemblyName>Avalonia.Skia</AssemblyName>
<PackageId>Avalonia.Skia</PackageId>
<IncludeLinuxSkia>true</IncludeLinuxSkia>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

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

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>Avalonia.Direct2D1</PackageId>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Win32\Interop\UnmanagedMethods.cs">

24
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -160,19 +160,19 @@ namespace Avalonia.Win32.Interop.Wpf
return rv;
}
void MouseEvent(RawMouseEventType type, MouseEventArgs e)
=> _ttl.Input?.Invoke(new RawMouseEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
void MouseEvent(RawPointerEventType type, MouseEventArgs e)
=> _ttl.Input?.Invoke(new RawPointerEventArgs(_mouse, (uint)e.Timestamp, _inputRoot, type,
e.GetPosition(this).ToAvaloniaPoint(), GetModifiers()));
protected override void OnMouseDown(MouseButtonEventArgs e)
{
RawMouseEventType type;
RawPointerEventType type;
if(e.ChangedButton == MouseButton.Left)
type = RawMouseEventType.LeftButtonDown;
type = RawPointerEventType.LeftButtonDown;
else if (e.ChangedButton == MouseButton.Middle)
type = RawMouseEventType.MiddleButtonDown;
type = RawPointerEventType.MiddleButtonDown;
else if (e.ChangedButton == MouseButton.Right)
type = RawMouseEventType.RightButtonDown;
type = RawPointerEventType.RightButtonDown;
else
return;
MouseEvent(type, e);
@ -181,13 +181,13 @@ namespace Avalonia.Win32.Interop.Wpf
protected override void OnMouseUp(MouseButtonEventArgs e)
{
RawMouseEventType type;
RawPointerEventType type;
if (e.ChangedButton == MouseButton.Left)
type = RawMouseEventType.LeftButtonUp;
type = RawPointerEventType.LeftButtonUp;
else if (e.ChangedButton == MouseButton.Middle)
type = RawMouseEventType.MiddleButtonUp;
type = RawPointerEventType.MiddleButtonUp;
else if (e.ChangedButton == MouseButton.Right)
type = RawMouseEventType.RightButtonUp;
type = RawPointerEventType.RightButtonUp;
else
return;
MouseEvent(type, e);
@ -196,14 +196,14 @@ namespace Avalonia.Win32.Interop.Wpf
protected override void OnMouseMove(MouseEventArgs e)
{
MouseEvent(RawMouseEventType.Move, e);
MouseEvent(RawPointerEventType.Move, e);
}
protected override void OnMouseWheel(MouseWheelEventArgs e) =>
_ttl.Input?.Invoke(new RawMouseWheelEventArgs(_mouse, (uint) e.Timestamp, _inputRoot,
e.GetPosition(this).ToAvaloniaPoint(), new Vector(0, e.Delta), GetModifiers()));
protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawMouseEventType.LeaveWindow, e);
protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawPointerEventType.LeaveWindow, e);
protected override void OnKeyDown(KeyEventArgs e)
=> _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown,

1
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Win32</PackageId>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />

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

@ -574,6 +574,7 @@ namespace Avalonia.Win32.Interop
WM_AFXLAST = 0x037F,
WM_PENWINFIRST = 0x0380,
WM_PENWINLAST = 0x038F,
WM_TOUCH = 0x0240,
WM_APP = 0x8000,
WM_USER = 0x0400,
@ -836,10 +837,16 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
[DllImport("user32")]
public static extern IntPtr GetMessageExtraInfo();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "RegisterClassExW")]
public static extern ushort RegisterClassEx(ref WNDCLASSEX lpwcx);
[DllImport("user32.dll")]
public static extern void RegisterTouchWindow(IntPtr hWnd, int flags);
[DllImport("user32.dll")]
public static extern bool ReleaseCapture();
@ -1035,6 +1042,17 @@ namespace Avalonia.Win32.Interop
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetMonitorInfo([In] IntPtr hMonitor, ref MONITORINFO lpmi);
[DllImport("user32")]
public static extern bool GetTouchInputInfo(
IntPtr hTouchInput,
uint cInputs,
[Out]TOUCHINPUT[] pInputs,
int cbSize
);
[DllImport("user32")]
public static extern bool CloseTouchInputHandle(IntPtr hTouchInput);
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "PostMessageW")]
public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
@ -1309,6 +1327,60 @@ namespace Avalonia.Win32.Interop
public IntPtr hIconSm;
}
[StructLayout(LayoutKind.Sequential)]
public struct TOUCHINPUT
{
public int X;
public int Y;
public IntPtr Source;
public uint Id;
public TouchInputFlags Flags;
public int Mask;
public uint Time;
public IntPtr ExtraInfo;
public int CxContact;
public int CyContact;
}
[Flags]
public enum TouchInputFlags
{
/// <summary>
/// Movement has occurred. Cannot be combined with TOUCHEVENTF_DOWN.
/// </summary>
TOUCHEVENTF_MOVE = 0x0001,
/// <summary>
/// The corresponding touch point was established through a new contact. Cannot be combined with TOUCHEVENTF_MOVE or TOUCHEVENTF_UP.
/// </summary>
TOUCHEVENTF_DOWN = 0x0002,
/// <summary>
/// A touch point was removed.
/// </summary>
TOUCHEVENTF_UP = 0x0004,
/// <summary>
/// A touch point is in range. This flag is used to enable touch hover support on compatible hardware. Applications that do not want support for hover can ignore this flag.
/// </summary>
TOUCHEVENTF_INRANGE = 0x0008,
/// <summary>
/// Indicates that this TOUCHINPUT structure corresponds to a primary contact point. See the following text for more information on primary touch points.
/// </summary>
TOUCHEVENTF_PRIMARY = 0x0010,
/// <summary>
/// When received using GetTouchInputInfo, this input was not coalesced.
/// </summary>
TOUCHEVENTF_NOCOALESCE = 0x0020,
/// <summary>
/// The touch event came from the user's palm.
/// </summary>
TOUCHEVENTF_PALM = 0x0080
}
[Flags]
public enum OpenFileNameFlags
{

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

@ -40,6 +40,7 @@ namespace Avalonia
{
public bool UseDeferredRendering { get; set; } = true;
public bool AllowEglInitialization { get; set; }
public bool? EnableMultitouch { get; set; }
}
}
@ -59,7 +60,8 @@ namespace Avalonia.Win32
CreateMessageWindow();
}
public static bool UseDeferredRendering { get; set; }
public static bool UseDeferredRendering => Options.UseDeferredRendering;
public static Win32PlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(
UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK),
@ -74,6 +76,7 @@ namespace Avalonia.Win32
public static void Initialize(Win32PlatformOptions options)
{
Options = options;
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IStandardCursorFactory>().ToConstant(CursorFactory.Instance)
@ -88,7 +91,7 @@ namespace Avalonia.Win32
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
if (options.AllowEglInitialization)
Win32GlManager.Initialize();
UseDeferredRendering = options.UseDeferredRendering;
_uiThread = Thread.CurrentThread;
if (OleContext.Current != null)

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

@ -30,6 +30,8 @@ namespace Avalonia.Win32
private UnmanagedMethods.WndProc _wndProcDelegate;
private string _className;
private IntPtr _hwnd;
private bool _multitouch;
private TouchDevice _touchDevice = new TouchDevice();
private IInputRoot _owner;
private bool _trackingMouse;
private bool _decorated = true;
@ -414,6 +416,15 @@ namespace Avalonia.Win32
IntPtr.Zero);
}
bool ShouldIgnoreTouchEmulatedMessage()
{
if (!_multitouch)
return false;
var marker = 0xFF515700L;
var info = GetMessageExtraInfo().ToInt64();
return (info & marker) == marker;
}
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
@ -519,34 +530,40 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONDOWN:
e = new RawMouseEventArgs(
if(ShouldIgnoreTouchEmulatedMessage())
break;
e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN
? RawMouseEventType.LeftButtonDown
? RawPointerEventType.LeftButtonDown
: msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONDOWN
? RawMouseEventType.RightButtonDown
: RawMouseEventType.MiddleButtonDown,
? RawPointerEventType.RightButtonDown
: RawPointerEventType.MiddleButtonDown,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_RBUTTONUP:
case UnmanagedMethods.WindowsMessage.WM_MBUTTONUP:
e = new RawMouseEventArgs(
if(ShouldIgnoreTouchEmulatedMessage())
break;
e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_LBUTTONUP
? RawMouseEventType.LeftButtonUp
? RawPointerEventType.LeftButtonUp
: msg == (int)UnmanagedMethods.WindowsMessage.WM_RBUTTONUP
? RawMouseEventType.RightButtonUp
: RawMouseEventType.MiddleButtonUp,
? RawPointerEventType.RightButtonUp
: RawPointerEventType.MiddleButtonUp,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE:
if(ShouldIgnoreTouchEmulatedMessage())
break;
if (!_trackingMouse)
{
var tm = new UnmanagedMethods.TRACKMOUSEEVENT
@ -560,11 +577,11 @@ namespace Avalonia.Win32
UnmanagedMethods.TrackMouseEvent(ref tm);
}
e = new RawMouseEventArgs(
e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
RawMouseEventType.Move,
RawPointerEventType.Move,
DipFromLParam(lParam), GetMouseModifiers(wParam));
break;
@ -589,29 +606,52 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE:
_trackingMouse = false;
e = new RawMouseEventArgs(
e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
RawMouseEventType.LeaveWindow,
RawPointerEventType.LeaveWindow,
new Point(), WindowsKeyboardDevice.Instance.Modifiers);
break;
case UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN:
case UnmanagedMethods.WindowsMessage.WM_NCMBUTTONDOWN:
e = new RawMouseEventArgs(
e = new RawPointerEventArgs(
WindowsMouseDevice.Instance,
timestamp,
_owner,
msg == (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN
? RawMouseEventType.NonClientLeftButtonDown
? RawPointerEventType.NonClientLeftButtonDown
: msg == (int)UnmanagedMethods.WindowsMessage.WM_NCRBUTTONDOWN
? RawMouseEventType.RightButtonDown
: RawMouseEventType.MiddleButtonDown,
? RawPointerEventType.RightButtonDown
: RawPointerEventType.MiddleButtonDown,
new Point(0, 0), GetMouseModifiers(wParam));
break;
case WindowsMessage.WM_TOUCH:
var touchInputs = new TOUCHINPUT[wParam.ToInt32()];
if (GetTouchInputInfo(lParam, (uint)wParam.ToInt32(), touchInputs, Marshal.SizeOf<TOUCHINPUT>()))
{
foreach (var touchInput in touchInputs)
{
var pt = new POINT {X = touchInput.X / 100, Y = touchInput.Y / 100};
UnmanagedMethods.ScreenToClient(_hwnd, ref pt);
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time,
_owner,
touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_UP) ?
RawPointerEventType.TouchEnd :
touchInput.Flags.HasFlag(TouchInputFlags.TOUCHEVENTF_DOWN) ?
RawPointerEventType.TouchBegin :
RawPointerEventType.TouchUpdate,
new Point(pt.X, pt.Y),
WindowsKeyboardDevice.Instance.Modifiers,
touchInput.Id));
}
CloseTouchInputHandle(lParam);
return IntPtr.Zero;
}
break;
case WindowsMessage.WM_NCPAINT:
if (!_decorated)
{
@ -754,6 +794,10 @@ namespace Avalonia.Win32
Handle = new PlatformHandle(_hwnd, PlatformConstants.WindowHandleType);
_multitouch = Win32Platform.Options.EnableMultitouch ?? false;
if (_multitouch)
RegisterTouchWindow(_hwnd, 0);
if (UnmanagedMethods.ShCoreAvailable)
{
uint dpix, dpiy;

1
src/iOS/Avalonia.iOS/Avalonia.iOS.csproj

@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" ExcludeAssets="All" />
</ItemGroup>
<Import Project="..\..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
<Import Project="..\..\..\build\Rx.props" />

16
src/iOS/Avalonia.iOS/TopLevelImpl.cs

@ -86,11 +86,11 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
Input?.Invoke(new RawMouseEventArgs(
Input?.Invoke(new RawPointerEventArgs(
iOSPlatform.MouseDevice,
(uint)touch.Timestamp,
_inputRoot,
RawMouseEventType.LeftButtonUp,
RawPointerEventType.LeftButtonUp,
location,
InputModifiers.None));
}
@ -104,11 +104,11 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
_touchLastPoint = location;
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.None));
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.Move, location, InputModifiers.None));
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.LeftButtonDown, location, InputModifiers.None));
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.LeftButtonDown, location, InputModifiers.None));
}
}
@ -119,8 +119,8 @@ namespace Avalonia.iOS
{
var location = touch.LocationInView(this).ToAvalonia();
if (iOSPlatform.MouseDevice.Captured != null)
Input?.Invoke(new RawMouseEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawMouseEventType.Move, location, InputModifiers.LeftMouseButton));
Input?.Invoke(new RawPointerEventArgs(iOSPlatform.MouseDevice, (uint)touch.Timestamp, _inputRoot,
RawPointerEventType.Move, location, InputModifiers.LeftMouseButton));
else
{
//magic number based on test - correction of 0.02 is working perfect

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

@ -14,6 +14,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ButtonTests
{
private MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void Button_Is_Disabled_When_Command_Is_Disabled()
{
@ -102,12 +104,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(50, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
var pt = new Point(50, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@ -122,15 +120,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerEnter(target);
RaisePointerMove(target, pt);
RaisePointerPressed(target, 1, MouseButton.Left, pt);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, pt);
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@ -138,12 +136,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_Does_Not_Raise_Click_When_PointerReleased_Outside()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(200, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p) ? new IVisual[] { r } : new IVisual[0]);
@ -158,16 +152,16 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerLeave(target, mouse);
RaisePointerEnter(target);
RaisePointerMove(target, new Point(50,50));
RaisePointerPressed(target, 1, MouseButton.Left, new Point(50, 50));
RaisePointerLeave(target);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, new Point(200, 50));
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.False(clicked);
}
@ -175,12 +169,8 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Button_With_RenderTransform_Raises_Click()
{
var mouse = Mock.Of<IMouseDevice>();
var renderer = Mock.Of<IRenderer>();
IInputElement captured = null;
Mock.Get(mouse).Setup(m => m.GetPosition(It.IsAny<IVisual>())).Returns(new Point(150, 50));
Mock.Get(mouse).Setup(m => m.Capture(It.IsAny<IInputElement>())).Callback<IInputElement>(v => captured = v);
Mock.Get(mouse).Setup(m => m.Captured).Returns(() => captured);
var pt = new Point(150, 50);
Mock.Get(renderer).Setup(r => r.HitTest(It.IsAny<Point>(), It.IsAny<IVisual>(), It.IsAny<Func<IVisual, bool>>()))
.Returns<Point, IVisual, Func<IVisual, bool>>((p, r, f) =>
r.Bounds.Contains(p.Transform(r.RenderTransform.Value.Invert())) ?
@ -204,15 +194,15 @@ namespace Avalonia.Controls.UnitTests
target.Click += (s, e) => clicked = true;
RaisePointerEnter(target, mouse);
RaisePointerMove(target, mouse);
RaisePointerPressed(target, mouse, 1, MouseButton.Left);
RaisePointerEnter(target);
RaisePointerMove(target, pt);
RaisePointerPressed(target, 1, MouseButton.Left, pt);
Assert.Equal(captured, target);
Assert.Equal(_helper.Captured, target);
RaisePointerReleased(target, mouse, MouseButton.Left);
RaisePointerReleased(target, MouseButton.Left, pt);
Assert.Equal(captured, null);
Assert.Equal(_helper.Captured, null);
Assert.True(clicked);
}
@ -278,57 +268,29 @@ namespace Avalonia.Controls.UnitTests
public PixelPoint PointToScreen(Point p) => throw new NotImplementedException();
}
private void RaisePointerPressed(Button button, IMouseDevice device, int clickCount, MouseButton mouseButton)
private void RaisePointerPressed(Button button, int clickCount, MouseButton mouseButton, Point position)
{
button.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
Source = button,
MouseButton = mouseButton,
ClickCount = clickCount,
Device = device,
});
_helper.Down(button, mouseButton, position, clickCount: clickCount);
}
private void RaisePointerReleased(Button button, IMouseDevice device, MouseButton mouseButton)
private void RaisePointerReleased(Button button, MouseButton mouseButton, Point pt)
{
button.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
Source = button,
MouseButton = mouseButton,
Device = device,
});
_helper.Up(button, mouseButton, pt);
}
private void RaisePointerEnter(Button button, IMouseDevice device)
private void RaisePointerEnter(Button button)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerEnterEvent,
Source = button,
Device = device,
});
_helper.Enter(button);
}
private void RaisePointerLeave(Button button, IMouseDevice device)
private void RaisePointerLeave(Button button)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerLeaveEvent,
Source = button,
Device = device,
});
_helper.Leave(button);
}
private void RaisePointerMove(Button button, IMouseDevice device)
private void RaisePointerMove(Button button, Point pos)
{
button.RaiseEvent(new PointerEventArgs
{
RoutedEvent = InputElement.PointerMovedEvent,
Source = button,
Device = device,
});
_helper.Move(button, pos);
}
private class TestCommand : ICommand

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

@ -15,6 +15,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ComboBoxTests
{
MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void Clicking_On_Control_Toggles_IsDropDownOpen()
{
@ -23,17 +25,11 @@ namespace Avalonia.Controls.UnitTests
Items = new[] { "Foo", "Bar" },
};
target.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
});
_helper.Down(target);
_helper.Up(target);
Assert.True(target.IsDropDownOpen);
target.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
});
_helper.Down(target);
Assert.False(target.IsDropDownOpen);
}

43
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -14,6 +14,7 @@ namespace Avalonia.Controls.UnitTests
public class ContextMenuTests
{
private Mock<IPopupImpl> popupImpl;
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Clicking_On_Control_Toggles_ContextMenu()
@ -31,19 +32,11 @@ namespace Avalonia.Controls.UnitTests
new Window { Content = target };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.None
});
_mouse.Click(target);
Assert.False(sut.IsOpen);
popupImpl.Verify(x => x.Show(), Times.Once);
@ -69,19 +62,11 @@ namespace Avalonia.Controls.UnitTests
Avalonia.Application.Current.MainWindow = window;
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
popupImpl.Verify(x => x.Hide(), Times.Once);
@ -106,11 +91,7 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.False(sut.IsOpen);
@ -136,19 +117,11 @@ namespace Avalonia.Controls.UnitTests
sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; };
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.Right
});
_mouse.Click(target, MouseButton.Right);
Assert.True(sut.IsOpen);
target.RaiseEvent(new PointerReleasedEventArgs
{
RoutedEvent = InputElement.PointerReleasedEvent,
MouseButton = MouseButton.None
});
_mouse.Click(target, MouseButton.Right);
Assert.True(eventCalled);
Assert.True(sut.IsOpen);

9
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -16,6 +16,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests
{
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Should_Use_ItemTemplate_To_Create_Item_Content()
{
@ -225,12 +227,7 @@ namespace Avalonia.Controls.UnitTests
private void RaisePressedEvent(ListBox listBox, ListBoxItem item, MouseButton mouseButton)
{
listBox.RaiseEvent(new PointerPressedEventArgs
{
Source = item,
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = mouseButton
});
_mouse.Click(listBox, item, mouseButton);
}
[Fact]

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

@ -18,6 +18,8 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests_Single
{
MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Focusing_Item_With_Tab_Should_Not_Select_It()
{
@ -68,12 +70,7 @@ namespace Avalonia.Controls.UnitTests
};
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -90,11 +87,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -111,11 +104,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -133,11 +122,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(-1, target.SelectedIndex);
}
@ -155,11 +140,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 0;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -177,11 +158,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target);
target.SelectedIndex = 1;
target.Presenter.Panel.Children[0].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(target.Presenter.Panel.Children[0]);
Assert.Equal(0, target.SelectedIndex);
}
@ -306,4 +283,4 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate();
}
}
}
}

116
tests/Avalonia.Controls.UnitTests/MouseTestHelper.cs

@ -0,0 +1,116 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
namespace Avalonia.Controls.UnitTests
{
public class MouseTestHelper
{
class TestPointer : IPointer
{
public int Id { get; } = Pointer.GetNextFreeId();
public void Capture(IInputElement control)
{
Captured = control;
}
public IInputElement Captured { get; set; }
public PointerType Type => PointerType.Mouse;
public bool IsPrimary => true;
}
TestPointer _pointer = new TestPointer();
private InputModifiers _pressedButtons;
public IInputElement Captured => _pointer.Captured;
InputModifiers Convert(MouseButton mouseButton)
=> (mouseButton == MouseButton.Left ? InputModifiers.LeftMouseButton
: mouseButton == MouseButton.Middle ? InputModifiers.MiddleMouseButton
: mouseButton == MouseButton.Right ? InputModifiers.RightMouseButton : InputModifiers.None);
int ButtonCount(PointerPointProperties props)
{
var rv = 0;
if (props.IsLeftButtonPressed)
rv++;
if (props.IsMiddleButtonPressed)
rv++;
if (props.IsRightButtonPressed)
rv++;
return rv;
}
private MouseButton _pressedButton;
InputModifiers GetModifiers(InputModifiers modifiers) => modifiers | _pressedButtons;
public void Down(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
InputModifiers modifiers = default, int clickCount = 1)
=> Down(target, target, mouseButton, position, modifiers, clickCount);
public void Down(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
Point position = default, InputModifiers modifiers = default, int clickCount = 1)
{
_pressedButtons |= Convert(mouseButton);
var props = new PointerPointProperties(_pressedButtons);
if (ButtonCount(props) > 1)
Move(target, source, position);
else
{
_pressedButton = mouseButton;
target.RaiseEvent(new PointerPressedEventArgs(source, _pointer, (IVisual)source, position, props,
GetModifiers(modifiers), clickCount));
}
}
public void Move(IInteractive target, in Point position, InputModifiers modifiers = default) => Move(target, target, position, modifiers);
public void Move(IInteractive target, IInteractive source, in Point position, InputModifiers modifiers = default)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (IVisual)target, position,
new PointerPointProperties(_pressedButtons), GetModifiers(modifiers)));
}
public void Up(IInteractive target, MouseButton mouseButton = MouseButton.Left, Point position = default,
InputModifiers modifiers = default)
=> Up(target, target, mouseButton, position, modifiers);
public void Up(IInteractive target, IInteractive source, MouseButton mouseButton = MouseButton.Left,
Point position = default, InputModifiers modifiers = default)
{
var conv = Convert(mouseButton);
_pressedButtons = (_pressedButtons | conv) ^ conv;
var props = new PointerPointProperties(_pressedButtons);
if (ButtonCount(props) == 0)
target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, props,
GetModifiers(modifiers), _pressedButton));
else
Move(target, source, position);
}
public void Click(IInteractive target, MouseButton button = MouseButton.Left, Point position = default,
InputModifiers modifiers = default)
=> Click(target, target, button, position, modifiers);
public void Click(IInteractive target, IInteractive source, MouseButton button = MouseButton.Left,
Point position = default, InputModifiers modifiers = default)
{
Down(target, source, button, position, modifiers);
Up(target, source, button, position, modifiers);
}
public void Enter(IInteractive target)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerEnterEvent, target, _pointer, (IVisual)target, default,
new PointerPointProperties(_pressedButtons), _pressedButtons));
}
public void Leave(IInteractive target)
{
target.RaiseEvent(new PointerEventArgs(InputElement.PointerLeaveEvent, target, _pointer, (IVisual)target, default,
new PointerPointProperties(_pressedButtons), _pressedButtons));
}
}
}

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

@ -2,6 +2,7 @@
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -9,6 +10,16 @@ namespace Avalonia.Controls.UnitTests.Platform
{
public class DefaultMenuInteractionHandlerTests
{
static PointerEventArgs CreateArgs(RoutedEvent ev, IInteractive source)
=> new PointerEventArgs(ev, source, new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default);
static PointerPressedEventArgs CreatePressed(IInteractive source) => new PointerPressedEventArgs(source,
new FakePointer(), (IVisual)source, default, new PointerPointProperties {IsLeftButtonPressed = true},
default);
static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source,
new FakePointer(), (IVisual)source, default, new PointerPointProperties(), default, MouseButton.Left);
public class TopLevel
{
[Fact]
@ -121,7 +132,8 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreatePressed(item);
target.PointerPressed(item, e);
Mock.Get(menu).Verify(x => x.Close());
@ -141,7 +153,7 @@ namespace Avalonia.Controls.UnitTests.Platform
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = nextItem };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, nextItem);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@ -161,7 +173,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@ -176,7 +188,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var target = new DefaultMenuInteractionHandler(false);
var menu = new Mock<IMenu>();
var item = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.Parent == menu.Object);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
menu.SetupGet(x => x.IsOpen).Returns(true);
menu.SetupGet(x => x.SelectedItem).Returns(item);
@ -330,7 +342,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
@ -346,7 +358,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
target.PointerEnter(item, e);
Mock.Get(item).Verify(x => x.Open(), Times.Never);
@ -366,7 +378,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsSubMenuOpen == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerEnterItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SubItems).Returns(new[] { item, sibling });
@ -386,7 +398,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(item);
target.PointerLeave(item, e);
@ -403,7 +415,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var sibling = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
Mock.Get(parentItem).SetupGet(x => x.SelectedItem).Returns(sibling);
target.PointerLeave(item, e);
@ -419,7 +431,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true && x.IsPointerOverSubMenu == true);
var e = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var e = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
target.PointerLeave(item, e);
@ -434,7 +446,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem);
var e = new PointerReleasedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreateReleased(item);
target.PointerReleased(item, e);
@ -452,8 +464,8 @@ namespace Avalonia.Controls.UnitTests.Platform
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var childItem = Mock.Of<IMenuItem>(x => x.Parent == item);
var enter = new PointerEventArgs { RoutedEvent = MenuItem.PointerEnterItemEvent, Source = item };
var leave = new PointerEventArgs { RoutedEvent = MenuItem.PointerLeaveItemEvent, Source = item };
var enter = CreateArgs(MenuItem.PointerEnterItemEvent, item);
var leave = CreateArgs(MenuItem.PointerLeaveItemEvent, item);
// Pointer enters item; item is selected.
target.PointerEnter(item, enter);
@ -488,7 +500,7 @@ namespace Avalonia.Controls.UnitTests.Platform
var menu = Mock.Of<IMenu>();
var parentItem = Mock.Of<IMenuItem>(x => x.IsTopLevel == true && x.HasSubMenu == true && x.Parent == menu);
var item = Mock.Of<IMenuItem>(x => x.Parent == parentItem && x.HasSubMenu == true);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
var e = CreatePressed(item);
target.PointerPressed(item, e);
@ -537,5 +549,19 @@ namespace Avalonia.Controls.UnitTests.Platform
_action = action;
}
}
class FakePointer : IPointer
{
public int Id { get; } = Pointer.GetNextFreeId();
public void Capture(IInputElement control)
{
Captured = control;
}
public IInputElement Captured { get; set; }
public PointerType Type { get; }
public bool IsPrimary { get; } = true;
}
}
}

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

@ -22,6 +22,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
public class SelectingItemsControlTests
{
private MouseTestHelper _helper = new MouseTestHelper();
[Fact]
public void SelectedIndex_Should_Initially_Be_Minus_1()
{
@ -341,6 +343,33 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Equal(-1, target.SelectedIndex);
}
[Fact]
public void Moving_Selected_Item_Should_Update_Selection()
{
var items = new AvaloniaList<Item>
{
new Item(),
new Item(),
};
var target = new SelectingItemsControl
{
Items = items,
Template = Template(),
};
target.ApplyTemplate();
target.SelectedIndex = 0;
Assert.Equal(items[0], target.SelectedItem);
Assert.Equal(0, target.SelectedIndex);
items.Move(0, 1);
Assert.Equal(items[1], target.SelectedItem);
Assert.Equal(1, target.SelectedIndex);
}
[Fact]
public void Resetting_Items_Collection_Should_Clear_Selection()
{
@ -648,12 +677,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_helper.Down((Interactive)target.Presenter.Panel.Children[1]);
var panel = target.Presenter.Panel;
@ -676,11 +700,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
target.Presenter.Panel.Children[1].RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_helper.Down(target.Presenter.Panel.Children[1]);
items.RemoveAt(1);

15
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -385,6 +385,21 @@ namespace Avalonia.Controls.UnitTests
Assert.True(target.SelectionEnd <= "123".Length);
}
}
[Fact]
public void CoerceCaretIndex_Doesnt_Cause_Exception_with_malformed_line_ending()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "0123456789\r"
};
target.CaretIndex = 11;
Assert.True(true);
}
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
standardCursorFactory: Mock.Of<IStandardCursorFactory>());

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

@ -10,6 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Xunit;
@ -18,6 +19,8 @@ namespace Avalonia.Controls.UnitTests
{
public class TreeViewTests
{
MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Items_Should_Be_Created()
{
@ -129,11 +132,7 @@ namespace Avalonia.Controls.UnitTests
Assert.NotNull(container);
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
});
_mouse.Click(container);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
@ -164,12 +163,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container.IsSelected);
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = InputModifiers.Control
});
_mouse.Click(container, modifiers: InputModifiers.Control);
Assert.Null(target.SelectedItem);
Assert.False(container.IsSelected);
@ -204,13 +198,8 @@ namespace Avalonia.Controls.UnitTests
Assert.True(container1.IsSelected);
container2.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = InputModifiers.Control
});
_mouse.Click(container2, modifiers: InputModifiers.Control);
Assert.Equal(item2, target.SelectedItem);
Assert.False(container1.IsSelected);
Assert.True(container2.IsSelected);
@ -241,15 +230,15 @@ namespace Avalonia.Controls.UnitTests
var item1Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item1);
var item2Container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item2);
TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
ClickContainer(item1Container, InputModifiers.Control);
Assert.True(item1Container.IsSelected);
TreeTestHelper.ClickContainer(item2Container, InputModifiers.Control);
ClickContainer(item2Container, InputModifiers.Control);
Assert.True(item2Container.IsSelected);
Assert.Equal(new[] {item1, item2}, target.SelectedItems.OfType<Node>());
TreeTestHelper.ClickContainer(item1Container, InputModifiers.Control);
ClickContainer(item1Container, InputModifiers.Control);
Assert.False(item1Container.IsSelected);
Assert.DoesNotContain(item1, target.SelectedItems.OfType<Node>());
@ -280,12 +269,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
[Fact]
@ -313,12 +302,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
}
[Fact]
@ -346,12 +335,12 @@ namespace Avalonia.Controls.UnitTests
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
TreeTestHelper.ClickContainer(toContainer, InputModifiers.Shift);
TreeTestHelper.AssertChildrenSelected(target, rootNode);
ClickContainer(toContainer, InputModifiers.Shift);
AssertChildrenSelected(target, rootNode);
TreeTestHelper.ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(fromContainer, InputModifiers.None);
Assert.True(fromContainer.IsSelected);
@ -425,7 +414,6 @@ namespace Avalonia.Controls.UnitTests
Assert.True(called);
}
[Fact]
public void LogicalChildren_Should_Be_Set()
{
@ -623,6 +611,135 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Pressing_SelectAll_Gesture_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Pressing_SelectAll_Gesture_With_Downward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var from = rootNode.Children[0];
var to = rootNode.Children.Last();
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
AssertChildrenSelected(target, rootNode);
}
}
[Fact]
public void Pressing_SelectAll_Gesture_With_Upward_Range_Selected_Should_Select_All_Nodes()
{
using (UnitTestApplication.Start())
{
var tree = CreateTestTreeData();
var target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = tree,
SelectionMode = SelectionMode.Multiple
};
var visualRoot = new TestRoot();
visualRoot.Child = target;
CreateNodeDataTemplate(target);
ApplyTemplates(target);
var rootNode = tree[0];
var from = rootNode.Children.Last();
var to = rootNode.Children[0];
var fromContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(from);
var toContainer = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(to);
ClickContainer(fromContainer, InputModifiers.None);
ClickContainer(toContainer, InputModifiers.Shift);
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
var selectAllGesture = keymap.SelectAll.First();
var keyEvent = new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = selectAllGesture.Key,
Modifiers = selectAllGesture.Modifiers
};
target.RaiseEvent(keyEvent);
AssertChildrenSelected(target, rootNode);
}
}
private void ApplyTemplates(TreeView tree)
{
tree.ApplyTemplate();
@ -742,30 +859,23 @@ namespace Avalonia.Controls.UnitTests
}
}
private static class TreeTestHelper
void ClickContainer(IControl container, InputModifiers modifiers)
{
public static void ClickContainer(IControl container, InputModifiers modifiers)
{
container.RaiseEvent(new PointerPressedEventArgs
{
RoutedEvent = InputElement.PointerPressedEvent,
MouseButton = MouseButton.Left,
InputModifiers = modifiers
});
}
_mouse.Click(container, modifiers: modifiers);
}
public static void AssertChildrenSelected(TreeView treeView, Node rootNode)
void AssertChildrenSelected(TreeView treeView, Node rootNode)
{
foreach (var child in rootNode.Children)
{
foreach (var child in rootNode.Children)
{
var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
var container = (TreeViewItem)treeView.ItemContainerGenerator.Index.ContainerFromItem(child);
Assert.True(container.IsSelected);
}
Assert.True(container.IsSelected);
}
}
private class Node : NotifyingBase
private class Node : NotifyingBase
{
private IAvaloniaList<Node> _children;

4
tests/Avalonia.Input.UnitTests/MouseDeviceTests.cs

@ -225,11 +225,11 @@ namespace Avalonia.Input.UnitTests
private void SendMouseMove(IInputManager inputManager, TestRoot root, Point p = new Point())
{
inputManager.ProcessInput(new RawMouseEventArgs(
inputManager.ProcessInput(new RawPointerEventArgs(
root.MouseDevice,
0,
root,
RawMouseEventType.Move,
RawPointerEventType.Move,
p,
InputModifiers.None));
}

2
tests/Avalonia.Interactivity.UnitTests/Avalonia.Interactivity.UnitTests.csproj

@ -4,6 +4,7 @@
<TargetFrameworks>netcoreapp2.0;net47</TargetFrameworks>
<OutputType>Library</OutputType>
<IsTestProject>true</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<Import Project="..\..\build\UnitTests.NetCore.targets" />
<Import Project="..\..\build\UnitTests.NetFX.props" />
@ -19,6 +20,7 @@
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<Compile Include="..\Avalonia.Controls.UnitTests\MouseTestHelper.cs" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

44
tests/Avalonia.Interactivity.UnitTests/GestureTests.cs

@ -3,6 +3,7 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.UnitTests;
using Avalonia.Input;
using Xunit;
@ -10,6 +11,8 @@ namespace Avalonia.Interactivity.UnitTests
{
public class GestureTests
{
private MouseTestHelper _mouse = new MouseTestHelper();
[Fact]
public void Tapped_Should_Follow_Pointer_Pressed_Released()
{
@ -27,8 +30,7 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
_mouse.Click(border);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt" }, result);
}
@ -47,8 +49,7 @@ namespace Avalonia.Interactivity.UnitTests
decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
_mouse.Click(border);
Assert.Equal(new[] { "bt", "dt" }, result);
}
@ -72,11 +73,40 @@ namespace Avalonia.Interactivity.UnitTests
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
border.RaiseEvent(new PointerPressedEventArgs());
border.RaiseEvent(new PointerReleasedEventArgs());
border.RaiseEvent(new PointerPressedEventArgs { ClickCount = 2 });
_mouse.Click(border);
_mouse.Down(border, clickCount: 2);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp", "bdt", "ddt" }, result);
}
[Fact]
public void DoubleTapped_Should_Not_Be_Rasied_if_Pressed_is_Handled()
{
Border border = new Border();
var decorator = new Decorator
{
Child = border
};
var result = new List<string>();
decorator.AddHandler(Border.PointerPressedEvent, (s, e) =>
{
result.Add("dp");
e.Handled = true;
});
decorator.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("dr"));
decorator.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("dt"));
decorator.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("ddt"));
border.AddHandler(Border.PointerPressedEvent, (s, e) => result.Add("bp"));
border.AddHandler(Border.PointerReleasedEvent, (s, e) => result.Add("br"));
border.AddHandler(Gestures.TappedEvent, (s, e) => result.Add("bt"));
border.AddHandler(Gestures.DoubleTappedEvent, (s, e) => result.Add("bdt"));
_mouse.Click(border);
_mouse.Down(border, clickCount: 2);
Assert.Equal(new[] { "bp", "dp", "br", "dr", "bt", "dt", "bp", "dp" }, result);
}
}
}

3
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -32,7 +32,8 @@
<EmbeddedResource Include="Xaml\Style2.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml"/>
<AvaloniaResource Include="Xaml\XamlIlClassWithPrecompiledXaml.xaml" />
<AvaloniaResource Include="Xaml\XamlIlClassWithCustomProperty.xaml" />
</ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" />
</Project>

31
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/DataTemplateTests.cs

@ -38,6 +38,37 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
[Fact]
public void DataTemplate_Can_Contain_Named_UserControl()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:sys='clr-namespace:System;assembly=mscorlib'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<ItemsControl Name='itemsControl' Items='{Binding}'>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl Name='foo'/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var itemsControl = window.FindControl<ItemsControl>("itemsControl");
window.DataContext = new[] { "item1", "item2" };
window.ApplyTemplate();
itemsControl.ApplyTemplate();
itemsControl.Presenter.ApplyTemplate();
Assert.Equal(2, itemsControl.Presenter.Panel.Children.Count);
}
}
[Fact]
public void Can_Set_DataContext_In_DataTemplate()
{

6
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlClassWithCustomProperty.xaml

@ -0,0 +1,6 @@
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithCustomProperty'
Test="123">
</UserControl>

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

@ -5,8 +5,12 @@ using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using JetBrains.Annotations;
@ -117,6 +121,82 @@ namespace Avalonia.Markup.Xaml.UnitTests
Assert.Equal(Brushes.Red.Color, ((ISolidColorBrush)canvas.Background).Color);
}
}
[Fact]
public void Event_Handlers_Should_Work_For_Templates()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var w =new XamlIlBugTestsEventHandlerCodeBehind();
w.ApplyTemplate();
w.Show();
Dispatcher.UIThread.RunJobs();
var itemsPresenter = ((ItemsControl)w.Content).GetVisualChildren().FirstOrDefault();
var item = itemsPresenter
.GetVisualChildren().First()
.GetVisualChildren().First()
.GetVisualChildren().First();
((Control)item).DataContext = "test";
Assert.Equal("test", w.SavedContext);
}
}
[Fact]
public void Custom_Properties_Should_Work_With_XClass()
{
var precompiled = new XamlIlClassWithCustomProperty();
Assert.Equal("123", precompiled.Test);
var loaded = (XamlIlClassWithCustomProperty)AvaloniaXamlLoader.Parse(@"
<UserControl xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlClassWithCustomProperty'
Test='321'>
</UserControl>");
Assert.Equal("321", loaded.Test);
}
}
public class XamlIlBugTestsEventHandlerCodeBehind : Window
{
public object SavedContext;
public void HandleDataContextChanged(object sender, EventArgs args)
{
SavedContext = ((Control)sender).DataContext;
}
public XamlIlBugTestsEventHandlerCodeBehind()
{
new AvaloniaXamlLoader().Load(@"
<Window x:Class='Avalonia.Markup.Xaml.UnitTests.XamlIlBugTestsEventHandlerCodeBehind'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns='https://github.com/avaloniaui'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests;assembly=Avalonia.Markup.Xaml.UnitTests'
>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button DataContextChanged='HandleDataContextChanged' Content='{Binding .}' />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
", typeof(XamlIlBugTestsEventHandlerCodeBehind).Assembly, this);
((ItemsControl)Content).Items = new[] {"123"};
}
}
public class XamlIlClassWithCustomProperty : UserControl
{
public string Test { get; set; }
public XamlIlClassWithCustomProperty()
{
AvaloniaXamlLoader.Load(this);
}
}
public class XamlIlBugTestsBrushToColorConverter : IMultiValueConverter

7
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -273,13 +273,10 @@ namespace Avalonia.Styling.UnitTests
var root = new TestRoot();
var child = new Border();
((ISupportInitialize)child).BeginInit();
child.BeginInit();
root.Child = child;
child.Name = "foo";
Assert.Null(root.FindControl<Border>("foo"));
((ISupportInitialize)child).EndInit();
Assert.Same(root.FindControl<Border>("foo"), child);
child.EndInit();
}
}

23
tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs

@ -1,11 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
@ -70,23 +65,5 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo"));
}
[Fact]
public void Control_That_Is_NameScope_Should_Register_With_Parent_NameScope()
{
UserControl userControl;
var root = new TestTemplatedRoot
{
Content = userControl = new UserControl
{
Name = "foo",
}
};
root.ApplyTemplate();
Assert.Same(userControl, root.FindControl<UserControl>("foo"));
Assert.Same(userControl, userControl.FindControl<UserControl>("foo"));
}
}
}

46
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -325,6 +325,52 @@ namespace Avalonia.Visuals.UnitTests.Rendering
context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny<Rect>(), It.IsAny<Rect>(), BitmapInterpolationMode.Default));
}
[Fact]
public void Can_Dirty_Control_In_SceneInvalidated()
{
Border border1;
Border border2;
var root = new TestRoot
{
Width = 100,
Height = 100,
Child = new StackPanel
{
Children =
{
(border1 = new Border
{
Background = Brushes.Red,
Child = new Canvas(),
}),
(border2 = new Border
{
Background = Brushes.Red,
Child = new Canvas(),
}),
}
}
};
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var target = CreateTargetAndRunFrame(root);
var invalidated = false;
target.SceneInvalidated += (s, e) =>
{
invalidated = true;
target.AddDirty(border2);
};
target.AddDirty(border1);
target.Paint(new Rect(root.DesiredSize));
Assert.True(invalidated);
Assert.True(((IRenderLoopTask)target).NeedsUpdate);
}
private DeferredRenderer CreateTargetAndRunFrame(
TestRoot root,
Mock<IRenderTimer> timer = null,

Loading…
Cancel
Save