Browse Source

Merge pull request #8698 from AvaloniaUI/improve-input-android

pull/8765/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
5c5d02cedf
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      samples/ControlCatalog/Pages/PointersPage.xaml.cs
  2. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  3. 251
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
  4. 85
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  5. 4
      src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

2
samples/ControlCatalog/Pages/PointersPage.xaml.cs

@ -62,7 +62,7 @@ Position: ??? ???";
e.Pointer.Capture(null); e.Pointer.Capture(null);
e.Handled = true; e.Handled = true;
} }
else else if (e.Pointer.Captured is not null)
{ {
throw new InvalidOperationException("How?"); throw new InvalidOperationException("How?");
} }

17
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
using Android.Views; using Android.Views;
@ -30,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IFramebufferPlatformSurface _framebuffer; private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper; private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper; private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly ITextInputMethodImpl _textInputMethod; private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view; private ViewImpl _view;
@ -39,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view = new ViewImpl(avaloniaView.Context, this, placeOnTop); _view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod<ViewImpl>(_view); _textInputMethod = new AndroidInputMethod<ViewImpl>(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this); _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot, _pointerHelper = new AndroidMotionEventsHelper(this);
GetAvaloniaPointFromEvent);
_gl = GlPlatformSurface.TryCreate(this); _gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this); _framebuffer = new FramebufferManager(this);
@ -160,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_tl.Draw(); _tl.Draw();
} }
protected override bool DispatchGenericPointerEvent(MotionEvent e)
{
bool callBase;
bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
return result != null ? result.Value : baseResult;
}
public override bool DispatchTouchEvent(MotionEvent e) public override bool DispatchTouchEvent(MotionEvent e)
{ {
bool callBase; bool callBase;
bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase); bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false; bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult; return result != null ? result.Value : baseResult;

251
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs

@ -0,0 +1,251 @@
using System;
using System.Collections.Generic;
using Android.Views;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Collections.Pooled;
using Avalonia.Input;
using Avalonia.Input.Raw;
#nullable enable
namespace Avalonia.Android.Platform.Specific.Helpers
{
internal class AndroidMotionEventsHelper : IDisposable
{
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private static readonly float s_radiansToDegree = (float)(180f * Math.PI);
private readonly TouchDevice _touchDevice;
private readonly MouseDevice _mouseDevice;
private readonly PenDevice _penDevice;
private readonly TopLevelImpl _view;
private bool _disposed;
public AndroidMotionEventsHelper(TopLevelImpl view)
{
_touchDevice = new TouchDevice();
_penDevice = new PenDevice();
_mouseDevice = new MouseDevice();
_view = view;
}
public bool? DispatchMotionEvent(MotionEvent e, out bool callBase)
{
callBase = true;
if (_disposed)
{
return null;
}
var eventTime = (ulong)DateTime.Now.Millisecond;
var inputRoot = _view.InputRoot;
var actionMasked = e.ActionMasked;
var modifiers = GetModifiers(e.MetaState, e.ButtonState);
if (actionMasked == MotionEventActions.Move)
{
for (int index = 0; index < e.PointerCount; index++)
{
var toolType = e.GetToolType(index);
var device = GetDevice(toolType);
var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
var point = CreatePoint(e, index);
modifiers |= GetToolModifiers(toolType);
// ButtonState reports only mouse buttons, but not touch or stylus pointer.
if (toolType != MotionEventToolType.Mouse)
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index))
{
IntermediatePoints = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
var site = e.HistorySize;
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = site;
for (int pos = 0; pos < site; pos++)
{
s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos));
}
return s_intermediatePointsPooledList;
})
};
_view.Input(args);
}
}
else
{
var index = e.ActionIndex;
var toolType = e.GetToolType(index);
var device = GetDevice(toolType);
modifiers |= GetToolModifiers(toolType);
var point = CreatePoint(e, index);
if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse)
{
var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
_view.Input(args);
}
else
{
var eventType = GetActionType(e, actionMasked, toolType);
if (eventType >= 0)
{
var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index));
_view.Input(args);
}
}
}
return true;
}
private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState)
{
var modifiers = RawInputModifiers.None;
if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn))
{
modifiers |= RawInputModifiers.Shift;
}
if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn))
{
modifiers |= RawInputModifiers.Control;
}
if (metaState.HasAnyFlag(MetaKeyStates.AltOn))
{
modifiers |= RawInputModifiers.Alt;
}
if (metaState.HasAnyFlag(MetaKeyStates.MetaOn))
{
modifiers |= RawInputModifiers.Meta;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Primary))
{
modifiers |= RawInputModifiers.LeftMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary))
{
modifiers |= RawInputModifiers.RightMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary))
{
modifiers |= RawInputModifiers.MiddleMouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Back))
{
modifiers |= RawInputModifiers.XButton1MouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.Forward))
{
modifiers |= RawInputModifiers.XButton2MouseButton;
}
if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
{
modifiers |= RawInputModifiers.PenBarrelButton;
}
return modifiers;
}
#pragma warning disable CA1416 // Validate platform compatibility
private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType)
{
var isTouch = toolType == MotionEventToolType.Finger;
var isMouse = toolType == MotionEventToolType.Mouse;
switch (actionMasked)
{
// DOWN
case MotionEventActions.Down when !isMouse:
case MotionEventActions.PointerDown when !isMouse:
return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown;
case MotionEventActions.ButtonPress:
return e.ActionButton switch
{
MotionEventButtonState.Back => RawPointerEventType.XButton1Down,
MotionEventButtonState.Forward => RawPointerEventType.XButton2Down,
MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown,
MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown,
MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown,
MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown,
MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown,
_ => RawPointerEventType.LeftButtonDown
};
// UP
case MotionEventActions.Up when !isMouse:
case MotionEventActions.PointerUp when !isMouse:
return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp;
case MotionEventActions.ButtonRelease:
return e.ActionButton switch
{
MotionEventButtonState.Back => RawPointerEventType.XButton1Up,
MotionEventButtonState.Forward => RawPointerEventType.XButton2Up,
MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp,
MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp,
MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp,
MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp,
MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp,
_ => RawPointerEventType.LeftButtonUp
};
// MOVE
case MotionEventActions.Outside:
case MotionEventActions.HoverMove:
case MotionEventActions.Move:
return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
// CANCEL
case MotionEventActions.Cancel:
return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow;
default:
return (RawPointerEventType)(-1);
}
}
#pragma warning restore CA1416 // Validate platform compatibility
private IPointerDevice GetDevice(MotionEventToolType type)
{
return type switch
{
MotionEventToolType.Mouse => _mouseDevice,
MotionEventToolType.Stylus => _penDevice,
MotionEventToolType.Eraser => _penDevice,
MotionEventToolType.Finger => _touchDevice,
_ => _touchDevice
};
}
private RawPointerPoint CreatePoint(MotionEvent e, int index)
{
return new RawPointerPoint
{
Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
Twist = e.GetOrientation(index) * s_radiansToDegree
};
}
private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos)
{
return new RawPointerPoint
{
Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree
};
}
private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType)
{
// Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here.
return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None;
}
public void Dispose()
{
_disposed = true;
}
}
}

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

@ -1,85 +0,0 @@
using System;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.Specific.Helpers
{
public class AndroidTouchEventsHelper<TView> : IDisposable where TView : ITopLevelImpl, IAndroidView
{
private TView _view;
public bool HandleEvents { get; set; }
public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, int, Point> getPointfunc)
{
this._view = view;
HandleEvents = true;
_getPointFunc = getPointfunc;
_getInputRoot = getInputRoot;
}
private TouchDevice _touchDevice = new TouchDevice();
private Func<MotionEvent, int, Point> _getPointFunc;
private Func<IInputRoot> _getInputRoot;
public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
{
if (!HandleEvents)
{
callBase = true;
return null;
}
var eventTime = DateTime.Now;
//Basic touch support
var pointerEventType = e.Action switch
{
MotionEventActions.Down => RawPointerEventType.TouchBegin,
MotionEventActions.Up => RawPointerEventType.TouchEnd,
MotionEventActions.Cancel => RawPointerEventType.TouchCancel,
_ => RawPointerEventType.TouchUpdate
};
if (e.Action.HasFlag(MotionEventActions.PointerDown))
{
pointerEventType = RawPointerEventType.TouchBegin;
}
if (e.Action.HasFlag(MotionEventActions.PointerUp))
{
pointerEventType = RawPointerEventType.TouchEnd;
}
for (int i = 0; i < e.PointerCount; i++)
{
//if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event
var point = _getPointFunc(e, i);
double x = _view.View.GetX();
double y = _view.View.GetY();
double r = x + _view.View.Width;
double b = y + _view.View.Height;
if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y)
{
var inputRoot = _getInputRoot();
var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot,
i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i));
_view.Input(mouseEvent);
}
}
callBase = true;
//if return false events for move and up are not received!!!
return e.Action != MotionEventActions.Up;
}
public void Dispose()
{
HandleEvents = false;
}
}
}

4
src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs

@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw
/// </summary> /// </summary>
public Point Position { get; set; } public Point Position { get; set; }
/// <inheritdoc cref="PointerPointProperties.Twist" />
public float Twist { get; set; } public float Twist { get; set; }
/// <inheritdoc cref="PointerPointProperties.Pressure" />
public float Pressure { get; set; } public float Pressure { get; set; }
/// <inheritdoc cref="PointerPointProperties.XTilt" />
public float XTilt { get; set; } public float XTilt { get; set; }
/// <inheritdoc cref="PointerPointProperties.YTilt" />
public float YTilt { get; set; } public float YTilt { get; set; }

Loading…
Cancel
Save