|
// 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 MS.Internal;
using MS.Win32;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using SR = MS.Internal.PresentationCore.SR;
namespace System.Windows.Input.StylusWisp
{
/////////////////////////////////////////////////////////////////////////
/// <summary>
/// The StylusDevice class represents the stylus device
/// </summary>
internal class WispStylusDevice : StylusDeviceBase
{
/////////////////////////////////////////////////////////////////////
internal WispStylusDevice(WispTabletDevice tabletDevice, string sName, int id, bool fInverted, StylusButtonCollection stylusButtonCollection)
: base()
{
_tabletDevice = tabletDevice;
_sName = sName;
_id = id;
_fInverted = fInverted;
// For tablet devices that can go out of range default them to
// being out of range until we see some events from it.
_fInRange = false; // All tablets out of range by default.
_stylusButtonCollection = stylusButtonCollection;
#if MULTICAPTURE
_overIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsEnabledChanged);
_overIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsVisibleChanged);
_overIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnOverIsHitTestVisibleChanged);
_reevaluateStylusOverDelegate = new DispatcherOperationCallback(ReevaluateStylusOverAsync);
_reevaluateStylusOverOperation = null;
_captureIsEnabledChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsEnabledChanged);
_captureIsVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsVisibleChanged);
_captureIsHitTestVisibleChangedEventHandler = new DependencyPropertyChangedEventHandler(OnCaptureIsHitTestVisibleChanged);
_reevaluateCaptureDelegate = new DispatcherOperationCallback(ReevaluateCaptureAsync);
_reevaluateCaptureOperation = null;
#endif
if (_stylusButtonCollection != null)
{
foreach (StylusButton button in _stylusButtonCollection)
{
button.SetOwner(this);
}
}
// Because the stylus device gets a steady stream of input events when it is in range,
// we don't have to be so careful about responding to layout changes as we have to be
// with the mouse.
// InputManager.Current.HitTestInvalidatedAsync += new EventHandler(OnHitTestInvalidatedAsync);
_stylusLogic = StylusLogic.GetCurrentStylusLogicAs<WispLogic>();
_stylusLogic.RegisterStylusDeviceCore(StylusDevice);
}
/////////////////////////////////////////////////////////////////////
protected override void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_stylusLogic.UnregisterStylusDeviceCore(StylusDevice);
// DevDiv:1078091
// In case the corresponding touch device hasn't been deactivated
// we need to deactivate it here. If we don't and there are missed
// up messages, we can end up in a state where we will never promote
// mouse messages as the activated count is thread static and the
// CurrentMousePromotionStylusDevice is only set on initial activation.
// In the case of missed ups, we will have an old StylusTouchDevice for
// the promotion device and nothing will be promoted in the future.
if (_touchDevice?.IsActive ?? false)
{
_touchDevice.OnDeactivate();
}
// Make sure we clean up any references that could keep our object alive.
_inputSource = null;
_stylusCapture = null;
_stylusOver = null;
_nonVerifiedTarget = null;
_verifiedTarget = null;
_rtiCaptureChanged = null;
_stylusCapturePlugInCollection = null;
_fBlockMouseMoveChanges = false;
_tabletDevice = null;
_stylusLogic = null;
_fInRange = false;
_touchDevice = null;
}
_disposed = true;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that input from this device is sent to.
/// </summary>
internal override IInputElement Target
{
get
{
VerifyAccess();
return _stylusOver;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns whether the StylusDevice object has been internally disposed.
/// </summary>
internal override bool IsValid
{
get
{
return (_tabletDevice != null);
}
}
/// <summary>
/// Returns the PresentationSource that is reporting input for this device.
/// </summary>
internal override PresentationSource ActiveSource => _inputSource;
/// <summary>
/// Returns the PresentationSource that is reporting input for this device.
/// </summary>
internal override PresentationSource CriticalActiveSource => _inputSource;
/// <summary>
/// Returns the currently active PenContext (if seen) for this device.
/// Gets set on InRange and cleared on the out of range event (that matches PenContext).
/// </summary>
internal PenContext ActivePenContext => _activePenContext;
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that the stylus is over.
/// </summary>
internal StylusPlugInCollection CurrentNonVerifiedTarget
{
get
{
return _nonVerifiedTarget;
}
set
{
_nonVerifiedTarget = value;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that the stylus is over.
/// </summary>
internal override StylusPlugInCollection CurrentVerifiedTarget
{
get
{
return _verifiedTarget;
}
set
{
_verifiedTarget = value;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that the stylus is over.
/// </summary>
internal override IInputElement DirectlyOver
{
get
{
VerifyAccess();
return _stylusOver;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that has captured the stylus.
/// </summary>
internal override IInputElement Captured
{
get
{
VerifyAccess();
return _stylusCapture;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the element that has captured the stylus.
/// </summary>
internal override CaptureMode CapturedMode
{
get
{
return _captureMode;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Captures the stylus to a particular element.
/// </summary>
internal override 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 element is either a UIElement, a ContentElement or a UIElement3D.
DependencyObject doStylusCapture = element as DependencyObject;
if (doStylusCapture != null && !InputElement.IsValid(element))
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, doStylusCapture.GetType()));
}
if (doStylusCapture != null)
{
doStylusCapture.VerifyAccess();
}
bool success = false;
// The element we are capturing to must be both enabled and visible.
UIElement e = element as UIElement;
if (e != null)
{
if (e.IsVisible || e.IsEnabled)
{
success = true;
}
}
else
{
ContentElement ce = element as ContentElement;
if (ce != null)
{
if (ce.IsEnabled) // There is no IsVisible property for ContentElement
{
success = true;
}
}
else
{
// Setting capture to null.
success = true;
}
}
if (success)
{
ChangeStylusCapture(element, captureMode, timeStamp);
}
return success;
}
/// <summary>
/// Captures the stylus to a particular element.
/// </summary>
internal override bool Capture(IInputElement element)
{
// No need for calling ApplyTemplate since we forward the call.
return Capture(element, CaptureMode.Element);
}
// called from the penthread to find out if a plugincollection has capture.
internal override StylusPlugInCollection GetCapturedPlugInCollection(ref bool elementHasCapture)
{
// Take lock so both are returned with proper state since called from a pen thread.
lock (_rtiCaptureChanged)
{
elementHasCapture = (_stylusCapture != null);
return _stylusCapturePlugInCollection;
}
}
/// <summary>
/// Forces the stylusdevice to resynchronize at it's current location and state.
/// It can conditionally generate a Stylus Move/InAirMove (at the current location) if a change
/// in hittesting is detected that requires an event be generated to update elements
/// to the current state (typically due to layout changes without Stylus changes).
/// Has the same behavior as MouseDevice.Synchronize().
/// </summary>
internal override void Synchronize()
{
// Simulate a stylus move (if we are current stylus, inrange, visuals still valid to update
// and has moved).
if (InRange && _inputSource?.CompositionTarget is { } target && !target.IsDisposed)
{
Point ptDevice = PointUtil.ScreenToClient(_lastScreenLocation, _inputSource);
// GlobalHitTest always returns an IInputElement, so we are sure to have one.
IInputElement stylusOver = Input.StylusDevice.GlobalHitTest(_inputSource, ptDevice);
bool fOffsetChanged = false;
if (_stylusOver == stylusOver)
{
Point ptOffset = GetPosition(stylusOver);
fOffsetChanged = MS.Internal.DoubleUtil.AreClose(ptOffset.X, _rawElementRelativePosition.X) == false || MS.Internal.DoubleUtil.AreClose(ptOffset.Y, _rawElementRelativePosition.Y) == false;
}
if (fOffsetChanged || _stylusOver != stylusOver)
{
int timeStamp = Environment.TickCount;
PenContext penContext = _stylusLogic.GetStylusPenContextForHwnd(_inputSource, TabletDevice.Id);
if (_eventStylusPoints != null &&
_eventStylusPoints.Count > 0 &&
StylusPointDescription.AreCompatible(penContext.StylusPointDescription, _eventStylusPoints.Description))
{
StylusPoint stylusPoint = _eventStylusPoints[_eventStylusPoints.Count - 1];
int[] data = stylusPoint.GetPacketData();
// get back to the correct coordinate system
Matrix m = _tabletDevice.TabletToScreen;
m.Invert();
Point ptTablet = ptDevice * m;
data[0] = (int)ptTablet.X;
data[1] = (int)ptTablet.Y;
RawStylusInputReport report = new RawStylusInputReport(InputMode.Foreground,
timeStamp,
_inputSource,
penContext,
InAir ? RawStylusActions.InAirMove : RawStylusActions.Move,
TabletDevice.Id,
Id,
data);
report.Synchronized = true;
InputReportEventArgs inputReportEventArgs = new InputReportEventArgs(StylusDevice, report);
inputReportEventArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
_stylusLogic.InputManagerProcessInputEventArgs(inputReportEventArgs);
}
}
}
}
#if MULTICAPTURE
private void UpdateOverProperty(IInputElement oldOver, IInputElement newOver)
{
DependencyObject o = null;
// Adjust the handlers we use to track everything.
if (oldOver != null)
{
o = oldOver 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;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, oldOver.GetType()));
}
}
if (_stylusOver != null)
{
o = _stylusOver 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;
}
else
{
throw new InvalidOperationException(SR.Format(SR.Invalid_IInputElement, _stylusOver.GetType()));
}
}
// Oddly enough, update the IsStylusOver property first. This is
// so any callbacks will see the more-common IsStylusOver property
// set correctly.
UIElement.StylusOverProperty.OnOriginValueChanged(oldOver as DependencyObject, _stylusOver as DependencyObject, ref _stylusOverTreeState);
// Invalidate the IsStylusDirectlyOver property.
if (oldOver != null)
{
o = oldOver as DependencyObject;
o.SetValue(UIElement.IsStylusDirectlyOverPropertyKey, false); // Same property for ContentElements
}
if (_stylusOver != null)
{
o = _stylusOver as DependencyObject;
o.SetValue(UIElement.IsStylusDirectlyOverPropertyKey, true); // Same property for ContentElements
}
}
private void OnOverIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over just became disabled.
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
private void OnOverIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over just became non-visible (collapsed or hidden).
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
private void OnOverIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is over was affected by a change in hit-test visibility.
//
// We need to resynchronize the stylus so that we can figure out who
// the stylus is over now.
ReevaluateStylusOver(null, null, true);
}
/// <summary>
/// </summary>
internal void ReevaluateStylusOver(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
{
if (element != null)
{
if (isCoreParent)
{
StylusOverTreeState.SetCoreParent(element, oldParent);
}
else
{
StylusOverTreeState.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 stylus is over an element, hide it.
//
// This never resolves to a "correct" state. When the stylus moves over the
// element, the element is hidden, so the stylus is no longer over it, so the
// element is shown, but that means the stylus 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 (_reevaluateStylusOverOperation == null)
{
_reevaluateStylusOverOperation = Dispatcher.BeginInvoke(DispatcherPriority.Input, _reevaluateStylusOverDelegate, null);
}
}
/// <summary>
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private object ReevaluateStylusOverAsync(object arg)
{
_reevaluateStylusOverOperation = null;
// Synchronize causes state issues with the stylus events so we don't do this.
//if (_currentStylusDevice != null)
//{
// _currentStylusDevice.Synchronize();
//}
// Refresh StylusOverProperty 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 (_stylusOverTreeState != null && !_stylusOverTreeState.IsEmpty)
{
UIElement.StylusOverProperty.OnOriginValueChanged(_stylusOver as DependencyObject, _stylusOver as DependencyObject, ref _stylusOverTreeState);
}
return null;
}
#endif
/////////////////////////////////////////////////////////////////////
// NOTE: This will typically get called for each stylus device on the
// system since Stylus.Capture will enumerate them all and call
// capture.
#if MULTICAPTURE
private void ChangeStylusCapture(IInputElement stylusCapture, CaptureMode captureMode, int timestamp)
#else
internal void ChangeStylusCapture(IInputElement stylusCapture, CaptureMode captureMode, int timestamp)
#endif
{
// if the capture changed...
if (stylusCapture != _stylusCapture)
{
// Actually change the capture first. Invalidate the properties,
// and then send the events.
IInputElement oldStylusCapture = _stylusCapture;
using (Dispatcher.DisableProcessing()) // Disable reentrancy due to locks taken
{
lock (_rtiCaptureChanged)
{
_stylusCapture = stylusCapture;
_captureMode = captureMode;
// We also need to figure out ahead of time if any plugincollections on this captured element (or a parent)
// for the penthread hittesting code.
_stylusCapturePlugInCollection = null;
if (stylusCapture != null)
{
UIElement uiElement = InputElement.GetContainingUIElement(stylusCapture as DependencyObject) as UIElement;
if (uiElement != null)
{
PresentationSource source = PresentationSource.CriticalFromVisual(uiElement as Visual);
if (source != null)
{
PenContexts penContexts = _stylusLogic.GetPenContextsFromHwnd(source);
_stylusCapturePlugInCollection = penContexts.FindPlugInCollection(uiElement);
}
}
}
}
}
#if MULTICAPTURE
DetachFromPropertiesAffectingCapture(oldStylusCapture);
AttachToPropertiesAffectingCapture(_stylusCapture);
// Oddly enough, update the IsStylusCaptureWithin property first. This is
// so any callbacks will see the more-common IsStylusCaptureWithin property
// set correctly.
UIElement.StylusCaptureWithinProperty.OnOriginValueChanged(oldStylusCapture as DependencyObject, _stylusCapture as DependencyObject, ref _stylusCaptureWithinTreeState);
// Invalidate the IsStylusCaptured properties.
if (oldStylusCapture != null)
{
var o = oldStylusCapture as DependencyObject;
o.SetValue(UIElement.IsStylusCapturedPropertyKey, false); // Same property for ContentElements
}
if (_stylusCapture != null)
{
var o = _stylusCapture as DependencyObject;
o.SetValue(UIElement.IsStylusCapturedPropertyKey, true); // Same property for ContentElements
}
#else
_stylusLogic.UpdateStylusCapture(this, oldStylusCapture, _stylusCapture, timestamp);
#endif
// Send the LostStylusCapture and GotStylusCapture events.
if (oldStylusCapture != null)
{
StylusEventArgs lostCapture = new StylusEventArgs(StylusDevice, timestamp);
lostCapture.RoutedEvent = Stylus.LostStylusCaptureEvent;
lostCapture.Source = oldStylusCapture;
_stylusLogic.InputManagerProcessInputEventArgs(lostCapture);
}
if (_stylusCapture != null)
{
StylusEventArgs gotCapture = new StylusEventArgs(StylusDevice, timestamp);
gotCapture.RoutedEvent = Stylus.GotStylusCaptureEvent;
gotCapture.Source = _stylusCapture;
_stylusLogic.InputManagerProcessInputEventArgs(gotCapture);
}
// Now update the stylus over state (only if this is the current stylus and
// it is inrange).
if (_stylusLogic.CurrentStylusDevice == this || InRange)
{
if (_stylusCapture != null)
{
IInputElement inputElementHit = _stylusCapture;
// See if we need to update over for subtree mode.
if (CapturedMode == CaptureMode.SubTree && _inputSource != null)
{
Point pt = _stylusLogic.DeviceUnitsFromMeasureUnits(_inputSource, GetPosition(null));
inputElementHit = FindTarget(_inputSource, pt);
}
ChangeStylusOver(inputElementHit);
}
else
{
// Only try to update over if we have a valid input source.
if (_inputSource is not null)
{
Point pt = GetPosition(null); // relative to window (root element)
pt = _stylusLogic.DeviceUnitsFromMeasureUnits(_inputSource, pt); // change back to device coords.
IInputElement currentOver = Input.StylusDevice.GlobalHitTest(_inputSource, pt);
ChangeStylusOver(currentOver);
}
}
}
// For Mouse StylusDevice we want to make sure Mouse capture is set up the same.
#if MULTICAPTURE
if ((Mouse.PrimaryDevice.StylusDevice == this) && (Mouse.Captured != _stylusCapture || Mouse.CapturedMode != _captureMode))
#else
if (Mouse.Captured != _stylusCapture || Mouse.CapturedMode != _captureMode)
#endif
{
Mouse.Capture(_stylusCapture, _captureMode);
}
}
}
#if MULTICAPTURE
internal void AttachToPropertiesAffectingCapture(IInputElement element)
{
// Adjust the handlers we use to track everything.
if (element != null)
{
var o = element as DependencyObject;
if (InputElement.IsUIElement(o))
{
((UIElement)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler;
((UIElement)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler;
((UIElement)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else if (InputElement.IsContentElement(o))
{
((ContentElement)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ((ContentElement)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler;
// ((ContentElement)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
else
{
((UIElement3D)o).IsEnabledChanged += _captureIsEnabledChangedEventHandler;
((UIElement3D)o).IsVisibleChanged += _captureIsVisibleChangedEventHandler;
((UIElement3D)o).IsHitTestVisibleChanged += _captureIsHitTestVisibleChangedEventHandler;
}
}
}
internal void DetachFromPropertiesAffectingCapture(IInputElement element)
{
if (element != null)
{
var o = element as DependencyObject;
if (InputElement.IsUIElement(o))
{
((UIElement)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
((UIElement)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
((UIElement)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else if (InputElement.IsContentElement(o))
{
((ContentElement)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
// NOTE: there are no IsVisible or IsHitTestVisible properties for ContentElements.
//
// ((ContentElement)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
// ((ContentElement)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
else
{
((UIElement3D)o).IsEnabledChanged -= _captureIsEnabledChangedEventHandler;
((UIElement3D)o).IsVisibleChanged -= _captureIsVisibleChangedEventHandler;
((UIElement3D)o).IsHitTestVisibleChanged -= _captureIsHitTestVisibleChangedEventHandler;
}
}
}
private void OnCaptureIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to just became disabled.
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a disabled element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to just became non-visible (collapsed or hidden).
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a non-visible element.
ReevaluateCapture(null, null, true);
}
private void OnCaptureIsHitTestVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
// The element that the stylus is captured to was affected by a change in hit-test visibility.
//
// We need to re-evaluate the element that has stylus capture since
// we can't allow the stylus to remain captured by a non-hittest-visible element.
ReevaluateCapture(null, null, true);
}
/// <summary>
/// </summary>
internal void ReevaluateCapture(DependencyObject element, DependencyObject oldParent, bool isCoreParent)
{
if (element != null)
{
if (isCoreParent)
{
StylusCaptureWithinTreeState.SetCoreParent(element, oldParent);
}
else
{
StylusCaptureWithinTreeState.SetLogicalParent(element, oldParent);
}
}
// We re-evaluate the captured element to be consistent with how
// we re-evaluate the element the stylus is over.
//
// See ReevaluateStylusOver 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 (_stylusCapture == null)
return null;
bool killCapture = false;
DependencyObject dependencyObject = _stylusCapture as DependencyObject;
//
// First, check things like IsEnabled, IsVisible, etc. on a
// UIElement vs. ContentElement basis.
//
if (InputElement.IsUIElement(dependencyObject))
{
killCapture = !ValidateUIElementForCapture((UIElement)_stylusCapture);
}
else if (InputElement.IsContentElement(dependencyObject))
{
killCapture = !ValidateContentElementForCapture((ContentElement)_stylusCapture);
}
else
{
killCapture = !ValidateUIElement3DForCapture((UIElement3D)_stylusCapture);
}
//
// 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)
{
Stylus.Capture(null);
}
// Refresh StylusCaptureWithinProperty 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 (_stylusCaptureWithinTreeState != null && !_stylusCaptureWithinTreeState.IsEmpty)
{
UIElement.StylusCaptureWithinProperty.OnOriginValueChanged(_stylusCapture as DependencyObject, _stylusCapture as DependencyObject, ref _stylusCaptureWithinTreeState);
}
return null;
}
private static 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 static bool ValidateContentElementForCapture(ContentElement element)
{
if (element.IsEnabled == false)
return false;
// NOTE: there is no IsVisible property for ContentElements.
return true;
}
private static 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 ValidateVisualForCapture(DependencyObject visual)
{
if (visual == null)
return false;
PresentationSource presentationSource = PresentationSource.CriticalFromVisual(visual);
if (presentationSource == null)
{
return false;
}
if (CriticalActiveSource != presentationSource &&
Captured == null)
{
return false;
}
return true;
}
#endif
/////////////////////////////////////////////////////////////////////
internal void ChangeStylusOver(IInputElement stylusOver)
{
// We are not syncing the OverSourceChanged event
// the reasons for doing so are listed in the MouseDevice.cs OnOverSourceChanged implementation
if (_stylusOver != stylusOver)
{
#if MULTICAPTURE
var oldStylusOver = _stylusOver;
#endif
_stylusOver = stylusOver;
_rawElementRelativePosition = GetPosition(_stylusOver);
#if MULTICAPTURE
UpdateOverProperty(oldStylusOver, _stylusOver);
#endif
}
else
{
// Always update the relative position if InRange since ChangeStylusOver is only
// called when something changed (like capture or stylus moved) and in
// that case we want this updated properly. This value is used in Synchronize().
if (InRange)
{
_rawElementRelativePosition = GetPosition(_stylusOver);
}
}
#if !MULTICAPTURE
// The stylus over property is a singleton (only one stylus device at a time can
// be over an element) so we let StylusLogic manager the element over state.
// NOTE: StylusLogic only allows the CurrentStylusDevice to change the over state.
// Also note that Capture is also managed by StylusLogic in a similar fashion.
_stylusLogic.UpdateOverProperty(this, _stylusOver);
#endif
}
/////////////////////////////////////////////////////////////////////
internal IInputElement FindTarget(PresentationSource inputSource, Point position)
{
IInputElement stylusOver = null;
switch (_captureMode)
{
case CaptureMode.None:
{
stylusOver = StylusDevice.GlobalHitTest(inputSource, position);
// We understand UIElements and ContentElements.
// If we are over something else (like a raw visual)
// find the containing element.
if (!InputElement.IsValid(stylusOver))
stylusOver = InputElement.GetContainingInputElement(stylusOver as DependencyObject);
}
break;
case CaptureMode.Element:
// CONSIDER: Support isPhysicallyOver like MouseDevice?
stylusOver = _stylusCapture;
break;
case CaptureMode.SubTree:
{
IInputElement stylusCapture = InputElement.GetContainingInputElement(_stylusCapture as DependencyObject);
if (stylusCapture != null && inputSource != 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.
stylusOver = StylusDevice.GlobalHitTest(inputSource, position);
}
if (stylusOver != null && !InputElement.IsValid(stylusOver))
stylusOver = InputElement.GetContainingInputElement(stylusOver 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 (stylusOver != null)
{
IInputElement ieTest = stylusOver;
UIElement eTest = null;
ContentElement ceTest = null;
while (ieTest != null && ieTest != stylusCapture)
{
eTest = ieTest as UIElement;
if (eTest != null)
{
ieTest = InputElement.GetContainingInputElement(eTest.GetUIParent(true));
}
else
{
ceTest = ieTest as ContentElement; // Should never fail.
ieTest = InputElement.GetContainingInputElement(ceTest.GetUIParent(true));
}
}
// If we missed the capture point, we didn't hit anything.
if (ieTest != stylusCapture)
{
stylusOver = _stylusCapture;
}
}
else
{
// We didn't hit anything. Consider the stylus over the capture point.
stylusOver = _stylusCapture;
}
}
break;
}
return stylusOver;
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the tablet associated with the StylusDevice
/// </summary>
internal override TabletDevice TabletDevice
{
get
{
// Don't do the VerifyAccess call any more since we need to call this prop
// from the pen thread to get access to internal data. The TabletDevice
// is already a DispatcherObject so it will do VerifyAccess() on any
// methods called on the wrong thread.
// VerifyAccess();
return _tabletDevice.TabletDevice;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the name of the StylusDevice
/// </summary>
internal override string Name
{
get
{
VerifyAccess();
return _sName;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the friendly representation of the StylusDevice
/// </summary>
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "{0}({1})", base.ToString(), this.Name);
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the hardware id of the StylusDevice
/// </summary>
internal override int Id
{
get
{
VerifyAccess();
return _id;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns a StylusPointCollection object for processing the data in the packet.
/// This method creates a new StylusPointCollection and copies the data.
/// </summary>
internal override StylusPointCollection GetStylusPoints(IInputElement relativeTo)
{
VerifyAccess();
// Fake up an empty one if we have to.
if (_eventStylusPoints == null)
{
return new StylusPointCollection(_tabletDevice.StylusPointDescription);
}
return _eventStylusPoints.Clone(StylusDevice.GetElementTransform(relativeTo), _eventStylusPoints.Description);
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns a StylusPointCollection object for processing the data in the packet.
/// This method creates a new StylusPointCollection and copies the data.
/// </summary>
internal override StylusPointCollection GetStylusPoints(IInputElement relativeTo, StylusPointDescription subsetToReformatTo)
{
ArgumentNullException.ThrowIfNull(subsetToReformatTo);
// Fake up an empty one if we have to.
if (_eventStylusPoints == null)
{
return new StylusPointCollection(subsetToReformatTo);
}
return _eventStylusPoints.Reformat(subsetToReformatTo, StylusDevice.GetElementTransform(relativeTo));
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the button collection that is associated with the StylusDevice.
/// </summary>
internal override StylusButtonCollection StylusButtons
{
get
{
VerifyAccess();
return _stylusButtonCollection;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Calculates the position of the stylus relative to a particular element.
/// </summary>
internal override 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 != 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 = PointUtil.ScreenToClient(_lastScreenLocation, relativePresentationSource);
Point ptRoot = PointUtil.ClientToRoot(ptClient, relativePresentationSource);
Point ptRelative = InputElement.TranslatePoint(ptRoot, relativePresentationSource.RootVisual, (DependencyObject)relativeTo);
return ptRelative;
}
/// <summary>
/// This will return the same result as GetPosition if the packet data points
/// are not modified in the StylusPlugIns, otherwise it will return the unmodified
/// data
/// </summary>
/// <param name="relativeTo"></param>
/// <returns></returns>
internal Point GetRawPosition(IInputElement relativeTo)
{
GeneralTransform transform = StylusDevice.GetElementTransform(relativeTo);
Point pt;
transform.TryTransform((Point)_rawPosition, out pt);
return pt;
}
internal override StylusPoint RawStylusPoint
{
get { return _rawPosition; }
}
/// <summary>
/// Gets the current state of the specified button
/// </summary>
/// <param name="mouseButton">
/// The mouse button to get the state of
/// </param>
/// <param name="mouseDevice">
/// The MouseDevice that is making the request
/// </param>
/// <returns>
/// The state of the specified mouse button
/// </returns>
/// <remarks>
/// This is the hook where the Input system (via the MouseDevice) can call back into
/// the Stylus system when we are processing Stylus events instead of Mouse events
/// </remarks>
internal override MouseButtonState GetMouseButtonState(MouseButton mouseButton, MouseDevice mouseDevice)
{
if (mouseButton == MouseButton.Left)
{
return _stylusLogic.GetMouseLeftOrRightButtonState(true);
}
if (mouseButton == MouseButton.Right)
{
return _stylusLogic.GetMouseLeftOrRightButtonState(false);
}
// can defer back to the mouse device that called you and it will call Win32
return mouseDevice.GetButtonStateFromSystem(mouseButton);
}
/// <summary>
/// Gets the current position of the mouse in screen co-ords
/// </summary>
/// <param name="mouseDevice">
/// The MouseDevice that is making the request
/// </param>
/// <returns>
/// The current mouse location in screen co-ords
/// </returns>
/// <remarks>
/// This is the hook where the Input system (via the MouseDevice) can call back into
/// the Stylus system when we are processing Stylus events instead of Mouse events
/// </remarks>
internal override Point GetMouseScreenPosition(MouseDevice mouseDevice)
{
if (mouseDevice == null)
{
// return the last location this stylus device promoted a mouse for.
return _lastMouseScreenLocation;
}
else
{
// The mouse device now caches the last location seen from the last input
// report so we can just call back to them to get the location. We don't
// need to return our cached location currrently.
return mouseDevice.GetScreenPositionFromSystem();
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Returns the transform for converting from tablet to element
/// relative coordinates.
/// </summary>
private GeneralTransform GetTabletToElementTransform(PresentationSource source, IInputElement relativeTo)
{
GeneralTransformGroup group = new GeneralTransformGroup();
group.Children.Add(new MatrixTransform(_stylusLogic.GetTabletToViewTransform(source, _tabletDevice.TabletDevice)));
group.Children.Add(StylusDevice.GetElementTransform(relativeTo));
return group;
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Indicates the stylus is not touching the surface.
/// InAir events are general sent at a lower frequency.
/// </summary>
internal override bool InAir
{
get
{
VerifyAccess();
return _fInAir;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Indicates stylusDevice is in the inverted state.
/// </summary>
internal override bool Inverted
{
get
{
VerifyAccess();
return _fInverted;
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// Indicates stylusDevice is in the inverted state.
/// </summary>
internal override bool InRange
{
get
{
VerifyAccess();
return _fInRange;
}
}
/////////////////////////////////////////////////////////////////////
internal override void UpdateEventStylusPoints(RawStylusInputReport report, bool resetIfNoOverride)
{
if (report.RawStylusInput != null && report.RawStylusInput.StylusPointsModified)
{
GeneralTransform transformToElement = report.RawStylusInput.Target.ViewToElement.Inverse;
//note that RawStylusInput.Target (of type StylusPluginCollection)
//guarantees that ViewToElement is invertible
Debug.Assert(transformToElement != null);
_eventStylusPoints = report.RawStylusInput.GetStylusPoints(transformToElement);
}
else if (resetIfNoOverride)
{
_eventStylusPoints =
new StylusPointCollection(report.StylusPointDescription,
report.GetRawPacketData(),
GetTabletToElementTransform(report.InputSource, null),
Matrix.Identity);
}
}
internal override int TapCount
{
get { return _tapCount; }
set { _tapCount = value; }
}
internal int LastTapTime
{
get { return _lastTapTime; }
set { _lastTapTime = value; }
}
internal Point LastTapPoint
{
get { return _lastTapXY; }
set { _lastTapXY = value; }
}
internal bool LastTapBarrelDown
{
get { return _lastTapBarrelDown; }
set { _lastTapBarrelDown = value; }
}
internal override int DoubleTapDeltaX
{
get { return (int)_tabletDevice.DoubleTapSize.Width; }
}
internal override int DoubleTapDeltaY
{
get { return (int)_tabletDevice.DoubleTapSize.Height; }
}
internal override int DoubleTapDeltaTime
{
get { return _stylusLogic.DoubleTapDeltaTime; }
}
/////////////////////////////////////////////////////////////////////
internal void UpdateState(RawStylusInputReport report)
{
Debug.Assert(report.TabletDeviceId == _tabletDevice.Id);
Debug.Assert((report.Actions & RawStylusActions.None) == 0);
_eventStylusPoints =
new StylusPointCollection(report.StylusPointDescription,
report.GetRawPacketData(),
GetTabletToElementTransform(report.InputSource, null),
Matrix.Identity);
PresentationSource inputSource = DetermineValidSource(report.InputSource, _eventStylusPoints, report.PenContext.Contexts);
// See if we need to remap the stylus data X and Y values to different presentation source.
if (inputSource != null && inputSource != report.InputSource)
{
Point newWindowLocation = PointUtil.ClientToScreen(new Point(0, 0), inputSource);
newWindowLocation = _stylusLogic.MeasureUnitsFromDeviceUnits(inputSource, newWindowLocation);
Point oldWindowLocation = _stylusLogic.MeasureUnitsFromDeviceUnits(report.InputSource, report.PenContext.Contexts.DestroyedLocation);
// Create translate matrix transform to shift coords to map points to new window location.
MatrixTransform additionalTransform = new MatrixTransform(new Matrix(1, 0, 0, 1,
oldWindowLocation.X - newWindowLocation.X,
oldWindowLocation.Y - newWindowLocation.Y));
_eventStylusPoints = _eventStylusPoints.Reformat(report.StylusPointDescription, additionalTransform);
}
_rawPosition = _eventStylusPoints[_eventStylusPoints.Count - 1];
_inputSource = inputSource;
if (inputSource is not null)
{
// Update our screen position from this move.
Point pt = _stylusLogic.DeviceUnitsFromMeasureUnits(inputSource, (Point)_rawPosition);
_lastScreenLocation = PointUtil.ClientToScreen(pt, inputSource);
}
// If we are not blocked from updating the location we want to use for the
// promoted mouse location then update it. We set this flag in the post process phase
// of Stylus events (after they have fired).
if (!_fBlockMouseMoveChanges)
{
_lastMouseScreenLocation = _lastScreenLocation;
}
if ((report.Actions & RawStylusActions.Down) != 0 ||
(report.Actions & RawStylusActions.Move) != 0)
{
_fInAir = false;
// Keep the stylus down location for turning system gestures into mouse event
if ((report.Actions & RawStylusActions.Down) != 0)
{
_needToSendMouseDown = true;
// reset the gesture flag. This is used to determine if we will need to fabricate a systemgesture tap on the
// corresponding up event.
_fGestureWasFired = false;
_fDetectedDrag = false;
_seenHoldEnterGesture = false;
// Make sure our drag and move deltas are up to date.
_tabletDevice.UpdateSizeDeltas(report.StylusPointDescription, _stylusLogic);
}
// See if we need to do our own Drag detection (on Stylus Move event)
else if (inputSource != null && _fBlockMouseMoveChanges && _seenDoubleTapGesture && !_fGestureWasFired && !_fDetectedDrag)
{
Size delta = _tabletDevice.CancelSize;
// We use the first point of the packet data for Drag detection to try and
// filter out cases where the stylus skips when going down.
Point dragPosition = (Point)_eventStylusPoints[0];
dragPosition = _stylusLogic.DeviceUnitsFromMeasureUnits(inputSource, dragPosition);
dragPosition = PointUtil.ClientToScreen(dragPosition, inputSource);
// See if we need to detect a Drag gesture. If so do the calculation.
if ((Math.Abs(_lastMouseScreenLocation.X - dragPosition.X) > delta.Width) ||
(Math.Abs(_lastMouseScreenLocation.Y - dragPosition.Y) > delta.Height))
{
_fDetectedDrag = true;
}
}
}
UpdateEventStylusPoints(report, false);
if ((report.Actions & RawStylusActions.Up) != 0 ||
(report.Actions & RawStylusActions.InAirMove) != 0)
{
_fInAir = true;
if ((report.Actions & RawStylusActions.Up) != 0)
{
_sawMouseButton1Down = false; // reset this on Stylus Up.
}
}
}
///////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////
private PresentationSource DetermineValidSource(PresentationSource inputSource, StylusPointCollection stylusPoints, PenContexts penContextsOfPoints)
{
HwndSource hwndSource = (HwndSource)inputSource;
// See if window has been closed or is invalid
if (inputSource.CompositionTarget == null || inputSource.CompositionTarget.IsDisposed ||
hwndSource == null || hwndSource.IsHandleNull)
{
PresentationSource newSource = null;
// use capture as fallback first
if (_stylusCapture != null)
{
DependencyObject containingVisual = InputElement.GetContainingVisual(_stylusCapture as DependencyObject);
PresentationSource capturedSource = PresentationSource.CriticalFromVisual(containingVisual);
if (capturedSource != null &&
capturedSource.CompositionTarget != null &&
!capturedSource.CompositionTarget.IsDisposed)
{
newSource = capturedSource; // Good new source to use!
}
}
// Now try last screen point hittesting to find a new window/PresetationSource.
if (newSource == null && stylusPoints != null)
{
Point ptScreen;
// If we have the last penContext and a valid CompositionTarget, then we can remap the coordinates properly.
// Otherwise we just use the last stylus mouse location to figure out a PresenationSource.
if (penContextsOfPoints?.InputSource?.CompositionTarget != null)
{
ptScreen = _stylusLogic.DeviceUnitsFromMeasureUnits(penContextsOfPoints.InputSource, (Point)stylusPoints[0]);
// map from window to screen (ie - add the window location).
ptScreen.Offset(penContextsOfPoints.DestroyedLocation.X, penContextsOfPoints.DestroyedLocation.Y);
}
else
{
ptScreen = _lastMouseScreenLocation;
}
IntPtr hwndHit = UnsafeNativeMethods.WindowFromPoint((int)ptScreen.X, (int)ptScreen.Y);
if (hwndHit != IntPtr.Zero)
{
HwndSource newHwndSource = HwndSource.CriticalFromHwnd(hwndHit);
if (newHwndSource != null && newHwndSource.Dispatcher == Dispatcher)
{
newSource = newHwndSource;
}
}
}
return newSource;
}
else
{
return inputSource;
}
}
/////////////////////////////////////////////////////////////////////
internal void UpdateInRange(bool inRange, PenContext penContext)
{
_fInRange = inRange;
// Make sure we clean the last _inputSource for down at this time.
//_inputSourceForDown = null;
if (inRange)
_activePenContext = penContext;
else
_activePenContext = null;
}
/////////////////////////////////////////////////////////////////////
internal void UpdateStateForSystemGesture(RawStylusSystemGestureInputReport report)
{
UpdateStateForSystemGesture(report.SystemGesture, report);
}
private void UpdateStateForSystemGesture(SystemGesture gesture, RawStylusSystemGestureInputReport report)
{
switch (gesture)
{
case SystemGesture.Tap:
case SystemGesture.Drag:
// request the next mouse move to become LeftButtonDown
_fLeftButtonDownTrigger = true;
_fGestureWasFired = true;
break;
case SystemGesture.RightTap:
case SystemGesture.RightDrag:
// request the next mouse move to become RightButtonDown
_fLeftButtonDownTrigger = false;
_fGestureWasFired = true;
break;
case SystemGesture.HoldEnter:
// press & hold animation started..
_seenHoldEnterGesture = true;
break;
case SystemGesture.Flick:
// We don't do any mouse promotion for a flick!
_fGestureWasFired = true;
// Update the stylus location info just for flick gestures. This is because
// we want to fire the flick event not from the last stylus location
// (end of flick gesture) but from the beginning of the flick gesture
// (stylus down point) since this is the element that we query whether they
// allow flicks and since scrolling is targetted we need to scroll the
// element you really flicked on.
// Only route the flick if we have data we can send.
if (report != null && report.InputSource != null && _eventStylusPoints != null && _eventStylusPoints.Count > 0)
{
StylusPoint stylusPoint = _eventStylusPoints[_eventStylusPoints.Count - 1];
stylusPoint.X = report.GestureX;
stylusPoint.Y = report.GestureY;
// Update the current point with this data.
_eventStylusPoints = new StylusPointCollection(stylusPoint.Description,
stylusPoint.GetPacketData(),
GetTabletToElementTransform(report.InputSource, null),
Matrix.Identity);
PresentationSource inputSource = DetermineValidSource(report.InputSource, _eventStylusPoints, report.PenContext.Contexts);
if (inputSource != null)
{
// See if we need to remap the stylus data X and Y values to different presentation source.
if (inputSource != report.InputSource)
{
Point newWindowLocation = PointUtil.ClientToScreen(new Point(0, 0), inputSource);
newWindowLocation = _stylusLogic.MeasureUnitsFromDeviceUnits(inputSource, newWindowLocation);
Point oldWindowLocation = _stylusLogic.MeasureUnitsFromDeviceUnits(report.InputSource, report.PenContext.Contexts.DestroyedLocation);
// Create translate matrix transform to shift coords to map points to new window location.
MatrixTransform additionalTransform = new MatrixTransform(new Matrix(1, 0, 0, 1,
oldWindowLocation.X - newWindowLocation.X,
oldWindowLocation.Y - newWindowLocation.Y));
_eventStylusPoints = _eventStylusPoints.Reformat(report.StylusPointDescription, additionalTransform);
}
_rawPosition = _eventStylusPoints[_eventStylusPoints.Count - 1];
_inputSource = inputSource;
Point pt = _stylusLogic.DeviceUnitsFromMeasureUnits(inputSource, (Point)_rawPosition);
_lastScreenLocation = PointUtil.ClientToScreen(pt, inputSource);
}
}
break;
}
}
/////////////////////////////////////////////////////////////////////
internal void PlayBackCachedDownInputReport(int timestamp)
{
if (_needToSendMouseDown)
{
// if we have marked this as handled we need to play the down otherwise we can ignore the down
// as it will be process anyway and either way we need to clean up the cached down
PresentationSource mouseInputSource = GetMousePresentationSource();
if (mouseInputSource != null)
{
Point pt = PointUtil.ScreenToClient(_lastMouseScreenLocation, mouseInputSource);
_needToSendMouseDown = false; // We've sent down, don't send again.
// Update the state we report to the mouse (GetButtonState).
_promotedMouseState = MouseButtonState.Pressed;
RawMouseActions actions = _fLeftButtonDownTrigger ? RawMouseActions.Button1Press : RawMouseActions.Button2Press;
// StylusLogic manages the mouse state reported to the MouseDevice to deal with multiple stylusdevice input.
if (_stylusLogic.UpdateMouseButtonState(actions))
{
// See if we need to set the Mouse Activate flag.
InputManager inputManager = (InputManager)Dispatcher.InputManager;
if (inputManager != null)
{
if (inputManager.PrimaryMouseDevice.CriticalActiveSource != mouseInputSource)
{
actions |= RawMouseActions.Activate;
}
}
RawMouseInputReport mouseInputReport = new RawMouseInputReport(
InputMode.Foreground, timestamp, mouseInputSource,
actions,
(int)pt.X, (int)pt.Y, 0, IntPtr.Zero);
InputReportEventArgs inputReportArgs = new InputReportEventArgs(StylusDevice, mouseInputReport);
inputReportArgs.RoutedEvent = InputManager.PreviewInputReportEvent;
_stylusLogic.InputManagerProcessInputEventArgs(inputReportArgs);
}
}
_needToSendMouseDown = false; // so we don't try and resend it later.
}
}
/////////////////////////////////////////////////////////////////////
internal PresentationSource GetMousePresentationSource()
{
// See if we need to adjust the mouse point to a different
// presentation source. We have to do this if the mouse has capture.
InputManager inputManager = (InputManager)Dispatcher.InputManager;
PresentationSource mouseInputSource = null;
if (inputManager != null)
{
IInputElement mouseCaptured = inputManager.PrimaryMouseDevice.Captured;
if (mouseCaptured != null)
{
// See if mouse is captured to a different window (HwndSource will be different)
// NOTE: Today we can only translate points between HwndSources (PresentationSource doesn't support this)
DependencyObject mouseCapturedVisual = InputElement.GetContainingVisual((DependencyObject)mouseCaptured);
if (mouseCapturedVisual != null)
{
mouseInputSource = PresentationSource.CriticalFromVisual(mouseCapturedVisual);
}
}
else if (_stylusOver != null)
{
// Use our current input source (or one we're may be over) if no capture.
mouseInputSource = (_inputSource is not null) ?
DetermineValidSource(_inputSource, _eventStylusPoints, null) : null;
}
}
return mouseInputSource;
}
internal RawMouseActions GetMouseActionsFromStylusEventAndPlaybackCachedDown(RoutedEvent stylusEvent, StylusEventArgs stylusArgs)
{
if (stylusEvent == Stylus.StylusSystemGestureEvent)
{
// See if this is an OK gesture to trigger a mouse event on.
StylusSystemGestureEventArgs systemGestureArgs = (StylusSystemGestureEventArgs)stylusArgs;
if (systemGestureArgs.SystemGesture == SystemGesture.Tap ||
systemGestureArgs.SystemGesture == SystemGesture.RightTap ||
systemGestureArgs.SystemGesture == SystemGesture.Drag ||
systemGestureArgs.SystemGesture == SystemGesture.RightDrag ||
systemGestureArgs.SystemGesture == SystemGesture.Flick)
{
// Usually UpdateStateForSystemGesture happens in the PreNotify.
// And UpdateState for other stylus events happens during mouse promotion.
// But with manipulations when events are stored for future promotion,
// this difference in order could cause problems. Hence reexecute
// UpdateStateForSystemGesture to fix the order.
UpdateStateForSystemGesture(systemGestureArgs.SystemGesture, null);
if (systemGestureArgs.SystemGesture == SystemGesture.Drag ||
systemGestureArgs.SystemGesture == SystemGesture.RightDrag ||
systemGestureArgs.SystemGesture == SystemGesture.Flick)
{
_fBlockMouseMoveChanges = false;
TapCount = 1; // reset on a drag or flick.
if (systemGestureArgs.SystemGesture == SystemGesture.Flick)
{
// Don't want to play down or cached moves.
_needToSendMouseDown = false;
}
else
{
PlayBackCachedDownInputReport(systemGestureArgs.Timestamp);
}
}
else //we have a Tap
{
PlayBackCachedDownInputReport(systemGestureArgs.Timestamp);
}
}
}
else if (stylusEvent == Stylus.StylusInAirMoveEvent)
{
return RawMouseActions.AbsoluteMove;
}
else if (stylusEvent == Stylus.StylusDownEvent)
{
_fLeftButtonDownTrigger = true; // Default to left click until system gesture says otherwise.
_fBlockMouseMoveChanges = true;
// See if we can promote the mouse button down right now.
if (_seenDoubleTapGesture || _sawMouseButton1Down)
{
PlayBackCachedDownInputReport(stylusArgs.Timestamp);
}
}
else if (stylusEvent == Stylus.StylusMoveEvent)
{
if (!_fBlockMouseMoveChanges)
{
return RawMouseActions.AbsoluteMove;
}
}
else if (stylusEvent == Stylus.StylusUpEvent)
{
var tempPromotedMouseState = _promotedMouseState;
ResetStateForStylusUp();
if (tempPromotedMouseState == MouseButtonState.Pressed)
{
RawMouseActions actions = _fLeftButtonDownTrigger ?
RawMouseActions.Button1Release :
RawMouseActions.Button2Release;
// Make sure we only promote a mouse up if the mouse is in the down
// state (UpdateMousebuttonState returns true in that case)!
if (_stylusLogic.UpdateMouseButtonState(actions))
{
return actions;
}
// else - just return default of RawMouseActions.None since we don't want this
// duplicate mouse up to be processed.
}
}
// Default return
return RawMouseActions.None;
}
// Reset all StylusDevice state in response to a StylusUp
internal void ResetStateForStylusUp()
{
_fBlockMouseMoveChanges = false;
_seenDoubleTapGesture = false; // reset this on Stylus Up.
_sawMouseButton1Down = false; // reset to make sure we don't promote a mouse down on the next stylus down.
if (_promotedMouseState == MouseButtonState.Pressed)
{
_promotedMouseState = MouseButtonState.Released;
}
}
/////////////////////////////////////////////////////////////////////
internal Point LastMouseScreenPoint
{
get { return _lastMouseScreenLocation; }
set { _lastMouseScreenLocation = value; }
}
internal bool SeenDoubleTapGesture
{
get { return _seenDoubleTapGesture; }
set { _seenDoubleTapGesture = value; }
}
internal bool SeenHoldEnterGesture
{
get { return _seenHoldEnterGesture; }
}
internal bool GestureWasFired
{
get { return _fGestureWasFired; }
}
internal bool SentMouseDown
{
get { return _promotedMouseState == MouseButtonState.Pressed; }
}
internal bool DetectedDrag
{
get { return _fDetectedDrag; }
}
internal bool LeftIsActiveMouseButton
{
get { return _fLeftButtonDownTrigger; }
}
internal void SetSawMouseButton1Down(bool sawMouseButton1Down)
{
_sawMouseButton1Down = sawMouseButton1Down;
}
internal bool IgnoreStroke
{
get { return _ignoreStroke; }
set { _ignoreStroke = value; }
}
#region Touch
internal WispStylusTouchDevice TouchDevice
{
get
{
if (_touchDevice == null)
{
_touchDevice = new WispStylusTouchDevice(this);
}
return _touchDevice;
}
}
internal void UpdateTouchActiveSource()
{
if (_touchDevice != null)
{
PresentationSource activeSource = CriticalActiveSource;
if (activeSource != null)
{
_touchDevice.ChangeActiveSource(activeSource);
}
}
}
#endregion
#if MULTICAPTURE
private DeferredElementTreeState StylusOverTreeState
{
get
{
if (_stylusOverTreeState == null)
{
_stylusOverTreeState = new DeferredElementTreeState();
}
return _stylusOverTreeState;
}
}
private DeferredElementTreeState StylusCaptureWithinTreeState
{
get
{
if (_stylusCaptureWithinTreeState == null)
{
_stylusCaptureWithinTreeState = new DeferredElementTreeState();
}
return _stylusCaptureWithinTreeState;
}
}
#endif
/////////////////////////////////////////////////////////////////////
WispTabletDevice _tabletDevice;
string _sName;
int _id;
bool _fInverted;
bool _fInRange;
StylusButtonCollection _stylusButtonCollection;
IInputElement _stylusOver;
#if MULTICAPTURE
private DeferredElementTreeState _stylusOverTreeState;
#endif
IInputElement _stylusCapture;
CaptureMode _captureMode;
#if MULTICAPTURE
private DeferredElementTreeState _stylusCaptureWithinTreeState;
#endif
StylusPoint _rawPosition = new StylusPoint(0, 0);
Point _rawElementRelativePosition = new Point(0, 0);
StylusPointCollection _eventStylusPoints;
private PresentationSource _inputSource;
private PenContext _activePenContext;
bool _needToSendMouseDown;
private Point _lastMouseScreenLocation = new Point(0, 0);
private Point _lastScreenLocation = new Point(0, 0);
bool _fInAir = true;
bool _fLeftButtonDownTrigger = true; // default to left button down
bool _fGestureWasFired = true; // StylusDown resets this.
bool _fBlockMouseMoveChanges; // StylusDown sets to true, SystemGesture & StylusUp sets to false.
bool _fDetectedDrag; // StylusDown resets this. Used for generating DoubleTap gestures.
// Used to track the promoted mouse state.
MouseButtonState _promotedMouseState;
// real time pen input info that is tracked per stylus device
StylusPlugInCollection _nonVerifiedTarget;
StylusPlugInCollection _verifiedTarget;
object _rtiCaptureChanged = new object();
StylusPlugInCollection _stylusCapturePlugInCollection;
// Information used to distinguish double-clicks (actually, multi clicks) from
// multiple independent clicks.
private Point _lastTapXY = new Point(0, 0);
private int _tapCount;
private int _lastTapTime;
private bool _lastTapBarrelDown;
private bool _seenDoubleTapGesture;
private bool _seenHoldEnterGesture;
private bool _sawMouseButton1Down; // Did we see the mouse down before the stylus down?
private bool _ignoreStroke; // Should we ignore promoting the stylus/mouse events for the current stroke?
private WispLogic _stylusLogic;
private WispStylusTouchDevice _touchDevice;
#if MULTICAPTURE
private DependencyPropertyChangedEventHandler _overIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _overIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateStylusOverDelegate;
private DispatcherOperation _reevaluateStylusOverOperation;
private DependencyPropertyChangedEventHandler _captureIsEnabledChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsVisibleChangedEventHandler;
private DependencyPropertyChangedEventHandler _captureIsHitTestVisibleChangedEventHandler;
private DispatcherOperationCallback _reevaluateCaptureDelegate;
private DispatcherOperation _reevaluateCaptureOperation;
#endif
}
}
|