committed by
GitHub
51 changed files with 1074 additions and 550 deletions
@ -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))); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
} |
||||
|
} |
||||
@ -1,18 +1,20 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// 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.
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
using Avalonia.VisualTree; |
using Avalonia.VisualTree; |
||||
|
|
||||
namespace Avalonia.Input |
namespace Avalonia.Input |
||||
{ |
{ |
||||
public interface IPointerDevice : IInputDevice |
public interface IPointerDevice : IInputDevice |
||||
{ |
{ |
||||
|
[Obsolete("Use IPointer")] |
||||
IInputElement Captured { get; } |
IInputElement Captured { get; } |
||||
|
|
||||
|
[Obsolete("Use IPointer")] |
||||
void Capture(IInputElement control); |
void Capture(IInputElement control); |
||||
|
|
||||
|
[Obsolete("Use PointerEventArgs.GetPosition")] |
||||
Point GetPosition(IVisual relativeTo); |
Point GetPosition(IVisual relativeTo); |
||||
|
|
||||
void SceneInvalidated(IInputRoot root, Rect rect); |
|
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,10 +1,21 @@ |
|||||
// Copyright (c) The Avalonia Project. All rights reserved.
|
// 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.
|
// 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 |
namespace Avalonia.Input |
||||
{ |
{ |
||||
public class PointerWheelEventArgs : PointerEventArgs |
public class PointerWheelEventArgs : PointerEventArgs |
||||
{ |
{ |
||||
public Vector Delta { get; set; } |
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; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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, pointer.IsPrimary); |
||||
|
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, false); |
||||
|
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)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -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)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue