|
// 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.Windows.Input.StylusWisp;
using System.Windows.Media;
using System.Windows.Input.StylusPlugIns;
using System.Windows.Interop;
namespace System.Windows.Input
{
internal sealed class PenContexts
{
internal PenContexts(WispLogic stylusLogic, PresentationSource inputSource)
{
HwndSource hwndSource = inputSource as HwndSource;
if(hwndSource == null || IntPtr.Zero == (hwndSource).Handle)
{
throw new InvalidOperationException(SR.Stylus_PenContextFailure);
}
_stylusLogic = stylusLogic;
_inputSource = hwndSource;
}
/////////////////////////////////////////////////////////////////////////
internal void Enable()
{
if (_contexts == null)
{
// create contexts
_contexts = _stylusLogic.WispTabletDevices.CreateContexts(_inputSource.Handle, this);
foreach(PenContext context in _contexts)
{
context.Enable();
}
}
}
internal void Disable(bool shutdownWorkerThread)
{
if (_contexts != null)
{
foreach(PenContext context in _contexts)
{
context.Disable(shutdownWorkerThread);
}
_contexts = null; // release refs on PenContext objects
}
}
internal bool IsWindowDisabled
{
get { return _isWindowDisabled; }
set { _isWindowDisabled = value; }
}
internal Point DestroyedLocation
{
get { return _destroyedLocation; }
set { _destroyedLocation = value; }
}
/////////////////////////////////////////////////////////////////////
internal void OnPenDown(PenContext penContext, int tabletDeviceId, int stylusPointerId, int[] data, int timestamp)
{
ProcessInput(RawStylusActions.Down, penContext, tabletDeviceId, stylusPointerId, data, timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnPenUp(PenContext penContext, int tabletDeviceId, int stylusPointerId, int[] data, int timestamp)
{
ProcessInput(RawStylusActions.Up, penContext, tabletDeviceId, stylusPointerId, data, timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnPackets(PenContext penContext, int tabletDeviceId, int stylusPointerId, int[] data, int timestamp)
{
ProcessInput(RawStylusActions.Move, penContext, tabletDeviceId, stylusPointerId, data, timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnInAirPackets(PenContext penContext, int tabletDeviceId, int stylusPointerId, int[] data, int timestamp)
{
ProcessInput(RawStylusActions.InAirMove, penContext, tabletDeviceId, stylusPointerId, data, timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnPenInRange(PenContext penContext, int tabletDeviceId, int stylusPointerId, int[] data, int timestamp)
{
ProcessInput(RawStylusActions.InRange, penContext, tabletDeviceId, stylusPointerId, data, timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnPenOutOfRange(PenContext penContext, int tabletDeviceId, int stylusPointerId, int timestamp)
{
ProcessInput(RawStylusActions.OutOfRange, penContext, tabletDeviceId, stylusPointerId, Array.Empty<int>(), timestamp);
}
/////////////////////////////////////////////////////////////////////
internal void OnSystemEvent(PenContext penContext,
int tabletDeviceId,
int stylusPointerId,
int timestamp,
SystemGesture id,
int gestureX,
int gestureY,
int buttonState)
{
_stylusLogic.ProcessSystemEvent(penContext,
tabletDeviceId,
stylusPointerId,
timestamp,
id,
gestureX,
gestureY,
buttonState,
_inputSource);
}
/////////////////////////////////////////////////////////////////////
void ProcessInput(
RawStylusActions actions,
PenContext penContext,
int tabletDeviceId,
int stylusPointerId,
int[] data, int timestamp)
{
// (all events but SystemEvent go thru here)
_stylusLogic.ProcessInput(
actions,
penContext,
tabletDeviceId,
stylusPointerId,
data,
timestamp,
_inputSource);
}
/////////////////////////////////////////////////////////////////////////
internal PenContext GetTabletDeviceIDPenContext(int tabletDeviceId)
{
if (_contexts != null)
{
for (int i = 0; i < _contexts.Length; i++)
{
PenContext context = _contexts[i];
if (context.TabletDeviceId == tabletDeviceId)
return context;
}
}
return null;
}
/////////////////////////////////////////////////////////////////////////
internal bool ConsiderInRange(int timestamp)
{
if (_contexts != null)
{
for (int i = 0; i < _contexts.Length; i++)
{
PenContext context = _contexts[i];
// We consider it InRange if we have a queued up context event or
// the timestamp - LastInRangeTime <= 500 (seen one in the last 500ms)
// Here's some info on how this works...
// int.MaxValue - int.MinValue = -1 (subtracting any negative # from MaxValue keeps this negative)
// int.MinValue - int.MaxValue = 1 (subtracting any positive # from MinValue keeps this positive)
// So subtracting wrapping values will return proper sign depending on which was earlier.
// We do have the assumption that these values will be relative close in time. If the
// time wraps we'll say yet but the only harm is that we may defer a mouse move event temporarily
// which won't cause any harm.
if (context.QueuedInRangeCount > 0 || (Math.Abs(unchecked(timestamp - context.LastInRangeTime)) <= 500))
return true;
}
}
return false;
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// This method adds the specified pen context index in response
/// to the WM_TABLET_ADDED notification
/// </summary>
internal void AddContext(uint index)
{
// We only tear down the old context when PenContexts are enabled without being
// dispose and we have a valid index. Otherwise, no-op here.
if (_contexts is not null && index <= _contexts.Length && _inputSource.Handle != 0)
{
PenContext[] ctxs = new PenContext[_contexts.Length + 1];
uint preCopyCount = index;
uint postCopyCount = (uint)_contexts.Length - index;
Array.Copy(_contexts, 0, ctxs, 0, preCopyCount);
PenContext newContext = _stylusLogic.TabletDevices[(int)index].As<WispTabletDevice>().CreateContext(_inputSource.Handle, this);
ctxs[index] = newContext;
Array.Copy(_contexts, index, ctxs, index+1, postCopyCount);
_contexts = ctxs;
newContext.Enable();
}
}
/////////////////////////////////////////////////////////////////////
/// <summary>
/// This method removes the specified pen context index in response
/// to the WM_TABLET_REMOVED notification
/// </summary>
internal void RemoveContext(uint index)
{
// We only tear down the old context when PenContexts are enabled without being
// dispose and we have a valid index. Otherwise, no-op here.
if (_contexts != null && index < _contexts.Length)
{
PenContext removeCtx = _contexts[index];
PenContext[] ctxs = new PenContext[_contexts.Length - 1];
uint preCopyCount = index;
uint postCopyCount = (uint)_contexts.Length - index - 1;
Array.Copy(_contexts, 0, ctxs, 0, preCopyCount);
Array.Copy(_contexts, index+1, ctxs, index, postCopyCount);
removeCtx.Disable(false); // shut down this context.
_contexts = ctxs;
}
}
/////////////////////////////////////////////////////////////////////
internal object SyncRoot
{
get
{
return __rtiLock;
}
}
internal void AddStylusPlugInCollection(StylusPlugInCollection pic)
{
// must be called from inside of lock(__rtiLock)
// insert in ZOrder
_plugInCollectionList.Insert(FindZOrderIndex(pic), pic);
}
internal void RemoveStylusPlugInCollection(StylusPlugInCollection pic)
{
// must be called from inside of lock(__rtiLock)
_plugInCollectionList.Remove(pic);
}
internal int FindZOrderIndex(StylusPlugInCollection spicAdding)
{
//should be called inside of lock(__rtiLock)
DependencyObject spicAddingVisual = spicAdding.Element as Visual;
int i;
for (i=0; i < _plugInCollectionList.Count; i++)
{
// first see if parent of node, if it is then we can just scan till we find the
// first non parent and we're done
DependencyObject curV = _plugInCollectionList[i].Element as Visual;
if (VisualTreeHelper.IsAncestorOf(spicAddingVisual, curV))
{
i++;
while (i < _plugInCollectionList.Count)
{
curV = _plugInCollectionList[i].Element as Visual;
if (!VisualTreeHelper.IsAncestorOf(spicAddingVisual, curV))
break; // done
i++;
}
return i;
}
else
{
// Look to see if spicAddingVisual is higher in ZOrder than i, if so then we're done
DependencyObject commonParent = VisualTreeHelper.FindCommonAncestor(spicAddingVisual, curV);
// If no common parent found then we must have multiple plugincollection elements
// that have been removed from the visual tree and we haven't been notified yet of
// that change. In this case just ignore this plugincollection element and go to
// the next.
if (commonParent == null)
continue;
// If curV is the commonParent we find then we're done. This new plugin should be
// above this one.
if (curV == commonParent)
return i;
// now find first child for each under that common visual that these fall under (not they must be different or common parent is sort of busted.
while (VisualTreeHelper.GetParentInternal(spicAddingVisual) != commonParent)
spicAddingVisual = VisualTreeHelper.GetParentInternal(spicAddingVisual);
while (VisualTreeHelper.GetParentInternal(curV) != commonParent)
curV = VisualTreeHelper.GetParentInternal(curV);
// now see which is higher in zorder
int count = VisualTreeHelper.GetChildrenCount(commonParent);
for (int j = 0; j < count; j++)
{
DependencyObject child = VisualTreeHelper.GetChild(commonParent, j);
if (child == spicAddingVisual)
return i;
else if (child == curV)
break; // look at next index in _piList.
}
}
}
return i; // this wasn't higher so return last index.
}
internal StylusPlugInCollection InvokeStylusPluginCollectionForMouse(RawStylusInputReport inputReport, IInputElement directlyOver, StylusPlugInCollection currentPlugInCollection)
{
StylusPlugInCollection newPlugInCollection = null;
// lock to make sure only one event is processed at a time and no changes to state can
// be made until we finish routing this event.
lock(__rtiLock)
{
//Debug.Assert(inputReport.Actions == RawStylusActions.Down ||
// inputReport.Actions == RawStylusActions.Up ||
// inputReport.Actions == RawStylusActions.Move);
// Find new target plugin collection
if (directlyOver != null)
{
UIElement uiElement = InputElement.GetContainingUIElement(directlyOver as DependencyObject) as UIElement;
if (uiElement != null)
{
newPlugInCollection = FindPlugInCollection(uiElement);
}
}
// Fire Leave event to old pluginCollection if we need to.
if (currentPlugInCollection != null && currentPlugInCollection != newPlugInCollection)
{
// NOTE: input report points for mouse are in avalon measured units and not device!
RawStylusInput tempRawStylusInput = new RawStylusInput(inputReport, currentPlugInCollection.ViewToElement, currentPlugInCollection);
currentPlugInCollection.FireEnterLeave(false, tempRawStylusInput, true);
// Indicate we've used a stylus plugin
_stylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
}
if (newPlugInCollection != null)
{
// NOTE: input report points for mouse are in avalon measured units and not device!
RawStylusInput rawStylusInput = new RawStylusInput(inputReport, newPlugInCollection.ViewToElement, newPlugInCollection);
inputReport.RawStylusInput = rawStylusInput;
if (newPlugInCollection != currentPlugInCollection)
{
newPlugInCollection.FireEnterLeave(true, rawStylusInput, true);
}
// We are on the pen thread, just call directly.
newPlugInCollection.FireRawStylusInput(rawStylusInput);
// Indicate we've used a stylus plugin
_stylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
// Fire custom data events (always confirmed for mouse)
foreach (RawStylusInputCustomData customData in rawStylusInput.CustomDataList)
{
customData.Owner.FireCustomData(customData.Data, inputReport.Actions, true);
}
}
}
return newPlugInCollection;
}
internal void InvokeStylusPluginCollection(RawStylusInputReport inputReport)
{
// Find PenContexts object for this inputReport.
StylusPlugInCollection pic = null;
// lock to make sure only one event is processed at a time and no changes to state can
// be made until we finish routing this event.
lock(__rtiLock)
{
switch (inputReport.Actions)
{
case RawStylusActions.Down:
case RawStylusActions.Move:
case RawStylusActions.Up:
// Figure out current target plugincollection.
pic = TargetPlugInCollection(inputReport);
break;
default:
return; // Nothing to do unless one of the above events
}
WispStylusDevice stylusDevice = inputReport.StylusDevice.As<WispStylusDevice>();
StylusPlugInCollection currentPic = stylusDevice.CurrentNonVerifiedTarget;
// Fire Leave event if we need to.
if (currentPic != null && currentPic != pic)
{
// Create new RawStylusInput to send
GeneralTransformGroup transformTabletToView = new GeneralTransformGroup();
transformTabletToView.Children.Add(new MatrixTransform(_stylusLogic.GetTabletToViewTransform(inputReport.InputSource, stylusDevice.TabletDevice))); // this gives matrix in measured units (not device)
transformTabletToView.Children.Add(currentPic.ViewToElement); // Make it relative to the element.
transformTabletToView.Freeze(); // Must be frozen for multi-threaded access.
RawStylusInput tempRawStylusInput = new RawStylusInput(inputReport, transformTabletToView, currentPic);
currentPic.FireEnterLeave(false, tempRawStylusInput, false);
// Indicate we've used a stylus plugin
_stylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
stylusDevice.CurrentNonVerifiedTarget = null;
}
if (pic != null)
{
// NOTE: PenContext info will not change (it gets rebuilt instead so keeping ref is fine)
// The transformTabletToView matrix and plugincollection rects though can change based
// off of layout events which is why we need to lock this.
GeneralTransformGroup transformTabletToView = new GeneralTransformGroup();
transformTabletToView.Children.Add(new MatrixTransform(_stylusLogic.GetTabletToViewTransform(inputReport.InputSource, stylusDevice.TabletDevice))); // this gives matrix in measured units (not device)
transformTabletToView.Children.Add(pic.ViewToElement); // Make it relative to the element.
transformTabletToView.Freeze(); // Must be frozen for multi-threaded access.
RawStylusInput rawStylusInput = new RawStylusInput(inputReport, transformTabletToView, pic);
inputReport.RawStylusInput = rawStylusInput;
if (pic != currentPic)
{
stylusDevice.CurrentNonVerifiedTarget = pic;
pic.FireEnterLeave(true, rawStylusInput, false);
}
// We are on the pen thread, just call directly.
pic.FireRawStylusInput(rawStylusInput);
// Indicate we've used a stylus plugin
_stylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed;
}
} // lock(__rtiLock)
}
internal StylusPlugInCollection TargetPlugInCollection(RawStylusInputReport inputReport)
{
// Caller must make call to this routine inside of lock(__rtiLock)!
StylusPlugInCollection pic = null;
// We should only be called when not on the application thread!
System.Diagnostics.Debug.Assert(!inputReport.StylusDevice.CheckAccess());
WispStylusDevice stylusDevice = inputReport.StylusDevice.As<WispStylusDevice>();
// We're on the pen thread so can't touch visual tree. Use capturedPlugIn (if capture on) or cached rects.
bool elementHasCapture = false;
pic = stylusDevice.GetCapturedPlugInCollection(ref elementHasCapture);
int pointLength = inputReport.PenContext.StylusPointDescription.GetInputArrayLengthPerPoint();
// Make sure that the captured Plugin Collection is still in the list. CaptureChanges are
// deferred so there is a window where the stylus device is not updated yet. This protects us
// from using a bogus plugin collecton for an element that is in an invalid state.
if (elementHasCapture && !_plugInCollectionList.Contains(pic))
{
elementHasCapture = false; // force true hittesting to be done!
}
if (!elementHasCapture && inputReport.Data != null && inputReport.Data.Length >= pointLength)
{
int[] data = inputReport.Data;
System.Diagnostics.Debug.Assert(data.Length % pointLength == 0);
Point ptTablet = new Point(data[data.Length - pointLength], data[data.Length - pointLength + 1]);
// Note: the StylusLogic data inside DeviceUnitsFromMeasurUnits is protected by __rtiLock.
ptTablet = ptTablet * stylusDevice.TabletDevice.TabletDeviceImpl.TabletToScreen;
ptTablet.X = (int)Math.Round(ptTablet.X); // Make sure we snap to whole window pixels.
ptTablet.Y = (int)Math.Round(ptTablet.Y);
ptTablet = _stylusLogic.MeasureUnitsFromDeviceUnits(stylusDevice.CriticalActiveSource, ptTablet); // change to measured units now.
pic = HittestPlugInCollection(ptTablet); // Use cached rectangles for UIElements.
}
return pic;
}
/////////////////////////////////////////////////////////////////////
// NOTE: this should only be called inside of app Dispatcher
internal StylusPlugInCollection FindPlugInCollection(UIElement element)
{
// Since we are only called on app Dispatcher this cannot change out from under us.
// System.Diagnostics.Debug.Assert(_stylusLogic.Dispatcher.CheckAccess());
foreach (StylusPlugInCollection plugInCollection in _plugInCollectionList)
{
// If same element or element is child of the plugincollection element than say we hit it.
if (plugInCollection.Element == element ||
(plugInCollection.Element as Visual).IsAncestorOf(element as Visual))
{
return plugInCollection;
}
}
return null;
}
/////////////////////////////////////////////////////////////////////
// NOTE: this is called on pen thread (outside of apps Dispatcher)
StylusPlugInCollection HittestPlugInCollection(Point pt)
{
// Caller must make call to this routine inside of lock(__rtiLock)!
foreach (StylusPlugInCollection plugInCollection in _plugInCollectionList)
{
if (plugInCollection.IsHit(pt))
{
return plugInCollection;
}
}
return null;
}
internal HwndSource InputSource => _inputSource;
/////////////////////////////////////////////////////////////////////
internal HwndSource _inputSource;
WispLogic _stylusLogic;
object __rtiLock = new object();
List<StylusPlugInCollection> _plugInCollectionList = new List<StylusPlugInCollection>();
PenContext[] _contexts;
bool _isWindowDisabled;
Point _destroyedLocation = new Point(0,0);
}
}
|