internal void Update(HwndPointerInputProvider provider, PresentationSource inputSource, PointerData pointerData, RawStylusInputReport rsir) { _lastEventTimeTicks = Environment.TickCount; _inputSource = new SecurityCriticalDataClass <PresentationSource>(inputSource); _pointerData = pointerData; // First get the initial stylus points. Raw data from pointer input comes in screen coordinates, keep that here since that is what we expect. _currentStylusPoints = new StylusPointCollection(rsir.StylusPointDescription, rsir.GetRawPacketData(), GetTabletToElementTransform(null), Matrix.Identity); // If a plugin has modified these points, we need to fixup the points with the new input if (rsir?.RawStylusInput?.StylusPointsModified ?? false) { // Note that RawStylusInput.Target (of type StylusPluginCollection) // guarantees that ViewToElement is invertible. GeneralTransform transformToElement = rsir.RawStylusInput.Target.ViewToElement.Inverse; Debug.Assert(transformToElement != null); _currentStylusPoints = rsir.RawStylusInput.GetStylusPoints(transformToElement); } // Store the current hwnd provider so we know for what hwnd we are processing this message CurrentPointerProvider = provider; if (PointerTabletDevice.Type == TabletDeviceType.Touch) { // If we are a touch device, sync the ActiveSource TouchDevice.ChangeActiveSource(_inputSource.Value); } }
private void DetectFlick(RawStylusInputReport rsir) { // Make sure the flick engine has the latest data if applicable. _flickEngine?.Update(rsir); // If we have received an up then we should check if // we can still be a flick. If this is true, then fire a flick. if (rsir.Actions == RawStylusActions.Up && (_flickEngine?.Result?.CanBeFlick ?? false)) { // InteractionDetected?.Invoke(this, new RawStylusSystemGestureInputReport( InputMode.Foreground, Environment.TickCount, _stylusDevice.CriticalActiveSource, (Func <StylusPointDescription>)null, -1, -1, SystemGesture.Flick, Convert.ToInt32(_flickEngine.Result.TabletStart.X), Convert.ToInt32(_flickEngine.Result.TabletStart.Y), 0)); _firedFlick = true; } }
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 != null && _inputSource.Value != null && _inputSource.Value.CompositionTarget != null && !_inputSource.Value.CompositionTarget.IsDisposed) { Point rawScreenPoint = new Point(_pointerData.Info.ptPixelLocationRaw.X, _pointerData.Info.ptPixelLocationRaw.Y); Point ptDevice = PointUtil.ScreenToClient(rawScreenPoint, _inputSource.Value); // GlobalHitTest always returns an IInputElement, so we are sure to have one. IInputElement stylusOver = Input.StylusDevice.GlobalHitTest(_inputSource.Value, 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; if (_currentStylusPoints != null && _currentStylusPoints.Count > 0 && StylusPointDescription.AreCompatible(PointerTabletDevice.StylusPointDescription, _currentStylusPoints.Description)) { StylusPoint stylusPoint = _currentStylusPoints[_currentStylusPoints.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.Value, InAir ? RawStylusActions.InAirMove : RawStylusActions.Move, () => { return(PointerTabletDevice.StylusPointDescription); }, TabletDevice.Id, Id, data); report.Synchronized = true; InputReportEventArgs inputReportEventArgs = new InputReportEventArgs(StylusDevice, report); inputReportEventArgs.RoutedEvent = InputManager.PreviewInputReportEvent; InputManager.Current.ProcessInput(inputReportEventArgs); } } } }
/// <summary> /// </summary> internal StylusPlugInCollection InvokeStylusPluginCollectionForMouse(RawStylusInputReport inputReport, IInputElement directlyOver, StylusPlugInCollection currentPlugInCollection) { StylusPlugInCollection newPlugInCollection = null; // 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.CurrentStylusLogic.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); } // (Comment not applicable until RTI back in) We are on the RTI thread, just call directly. newPlugInCollection.FireRawStylusInput(rawStylusInput); // Indicate we've used a stylus plugin StylusLogic.CurrentStylusLogic.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 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); _currentStylusPoints = report.RawStylusInput.GetStylusPoints(transformToElement); } else if (resetIfNoOverride) { _currentStylusPoints = new StylusPointCollection(report.StylusPointDescription, report.GetRawPacketData(), GetTabletToElementTransform(null), Matrix.Identity); } }
internal void Update(RawStylusInputReport rsir) { try { // Queue up the latest message for processing UnsafeNativeMethods.BufferPointerPacketsInteractionContext(_interactionContext.Value, 1, new UnsafeNativeMethods.POINTER_INFO[] { _stylusDevice.CurrentPointerInfo }); // Hover processing should occur directly from message receipt. // Do this prior to the IC engine processing so HoverEnter/Leave has priority. DetectHover(); // DetectFlick(rsir); // Fire processing of the queued messages UnsafeNativeMethods.ProcessBufferedPacketsInteractionContext(_interactionContext.Value); } catch { } }
///////////////////////////////////////////////////////////////////// /// <summary> /// [TBS] /// </summary> /// <param name="report">[TBS]</param> /// <param name="tabletToElementTransform">[TBS]</param> /// <param name="targetPlugInCollection">[TBS]</param> internal RawStylusInput( RawStylusInputReport report, GeneralTransform tabletToElementTransform, StylusPlugInCollection targetPlugInCollection) { if (report == null) { throw new ArgumentNullException("report"); } if (tabletToElementTransform.Inverse == null) { throw new ArgumentException(SR.Get(SRID.Stylus_MatrixNotInvertable), "tabletToElementTransform"); } if (targetPlugInCollection == null) { throw new ArgumentNullException("targetPlugInCollection"); } // We should always see this GeneralTransform is frozen since we access this from multiple threads. System.Diagnostics.Debug.Assert(tabletToElementTransform.IsFrozen); _report = report; _tabletToElementTransform = tabletToElementTransform; _targetPlugInCollection = targetPlugInCollection; }
/// <summary> /// Target a plugin collection that either has capture or passes a hit test. /// raw input. /// </summary> /// <param name="inputReport">The raw input to process</param> /// <returns>The appropriate collection target</returns> internal StylusPlugInCollection TargetPlugInCollection(RawStylusInputReport inputReport) { StylusPlugInCollection pic = null; PointerStylusDevice stylusDevice = inputReport.StylusDevice.As <PointerStylusDevice>(); bool elementHasCapture = false; pic = stylusDevice.GetCapturedPlugInCollection(ref elementHasCapture); int pointLength = inputReport.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 *= inputReport.InputSource.CompositionTarget.TransformFromDevice; // change to measured units now. pic = HittestPlugInCollection(ptTablet); // Use cached rectangles for UIElements. } return(pic); }
/// <summary> /// Processes the latest WM_POINTER message and forwards it to the WPF input stack. /// </summary> /// <param name="pointerId">The id of the pointer message</param> /// <param name="action">The stylus action being done</param> /// <param name="timestamp">The time (in ticks) the message arrived</param> /// <returns>True if successfully processed (handled), false otherwise</returns> private bool ProcessMessage(uint pointerId, RawStylusActions action, int timestamp) { bool handled = false; // Acquire all pointer data needed PointerData data = new PointerData(pointerId); // Only process touch or pen messages, do not process mouse or touchpad if (data.IsValid && (data.Info.pointerType == UnsafeNativeMethods.POINTER_INPUT_TYPE.PT_TOUCH || data.Info.pointerType == UnsafeNativeMethods.POINTER_INPUT_TYPE.PT_PEN)) { uint cursorId = 0; if (UnsafeNativeMethods.GetPointerCursorId(pointerId, ref cursorId)) { IntPtr deviceId = data.Info.sourceDevice; // If we cannot acquire the latest tablet and stylus then wait for the // next message. if (!UpdateCurrentTabletAndStylus(deviceId, cursorId)) { return(false); } // Convert move to InAirMove if applicable if (action == RawStylusActions.Move && (!data.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INCONTACT) && data.Info.pointerFlags.HasFlag(UnsafeNativeMethods.POINTER_FLAGS.POINTER_FLAG_INRANGE))) { action = RawStylusActions.InAirMove; } // Generate a raw input to send to the input manager to start the event chain in PointerLogic RawStylusInputReport rsir = new RawStylusInputReport( InputMode.Foreground, timestamp, _source.Value, action, () => { return(_currentTabletDevice.StylusPointDescription); }, _currentTabletDevice.Id, _currentStylusDevice.Id, GenerateRawStylusData(data, _currentTabletDevice)) { StylusDevice = _currentStylusDevice.StylusDevice, }; // Send the input report to the stylus plugins if we're not doing a drag and the window // is currently enabled. if (!_pointerLogic.Value.InDragDrop && IsWindowEnabled) { PointerStylusPlugInManager manager; if (_pointerLogic.Value.PlugInManagers.TryGetValue(_source.Value, out manager)) { manager.InvokeStylusPluginCollection(rsir); } } // Update the data in the stylus device with the latest pointer data _currentStylusDevice.Update(this, _source.Value, data, rsir); // Call the StylusDevice to process and fire any interactions that // might have resulted from the input. If the originating inputs // have been handled, we don't want to generate any gestures. _currentStylusDevice.UpdateInteractions(rsir); InputReportEventArgs irea = new InputReportEventArgs(_currentStylusDevice.StylusDevice, rsir) { RoutedEvent = InputManager.PreviewInputReportEvent, }; // Now send the input report InputManager.UnsecureCurrent.ProcessInput(irea); // If this is not a primary pointer input, we don't want to // allow it to go to DefWindowProc, so we should handle it. // This ensures that primary pointer to mouse promotions // will occur in multi-touch scenarios. // We don't use the results of the input processing here as doing // so could possibly cause some messages of a pointer chain as // being handled, and some as being unhandled. This results in // undefined behavior in the WM_POINTER stack. // <see cref="https://msdn.microsoft.com/en-us/library/windows/desktop/hh454923(v=vs.85).aspx"/> handled = !_currentStylusDevice.IsPrimary; } } return(handled); }
internal void UpdateInteractions(RawStylusInputReport rsir) { _interactionEngine.Update(rsir); }
private void ProcessPacket(RawStylusInputReport rsir, bool initial) { UpdateTimePeriod(rsir.Timestamp, initial); if (!_collectingData) { return; } Point tabletPoint = rsir.GetLastTabletPoint(); // Get the device coordinates in HiMetric Point physPoint = GetPhysicalCoordinates(tabletPoint); if (initial) { _flickStartPhysical = physPoint; _flickStartTablet = tabletPoint; _elapsedTime = 0; SetStableRect(); } else { _elapsedTime += _timePeriod; } if (!_movedEnoughFromPenDown) { if (_lastPhysicalPointValid) { double dist = Distance(_lastPhysicalPoint, physPoint); _distance += dist; // If at any time a fair distance is moved from packet to packet // or the entire distance traveled thus far is greater than the side // of m_rcdrag, we say the pen has moved enough and start the flick // detection if ((dist > PreciseFlickMinimumVelocity) || (dist >= _flickMaximumStationaryDisplacementX)) { _movedEnoughFromPenDown = true; } } if (!_movedEnoughFromPenDown) { // If the pen has left the m_rcDrag rect or if adequate time // has elapsed, start the flick detection if (!_drag.Contains(physPoint) || (_elapsedTime > 3000)) { _movedEnoughFromPenDown = true; } } _lastPhysicalPoint = physPoint; _lastPhysicalPointValid = true; } if (_movedEnoughFromPenDown && !_analyzingData) { CheckWithThreshold(physPoint); } if (_analyzingData) { AddPoint(physPoint, tabletPoint); } }
internal void Update(RawStylusInputReport rsir, bool initial = false) { // Do not process non-Pen input if (_stylusDevice.TabletDevice.Type != TabletDeviceType.Stylus) { return; } switch (rsir.Actions) { case RawStylusActions.Down: { // Always reset on a down. Pens have one contact point so any down must be // a new pointer and requires fresh tracking. Reset(); // From this point on we can use inputs from manipulation tracked against the // current pointer id in order to tell if we need to update flick information. _collectingData = true; ProcessPacket(rsir, true); if (_analyzingData) { Analyze(decide: false); } } break; case RawStylusActions.Up: { if (_canDetectFlick) { ProcessPacket(rsir, false); if (_analyzingData) { Analyze(decide: true); } else { // Set Flick Result To Can't Be Flick } } _collectingData = false; _analyzingData = false; } break; case RawStylusActions.Move: { if (_canDetectFlick) { ProcessPacket(rsir, initial); if (_analyzingData) { Analyze(decide: false); } } } break; } }
/// <summary> /// Used in order to allow StylusPlugins to affect mouse inputs as well as touch. /// </summary> internal static void InvokePlugInsForMouse(ProcessInputEventArgs e) { if (!e.StagingItem.Input.Handled) { // if we see a preview mouse event that is not generated by a stylus // then send on to plugin if ((e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseDownEvent) && (e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseUpEvent) && (e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseMoveEvent) && (e.StagingItem.Input.RoutedEvent != InputManager.InputReportEvent)) { return; } // record the mouse capture for later reference.. MouseDevice mouseDevice; PresentationSource source; bool leftButtonDown; bool rightButtonDown; RawStylusActions stylusActions = RawStylusActions.None; int timestamp; // See if we need to deal sending a leave due to this PresentationSource being Deactivated // If not then we just return and do nothing. if (e.StagingItem.Input.RoutedEvent == InputManager.InputReportEvent) { if (_activeMousePlugInCollection?.Element == null) { return; } InputReportEventArgs input = e.StagingItem.Input as InputReportEventArgs; if (input.Report.Type != InputType.Mouse) { return; } RawMouseInputReport mouseInputReport = (RawMouseInputReport)input.Report; if ((mouseInputReport.Actions & RawMouseActions.Deactivate) != RawMouseActions.Deactivate) { return; } mouseDevice = InputManager.UnsecureCurrent.PrimaryMouseDevice; // Mouse set directly over to null when truly deactivating. if (mouseDevice == null || mouseDevice.DirectlyOver != null) { return; } leftButtonDown = mouseDevice.LeftButton == MouseButtonState.Pressed; rightButtonDown = mouseDevice.RightButton == MouseButtonState.Pressed; timestamp = mouseInputReport.Timestamp; // Get presentationsource from element. source = PresentationSource.CriticalFromVisual(_activeMousePlugInCollection.Element as Visual); } else { MouseEventArgs mouseEventArgs = e.StagingItem.Input as MouseEventArgs; mouseDevice = mouseEventArgs.MouseDevice; leftButtonDown = mouseDevice.LeftButton == MouseButtonState.Pressed; rightButtonDown = mouseDevice.RightButton == MouseButtonState.Pressed; // Only look at mouse input reports that truly come from a mouse (and is not an up or deactivate) and it // must be pressed state if a move (we don't fire stylus inair moves currently) if (mouseEventArgs.StylusDevice != null && e.StagingItem.Input.RoutedEvent != Mouse.PreviewMouseUpEvent) { return; } if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseMoveEvent) { if (!leftButtonDown) { return; } stylusActions = RawStylusActions.Move; } if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseDownEvent) { MouseButtonEventArgs mouseButtonEventArgs = mouseEventArgs as MouseButtonEventArgs; if (mouseButtonEventArgs.ChangedButton != MouseButton.Left) { return; } stylusActions = RawStylusActions.Down; } if (e.StagingItem.Input.RoutedEvent == Mouse.PreviewMouseUpEvent) { MouseButtonEventArgs mouseButtonEventArgs = mouseEventArgs as MouseButtonEventArgs; if (mouseButtonEventArgs.ChangedButton != MouseButton.Left) { return; } stylusActions = RawStylusActions.Up; } timestamp = mouseEventArgs.Timestamp; Visual directlyOverVisual = mouseDevice.DirectlyOver as Visual; if (directlyOverVisual == null) { return; } // // Take the presentation source which is associated to the directly over element. source = PresentationSource.CriticalFromVisual(directlyOverVisual); } if ((source != null) && (source.CompositionTarget != null) && !source.CompositionTarget.IsDisposed) { IInputElement directlyOver = mouseDevice.DirectlyOver; int packetStatus = (leftButtonDown ? 1 : 0) | (rightButtonDown ? 9 : 0); // pen tip down == 1, barrel = 8 Point ptClient = mouseDevice.GetPosition(source.RootVisual as IInputElement); ptClient = source.CompositionTarget.TransformToDevice.Transform(ptClient); int buttons = (leftButtonDown ? 1 : 0) | (rightButtonDown ? 3 : 0); int[] data = { (int)ptClient.X, (int)ptClient.Y, packetStatus, buttons }; RawStylusInputReport inputReport = new RawStylusInputReport( InputMode.Foreground, timestamp, source, stylusActions, () => { return(MousePointDescription); }, 0, 0, data); PointerStylusPlugInManager manager = StylusLogic.GetCurrentStylusLogicAs <PointerLogic>()?.GetManagerForSource(source); // Avoid re-entrancy due to lock() being used. using (Dispatcher.CurrentDispatcher.DisableProcessing()) { // Call plugins (does enter/leave and FireCustomData as well) _activeMousePlugInCollection = manager?.InvokeStylusPluginCollectionForMouse(inputReport, directlyOver, _activeMousePlugInCollection); } } } }
/// <summary> /// Calls the appropriate plugin collection for the raw input. /// </summary> /// <remarks> /// This would normally be called on the RTI thread. As we do not have one right now, we just call this in the /// Hwnd input provider in order to keep similar semantics to how the WISP stack accomplishes this. When the RTI is /// re-implemented, we need to ensure this gets called from there exclusively. /// </remarks> internal void InvokeStylusPluginCollection(RawStylusInputReport inputReport) { // Find PenContexts object for this inputReport. StylusPlugInCollection pic = null; 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 } PointerStylusDevice stylusDevice = inputReport.StylusDevice.As <PointerStylusDevice>(); StylusPlugInCollection currentPic = stylusDevice.CurrentVerifiedTarget; // 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(GetTabletToViewTransform(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.CurrentStylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed; stylusDevice.CurrentVerifiedTarget = null; } if (pic != null) { // NOTE: Comment not applicable without RTI. 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(GetTabletToViewTransform(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.CurrentVerifiedTarget = pic; pic.FireEnterLeave(true, rawStylusInput, false); } // Send the input pic.FireRawStylusInput(rawStylusInput); // Indicate we've used a stylus plugin StylusLogic.CurrentStylusLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed; } }
/// <summary> /// Verifies that the call to the stylus plugin on the RTI thread was valid against the current visual tree. /// If this verification fails, we will rebuild and resend the raw input and custom data in order to inform /// the target collections of the error and fix it. /// </summary> /// <remarks> /// This is a remnant of verifying hits to the collections based on the UI thread calling this /// after the real time input thread has previously called a collection. This is due to the RTI thread /// not being 100% synchronous with the visual tree in terms of hitting the collections. This is sort of /// redundant right now, but needs to be here when we re-implement the RTI. /// </remarks> internal void VerifyStylusPlugInCollectionTarget(RawStylusInputReport rawStylusInputReport) { switch (rawStylusInputReport.Actions) { case RawStylusActions.Down: case RawStylusActions.Move: case RawStylusActions.Up: break; default: return; // do nothing if not Down, Move or Up. } PointerLogic pointerLogic = StylusLogic.GetCurrentStylusLogicAs <PointerLogic>(); RawStylusInput originalRSI = rawStylusInputReport.RawStylusInput; // See if we have a plugin for the target of this input. StylusPlugInCollection targetPIC = null; StylusPlugInCollection targetRtiPIC = (originalRSI != null) ? originalRSI.Target : null; bool updateEventPoints = false; // Make sure we use UIElement for target if non NULL and hit ContentElement. UIElement newTarget = InputElement.GetContainingUIElement(rawStylusInputReport.StylusDevice.DirectlyOver as DependencyObject) as UIElement; if (newTarget != null) { targetPIC = FindPlugInCollection(newTarget); } // Make sure any lock() calls do not reenter on us. using (Dispatcher.CurrentDispatcher.DisableProcessing()) { // See if we hit the wrong PlugInCollection on the pen thread and clean things up if we did. if (targetRtiPIC != null && targetRtiPIC != targetPIC && originalRSI != null) { // Fire custom data not confirmed events for both pre and post since bad target... foreach (RawStylusInputCustomData customData in originalRSI.CustomDataList) { customData.Owner.FireCustomData(customData.Data, rawStylusInputReport.Actions, false); } updateEventPoints = originalRSI.StylusPointsModified; // Clear RawStylusInput data. rawStylusInputReport.RawStylusInput = null; } // See if we need to build up an RSI to send to the plugincollection (due to a mistarget). bool sendRawStylusInput = false; if (targetPIC != null && rawStylusInputReport.RawStylusInput == null) { // NOTE: (This applies when RTI is back in) 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(rawStylusInputReport.StylusDevice.As <PointerStylusDevice>().GetTabletToElementTransform(null)); // this gives matrix in measured units (not device) transformTabletToView.Children.Add(targetPIC.ViewToElement); // Make it relative to the element. transformTabletToView.Freeze(); // Must be frozen for multi-threaded access. RawStylusInput rawStylusInput = new RawStylusInput(rawStylusInputReport, transformTabletToView, targetPIC); rawStylusInputReport.RawStylusInput = rawStylusInput; sendRawStylusInput = true; } PointerStylusDevice stylusDevice = rawStylusInputReport.StylusDevice.As <PointerStylusDevice>(); // Now fire the confirmed enter/leave events as necessary. StylusPlugInCollection currentTarget = stylusDevice.CurrentVerifiedTarget; if (targetPIC != currentTarget) { if (currentTarget != null) { // Fire leave event. If we never had a plugin for this event then create a temp one. if (originalRSI == null) { GeneralTransformGroup transformTabletToView = new GeneralTransformGroup(); transformTabletToView.Children.Add(stylusDevice.GetTabletToElementTransform(null)); // this gives matrix in measured units (not device) transformTabletToView.Children.Add(currentTarget.ViewToElement); // Make it relative to the element. transformTabletToView.Freeze(); // Must be frozen for multi-threaded access. originalRSI = new RawStylusInput(rawStylusInputReport, transformTabletToView, currentTarget); } currentTarget.FireEnterLeave(false, originalRSI, true); } if (targetPIC != null) { // Fire Enter event targetPIC.FireEnterLeave(true, rawStylusInputReport.RawStylusInput, true); // Indicate we've used a stylus plugin pointerLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed; } // Update the verified target. stylusDevice.CurrentVerifiedTarget = targetPIC; } // Now fire RawStylusInput if needed to the right plugincollection. if (sendRawStylusInput) { // We are on the pen thread, just call directly. targetPIC.FireRawStylusInput(rawStylusInputReport.RawStylusInput); updateEventPoints = (updateEventPoints || rawStylusInputReport.RawStylusInput.StylusPointsModified); // Indicate we've used a stylus plugin pointerLogic.Statistics.FeaturesUsed |= Tracing.StylusTraceLogger.FeatureFlags.StylusPluginsUsed; } // Now fire PrePreviewCustomData events. if (targetPIC != null) { // Send custom data pre event foreach (RawStylusInputCustomData customData in rawStylusInputReport.RawStylusInput.CustomDataList) { customData.Owner.FireCustomData(customData.Data, rawStylusInputReport.Actions, true); } } // VerifyRawTarget might resend to correct plugins or may have hit the wrong plugincollection. The StylusPackets // may be overriden in those plugins so we need to call UpdateEventStylusPoints to update things. if (updateEventPoints) { rawStylusInputReport.StylusDevice.As <PointerStylusDevice>().UpdateEventStylusPoints(rawStylusInputReport, true); } } }