diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..c5a719ce90 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: avalonia diff --git a/.gitignore b/.gitignore index 2b2c9c3d0d..971c945246 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,5 @@ ModuleCache.noindex/ Build/Intermediates.noindex/ info.plist build-intermediate +obj-Direct2D1/ +obj-Skia/ diff --git a/dirs.proj b/dirs.proj index 4939a158bb..e56320e73f 100644 --- a/dirs.proj +++ b/dirs.proj @@ -8,10 +8,11 @@ - - + + + diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 489cb228aa..2c5a09bee7 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -1,6 +1,7 @@  netstandard2.0;net461;netcoreapp2.0 + Avalonia diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 93f5611ec4..c35b4a7919 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -46,6 +46,8 @@ namespace ControlCatalog.NetCore public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() + .With(new X11PlatformOptions {EnableMultiTouch = true}) + .With(new Win32PlatformOptions {EnableMultitouch = true}) .UseSkia() .UseReactiveUI(); diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index 9f1899acc5..1cddb9d295 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -31,6 +31,7 @@ + diff --git a/samples/ControlCatalog/Pages/PointersPage.cs b/samples/ControlCatalog/Pages/PointersPage.cs new file mode 100644 index 0000000000..a1359519e6 --- /dev/null +++ b/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 _pointers = new Dictionary(); + + 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))); + } + + } + } +} diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index 112925ab0f..463d499aad 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/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); diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index ec668f2a2b..df840464c1 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/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); } diff --git a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj index 21d34ae4d6..27853a1540 100644 --- a/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj +++ b/src/Avalonia.Controls.DataGrid/Avalonia.Controls.DataGrid.csproj @@ -1,6 +1,7 @@  netstandard2.0 + Avalonia.Controls.DataGrid diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index bbea3693cc..9f9fe5eae1 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/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 } /// - 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) || diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 224d9b782b..53852defb3 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -176,18 +176,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarLeftMouseButtonUp?.Invoke(this, e); } - - /// - /// 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. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index 1b36f92fd3..cb2a98e5ca 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -234,18 +234,5 @@ namespace Avalonia.Controls.Primitives if (e.MouseButton == MouseButton.Left) CalendarDayButtonMouseUp?.Invoke(this, e); } - - /// - /// 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. - /// - /// Event arguments. - internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e) - { - e.Handled = false; - base.OnPointerReleased(e); - } } } diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index fb6dacaf81..8232697c18 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/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; } } diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index d8473dc613..d0ab0a0c8b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/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)); } /// @@ -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)); } /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 73854b9f60..5f63a44717 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/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(); } diff --git a/src/Avalonia.Controls/Platform/InProcessDragSource.cs b/src/Avalonia.Controls/Platform/InProcessDragSource.cs index 0918da1a90..76f17332bf 100644 --- a/src/Avalonia.Controls/Platform/InProcessDragSource.cs +++ b/src/Avalonia.Controls/Platform/InProcessDragSource.cs @@ -43,7 +43,7 @@ namespace Avalonia.Platform _lastPosition = default(Point); _allowedEffects = allowedEffects; - using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) + using (_inputManager.PreProcess.OfType().Subscribe(ProcessMouseEvents)) { using (_inputManager.PreProcess.OfType().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) { diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index f349bcf059..e02d46c1df 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/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(); } diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 280c3ad93a..152b551f9a 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/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 /// The desired items. internal static void SynchronizeItems(IList items, IEnumerable 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; diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index ba4c5027d0..32e220b789 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/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); } diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index 6293cbfbfd..1ef03b49ce 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -39,21 +39,21 @@ namespace Avalonia.Controls.Remote.Server KeyboardDevice = AvaloniaLocator.Current.GetService(); } - 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, diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 04b088e35c..d43957313e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/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; } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 01c9a3a110..01614ba87b 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/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); } + + /// + /// Raises the event. + /// + /// The event args. + /// + /// A type that derives from may override . The + /// overridden method must call on the base class if the + /// event needs to be raised. + /// + protected virtual void OnClosing(CancelEventArgs e) => Closing?.Invoke(this, e); } } diff --git a/src/Avalonia.Desktop/Avalonia.Desktop.csproj b/src/Avalonia.Desktop/Avalonia.Desktop.csproj index f7c06306f5..f45b0c54e3 100644 --- a/src/Avalonia.Desktop/Avalonia.Desktop.csproj +++ b/src/Avalonia.Desktop/Avalonia.Desktop.csproj @@ -1,6 +1,7 @@ netstandard2.0 + Avalonia.Desktop diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 8a514ca685..23b0ad466e 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/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)); + } } } } diff --git a/src/Avalonia.Input/IMouseDevice.cs b/src/Avalonia.Input/IMouseDevice.cs index a1d1bb3eb8..7e6bf657ae 100644 --- a/src/Avalonia.Input/IMouseDevice.cs +++ b/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 { /// @@ -11,6 +13,9 @@ namespace Avalonia.Input /// /// Gets the mouse position, in screen coordinates. /// + [Obsolete("Use PointerEventArgs.GetPosition")] PixelPoint Position { get; } + + void SceneInvalidated(IInputRoot root, Rect rect); } } diff --git a/src/Avalonia.Input/IPointer.cs b/src/Avalonia.Input/IPointer.cs new file mode 100644 index 0000000000..a3f051ce7f --- /dev/null +++ b/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 + } +} diff --git a/src/Avalonia.Input/IPointerDevice.cs b/src/Avalonia.Input/IPointerDevice.cs index 932cbc989f..de775d90f2 100644 --- a/src/Avalonia.Input/IPointerDevice.cs +++ b/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); } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index d3e62ece6f..90d9c37bd4 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -14,14 +14,18 @@ namespace Avalonia.Input /// /// Represents a mouse device. /// - 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(); + /// /// Gets the control that is currently capturing by the mouse, if any. /// @@ -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(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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(device != null); Contract.Requires(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); diff --git a/src/Avalonia.Input/Pointer.cs b/src/Avalonia.Input/Pointer.cs new file mode 100644 index 0000000000..bdf2501b32 --- /dev/null +++ b/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().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; + } + } +} diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 7b2497c460..1d07190a81 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/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; } } } diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs new file mode 100644 index 0000000000..7117b5709c --- /dev/null +++ b/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; + } + } +} diff --git a/src/Avalonia.Input/PointerWheelEventArgs.cs b/src/Avalonia.Input/PointerWheelEventArgs.cs index 401d1d847c..b409cc81bd 100644 --- a/src/Avalonia.Input/PointerWheelEventArgs.cs +++ b/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; + } } } diff --git a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs b/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs index d2e5faab6c..186ad99efc 100644 --- a/src/Avalonia.Input/Raw/RawMouseWheelEventArgs.cs +++ b/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; } diff --git a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs similarity index 84% rename from src/Avalonia.Input/Raw/RawMouseEventArgs.cs rename to src/Avalonia.Input/Raw/RawPointerEventArgs.cs index c5637d66cc..b728844e97 100644 --- a/src/Avalonia.Input/Raw/RawMouseEventArgs.cs +++ b/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 } /// /// A raw mouse event. /// - public class RawMouseEventArgs : RawInputEventArgs + public class RawPointerEventArgs : RawInputEventArgs { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The associated device. /// The event timestamp. @@ -33,11 +36,11 @@ namespace Avalonia.Input.Raw /// The type of the event. /// The mouse position, in client DIPs. /// The input modifiers. - 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 /// /// Gets the type of the event. /// - public RawMouseEventType Type { get; private set; } + public RawPointerEventType Type { get; private set; } /// /// Gets the input modifiers. diff --git a/src/Avalonia.Input/Raw/RawTouchEventArgs.cs b/src/Avalonia.Input/Raw/RawTouchEventArgs.cs new file mode 100644 index 0000000000..5299633b26 --- /dev/null +++ b/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; } + } +} diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs new file mode 100644 index 0000000000..e9715bd87c --- /dev/null +++ b/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 +{ + /// + /// Handles raw touch events + /// + /// This class is supposed to be used on per-toplevel basis, don't use a shared one + /// + /// + public class TouchDevice : IInputDevice + { + Dictionary _pointers = new Dictionary(); + + 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)); + } + } + + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 73b8834375..638879ba14 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/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; } } diff --git a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj index 1851f9fb70..64145b9c3c 100644 --- a/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj +++ b/src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj @@ -1,6 +1,7 @@  netstandard2.0 + Avalonia.ReactiveUI diff --git a/src/Avalonia.Styling/Controls/IResourceProvider.cs b/src/Avalonia.Styling/Controls/IResourceProvider.cs index eec783623c..cbaacee012 100644 --- a/src/Avalonia.Styling/Controls/IResourceProvider.cs +++ b/src/Avalonia.Styling/Controls/IResourceProvider.cs @@ -28,6 +28,6 @@ namespace Avalonia.Controls /// /// True if the resource if found, otherwise false. /// - bool TryGetResource(string key, out object value); + bool TryGetResource(object key, out object value); } } diff --git a/src/Avalonia.Styling/Controls/ResourceDictionary.cs b/src/Avalonia.Styling/Controls/ResourceDictionary.cs index 74a861b36b..901e27b7b7 100644 --- a/src/Avalonia.Styling/Controls/ResourceDictionary.cs +++ b/src/Avalonia.Styling/Controls/ResourceDictionary.cs @@ -69,7 +69,7 @@ namespace Avalonia.Controls } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (TryGetValue(key, out value)) { diff --git a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs index 52309b87a2..01112eaf2c 100644 --- a/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs +++ b/src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls /// The control. /// The resource key. /// The resource, or if not found. - 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 /// The resource key. /// On return, contains the resource if found, otherwise null. /// True if the resource was found; otherwise false. - 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(control != null); Contract.Requires(key != null); @@ -52,7 +52,7 @@ namespace Avalonia.Controls return false; } - public static IObservable GetResourceObservable(this IResourceNode target, string key) + public static IObservable GetResourceObservable(this IResourceNode target, object key) { return new ResourceObservable(target, key); } @@ -60,9 +60,9 @@ namespace Avalonia.Controls private class ResourceObservable : LightweightObservableBase { 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; diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index d314a8d44e..f7e063dfb5 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -415,7 +415,7 @@ namespace Avalonia } /// - 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); - } } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index d799df7ac9..3ce82b4160 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/src/Avalonia.Styling/Styling/Style.cs @@ -171,7 +171,7 @@ namespace Avalonia.Styling } /// - 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; diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 288cf35d08..789bb6ffd3 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -178,7 +178,7 @@ namespace Avalonia.Styling } /// - public bool TryGetResource(string key, out object value) + public bool TryGetResource(object key, out object value) { if (_resources != null && _resources.TryGetValue(key, out value)) { diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index f84e09510b..8f7d56dbc6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -46,11 +46,11 @@ - - - - - + + + + + 1,1,1,1 0.5 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 18c32b02bc..666596d710 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -46,11 +46,11 @@ - - - - - + + + + + 1 0.5 diff --git a/src/Avalonia.Themes.Default/NotificationCard.xaml b/src/Avalonia.Themes.Default/NotificationCard.xaml index e94cb33d1e..47d5988e8c 100644 --- a/src/Avalonia.Themes.Default/NotificationCard.xaml +++ b/src/Avalonia.Themes.Default/NotificationCard.xaml @@ -13,7 +13,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Margin="8,8,0,0"> - + @@ -40,6 +40,10 @@ + +