|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections;
using System.Windows.Input.StylusPointer;
using System.Windows.Interop;
using System.Windows.Threading;
using MS.Internal;
using MS.Win32;
using System.Runtime.InteropServices;
#pragma warning disable 1634, 1691 // suppressing PreSharp warnings
// There's a choice of where to send MouseWheel events - to the element under
// the mouse (like IE does) or to the element with keyboard focus (like Win32
// does). The latter choice lets you move the mouse away from the area you're
// scrolling and still use the wheel. To get this effect, uncomment this line.
//#define SEND_WHEEL_EVENTS_TO_FOCUS
namespace System.Windows.Input
{
/// <summary>
/// The MouseDevice class represents the mouse device to the
/// members of a context.
/// </summary>
public abstract class MouseDevice : InputDevice
{
internal MouseDevice(InputManager inputManager)
{
_inputManager = inputManager;
_inputManager.PreProcessInput += new PreProcessInputEventHandler(PreProcessInput);
_inputManager.PreNotifyInput += new NotifyInputEventHandler(PreNotifyInput);
_inputManager.PostProcessInput += new ProcessInputEventHandler(PostProcessInput);
// Get information about how far two clicks of a double click can be considered
// to be in the "same place and time".
//
// The call here goes into the safe helper calls, more of a consistency in approach
//
_doubleClickDeltaX = SafeSystemMetrics.DoubleClickDeltaX;
_doubleClickDeltaY = SafeSystemMetrics.DoubleClickDeltaY;
_doubleClickDeltaTime = SafeNativeMethods.GetDoubleClickTime();
_overIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsEnabledChanged);
_overIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsVisibleChanged);
_overIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsHitTestVisibleChanged);
_reevaluateMouseOverDelegate = new DispatcherOperationCallback(ReevaluateMouseOverAsync);
_reevaluateMouseOverOperation = null;
_captureIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsEnabledChanged);
_captureIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsVisibleChanged);
_captureIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsHitTestVisibleChanged);
_reevaluateCaptureDelegate = new DispatcherOperationCallback(ReevaluateCaptureAsync);
_reevaluateCaptureOperation = null;
_inputManager.HitTestInvalidatedAsync += new EventHandler(OnHitTestInvalidatedAsync);
}
/// <summary>
/// Gets the current state of the specified button from the device from either the underlying system or the StylusDevice
/// </summary>
/// <param name="mouseButton">
/// The mouse button to get the state of
/// </param>
/// <returns>
/// The state of the specified mouse button
/// </returns>
protected MouseButtonState GetButtonState(MouseButton mouseButton)
{
// StylusDevice could have been disposed internally here.
if ( _stylusDevice != null && _stylusDevice.IsValid)
return _stylusDevice.GetMouseButtonState(mouseButton, this);
else
return GetButtonStateFromSystem(mouseButton);
}
/// <summary>
/// Gets the current position of the mouse in screen co-ords from either the underlying system or the StylusDevice
/// </summary>
/// <returns>
/// The current mouse location in screen co-ords
/// </returns>
protected Point GetScreenPosition()
{
if (_stylusDevice != null)
return _stylusDevice.GetMouseScreenPosition(this);
else
return GetScreenPositionFromSystem();
}
/// <summary>
/// Gets the current state of the specified button from the device from the underlying system
/// </summary>
/// <param name="mouseButton">
/// The mouse button to get the state of
/// </param>
/// <returns>
/// The state of the specified mouse button
/// </returns>
internal abstract MouseButtonState GetButtonStateFromSystem(MouseButton mouseButton);
/// <summary>
/// Gets the current position of the mouse in screen co-ords from the underlying system
/// </summary>
/// <returns>
/// The current mouse location in screen co-ords
/// </returns>
internal Point GetScreenPositionFromSystem()
{
// Win32 has issues reliably returning where the mouse is. Until we figure
// out a better way, just return the last mouse position in screen coordinates.
Point ptScreen = new Point(0, 0);
// Security Mitigation: do not give out input state if the device is not active.
if (IsActive)
{
try
{
PresentationSource activeSource = CriticalActiveSource;
if (activeSource != null)
{
ptScreen = PointUtil.ClientToScreen(_lastPosition, activeSource);
}
}
catch (System.ComponentModel.Win32Exception)
{
// The window could be shutting down, so just return (0,0).
ptScreen = new Point(0, 0);
}
}
return ptScreen;
}
/// <summary>
/// Gets the current position of the mouse in client co-ords of the current PresentationSource
/// </summary>
/// <returns>
/// The current mouse position in client co-ords
/// </returns>
protected Point GetClientPosition()
{
Point ptClient = new Point(0, 0);
try
{
PresentationSource activeSource = CriticalActiveSource;
if (activeSource != null)
{
ptClient = GetClientPosition(activeSource);
}
}
catch (System.ComponentModel.Win32Exception)
{
// The window could be shutting down, so just return (0,0).
ptClient = new Point(0, 0);
}
return ptClient;
}
/// <summary>
/// Gets the current position of the mouse in client co-ords of the specified PresentationSource
/// </summary>
/// <returns>
/// The current mouse position in client co-ords
/// </returns>
protected Point GetClientPosition(PresentationSource presentationSource)
{
Point ptScreen = GetScreenPosition();
Point ptClient = PointUtil.ScreenToClient(ptScreen, presentationSource);
return ptClient;
}
/// <summary>
/// Returns the element that input from this device is sent to.
/// </summary>
public override IInputElement Target
{
get
{
// VerifyAccess();
// Return the element that the mouse is over. If the mouse
// has been captured, the mouse will be considered "over"
// the capture point if the mouse is outside of the
// captured element (or subtree).
return _mouseOver;
}
}
/// <summary>
/// Returns the PresentationSource that is reporting input for this device.
/// </summary>
/// <remarks>
/// Callers must have UIPermission(UIPermissionWindow.AllWindows) to call this API.
/// </remarks>
public override PresentationSource ActiveSource => _inputSource;
/// <summary>
/// Returns the PresentationSource that is reporting input for this device.
/// </summary>
internal PresentationSource CriticalActiveSource => _inputSource;
/// <summary>
/// Returns the element that the mouse is over.
/// </summary>
/// <remarks>
/// The mouse is considered directly over an element if the mouse
/// has been captured to that element.
/// </remarks>
public IInputElement DirectlyOver
{
get
{
return _mouseOver;
}
}
/// <summary>
/// Returns the element that the mouse is over regardless of
/// its IsEnabled state.
/// </summary>
internal IInputElement RawDirectlyOver
{
get
{
if (_rawMouseOver != null)
{
IInputElement rawMouseOver = (IInputElement)_rawMouseOver.Target;
if (rawMouseOver != null)
{
return rawMouseOver;
}
}
return DirectlyOver;
}
}
/// <summary>
/// Returns the element that has captured the mouse.
/// </summary>
public IInputElement Captured
{
get
{
// VerifyAccess();
return (!_isCaptureMouseInProgress) ? _mouseCapture : null;
}
}
/// <summary>
/// Returns the element that has captured the mouse.
/// </summary>
internal CaptureMode CapturedMode
{
get
{
return _captureMode;
}
}
/// <summary>
/// Captures the mouse to a particular element.
/// </summary>
public bool Capture(IInputElement element)
{
return Capture(element, CaptureMode.Element);
}
/// <summary>
/// Captures the mouse to a particular element.
/// </summary>
public bool Capture(IInputElement element, CaptureMode captureMode)
{
int timeStamp = Environment.TickCount;
// VerifyAccess();
if (!(captureMode == CaptureMode.None || captureMode == CaptureMode.Element || captureMode == CaptureMode.SubTree))
{
throw new System.ComponentModel.InvalidEnumArgumentException("captureMode", (int)captureMode, typeof(CaptureMode));
}
if (element == null)
{
captureMode = CaptureMode.None;
}
if (captureMode == CaptureMode.None)
{
element = null;
}
// Validate that elt is either a UIElement, a ContentElement or a UIElement3D.
DependencyObject eltDO = element as DependencyObject;
if (eltDO != null && !InputElement.IsValid(element))
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, eltDO.GetType()));
}
bool success = false;
// The element we are capturing to must be both enabled and visible.
if (element is UIElement)
{
UIElement e = element as UIElement;
#pragma warning suppress 6506 // e is obviously not null
if(e.IsVisible && e.IsEnabled)
{
success = true;
}
}
else if (element is ContentElement)
{
ContentElement ce = element as ContentElement;
#pragma warning suppress 6506 // ce is obviosuly not null
if(ce.IsEnabled) // There is no IsVisible property for ContentElement
{
success = true;
}
}
else if (element is UIElement3D)
{
UIElement3D e = element as UIElement3D;
#pragma warning suppress 6506 // e is obviously not null
if(e.IsVisible && e.IsEnabled)
{
success = true;
}
}
else
{
// Setting capture to null.
success = true;
}
if(success)
{
success = false;
// Find a mouse input provider that provides input for either
// the new element (if taking capture) or the existing capture
// element (if releasing capture).
IMouseInputProvider mouseInputProvider = null;
if (element != null)
{
DependencyObject containingVisual = InputElement.GetContainingVisual(eltDO);
if (containingVisual != null)
{
PresentationSource captureSource = PresentationSource.CriticalFromVisual(containingVisual);
if (captureSource != null)
{
mouseInputProvider = captureSource.GetInputProvider(typeof(MouseDevice)) as IMouseInputProvider;
}
}
}
else if (_mouseCapture != null)
{
mouseInputProvider = _providerCapture;
}
// If we found a mouse input provider, ask it to either capture
// or release the mouse for us.
if(mouseInputProvider != null)
{
if (element != null)
{
// CaptureMouse can raise a MouseMove event in some cases
// and listeners that query Mouse.Captured should not see the old
// value that's being replaced. We'll expose 'null' instead.
// [This situation arises in a ComboBox that has a TextBox in
// its dropdown window]
bool savedIsCaptureMouseInProgress = _isCaptureMouseInProgress;
_isCaptureMouseInProgress = true;
success = mouseInputProvider.CaptureMouse();
_isCaptureMouseInProgress = savedIsCaptureMouseInProgress;
if (success)
{
ChangeMouseCapture(element, mouseInputProvider, captureMode, timeStamp);
}
}
else
{
mouseInputProvider.ReleaseMouseCapture();
// If we had capture, the input provider will release it. That will
// cause a RawMouseAction.CancelCapture to be processed, which will
// update our internal states.
success = true;
}
}
}
return success;
}
//
// Find an IMouseInputProvider on which the cursor can be set
private IMouseInputProvider FindMouseInputProviderForCursor( )
{
// The shape of this API goes on the assumption that, like Win32, the cursor
// is set for the whole desktop, not just a particular element or a particular
// root visual. So instead of trying to find the IMouseInputProvider
// that covers a particular element, we just find any IMouseInputProvider
// and set the cursor on it.
IMouseInputProvider mouseInputProvider = null;
IEnumerator inputProviders = _inputManager.UnsecureInputProviders.GetEnumerator();
while (inputProviders.MoveNext())
{
IMouseInputProvider provider = inputProviders.Current as IMouseInputProvider;
if (provider != null )
{
mouseInputProvider = provider;
break;
}
}
return mouseInputProvider;
}
/// <summary>
/// The override cursor
/// </summary>
public Cursor OverrideCursor
{
get
{
// VerifyAccess();
return _overrideCursor;
}
set
{
// VerifyAccess();
_overrideCursor = value;
UpdateCursorPrivate();
}
}
/// <summary>
/// Set the cursor
/// </summary>
/// <param ref="cursor">The new cursor</param>
/// <remarks>Note that this cursor doesn't apply any particular UIElement, it applies
/// to the whole desktop.
/// </remarks>
public bool SetCursor(Cursor cursor)
{
// VerifyAccess();
// Override the cursor if one is set.
if (_overrideCursor != null)
{
cursor = _overrideCursor;
}
if (cursor == null)
{
cursor = Cursors.None;
}
// Get a mouse provider
IMouseInputProvider mouseInputProvider = FindMouseInputProviderForCursor();
// If we found one, set the cursor
if (mouseInputProvider != null)
return mouseInputProvider.SetCursor(cursor);
else
return false;
}
/// <summary>
/// The state of the left button.
/// </summary>
public MouseButtonState LeftButton
{
get
{
return GetButtonState(MouseButton.Left);
}
}
/// <summary>
/// The state of the right button.
/// </summary>
public MouseButtonState RightButton
{
get
{
return GetButtonState(MouseButton.Right);
}
}
/// <summary>
/// The state of the middle button.
/// </summary>
public MouseButtonState MiddleButton
{
get
{
return GetButtonState(MouseButton.Middle);
}
}
/// <summary>
/// The state of the first extended button.
/// </summary>
public MouseButtonState XButton1
{
get
{
return GetButtonState(MouseButton.XButton1);
}
}
/// <summary>
/// The state of the second extended button.
/// </summary>
public MouseButtonState XButton2
{
get
{
return GetButtonState(MouseButton.XButton2);
}
}
/// <summary>
/// Calculates the position of the mouse relative to
/// a particular element.
/// </summary>
public Point GetPosition(IInputElement relativeTo)
{
// VerifyAccess();
// Validate that relativeTo is either a UIElement, a ContentElement or a UIElement3D.
if (relativeTo != null && !InputElement.IsValid(relativeTo))
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, relativeTo.GetType()));
}
PresentationSource relativePresentationSource = null;
if (relativeTo != null)
{
DependencyObject dependencyObject = relativeTo as DependencyObject;
DependencyObject containingVisual = InputElement.GetContainingVisual(dependencyObject);
if (containingVisual != null)
{
relativePresentationSource = PresentationSource.CriticalFromVisual(containingVisual);
}
}
else
{
if (_inputSource is not null)
{
relativePresentationSource = _inputSource;
}
}
// Verify that we have a valid PresentationSource with a valid RootVisual
// - if we don't we won't be able to invoke ClientToRoot or TranslatePoint and
// we will just return 0,0
if (relativePresentationSource == null || relativePresentationSource.RootVisual == null)
{
return new Point(0, 0);
}
Point ptClient;
Point ptRoot;
bool success;
Point ptRelative;
ptClient = GetClientPosition(relativePresentationSource);
ptRoot = PointUtil.TryClientToRoot(ptClient, relativePresentationSource, false, out success);
if (!success)
{
// ClientToRoot failed, usually because the client area is degenerate.
// Just return 0,0
return new Point(0, 0);
}
ptRelative = InputElement.TranslatePoint(ptRoot, relativePresentationSource.RootVisual, (DependencyObject)relativeTo);
return ptRelative;
}
/// <summary>
/// </summary>
internal void ReevaluateMouseOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
{
if (element != null)
{
if (isCoreParent)
{
MouseOverTreeState.SetCoreParent(element, oldParent);
}
else
{
MouseOverTreeState.SetLogicalParent(element, oldParent);
}
}
// It would be best to re-evaluate anything dependent on the hit-test results
// immediately after layout & rendering are complete. Unfortunately this can
// lead to an infinite loop. Consider the following scenario:
//
// If the mouse is over an element, hide it.
//
// This never resolves to a "correct" state. When the mouse moves over the
// element, the element is hidden, so the mouse is no longer over it, so the
// element is shown, but that means the mouse is over it again. Repeat.
//
// We push our re-evaluation to a priority lower than input processing so that
// the user can change the input device to avoid the infinite loops, or close
// the app if nothing else works.
//
if (_reevaluateMouseOverOperation == null)
{
_reevaluateMouseOverOperation = Dispatcher.BeginInvoke(DispatcherPriority.Input, _reevaluateMouseOverDelegate, null);
}
}
/// <summary>
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object ReevaluateMouseOverAsync(object arg)
{
_reevaluateMouseOverOperation = null;
Synchronize();
// Refresh MouseOverProperty so that ReverseInherited Flags are updated.
//
// We only need to do this is there is any information about the old
// tree state. This is because it is possible (even likely) that
// Synchronize() would have already done this if we hit-tested to a
// different element.
if (_mouseOverTreeState != null && !_mouseOverTreeState.IsEmpty)
{
UIElement.MouseOverProperty.OnOriginValueChanged(_mouseOver as DependencyObject, _mouseOver as DependencyObject, ref _mouseOverTreeState);
}
return null;
}
/// <summary>
/// </summary>
internal void ReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
{
if (element != null)
{
if (isCoreParent)
{
MouseCaptureWithinTreeState.SetCoreParent(element, oldParent);
}
else
{
MouseCaptureWithinTreeState.SetLogicalParent(element, oldParent);
}
}
// We re-evaluate the captured element to be consistent with how
// we re-evaluate the element the mouse is over.
//
// See ReevaluateMouseOver for details.
//
if (_reevaluateCaptureOperation == null)
{
_reevaluateCaptureOperation = Dispatcher.BeginInvoke(DispatcherPriority.Input, _reevaluateCaptureDelegate, null);
}
}
/// <summary>
///
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object ReevaluateCaptureAsync(object arg)
{
_reevaluateCaptureOperation = null;
if (_mouseCapture == null )
return null;
bool killCapture = false;
DependencyObject dependencyObject = _mouseCapture as DependencyObject;
//
// First, check things like IsEnabled, IsVisible, etc. on a
// UIElement vs. ContentElement basis.
//
if (dependencyObject is UIElement uie)
{
killCapture = !ValidateUIElementForCapture(uie);
}
else if (dependencyObject is ContentElement ce)
{
killCapture = !ValidateContentElementForCapture(ce);
}
else if (dependencyObject is UIElement3D uie3D)
{
killCapture = !ValidateUIElement3DForCapture(uie3D);
}
//
// Second, if we still haven't thought of a reason to kill capture, validate
// it on a Visual basis for things like still being in the right tree.
//
if (killCapture == false)
{
DependencyObject containingVisual = InputElement.GetContainingVisual(dependencyObject);
killCapture = !ValidateVisualForCapture(containingVisual);
}
//
// Lastly, if we found any reason above, kill capture.
//
if (killCapture)
{
Capture(null);
}
// Refresh MouseCaptureWithinProperty so that ReverseInherited flags are updated.
//
// We only need to do this is there is any information about the old
// tree state. This is because it is possible (even likely) that
// we would have already killed capture if the capture criteria was
// no longer met.
if (_mouseCaptureWithinTreeState != null && !_mouseCaptureWithinTreeState.IsEmpty)
{
UIElement.MouseCaptureWithinProperty.OnOriginValueChanged(_mouseCapture as DependencyObject, _mouseCapture as DependencyObject, ref _mouseCaptureWithinTreeState);
}
return null;
}
private bool ValidateUIElementForCapture(UIElement element)
{
if (element.IsEnabled == false)
return false;
if (element.IsVisible == false)
return false;
if (element.IsHitTestVisible == false)
return false;
return true;
}
private bool ValidateUIElement3DForCapture(UIElement3D element)
{
if (element.IsEnabled == false)
return false;
if (element.IsVisible == false)
return false;
if (element.IsHitTestVisible == false)
return false;
return true;
}
private bool ValidateContentElementForCapture(ContentElement element)
{
if (element.IsEnabled == false)
return false;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
return true;
}
private bool ValidateVisualForCapture(DependencyObject visual)
{
if (visual == null)
return false;
PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
if (presentationSource == null)
return false;
if (presentationSource != CriticalActiveSource)
return false;
return true;
}
private void OnOverIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is over just became disabled.
//
// We need to resynchronize the mouse so that we can figure out who
// the mouse is over now.
ReevaluateMouseOver(null, null, true);
}
private void OnOverIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is over just became non-visible (collapsed or hidden).
//
// We need to resynchronize the mouse so that we can figure out who
// the mouse is over now.
ReevaluateMouseOver(null, null, true);
}
private void OnOverIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is over was affected by a change in hit-test visibility.
//
// We need to resynchronize the mouse so that we can figure out who
// the mouse is over now.
ReevaluateMouseOver(null, null, true);
}
private void OnCaptureIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is captured to just became disabled.
//
// We need to re-evaluate the element that has mouse capture since
// we can't allow the mouse to remain captured by a disabled element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is captured to just became non-visible (collapsed or hidden).
//
// We need to re-evaluate the element that has mouse capture since
// we can't allow the mouse to remain captured by a non-visible element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the mouse is captured to was affected by a change in hit-test visibility.
//
// We need to re-evaluate the element that has mouse capture since
// we can't allow the mouse to remain captured by a non-hittest-visible element.
ReevaluateCapture(null, null, true);
}
private void OnHitTestInvalidatedAsync(object sender, EventArgs e)
{
// The hit-test result may have changed.
Synchronize();
}
/// <summary>
/// Forces the mouse to resynchronize.
/// </summary>
public void Synchronize()
{
// System.Console.WriteLine("Synchronize");
// VerifyAccess();
// Simulate a mouse move
PresentationSource activeSource = CriticalActiveSource;
if (activeSource != null && activeSource.CompositionTarget != null && !activeSource.CompositionTarget.IsDisposed)
{
int timeStamp = Environment.TickCount;
Point ptClient = GetClientPosition();
RawMouseInputReport report = new RawMouseInputReport(InputMode.Foreground,
timeStamp,
activeSource,
RawMouseActions.AbsoluteMove,
(int) ptClient.X,
(int) ptClient.Y,
0,
IntPtr.Zero);
report._isSynchronize = true;
InputReportEventArgs inputReportEventArgs;
if (_stylusDevice != null)
{
// if we have a current stylusdevice .. use it
inputReportEventArgs = new InputReportEventArgs(_stylusDevice, report);
}
else
{
inputReportEventArgs = new InputReportEventArgs(this, report);
}
inputReportEventArgs.RoutedEvent=InputManager.PreviewInputReportEvent;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(inputReportEventArgs);
}
}
/// <summary>
/// Forces the mouse cursor to be updated.
/// </summary>
public void UpdateCursor()
{
// Call Forwarded
UpdateCursorPrivate();
}
/// <summary>
/// Forces the mouse cursor to be updated.
/// </summary>
/// <remarks>
/// This method has been added just because changing the public
/// API UpdateCursor will be a breaking change
/// </remarks>
private bool UpdateCursorPrivate()
{
int timeStamp = Environment.TickCount;
QueryCursorEventArgs queryCursor = new QueryCursorEventArgs(this, timeStamp);
queryCursor.Cursor = Cursors.Arrow;
queryCursor.RoutedEvent=Mouse.QueryCursorEvent;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(queryCursor);
return queryCursor.Handled;
}
private void ChangeMouseOver(IInputElement mouseOver, int timestamp)
{
DependencyObject o = null;
if (_mouseOver != mouseOver)
{
// Console.WriteLine("ChangeMouseOver(" + mouseOver + ")");
// Update the critical piece of data.
IInputElement oldMouseOver = _mouseOver;
_mouseOver = mouseOver;
using(Dispatcher.DisableProcessing()) // Disable reentrancy due to locks taken
{
// Adjust the handlers we use to track everything.
if(oldMouseOver != null)
{
o = oldMouseOver as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
uie.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged -= _overIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged -= _overIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged -= _overIsHitTestVisibleChangedEventHandler;
}
}
if(_mouseOver != null)
{
o = _mouseOver as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged += _overIsEnabledChangedEventHandler;
uie.IsVisibleChanged += _overIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged += _overIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged += _overIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged += _overIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged += _overIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged += _overIsHitTestVisibleChangedEventHandler;
}
}
}
// Oddly enough, update the IsMouseOver property first. This is
// so any callbacks will see the more-common IsMouseOver property
// set correctly.
UIElement.MouseOverProperty.OnOriginValueChanged(oldMouseOver as DependencyObject, _mouseOver as DependencyObject, ref _mouseOverTreeState);
// Invalidate the IsMouseDirectlyOver property.
if (oldMouseOver != null)
{
o = oldMouseOver as DependencyObject;
o.SetValue(UIElement.IsMouseDirectlyOverPropertyKey, false); // Same property for ContentElements
}
if (_mouseOver != null)
{
o = _mouseOver as DependencyObject;
o.SetValue(UIElement.IsMouseDirectlyOverPropertyKey, true); // Same property for ContentElements
}
}
}
private void ChangeMouseCapture(IInputElement mouseCapture, IMouseInputProvider providerCapture, CaptureMode captureMode, int timestamp)
{
DependencyObject o = null;
if(mouseCapture != _mouseCapture)
{
// Console.WriteLine("ChangeMouseCapture(" + mouseCapture + ")");
// Update the critical pieces of data.
IInputElement oldMouseCapture = _mouseCapture;
_mouseCapture = mouseCapture;
if (_mouseCapture != null)
{
_providerCapture = providerCapture;
}
else
{
_providerCapture = null;
}
_captureMode = captureMode;
using (Dispatcher.DisableProcessing()) // Disable reentrancy due to locks taken
{
// Adjust the handlers we use to track everything.
if (oldMouseCapture != null)
{
o = oldMouseCapture as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
uie.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
}
if (_mouseCapture != null)
{
o = _mouseCapture as DependencyObject;
if (o is UIElement uie)
{
uie.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
uie.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
uie.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is ContentElement ce)
{
ce.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ce.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
// ce.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else if (o is UIElement3D uie3D)
{
uie3D.IsEnabledChanged += _captureIsEnabledChangedEventHandler;
uie3D.IsVisibleChanged += _captureIsVisibleChangedEventHandler;
uie3D.IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
}
}
// Oddly enough, update the IsMouseCaptureWithin property first. This is
// so any callbacks will see the more-common IsMouseCaptureWithin property
// set correctly.
UIElement.MouseCaptureWithinProperty.OnOriginValueChanged(oldMouseCapture as DependencyObject, _mouseCapture as DependencyObject, ref _mouseCaptureWithinTreeState);
// Invalidate the IsMouseCaptured properties.
if (oldMouseCapture != null)
{
o = oldMouseCapture as DependencyObject;
o.SetValue(UIElement.IsMouseCapturedPropertyKey, false); // Same property for ContentElements
}
if (_mouseCapture != null)
{
o = _mouseCapture as DependencyObject;
o.SetValue(UIElement.IsMouseCapturedPropertyKey, true); // Same property for ContentElements
}
// Send the LostMouseCapture and GotMouseCapture events.
if (oldMouseCapture != null)
{
MouseEventArgs lostCapture = new MouseEventArgs(this, timestamp, _stylusDevice);
lostCapture.RoutedEvent=Mouse.LostMouseCaptureEvent;
lostCapture.Source= oldMouseCapture;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(lostCapture);
}
if (_mouseCapture != null)
{
MouseEventArgs gotCapture = new MouseEventArgs(this, timestamp, _stylusDevice);
gotCapture.RoutedEvent=Mouse.GotMouseCaptureEvent;
gotCapture.Source= _mouseCapture;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(gotCapture);
}
// Force a mouse move so we can update the mouse over.
Synchronize();
}
}
private void PreProcessInput(object sender, PreProcessInputEventArgs e)
{
if (e.StagingItem.Input.RoutedEvent == InputManager.PreviewInputReportEvent)
{
InputReportEventArgs inputReportEventArgs = e.StagingItem.Input as InputReportEventArgs;
if (!inputReportEventArgs.Handled && inputReportEventArgs.Report.Type == InputType.Mouse)
{
RawMouseInputReport rawMouseInputReport = (RawMouseInputReport)inputReportEventArgs.Report;
// Normally we only process mouse input that is from our
// active visual manager. The only exception to this is
// the activate report, which is how we change the visual
// manager that is active.
if ((rawMouseInputReport.Actions & RawMouseActions.Activate) == RawMouseActions.Activate)
{
// Console.WriteLine("RawMouseActions.Activate");
// If other actions are being reported besides the
// activate, separate them into different events.
if ((rawMouseInputReport.Actions & ~RawMouseActions.Activate) != 0)
{
// Cancel this event. We'll push a new event for the activate.
e.Cancel();
// Push a new RawMouseInputReport for the non-activate actions.
RawMouseInputReport reportActions = new RawMouseInputReport(rawMouseInputReport.Mode,
rawMouseInputReport.Timestamp,
rawMouseInputReport.InputSource,
rawMouseInputReport.Actions & ~RawMouseActions.Activate,
rawMouseInputReport.X,
rawMouseInputReport.Y,
rawMouseInputReport.Wheel,
rawMouseInputReport.ExtraInformation);
InputReportEventArgs actionsArgs = new InputReportEventArgs(inputReportEventArgs.Device, reportActions);
actionsArgs.RoutedEvent=InputManager.PreviewInputReportEvent;
e.PushInput(actionsArgs, null);
PushActivateInputReport(e, inputReportEventArgs, rawMouseInputReport, clearExtraInformation:false);
}
}
// Only process mouse input that is from our active PresentationSource.
else if ((_inputSource is not null) && (rawMouseInputReport.InputSource == _inputSource))
{
// We need to remember the StylusDevice that generated this input. Use the _tagStylusDevice
// to store this in before we take over the inputReport Device and loose it. Any
// input reports we re-push need to preserve this too. This is used to set the StylusDevice
// property on MouseEventArgs.
InputDevice inputDevice = e.StagingItem.GetData(_tagStylusDevice) as StylusDevice;
if (inputDevice == null)
{
if (StylusLogic.IsPointerStackEnabled
&& StylusLogic.IsPromotedMouseEvent(rawMouseInputReport))
{
// Due to the WPF pointer stack not promoting mouse internally, we must first
// detect a promoted mouse messages and then fill the stylus device
// from the promoted device used. This comes from the extra information
// on the mouse message.
uint cursorId = StylusLogic.GetCursorIdFromMouseEvent(rawMouseInputReport);
var tablets = Tablet.TabletDevices.As<PointerTabletDeviceCollection>();
inputDevice = tablets.GetStylusDeviceByCursorId(cursorId)?.StylusDevice;
}
else
{
inputDevice = inputReportEventArgs.Device as StylusDevice;
}
if (inputDevice != null)
{
e.StagingItem.SetData(_tagStylusDevice, inputDevice);
}
}
// Claim the input for the mouse.
inputReportEventArgs.Device = this;
// If the input is reporting mouse deactivation, we need
// to ensure that the element receives a final leave.
// Note that activation could have been moved to another
// visual manager in our app, which means that the leave
// was already sent. So only do this if the deactivate
// event is from the visual manager that we think is active.
if ((rawMouseInputReport.Actions & RawMouseActions.Deactivate) == RawMouseActions.Deactivate)
{
if (_mouseOver != null)
{
// Push back this event, and cancel the current processing.
e.PushInput(e.StagingItem);
e.Cancel();
_isPhysicallyOver = false;
ChangeMouseOver(null, e.StagingItem.Input.Timestamp);
}
}
// If the input is reporting mouse movement, we need to check
// if we need to update our sense of "mouse over".
// RelativeMove, VirtualDesktopMove have not been handled yet
if ((rawMouseInputReport.Actions & RawMouseActions.AbsoluteMove) == RawMouseActions.AbsoluteMove)
{
// If other actions are being reported besides the
// move, separate them into different events.
if ((rawMouseInputReport.Actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor)) != 0)
{
// Cancel this event. We'll push a new event for the move.
e.Cancel();
// Push a new RawMouseInputReport for the non-move actions.
RawMouseInputReport reportActions = new RawMouseInputReport(rawMouseInputReport.Mode,
rawMouseInputReport.Timestamp,
rawMouseInputReport.InputSource,
rawMouseInputReport.Actions & ~(RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor),
0,
0,
rawMouseInputReport.Wheel,
rawMouseInputReport.ExtraInformation);
InputReportEventArgs actionsArgs = new InputReportEventArgs(inputDevice, reportActions);
actionsArgs.RoutedEvent=InputManager.PreviewInputReportEvent;
e.PushInput(actionsArgs, null);
// Push a new RawMouseInputReport for the AbsoluteMove.
RawMouseInputReport reportMove = new RawMouseInputReport(rawMouseInputReport.Mode,
rawMouseInputReport.Timestamp,
rawMouseInputReport.InputSource,
rawMouseInputReport.Actions & (RawMouseActions.AbsoluteMove | RawMouseActions.QueryCursor),
rawMouseInputReport.X,
rawMouseInputReport.Y,
0,
IntPtr.Zero);
InputReportEventArgs moveArgs = new InputReportEventArgs(inputDevice, reportMove);
moveArgs.RoutedEvent=InputManager.PreviewInputReportEvent;
e.PushInput(moveArgs, null);
}
else
{
// Convert the point from client coordinates into "root" coordinates.
// We do this in the pre-process stage because it is possible that
// this conversion will fail, in which case we want to cancel the
// mouse move event.
bool success = true;
Point ptClient = new Point(rawMouseInputReport.X, rawMouseInputReport.Y);
Point ptRoot = PointUtil.TryClientToRoot(ptClient, rawMouseInputReport.InputSource, false, out success);
if(success)
{
e.StagingItem.SetData(_tagRootPoint, ptRoot);
}
else
{
e.Cancel();
}
}
}
}
}
}
else
{
// All mouse event processing should only happen if we still have an active input source.
if (_inputSource != null)
{
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent)
{
MouseButtonEventArgs mouseButtonEventArgs = e.StagingItem.Input as MouseButtonEventArgs;
if (_mouseCapture != null && !_isPhysicallyOver)
{
// The mouse is not physically over the capture point (or
// subtree), so raise the PreviewMouseDownOutsideCapturedElement
// event first.
MouseButtonEventArgs clickThrough = new MouseButtonEventArgs(this, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton, GetStylusDevice(e.StagingItem));
clickThrough.RoutedEvent=Mouse.PreviewMouseDownOutsideCapturedElementEvent;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(clickThrough);
}
}
else if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseUpEvent)
{
MouseButtonEventArgs mouseButtonEventArgs = e.StagingItem.Input as MouseButtonEventArgs;
if (_mouseCapture != null && !_isPhysicallyOver)
{
// The mouse is not physically over the capture point (or
// subtree), so raise the PreviewMouseUpOutsideCapturedElement
// event first.
MouseButtonEventArgs clickThrough = new MouseButtonEventArgs(this, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton, GetStylusDevice(e.StagingItem));
clickThrough.RoutedEvent=Mouse.PreviewMouseUpOutsideCapturedElementEvent;
//ProcessInput has a linkdemand
_inputManager.ProcessInput(clickThrough);
}
}
}
}
}
/// <summary>
/// Push an Activate input report, on behalf of the given RawMouseInputReport.
/// Common logic used by MouseDevice.PreProcessInput and PointerDevice.PreProcessMouseInput
/// </summary>
internal static void PushActivateInputReport(PreProcessInputEventArgs e, InputReportEventArgs inputReportEventArgs, RawMouseInputReport rawMouseInputReport, bool clearExtraInformation)
{
IntPtr extraInformation = clearExtraInformation ? IntPtr.Zero : rawMouseInputReport.ExtraInformation;
// Create a new RawMouseInputReport for the activate.
RawMouseInputReport reportActivate = new RawMouseInputReport(rawMouseInputReport.Mode,
rawMouseInputReport.Timestamp,
rawMouseInputReport.InputSource,
RawMouseActions.Activate,
rawMouseInputReport.X,
rawMouseInputReport.Y,
rawMouseInputReport.Wheel,
extraInformation);
// Push a new RawMouseInputReport for the activate.
InputReportEventArgs activateArgs = new InputReportEventArgs(inputReportEventArgs.Device, reportActivate);
activateArgs.RoutedEvent=InputManager.PreviewInputReportEvent;
e.PushInput(activateArgs, null);
}
private void PreNotifyInput(object sender, NotifyInputEventArgs e)
{
if ( e.StagingItem.Input.RoutedEvent == InputManager.PreviewInputReportEvent )
{
InputReportEventArgs inputReportEventArgs = e.StagingItem.Input as InputReportEventArgs;
if (!inputReportEventArgs.Handled && inputReportEventArgs.Report.Type == InputType.Mouse)
{
RawMouseInputReport rawMouseInputReport = (RawMouseInputReport) inputReportEventArgs.Report;
// Generally, we need to check against redundant actions.
// We never prevent the raw event from going through, but we
// will only generate the high-level events for non-redundant
// actions. We store the set of non-redundant actions in
// the dictionary of this event.
// Get the current Non-Redundant Actions for this event and
// make a copy. We will compare the original value against the copy
// at the end of this function and write it back in if changed.
RawMouseActions actions = GetNonRedundantActions(e);
RawMouseActions originalActions = actions;
_stylusDevice = GetStylusDevice(e.StagingItem);
// Normally we only process mouse input that is from our
// active presentation source. The only exception to this is
// the activate report, which is how we change the visual
// manager that is active.
if ((rawMouseInputReport.Actions & RawMouseActions.Activate) == RawMouseActions.Activate)
{
// System.Console.WriteLine("Initializing the mouse state.");
actions |= RawMouseActions.Activate;
_positionRelativeToOver.X = 0;
_positionRelativeToOver.Y = 0;
_lastPosition.X = rawMouseInputReport.X;
_lastPosition.Y = rawMouseInputReport.Y;
_forceUpdateLastPosition = true;
_stylusDevice = inputReportEventArgs.Device as StylusDevice;
// if the existing source is null, no need to do any special-case handling
if (_inputSource == null)
{
_inputSource = rawMouseInputReport.InputSource;
}
// if the new source is the same as the old source, don't bother doing anything
else if (_inputSource != rawMouseInputReport.InputSource)
{
IMouseInputProvider toDeactivate = _inputSource.GetInputProvider(typeof(MouseDevice)) as IMouseInputProvider;
// All mouse information is now restricted to this presentation source.
_inputSource = rawMouseInputReport.InputSource;
if (toDeactivate != null)
{
toDeactivate.NotifyDeactivate();
}
}
}
// Only process mouse input that is from our active presentation source.
if ((_inputSource is not null) && (rawMouseInputReport.InputSource == _inputSource))
{
// If the input is reporting mouse deactivation, we need
// to break any capture we may have. Note that we only do
// this if the presentation source associated with this event
// is the same presentation source we are already over.
if ((rawMouseInputReport.Actions & RawMouseActions.Deactivate) == RawMouseActions.Deactivate)
{
// Console.WriteLine("RawMouseActions.Deactivate");
Debug.Assert(_mouseOver == null, "_mouseOver should be null because we have called ChangeMouseOver(null) already.");
_inputSource = null;
ChangeMouseCapture(null, null, CaptureMode.None, e.StagingItem.Input.Timestamp);
}
if ((rawMouseInputReport.Actions & RawMouseActions.CancelCapture) == RawMouseActions.CancelCapture)
{
// Console.WriteLine("RawMouseActions.CancelCapture");
ChangeMouseCapture(null, null, CaptureMode.None, e.StagingItem.Input.Timestamp);
}
// If the input is reporting mouse movement, only update the
// set of non-redundant actions if the position changed.
// RelativeMove, VirtualDesktopMove have not been handled yet
if ((rawMouseInputReport.Actions & RawMouseActions.AbsoluteMove) == RawMouseActions.AbsoluteMove)
{
//Console.WriteLine("RawMouseActions.AbsoluteMove: X=" + rawMouseInputReport.X + " Y=" + rawMouseInputReport.Y );
// Translate the mouse coordinates to both root relative and "mouseOver" relate.
// - Note: "mouseOver" in this case is the element the mouse "was" over before this move.
bool mouseOverAvailable = false;
Point ptClient = new Point(rawMouseInputReport.X, rawMouseInputReport.Y);
Point ptRoot = (Point) e.StagingItem.GetData(_tagRootPoint);
Point ptRelativeToOver = InputElement.TranslatePoint(ptRoot, rawMouseInputReport.InputSource.RootVisual, (DependencyObject)_mouseOver, out mouseOverAvailable);
IInputElement mouseOver = _mouseOver; // assume mouse is still over whatever it was before
IInputElement rawMouseOver = (_rawMouseOver != null) ? (IInputElement)_rawMouseOver.Target : null;
bool isPhysicallyOver = _isPhysicallyOver;
bool isGlobalChange = ArePointsClose(ptClient, _lastPosition) == false; // determine if the mouse actually physically moved
// Invoke Hit Test logic to determine what element the mouse will be over AFTER the move is processed.
// - Only do this if:
// - The mouse physcially moved (isGlobalChange)
// - We are simulating a mouse move (_isSynchronize)
// - mouseOver isn't availabe (!mouseOverAvailable) Could be caused by a degenerate transform.
// - This is to mitigate the redundant AbsoluteMove notifications associated with QueryCursor
if (isGlobalChange || rawMouseInputReport._isSynchronize || !mouseOverAvailable)
{
isPhysicallyOver = true; // assume mouse is physical over element, we'll set it false if it's due to capture
switch (_captureMode)
{
// In this case there is no capture, so a simple hit test will determine which element becomes "mouseOver"
case CaptureMode.None:
{
if (rawMouseInputReport._isSynchronize)
{
GlobalHitTest(true, ptClient, _inputSource, out mouseOver, out rawMouseOver);
}
else
{
LocalHitTest(true, ptClient, _inputSource, out mouseOver, out rawMouseOver);
}
if (mouseOver == rawMouseOver)
{
// Since they are the same, there is no reason to process rawMouseOver
rawMouseOver = null;
}
// We understand UIElements and ContentElements.
// If we are over something else (like a raw visual)
// find the containing element.
if (!InputElement.IsValid(mouseOver))
{
mouseOver = InputElement.GetContainingInputElement(mouseOver as DependencyObject);
}
if ((rawMouseOver != null) && !InputElement.IsValid(rawMouseOver))
{
rawMouseOver = InputElement.GetContainingInputElement(rawMouseOver as DependencyObject);
}
}
break;
// In this case, capture is to a specific element, so it will ALWAYS become "mouseOver"
// - however, we do a hit test to see if the mouse is actually physically over the element,
// - if it is not, we toggle isPhysicallyOver
case CaptureMode.Element:
if (rawMouseInputReport._isSynchronize)
{
mouseOver = GlobalHitTest(true, ptClient, _inputSource);
}
else
{
mouseOver = LocalHitTest(true, ptClient, _inputSource);
}
// There is no reason to process rawMouseOver when
// the element should always be the one with mouse capture.
rawMouseOver = null;
if (mouseOver != _mouseCapture)
{
// Always consider the mouse over the capture point.
mouseOver = _mouseCapture;
isPhysicallyOver = false;
}
break;
// In this case, capture is set to an entire subtree. We use simple hit testing to determine
// which, if any element in the subtree it is over, and set "mouseOver to that element
// If it is not over any specific subtree element, "mouseOver" is set to the root of the subtree.
// - Note: a subtree can span multiple HWNDs
case CaptureMode.SubTree:
{
IInputElement mouseCapture = InputElement.GetContainingInputElement(_mouseCapture as DependencyObject);
if (mouseCapture != null)
{
// We need to re-hit-test to get the "real" UIElement we are over.
// This allows us to have our capture-to-subtree span multiple windows.
// GlobalHitTest always returns an IInputElement, so we are sure to have one.
GlobalHitTest(true, ptClient, _inputSource, out mouseOver, out rawMouseOver);
}
if (mouseOver != null && !InputElement.IsValid(mouseOver) )
mouseOver = InputElement.GetContainingInputElement(mouseOver as DependencyObject);
// Make sure that the element we hit is acutally underneath
// our captured element. Because we did a global hit test, we
// could have hit an element in a completely different window.
//
// Note that we support the child being in a completely different window.
// So we use the GetUIParent method instead of just looking at
// visual/content parents.
if (mouseOver != null)
{
IInputElement ieTest = mouseOver;
UIElement eTest = null;
ContentElement ceTest = null;
UIElement3D e3DTest = null;
while (ieTest != null && ieTest != mouseCapture)
{
eTest = ieTest as UIElement;
if (eTest != null)
{
ieTest = InputElement.GetContainingInputElement(eTest.GetUIParent(true));
}
else
{
ceTest = ieTest as ContentElement;
if (ceTest != null)
{
ieTest = InputElement.GetContainingInputElement(ceTest.GetUIParent(true));
}
else
{
e3DTest = ieTest as UIElement3D; // Should never fail.
ieTest = InputElement.GetContainingInputElement(e3DTest.GetUIParent(true));
}
}
}
// If we missed the capture point, we didn't hit anything.
if (ieTest != mouseCapture)
{
mouseOver = _mouseCapture;
isPhysicallyOver = false;
// Since they are the same, there is no reason to process rawMouseOver
rawMouseOver = null;
}
}
else
{
// We didn't hit anything. Consider the mouse over the capture point.
mouseOver = _mouseCapture;
isPhysicallyOver = false;
// Since they are the same, there is no reason to process rawMouseOver
rawMouseOver = null;
}
if (rawMouseOver != null)
{
if (mouseOver == rawMouseOver)
{
// Since they are the same, there is no reason to process rawMouseOver
rawMouseOver = null;
}
else if (!InputElement.IsValid(rawMouseOver))
{
rawMouseOver = InputElement.GetContainingInputElement(rawMouseOver as DependencyObject);
}
}
}
break;
}
}
_isPhysicallyOver = mouseOver == null ? false : isPhysicallyOver;
// Now that we've determine what element the mouse is over now (mouseOver)
// - we need to check if it's changed
bool isMouseOverChange = mouseOver != _mouseOver;
// If mouseOver changed, we need to recalculate the ptRelativeToOver, because "Over" changed!
if (isMouseOverChange)
{
ptRelativeToOver = InputElement.TranslatePoint(ptRoot, rawMouseInputReport.InputSource.RootVisual, (DependencyObject)mouseOver);
}
//Console.WriteLine("RawMouseActions.AbsoluteMove: mouse moved over " + (isMouseOverChange ? "same" : "different") + " element. old=" + _mouseOver + " new=" + mouseOver);
//Console.WriteLine("RawMouseActions.AbsoluteMove: capture=" + _mouseCapture);
// Check to see if the local mouse position changed. This can be
// caused by a change to the geometry of the
// element we are over or a change in which element
// we are over.
//
bool isLocalChange = isMouseOverChange || ArePointsClose(ptRelativeToOver, _positionRelativeToOver) == false;
// Console.WriteLine("RawMouseActions.AbsoluteMove: isGlobalChange=" + isGlobalChange + " isLocalChange=" + isLocalChange);
// We only update our cached position (_lastPosition & _positionRelativeToOver )
// if we have moved "far enough" allowing small incrementaly moves to accumulate
if (isGlobalChange || isLocalChange || _forceUpdateLastPosition)
{
_forceUpdateLastPosition = false;
_lastPosition = ptClient;
_positionRelativeToOver = ptRelativeToOver;
if (isMouseOverChange)
{
ChangeMouseOver(mouseOver, e.StagingItem.Input.Timestamp);
}
if ((_rawMouseOver == null) && (rawMouseOver != null))
{
_rawMouseOver = new WeakReference(rawMouseOver);
}
else if (_rawMouseOver != null)
{
_rawMouseOver.Target = rawMouseOver;
}
// Console.WriteLine("RawMouseActions.AbsoluteMove: ptRoot=" + ptRoot);
// RelativeMove, VirtualDesktopMove have not been handled yet
actions |= RawMouseActions.AbsoluteMove;
// In most cases the sequence of messages received from the system are HitTest, SetCursor & MouseMove.
// The SetCursor message in this case will be traslated into an Avalon MouseMove & QueryCursor.
// The MouseMove message to follow is redundant and is thrown away.
// But imagine a case where Capture is taken. Here the system produces only two messages HitTest & MouseMove.
// Hence we translate the MouseMove into an Avalon MouseMove & QueryCursor.
// Logically MouseMove and QueryCursor go as a pair.
actions |= RawMouseActions.QueryCursor;
}
}
// Mouse wheel rotate events are never considered redundant.
if ((rawMouseInputReport.Actions & RawMouseActions.VerticalWheelRotate) == RawMouseActions.VerticalWheelRotate)
{
// Console.WriteLine("RawMouseActions.VerticalWheelRotate");
actions |= RawMouseActions.VerticalWheelRotate;
// Tell the InputManager that the MostRecentDevice is us.
_inputManager.MostRecentInputDevice = this;
}
// Mouse query cursor events are never considered redundant.
if ((rawMouseInputReport.Actions & RawMouseActions.QueryCursor) == RawMouseActions.QueryCursor)
{
// Console.WriteLine("RawMouseActions.QueryCursor");
actions |= RawMouseActions.QueryCursor;
}
ReadOnlySpan<RawMouseActions> ButtonPressActions = stackalloc RawMouseActions[5]
{
RawMouseActions.Button1Press,
RawMouseActions.Button2Press,
RawMouseActions.Button3Press,
RawMouseActions.Button4Press,
RawMouseActions.Button5Press
};
ReadOnlySpan<RawMouseActions> ButtonReleaseActions = stackalloc RawMouseActions[5]
{
RawMouseActions.Button1Release,
RawMouseActions.Button2Release,
RawMouseActions.Button3Release,
RawMouseActions.Button4Release,
RawMouseActions.Button5Release
};
for (int iButton = 0; iButton < 5; iButton++)
{
if ((rawMouseInputReport.Actions & ButtonPressActions[iButton]) == ButtonPressActions[iButton])
{
actions |= ButtonPressActions[iButton];
// Tell the InputManager that the MostRecentDevice is us.
_inputManager.MostRecentInputDevice = this;
}
if ((rawMouseInputReport.Actions & ButtonReleaseActions[iButton]) == ButtonReleaseActions[iButton])
{
actions |= ButtonReleaseActions[iButton];
// Tell the InputManager that the MostRecentDevice is us.
_inputManager.MostRecentInputDevice = this;
}
}
}
if (actions != originalActions)
{
e.StagingItem.SetData(_tagNonRedundantActions, actions);
}
}
}
else
{
// All mouse event processing should only happen if we still have an active input source.
if (_inputSource != null)
{
// During the PreviewMouseDown event, we update the click count, if there are
// multiple "quick" clicks in approximately the "same" location (as defined
// by the hosting environment, aka the registry).
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent)
{
MouseButtonEventArgs mouseButtonArgs = e.StagingItem.Input as MouseButtonEventArgs;
StylusDevice stylusDevice = GetStylusDevice(e.StagingItem);
Point ptClient = GetClientPosition();
_clickCount = CalculateClickCount(mouseButtonArgs.ChangedButton, mouseButtonArgs.Timestamp, stylusDevice, ptClient);
if (_clickCount == 1)
{
// we need to reset out data, since this is the start of the click count process...
_lastClick = ptClient;
_lastButton = mouseButtonArgs.ChangedButton;
_lastClickTime = mouseButtonArgs.Timestamp;
}
// Put the updated count into the args.
mouseButtonArgs.ClickCount = _clickCount;
}
}
}
}
// Due to the inexactness of math calculations of
// floating-point numbers, we use the AreClose method
// to determine if the coordinates are close enough
// to consider the same.
private bool ArePointsClose(Point A, Point B)
{
return MS.Internal.DoubleUtil.AreClose(A.X, B.X) && MS.Internal.DoubleUtil.AreClose(A.Y, B.Y);
}
private void PostProcessInput(object sender, ProcessInputEventArgs e)
{
// PreviewMouseWheel --> MouseWheel
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseWheelEvent)
{
if (!e.StagingItem.Input.Handled)
{
MouseWheelEventArgs previewWheel = (MouseWheelEventArgs) e.StagingItem.Input;
MouseWheelEventArgs wheel = new MouseWheelEventArgs(this, previewWheel.Timestamp, previewWheel.Delta);
wheel.RoutedEvent=Mouse.MouseWheelEvent;
#if SEND_WHEEL_EVENTS_TO_FOCUS
// wheel events are treated as if they came from the
// element with keyboard focus
wheel.Source = previewWheel.Source;
#endif
e.PushInput(wheel, e.StagingItem);
}
}
// PreviewMouseDown --> MouseDown
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent)
{
if (!e.StagingItem.Input.Handled)
{
MouseButtonEventArgs previewDown = (MouseButtonEventArgs) e.StagingItem.Input;
MouseButtonEventArgs down = new MouseButtonEventArgs(this, previewDown.Timestamp, previewDown.ChangedButton, GetStylusDevice(e.StagingItem));
down.ClickCount = previewDown.ClickCount;
down.RoutedEvent=Mouse.MouseDownEvent;
e.PushInput(down, e.StagingItem);
}
}
// PreviewMouseUp --> MouseUp
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseUpEvent)
{
if (!e.StagingItem.Input.Handled)
{
MouseButtonEventArgs previewUp = (MouseButtonEventArgs) e.StagingItem.Input;
MouseButtonEventArgs up = new MouseButtonEventArgs(this, previewUp.Timestamp, previewUp.ChangedButton, GetStylusDevice(e.StagingItem));
up.RoutedEvent=Mouse.MouseUpEvent;
e.PushInput(up, e.StagingItem);
}
}
// PreviewMouseMove --> MouseMove
if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseMoveEvent)
{
if (!e.StagingItem.Input.Handled)
{
MouseEventArgs previewMove = (MouseEventArgs) e.StagingItem.Input;
MouseEventArgs move = new MouseEventArgs(this, previewMove.Timestamp, GetStylusDevice(e.StagingItem));
move.RoutedEvent=Mouse.MouseMoveEvent;
e.PushInput(move, e.StagingItem);
}
}
// We are finished processing the QueryCursor event. Just update the
// mouse cursor to be what was decided during the event route.
if (e.StagingItem.Input.RoutedEvent == Mouse.QueryCursorEvent)
{
QueryCursorEventArgs queryCursor = (QueryCursorEventArgs)e.StagingItem.Input;
SetCursor(queryCursor.Cursor);
}
if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent)
{
InputReportEventArgs inputReportEventArgs = e.StagingItem.Input as InputReportEventArgs;
if (!inputReportEventArgs.Handled && inputReportEventArgs.Report.Type == InputType.Mouse)
{
RawMouseInputReport rawMouseInputReport = (RawMouseInputReport) inputReportEventArgs.Report;
// Only process mouse input that is from our active visual manager.
if ((_inputSource is not null) && (rawMouseInputReport.InputSource == _inputSource))
{
// In general, this is where we promote the non-redundant
// reported actions to our premier events.
RawMouseActions actions = GetNonRedundantActions(e);
// Raw Activate --> Raw MouseMove
// Whenever the mouse device is activated we need to
// cause a mouse move so that elements realize that
// the mouse is over them again. In most cases, the
// action that caused the mouse to activate is a move,
// but this is to guard against any other cases.
if ((actions & RawMouseActions.Activate) == RawMouseActions.Activate)
{
Synchronize();
}
// Raw --> PreviewMouseWheel
// HorizontalWheelRotate hasn't been handled yet
if ((actions & RawMouseActions.VerticalWheelRotate) == RawMouseActions.VerticalWheelRotate)
{
MouseWheelEventArgs previewWheel = new MouseWheelEventArgs(this, rawMouseInputReport.Timestamp, rawMouseInputReport.Wheel);
previewWheel.RoutedEvent=Mouse.PreviewMouseWheelEvent;
#if SEND_WHEEL_EVENTS_TO_FOCUS
// wheel events are treated as if they came from the
// element with keyboard focus
DependencyObject focus = Keyboard.FocusedElement as DependencyObject;
if (focus != null)
{
previewWheel.Source = focus;
}
#endif
e.PushInput(previewWheel, e.StagingItem);
}
// Raw --> PreviewMouseDown
if ((actions & RawMouseActions.Button1Press) == RawMouseActions.Button1Press)
{
MouseButtonEventArgs previewDown = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Left, GetStylusDevice(e.StagingItem));
previewDown.RoutedEvent=Mouse.PreviewMouseDownEvent;
e.PushInput(previewDown, e.StagingItem);
}
// Raw --> PreviewMouseUp
if ((actions & RawMouseActions.Button1Release) == RawMouseActions.Button1Release)
{
MouseButtonEventArgs previewUp = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Left, GetStylusDevice(e.StagingItem));
previewUp.RoutedEvent=Mouse.PreviewMouseUpEvent;
e.PushInput(previewUp, e.StagingItem);
}
// Raw --> PreviewMouseDown
if ((actions & RawMouseActions.Button2Press) == RawMouseActions.Button2Press)
{
MouseButtonEventArgs previewDown = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Right, GetStylusDevice(e.StagingItem));
previewDown.RoutedEvent=Mouse.PreviewMouseDownEvent;
e.PushInput(previewDown, e.StagingItem);
}
// Raw --> PreviewMouseUp
if ((actions & RawMouseActions.Button2Release) == RawMouseActions.Button2Release)
{
MouseButtonEventArgs previewUp = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Right, GetStylusDevice(e.StagingItem));
previewUp.RoutedEvent=Mouse.PreviewMouseUpEvent;
e.PushInput(previewUp, e.StagingItem);
}
// Raw --> PreviewMouseDown
if ((actions & RawMouseActions.Button3Press) == RawMouseActions.Button3Press)
{
MouseButtonEventArgs previewDown = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Middle, GetStylusDevice(e.StagingItem));
previewDown.RoutedEvent=Mouse.PreviewMouseDownEvent;
e.PushInput(previewDown, e.StagingItem);
}
// Raw --> PreviewMouseUp
if ((actions & RawMouseActions.Button3Release) == RawMouseActions.Button3Release)
{
MouseButtonEventArgs previewUp = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.Middle, GetStylusDevice(e.StagingItem));
previewUp.RoutedEvent=Mouse.PreviewMouseUpEvent;
e.PushInput(previewUp, e.StagingItem);
}
// Raw --> PreviewMouseDown
if ((actions & RawMouseActions.Button4Press) == RawMouseActions.Button4Press)
{
MouseButtonEventArgs previewDown = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.XButton1, GetStylusDevice(e.StagingItem));
previewDown.RoutedEvent=Mouse.PreviewMouseDownEvent;
e.PushInput(previewDown, e.StagingItem);
}
// Raw --> PreviewMouseUp
if ((actions & RawMouseActions.Button4Release) == RawMouseActions.Button4Release)
{
MouseButtonEventArgs previewUp = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.XButton1, GetStylusDevice(e.StagingItem));
previewUp.RoutedEvent=Mouse.PreviewMouseUpEvent;
e.PushInput(previewUp, e.StagingItem);
}
// Raw --> PreviewMouseDown
if ((actions & RawMouseActions.Button5Press) == RawMouseActions.Button5Press)
{
MouseButtonEventArgs previewDown = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.XButton2, GetStylusDevice(e.StagingItem));
previewDown.RoutedEvent=Mouse.PreviewMouseDownEvent;
e.PushInput(previewDown, e.StagingItem);
}
// Raw --> PreviewMouseUp
if ((actions & RawMouseActions.Button5Release) == RawMouseActions.Button5Release)
{
MouseButtonEventArgs previewUp = new MouseButtonEventArgs(this, rawMouseInputReport.Timestamp, MouseButton.XButton2, GetStylusDevice(e.StagingItem));
previewUp.RoutedEvent=Mouse.PreviewMouseUpEvent;
e.PushInput(previewUp, e.StagingItem);
}
// Raw --> PreviewMouseMove
// RelativeMove, VirtualDesktopMove haven't been handled yet
if ((actions & RawMouseActions.AbsoluteMove) == RawMouseActions.AbsoluteMove)
{
MouseEventArgs previewMove = new MouseEventArgs(this, rawMouseInputReport.Timestamp, GetStylusDevice(e.StagingItem));
previewMove.RoutedEvent=Mouse.PreviewMouseMoveEvent;
e.PushInput(previewMove, e.StagingItem);
}
// Raw --> QueryCursor
if ((actions & RawMouseActions.QueryCursor) == RawMouseActions.QueryCursor)
{
inputReportEventArgs.Handled = UpdateCursorPrivate();
}
}
}
}
}
private RawMouseActions GetNonRedundantActions(NotifyInputEventArgs e)
{
RawMouseActions actions = new RawMouseActions();
// The CLR throws a null-ref exception if it tries to unbox a
// null. So we have to special case that.
object o = e.StagingItem.GetData(_tagNonRedundantActions);
if (o != null)
{
actions = (RawMouseActions) o;
}
return actions;
}
internal static IInputElement GlobalHitTest(bool clientUnits, Point pt, PresentationSource inputSource)
{
IInputElement enabledHit;
IInputElement originalHit;
GlobalHitTest(clientUnits, pt, inputSource, out enabledHit, out originalHit);
return enabledHit;
}
internal static IInputElement GlobalHitTest(Point ptClient, PresentationSource inputSource)
{
return GlobalHitTest(true, ptClient, inputSource);
}
// Take a point relative the the specified visual manager, and translate
// up to the screen, hit-test to a window, and then hit-test down to an
// element.
private static void GlobalHitTest(bool clientUnits, Point pt, PresentationSource inputSource, out IInputElement enabledHit, out IInputElement originalHit)
{
enabledHit = originalHit = null;
Point ptClient = clientUnits ? pt : PointUtil.RootToClient(pt, inputSource);
// Note: this only works for HWNDs for now.
HwndSource source = inputSource as HwndSource;
if (source != null && source.CompositionTarget != null && !source.IsHandleNull)
{
Point ptScreen = PointUtil.ClientToScreen(ptClient, source);
IntPtr hwndHit = IntPtr.Zero ;
HwndSource sourceHit = null ;
// Find the HWND under the point.
hwndHit = UnsafeNativeMethods.WindowFromPoint((int)ptScreen.X, (int)ptScreen.Y);
// Make sure the window is enabled!
if (!SafeNativeMethods.IsWindowEnabled(new HandleRef(null, hwndHit)))
{
hwndHit = IntPtr.Zero;
}
if (hwndHit != IntPtr.Zero)
{
// See if this is one of our windows.
sourceHit = HwndSource.CriticalFromHwnd(hwndHit);
}
if (sourceHit != null && sourceHit.Dispatcher == inputSource.CompositionTarget.Dispatcher)
{
Point ptClientHit = PointUtil.ScreenToClient(ptScreen, sourceHit);
// Perform a local hit-test within this visual manager.
LocalHitTest(true, ptClientHit, sourceHit, out enabledHit, out originalHit);
}
}
}
internal static IInputElement LocalHitTest(bool clientUnits, Point pt, PresentationSource inputSource)
{
IInputElement enabledHit;
IInputElement originalHit;
LocalHitTest(clientUnits, pt, inputSource, out enabledHit, out originalHit);
return enabledHit;
}
internal static IInputElement LocalHitTest(Point ptClient, PresentationSource inputSource)
{
return LocalHitTest(true, ptClient, inputSource);
}
// Take a point relative the the specified visual manager and hit-test
// down to an element.
private static void LocalHitTest(bool clientUnits, Point pt, PresentationSource inputSource, out IInputElement enabledHit, out IInputElement originalHit)
{
enabledHit = originalHit = null;
// Hit-test starting from the root UIElement.
// Note: this restricts us to windows with UIElement as the root (not just visuals).
if (inputSource != null)
{
UIElement root = inputSource.RootVisual as UIElement;
if(root != null)
{
Point rootPt = clientUnits ? PointUtil.ClientToRoot(pt, inputSource) : pt;
root.InputHitTest(rootPt, out enabledHit, out originalHit);
}
}
}
internal bool IsSameSpot(Point newPosition, StylusDevice stylusDevice)
{
int doubleClickDeltaX = (stylusDevice != null)?stylusDevice.DoubleTapDeltaX:_doubleClickDeltaX;
int doubleClickDeltaY = (stylusDevice != null)?stylusDevice.DoubleTapDeltaY:_doubleClickDeltaY;
// Is the delta coordinates of this click close enough to the last click?
return (Math.Abs(newPosition.X - _lastClick.X) < doubleClickDeltaX) &&
(Math.Abs(newPosition.Y - _lastClick.Y) < doubleClickDeltaY);
}
internal int CalculateClickCount(MouseButton button, int timeStamp, StylusDevice stylusDevice, Point downPt)
{
// How long since the last click?
int timeSpan = timeStamp - _lastClickTime;
int doubleClickDeltaTime = (stylusDevice != null)?stylusDevice.DoubleTapDeltaTime:_doubleClickDeltaTime;
// Is the delta coordinates of this click close enough to the last click?
bool isSameSpot = IsSameSpot(downPt, stylusDevice);
// Is this the same mouse button as the last click?
bool isSameButton = (_lastButton == button);
// Now check everything to see if this is a multi-click.
if (timeSpan < doubleClickDeltaTime
&& isSameSpot
&& isSameButton)
{
// Yes, increment the count
return _clickCount +1;
}
else
{
// No, not a multi-click.
return 1;
}
}
internal Point PositionRelativeToOver
{
get
{
return _positionRelativeToOver;
}
}
internal Point NonRelativePosition
{
get
{
return _lastPosition;
}
}
internal bool IsActive
{
get
{
return _inputSource != null;
}
}
// Helper to access the StylusDevice property.
private StylusDevice GetStylusDevice(StagingAreaInputItem stagingItem)
{
return stagingItem.GetData(_tagStylusDevice) as StylusDevice;
}
internal StylusDevice StylusDevice
{
get
{
return _stylusDevice;
}
}
private DeferredElementTreeState MouseOverTreeState
{
get
{
if (_mouseOverTreeState == null)
{
_mouseOverTreeState = new DeferredElementTreeState();
}
return _mouseOverTreeState;
}
}
private DeferredElementTreeState MouseCaptureWithinTreeState
{
get
{
if (_mouseCaptureWithinTreeState == null)
{
_mouseCaptureWithinTreeState = new DeferredElementTreeState();
}
return _mouseCaptureWithinTreeState;
}
}
private PresentationSource _inputSource;
private InputManager _inputManager;
private IInputElement _mouseOver;
private DeferredElementTreeState _mouseOverTreeState;
private bool _isPhysicallyOver;
private WeakReference _rawMouseOver;
private IInputElement _mouseCapture;
private DeferredElementTreeState _mouseCaptureWithinTreeState;
private IMouseInputProvider _providerCapture;
private CaptureMode _captureMode;
private bool _isCaptureMouseInProgress;
private DependencyPropertyChangedEventHandler _overIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateMouseOverDelegate;
private DispatcherOperation _reevaluateMouseOverOperation;
private DependencyPropertyChangedEventHandler _captureIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateCaptureDelegate;
private DispatcherOperation _reevaluateCaptureOperation;
// Device state we track
private Point _positionRelativeToOver = new Point();
private Point _lastPosition = new Point();
private bool _forceUpdateLastPosition = false;
// Data tags for information we pass around the staging area.
private object _tagNonRedundantActions = new object();
private object _tagStylusDevice = new object();
private object _tagRootPoint = new object();
// Information used to distinguish double-clicks (actually, multi clicks) from
// multiple independent clicks.
private Point _lastClick = new Point();
private MouseButton _lastButton;
private int _clickCount;
private int _lastClickTime;
private int _doubleClickDeltaTime;
private int _doubleClickDeltaX;
private int _doubleClickDeltaY;
private Cursor _overrideCursor;
// Reference to StylusDevice to defer to for physical mouse state (position/button state)
private StylusDevice _stylusDevice = null;
}
}
|