|
@@ -1,126 +1,69 @@
|
|
-using System.Diagnostics;
|
|
|
|
-using System.Windows;
|
|
|
|
|
|
+using System.Windows;
|
|
using System.Windows.Input;
|
|
using System.Windows.Input;
|
|
-using System.Windows.Threading;
|
|
|
|
|
|
|
|
namespace PixiEditor.Models.Controllers;
|
|
namespace PixiEditor.Models.Controllers;
|
|
|
|
|
|
#nullable enable
|
|
#nullable enable
|
|
public class MouseUpdateController : IDisposable
|
|
public class MouseUpdateController : IDisposable
|
|
{
|
|
{
|
|
- private const double MouseUpdateIntervalMs = 1000 / 142.0; //142 Hz
|
|
|
|
-
|
|
|
|
- private Thread timerThread;
|
|
|
|
- private readonly AutoResetEvent resetEvent = new(false);
|
|
|
|
- private readonly object lockObj = new();
|
|
|
|
- private bool isAborted = false;
|
|
|
|
|
|
+ private bool isDisposed = false;
|
|
|
|
|
|
private readonly FrameworkElement element;
|
|
private readonly FrameworkElement element;
|
|
private readonly MouseEventHandler mouseMoveHandler;
|
|
private readonly MouseEventHandler mouseMoveHandler;
|
|
|
|
+ private MouseUpdateControllerSession? session;
|
|
|
|
|
|
public MouseUpdateController(FrameworkElement uiElement, MouseEventHandler onMouseMove)
|
|
public MouseUpdateController(FrameworkElement uiElement, MouseEventHandler onMouseMove)
|
|
{
|
|
{
|
|
mouseMoveHandler = onMouseMove;
|
|
mouseMoveHandler = onMouseMove;
|
|
element = uiElement;
|
|
element = uiElement;
|
|
- element.MouseMove += OnMouseMove;
|
|
|
|
-
|
|
|
|
- bool wasThreadCreated = !element.IsLoaded;
|
|
|
|
- element.Loaded += (_, _) =>
|
|
|
|
- {
|
|
|
|
- wasThreadCreated = true;
|
|
|
|
- CreateTimerThread();
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- if (!wasThreadCreated)
|
|
|
|
- CreateTimerThread();
|
|
|
|
-
|
|
|
|
- element.Unloaded += (_, _) =>
|
|
|
|
- {
|
|
|
|
- isAborted = true;
|
|
|
|
- };
|
|
|
|
|
|
+
|
|
|
|
+ element.Loaded += OnElementLoaded;
|
|
|
|
+ element.Unloaded += OnElementUnloaded;
|
|
|
|
+
|
|
|
|
+ session ??= new MouseUpdateControllerSession(StartListening, StopListening, mouseMoveHandler);
|
|
|
|
+
|
|
|
|
+ element.MouseMove += CallMouseMoveInput;
|
|
}
|
|
}
|
|
-
|
|
|
|
- private void CreateTimerThread()
|
|
|
|
- {
|
|
|
|
- timerThread = new Thread(TimerThread);
|
|
|
|
- timerThread.Name = "MouseUpdateController thread";
|
|
|
|
- timerThread.Start();
|
|
|
|
- isAborted = false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private bool IsThreadShouldStop()
|
|
|
|
|
|
+
|
|
|
|
+ void OnElementLoaded(object o, RoutedEventArgs routedEventArgs)
|
|
{
|
|
{
|
|
- return isAborted || timerThread != Thread.CurrentThread || Application.Current is null;
|
|
|
|
|
|
+ session ??= new MouseUpdateControllerSession(StartListening, StopListening, mouseMoveHandler);
|
|
}
|
|
}
|
|
|
|
|
|
- private void TimerThread()
|
|
|
|
|
|
+ private void OnElementUnloaded(object o, RoutedEventArgs routedEventArgs)
|
|
{
|
|
{
|
|
- try
|
|
|
|
- {
|
|
|
|
- long lastThreadIter = Stopwatch.GetTimestamp();
|
|
|
|
-
|
|
|
|
- // abort if a new thread was created
|
|
|
|
- while (!IsThreadShouldStop())
|
|
|
|
- {
|
|
|
|
- // call waitOne periodically instead of waiting infinitely to make sure we crash or exit when resetEvent is disposed
|
|
|
|
- if (!resetEvent.WaitOne(300))
|
|
|
|
- {
|
|
|
|
- lastThreadIter = Stopwatch.GetTimestamp();
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ session.Dispose();
|
|
|
|
+ session = null;
|
|
|
|
+ }
|
|
|
|
|
|
- lock (lockObj)
|
|
|
|
- {
|
|
|
|
- double sleepDur = Math.Clamp(MouseUpdateIntervalMs - Stopwatch.GetElapsedTime(lastThreadIter).TotalMilliseconds, 0, MouseUpdateIntervalMs);
|
|
|
|
- lastThreadIter += (long)(MouseUpdateIntervalMs * Stopwatch.Frequency / 1000);
|
|
|
|
- if (sleepDur > 0)
|
|
|
|
- Thread.Sleep((int)Math.Round(sleepDur));
|
|
|
|
-
|
|
|
|
- if (IsThreadShouldStop())
|
|
|
|
- return;
|
|
|
|
- Application.Current?.Dispatcher.Invoke(() =>
|
|
|
|
- {
|
|
|
|
- element.MouseMove += OnMouseMove;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- catch (ObjectDisposedException)
|
|
|
|
- {
|
|
|
|
|
|
+ private void StartListening()
|
|
|
|
+ {
|
|
|
|
+ if (isDisposed)
|
|
return;
|
|
return;
|
|
- }
|
|
|
|
- catch (Exception e)
|
|
|
|
- {
|
|
|
|
- Application.Current?.Dispatcher.BeginInvoke(() => throw new AggregateException("Input handling thread died", e), DispatcherPriority.SystemIdle);
|
|
|
|
- throw;
|
|
|
|
- }
|
|
|
|
|
|
+ element.MouseMove -= CallMouseMoveInput;
|
|
|
|
+ element.MouseMove += CallMouseMoveInput;
|
|
}
|
|
}
|
|
|
|
|
|
- private void OnMouseMove(object sender, MouseEventArgs e)
|
|
|
|
|
|
+ private void CallMouseMoveInput(object sender, MouseEventArgs e)
|
|
|
|
+ {
|
|
|
|
+ if (isDisposed)
|
|
|
|
+ return;
|
|
|
|
+ session?.MouseMoveInput(sender, e);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void StopListening()
|
|
{
|
|
{
|
|
- bool lockWasTaken = false;
|
|
|
|
- try
|
|
|
|
- {
|
|
|
|
- Monitor.TryEnter(lockObj, ref lockWasTaken);
|
|
|
|
- if (lockWasTaken)
|
|
|
|
- {
|
|
|
|
- resetEvent.Set();
|
|
|
|
- element.MouseMove -= OnMouseMove;
|
|
|
|
- mouseMoveHandler(sender, e);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- finally
|
|
|
|
- {
|
|
|
|
- if (lockWasTaken)
|
|
|
|
- Monitor.Exit(lockObj);
|
|
|
|
- }
|
|
|
|
|
|
+ if (isDisposed)
|
|
|
|
+ return;
|
|
|
|
+ element.MouseMove -= CallMouseMoveInput;
|
|
}
|
|
}
|
|
|
|
|
|
public void Dispose()
|
|
public void Dispose()
|
|
{
|
|
{
|
|
- element.MouseMove -= OnMouseMove;
|
|
|
|
- isAborted = true;
|
|
|
|
- resetEvent.Dispose();
|
|
|
|
|
|
+ element.MouseMove -= CallMouseMoveInput;
|
|
|
|
+ element.Loaded -= OnElementLoaded;
|
|
|
|
+ element.Unloaded -= OnElementUnloaded;
|
|
|
|
+ session?.Dispose();
|
|
|
|
+ isDisposed = true;
|
|
}
|
|
}
|
|
}
|
|
}
|