committed by
GitHub
16 changed files with 576 additions and 213 deletions
@ -0,0 +1,43 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Threading; |
||||
|
|
||||
|
namespace Avalonia.LinuxFramebuffer.Input; |
||||
|
|
||||
|
internal class RawEventGroupingThreadingHelper : IDisposable |
||||
|
{ |
||||
|
private readonly RawEventGrouper _grouper; |
||||
|
private readonly Queue<RawInputEventArgs> _rawQueue = new(); |
||||
|
private readonly Action _queueHandler; |
||||
|
|
||||
|
public RawEventGroupingThreadingHelper(Action<RawInputEventArgs> eventCallback) |
||||
|
{ |
||||
|
_grouper = new RawEventGrouper(eventCallback); |
||||
|
_queueHandler = QueueHandler; |
||||
|
} |
||||
|
|
||||
|
private void QueueHandler() |
||||
|
{ |
||||
|
lock (_rawQueue) |
||||
|
{ |
||||
|
while (_rawQueue.Count > 0) |
||||
|
_grouper.HandleEvent(_rawQueue.Dequeue()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void OnEvent(RawInputEventArgs args) |
||||
|
{ |
||||
|
lock (_rawQueue) |
||||
|
{ |
||||
|
_rawQueue.Enqueue(args); |
||||
|
if (_rawQueue.Count == 1) |
||||
|
{ |
||||
|
Dispatcher.UIThread.Post(_queueHandler, DispatcherPriority.Input); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Dispose() => |
||||
|
Dispatcher.UIThread.Post(() => _grouper.Dispose(), DispatcherPriority.Input + 1); |
||||
|
} |
||||
@ -0,0 +1,129 @@ |
|||||
|
#nullable enable |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Collections.Pooled; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Threading; |
||||
|
using JetBrains.Annotations; |
||||
|
|
||||
|
namespace Avalonia; |
||||
|
|
||||
|
/* |
||||
|
This helper maintains an input queue for backends that handle input asynchronously. |
||||
|
While doing that it groups Move and TouchUpdate events so we could provide GetIntermediatePoints API |
||||
|
*/ |
||||
|
|
||||
|
internal class RawEventGrouper : IDisposable |
||||
|
{ |
||||
|
private readonly Action<RawInputEventArgs> _eventCallback; |
||||
|
private readonly Queue<RawInputEventArgs> _inputQueue = new(); |
||||
|
private readonly Action _dispatchFromQueue; |
||||
|
readonly Dictionary<long, RawTouchEventArgs> _lastTouchPoints = new(); |
||||
|
RawInputEventArgs? _lastEvent; |
||||
|
|
||||
|
public RawEventGrouper(Action<RawInputEventArgs> eventCallback) |
||||
|
{ |
||||
|
_eventCallback = eventCallback; |
||||
|
_dispatchFromQueue = DispatchFromQueue; |
||||
|
} |
||||
|
|
||||
|
private void AddToQueue(RawInputEventArgs args) |
||||
|
{ |
||||
|
_lastEvent = args; |
||||
|
_inputQueue.Enqueue(args); |
||||
|
if (_inputQueue.Count == 1) |
||||
|
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input); |
||||
|
} |
||||
|
|
||||
|
private void DispatchFromQueue() |
||||
|
{ |
||||
|
while (true) |
||||
|
{ |
||||
|
if(_inputQueue.Count == 0) |
||||
|
return; |
||||
|
|
||||
|
var ev = _inputQueue.Dequeue(); |
||||
|
|
||||
|
if (_lastEvent == ev) |
||||
|
_lastEvent = null; |
||||
|
|
||||
|
if (ev is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchUpdate) |
||||
|
_lastTouchPoints.Remove(touchUpdate.TouchPointId); |
||||
|
|
||||
|
_eventCallback?.Invoke(ev); |
||||
|
|
||||
|
if (ev is RawPointerEventArgs { IntermediatePoints: PooledList<Point> list }) |
||||
|
list.Dispose(); |
||||
|
|
||||
|
if (Dispatcher.UIThread.HasJobsWithPriority(DispatcherPriority.Input + 1)) |
||||
|
{ |
||||
|
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void HandleEvent(RawInputEventArgs args) |
||||
|
{ |
||||
|
/* |
||||
|
Try to update already enqueued events if |
||||
|
1) they are still not handled (_lastEvent and _lastTouchPoints shouldn't contain said event in that case) |
||||
|
2) previous event belongs to the same "event block", events in the same block: |
||||
|
- belong from the same device |
||||
|
- are pointer move events (Move/TouchUpdate) |
||||
|
- have the same type |
||||
|
- have same modifiers |
||||
|
|
||||
|
Even if nothing is updated and the event is actually enqueued, we need to update the relevant tracking info |
||||
|
*/ |
||||
|
if ( |
||||
|
args is RawPointerEventArgs pointerEvent |
||||
|
&& _lastEvent != null |
||||
|
&& _lastEvent.Device == args.Device |
||||
|
&& _lastEvent is RawPointerEventArgs lastPointerEvent |
||||
|
&& lastPointerEvent.InputModifiers == pointerEvent.InputModifiers |
||||
|
&& lastPointerEvent.Type == pointerEvent.Type |
||||
|
&& lastPointerEvent.Type is RawPointerEventType.Move or RawPointerEventType.TouchUpdate) |
||||
|
{ |
||||
|
if (args is RawTouchEventArgs touchEvent) |
||||
|
{ |
||||
|
if (_lastTouchPoints.TryGetValue(touchEvent.TouchPointId, out var lastTouchEvent)) |
||||
|
MergeEvents(lastTouchEvent, touchEvent); |
||||
|
else |
||||
|
{ |
||||
|
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent; |
||||
|
AddToQueue(touchEvent); |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
MergeEvents(lastPointerEvent, pointerEvent); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_lastTouchPoints.Clear(); |
||||
|
if (args is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchEvent) |
||||
|
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent; |
||||
|
} |
||||
|
AddToQueue(args); |
||||
|
} |
||||
|
|
||||
|
private static void MergeEvents(RawPointerEventArgs last, RawPointerEventArgs current) |
||||
|
{ |
||||
|
last.IntermediatePoints ??= new PooledList<Point>(); |
||||
|
((PooledList<Point>)last.IntermediatePoints).Add(last.Position); |
||||
|
last.Position = current.Position; |
||||
|
last.Timestamp = current.Timestamp; |
||||
|
last.InputModifiers = current.InputModifiers; |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_inputQueue.Clear(); |
||||
|
_lastEvent = null; |
||||
|
_lastTouchPoints.Clear(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
Loading…
Reference in new issue