|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
//
//
using System.Collections.ObjectModel;
using System.Windows.Input.Manipulations;
using System.Windows.Media;
using MS.Utility;
namespace System.Windows.Input
{
/// <summary>
/// Analyzes input events to be processed into manipulation and inertia events.
/// </summary>
internal sealed class ManipulationDevice : InputDevice
{
/// <remarks>
/// Created in AddManipulationDevice.
/// </remarks>
private ManipulationDevice(UIElement element) : base()
{
_target = element;
_activeSource = PresentationSource.CriticalFromVisual(element);
_inputManager = InputManager.UnsecureCurrent;
_inputManager.PostProcessInput += new ProcessInputEventHandler(PostProcessInput);
_manipulationLogic = new ManipulationLogic(this);
}
private void DetachManipulationDevice()
{
_inputManager.PostProcessInput -= new ProcessInputEventHandler(PostProcessInput);
}
/// <summary>
/// Returns the element that input from this device is sent to.
/// </summary>
public override IInputElement Target
{
get { return _target; }
}
/// <summary>
/// Returns the PresentationSource of Target.
/// </summary>
public override PresentationSource ActiveSource
{
get
{
return _activeSource;
}
}
/// <summary>
/// Returns a ManipulationDevice associated with the given UIElement.
/// </summary>
/// <param name="element">The target of the ManipulationDevice.</param>
/// <returns>
/// A ManipulationDevice associated with the element.
/// If a device already exists for the element, a reference to that instance
/// will be returned, otherwise a new instance will be created.
/// </returns>
/// <remarks>
/// This function is thread-safe but should be called only on the
/// same thread that 'element' is bound to, due to possibly calling
/// the ManipulationDevice constructor.
/// </remarks>
internal static ManipulationDevice AddManipulationDevice(UIElement element)
{
Debug.Assert(element != null, "element should be non-null.");
element.VerifyAccess();
ManipulationDevice device = GetManipulationDevice(element);
if (device == null)
{
if (_manipulationDevices == null)
{
_manipulationDevices = new Dictionary<UIElement, ManipulationDevice>(2);
}
device = new ManipulationDevice(element);
_manipulationDevices[element] = device;
}
return device;
}
/// <summary>
/// Returns a ManipulationDevice associated with the given UIElement.
/// </summary>
/// <param name="element">The target of the ManipulationDevice.</param>
/// <returns>
/// A ManipulationDevice associated with the element.
/// If a device does not already exists for the element, null is returned.
/// </returns>
internal static ManipulationDevice GetManipulationDevice(UIElement element)
{
Debug.Assert(element != null, "element should be non-null.");
if (_manipulationDevices != null)
{
ManipulationDevice device;
_manipulationDevices.TryGetValue(element, out device);
return device;
}
return null;
}
/// <summary>
/// When a ManipulationDevice is no longer needed, remove it
/// from the global list of devices.
/// </summary>
private void RemoveManipulationDevice()
{
_wasTicking = false;
StopTicking();
DetachManipulationDevice();
_compensateForBoundaryFeedback = null;
RemoveAllManipulators();
if (_manipulationDevices != null)
{
_manipulationDevices.Remove(_target);
}
}
private void RemoveAllManipulators()
{
if (_manipulators != null)
{
for (int i = _manipulators.Count - 1; i >= 0; i--)
{
_manipulators[i].Updated -= OnManipulatorUpdated;
}
_manipulators.Clear();
}
}
internal void AddManipulator(IManipulator manipulator)
{
Debug.Assert(manipulator != null);
VerifyAccess();
_manipulationEnded = false;
if (_manipulators == null)
{
_manipulators = new List<IManipulator>(2);
}
_manipulators.Add(manipulator);
manipulator.Updated += OnManipulatorUpdated;
// Adding a manipulator counts as an update
OnManipulatorUpdated(manipulator, EventArgs.Empty);
}
internal void RemoveManipulator(IManipulator manipulator)
{
Debug.Assert(manipulator != null);
VerifyAccess();
manipulator.Updated -= OnManipulatorUpdated;
if (_manipulators != null)
{
_manipulators.Remove(manipulator);
}
// Removing a manipulator counts as an update
OnManipulatorUpdated(manipulator, EventArgs.Empty);
if (!_manipulationEnded)
{
if (_manipulators == null || _manipulators.Count == 0)
{
// cache the last removed manipulator
_removedManipulator = manipulator;
}
// Call ReportFrame so that ManipulationInertiaStarting / ManipulationCompleted
// gets called synchronously if needed
ReportFrame();
_removedManipulator = null;
}
}
internal ManipulationModes ManipulationMode
{
get { return _manipulationLogic.ManipulationMode; }
set { _manipulationLogic.ManipulationMode = value; }
}
internal ManipulationPivot ManipulationPivot
{
get { return _manipulationLogic.ManipulationPivot; }
set { _manipulationLogic.ManipulationPivot = value; }
}
internal IInputElement ManipulationContainer
{
get { return _manipulationLogic.ManipulationContainer; }
set { _manipulationLogic.ManipulationContainer = value; }
}
internal IEnumerable<IManipulator> GetManipulatorsReadOnly()
{
if (_manipulators != null)
{
return new ReadOnlyCollection<IManipulator>(_manipulators);
}
else
{
return new ReadOnlyCollection<IManipulator>(new List<IManipulator>(2));
}
}
internal void OnManipulatorUpdated(object sender, EventArgs e)
{
// After a period of inactivity, the ManipulationDevice will stop polling at the screen framerate
// to stop wasting CPU usage. This notification will tell the device that activity is happening
// so that it can know to poll.
LastUpdatedTimestamp = ManipulationLogic.GetCurrentTimestamp();
ResumeAllTicking(); // Resumes the ticking of all the suspended devices on the thread
StartTicking(); // Ensures that we continue ticking or restart ticking for this device
}
internal Point GetTransformedManipulatorPosition(Point point)
{
if (_compensateForBoundaryFeedback != null)
{
return _compensateForBoundaryFeedback(point);
}
return point;
}
/// <summary>
/// Starts the ticking for all the ManipulationDevices
/// on the thread only if they were ticking earlier.
/// </summary>
private static void ResumeAllTicking()
{
if (_manipulationDevices != null)
{
foreach (UIElement element in _manipulationDevices.Keys)
{
ManipulationDevice device = _manipulationDevices[element];
if (device != null && device._wasTicking)
{
device.StartTicking();
device._wasTicking = false;
}
}
}
}
private void StartTicking()
{
if (!_ticking)
{
_ticking = true;
CompositionTarget.Rendering += new EventHandler(OnRendering);
SubscribeToLayoutUpdate();
}
}
private void StopTicking()
{
if (_ticking)
{
CompositionTarget.Rendering -= new EventHandler(OnRendering);
_ticking = false;
UnsubscribeFromLayoutUpdate();
}
}
private void SubscribeToLayoutUpdate()
{
_manipulationLogic.ContainerLayoutUpdated += OnContainerLayoutUpdated;
}
private void UnsubscribeFromLayoutUpdate()
{
_manipulationLogic.ContainerLayoutUpdated -= OnContainerLayoutUpdated;
}
private void OnContainerLayoutUpdated(object sender, EventArgs e)
{
ReportFrame();
}
private void OnRendering(object sender, EventArgs e)
{
ReportFrame();
// If Manipulation didn't activate or becomes disabled, then stop ticking.
// If we've exceeded the timeout without any manipulators updating, then stop ticking
// to save energy. If a manipulator updates, we'll start ticking again.
if (!IsManipulationActive ||
(ManipulationLogic.GetCurrentTimestamp() - LastUpdatedTimestamp) > ThrottleTimeout)
{
_wasTicking = _ticking; // ReportFrame could have stopped the ticking, hence take the latest value.
StopTicking();
}
}
private void ReportFrame()
{
if (!_manipulationEnded)
{
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordInput | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.ManipulationReportFrame, 0);
_manipulationLogic.ReportFrame(_manipulators);
}
}
internal bool IsManipulationActive
{
get
{
return _manipulationLogic.IsManipulationActive;
}
}
private void PostProcessInput(object sender, ProcessInputEventArgs e)
{
InputEventArgs inputEventArgs = e.StagingItem.Input;
if (inputEventArgs.Device == this)
{
RoutedEvent routedEvent = inputEventArgs.RoutedEvent;
if (routedEvent == Manipulation.ManipulationDeltaEvent)
{
ManipulationDeltaEventArgs deltaEventArgs = inputEventArgs as ManipulationDeltaEventArgs;
if (deltaEventArgs != null)
{
// During deltas, see if panning feedback is needed on the window
ManipulationDelta unusedManipulation = deltaEventArgs.UnusedManipulation;
_manipulationLogic.RaiseBoundaryFeedback(unusedManipulation, deltaEventArgs.RequestedComplete);
_manipulationLogic.PushEventsToDevice();
// If a Complete is requested, then pass it along to the manipulation processor
if (deltaEventArgs.RequestedComplete)
{
_manipulationLogic.Complete(/* withInertia = */ deltaEventArgs.RequestedInertia);
_manipulationLogic.PushEventsToDevice();
}
else if (deltaEventArgs.RequestedCancel)
{
Debug.Assert(!deltaEventArgs.IsInertial);
OnManipulationCancel();
}
}
}
else if (routedEvent == Manipulation.ManipulationStartingEvent)
{
ManipulationStartingEventArgs startingEventArgs = inputEventArgs as ManipulationStartingEventArgs;
if (startingEventArgs != null && startingEventArgs.RequestedCancel)
{
OnManipulationCancel();
}
}
else if (routedEvent == Manipulation.ManipulationStartedEvent)
{
ManipulationStartedEventArgs startedEventArgs = inputEventArgs as ManipulationStartedEventArgs;
if (startedEventArgs != null)
{
if (startedEventArgs.RequestedComplete)
{
// If a Complete is requested, pass it along to the manipulation processor
_manipulationLogic.Complete(/* withInertia = */ false);
_manipulationLogic.PushEventsToDevice();
}
else if (startedEventArgs.RequestedCancel)
{
OnManipulationCancel();
}
else
{
// Start ticking to produce delta events
ResumeAllTicking(); // Resumes the ticking of all the suspended devices on the thread
StartTicking(); // Ensures that we continue ticking or restart ticking for this device
}
}
}
else if (routedEvent == Manipulation.ManipulationInertiaStartingEvent)
{
// Switching from using rendering for ticking to a timer at lower priority (handled by ManipulationLogic)
StopTicking();
// Remove all the manipulators so that we dont re-start manipulations accidentally
RemoveAllManipulators();
// Initialize inertia
ManipulationInertiaStartingEventArgs inertiaEventArgs = inputEventArgs as ManipulationInertiaStartingEventArgs;
if (inertiaEventArgs != null)
{
if (inertiaEventArgs.RequestedCancel)
{
OnManipulationCancel();
}
else
{
_manipulationLogic.BeginInertia(inertiaEventArgs);
}
}
}
else if (routedEvent == Manipulation.ManipulationCompletedEvent)
{
_manipulationLogic.OnCompleted();
ManipulationCompletedEventArgs completedEventArgs = inputEventArgs as ManipulationCompletedEventArgs;
if (completedEventArgs != null)
{
if (completedEventArgs.RequestedCancel)
{
Debug.Assert(!completedEventArgs.IsInertial);
OnManipulationCancel();
}
else if (!(completedEventArgs.IsInertial && _ticking))
{
// Remove the manipulation device only if
// another manipulation didnot start
OnManipulationComplete();
}
}
}
else if (routedEvent == Manipulation.ManipulationBoundaryFeedbackEvent)
{
ManipulationBoundaryFeedbackEventArgs boundaryEventArgs = inputEventArgs as ManipulationBoundaryFeedbackEventArgs;
if (boundaryEventArgs != null)
{
_compensateForBoundaryFeedback = boundaryEventArgs.CompensateForBoundaryFeedback;
}
}
}
}
private void OnManipulationCancel()
{
_manipulationEnded = true;
if (_manipulators != null)
{
if (_removedManipulator != null)
{
Debug.Assert(_manipulators == null || _manipulators.Count == 0);
// Report Manipulation Cancel to last removed manipulator
_removedManipulator.ManipulationEnded(true);
}
else
{
// Report Manipulation Cancel to all the remaining manipulators
List<IManipulator> manipulators = new List<IManipulator>(_manipulators);
foreach (IManipulator manipulator in manipulators)
{
manipulator.ManipulationEnded(true);
}
}
}
RemoveManipulationDevice();
}
private void OnManipulationComplete()
{
_manipulationEnded = true;
if (_manipulators != null)
{
// Report Manipulation Complete to all the remaining manipulators
List<IManipulator> manipulators = new List<IManipulator>(_manipulators);
foreach (IManipulator manipulator in manipulators)
{
manipulator.ManipulationEnded(false);
}
}
RemoveManipulationDevice();
}
internal void SetManipulationParameters(ManipulationParameters2D parameter)
{
_manipulationLogic.SetManipulationParameters(parameter);
}
/// <summary>
/// Completes the pending manipulation or inertia.
/// </summary>
internal void CompleteManipulation(bool withInertia)
{
if (_manipulationLogic != null)
{
_manipulationLogic.Complete(withInertia);
_manipulationLogic.PushEventsToDevice();
}
}
internal void ProcessManipulationInput(InputEventArgs e)
{
EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordInput | EventTrace.Keyword.KeywordPerf, EventTrace.Level.Info, EventTrace.Event.ManipulationEventRaised, 0);
_inputManager.ProcessInput(e);
}
private InputManager _inputManager;
private ManipulationLogic _manipulationLogic;
private PresentationSource _activeSource;
private UIElement _target;
private List<IManipulator> _manipulators;
private bool _ticking;
private bool _wasTicking; // boolean used to track suspended manipulation devices
private Func<Point, Point> _compensateForBoundaryFeedback;
private bool _manipulationEnded = false;
IManipulator _removedManipulator = null;
[ThreadStatic]
private static Int64 LastUpdatedTimestamp;
private const Int64 ThrottleTimeout = TimeSpan.TicksPerSecond * 5; // 5 seconds (in 100ns units) of no activity will throttle down
[ThreadStatic]
private static Dictionary<UIElement, ManipulationDevice> _manipulationDevices;
}
}
|