committed by
GitHub
66 changed files with 1153 additions and 566 deletions
@ -0,0 +1 @@ |
|||
open_collective: avalonia |
|||
@ -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.
|
|||
// 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); |
|||
} |
|||
} |
|||
|
|||
@ -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.
|
|||
// 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; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -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