diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FrameworkElementExtensions/FrameworkElementExtensionsCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FrameworkElementExtensions/FrameworkElementExtensionsCode.bind
index 18e22db846f..261c243a9e6 100644
--- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FrameworkElementExtensions/FrameworkElementExtensionsCode.bind
+++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FrameworkElementExtensions/FrameworkElementExtensionsCode.bind
@@ -86,6 +86,17 @@
Stroke="{ThemeResource Brush-Grey-04}"
StrokeThickness="1" />
+
+
+
+
diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.CanDragElement.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.CanDragElement.cs
new file mode 100644
index 00000000000..1aca60c0808
--- /dev/null
+++ b/Microsoft.Toolkit.Uwp.UI/Extensions/FrameworkElement/FrameworkElementExtensions.CanDragElement.cs
@@ -0,0 +1,781 @@
+// 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.
+// Based on: https://github.com/microsoft/XamlBehaviorsWpf/blob/master/src/Microsoft.Xaml.Behaviors/Layout/MouseDragElementBehavior.cs
+
+#nullable enable
+
+using System;
+using System.Diagnostics;
+using Windows.Foundation;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+
+namespace Microsoft.Toolkit.Uwp.UI;
+
+#pragma warning disable SA1124 // I'm not sure about this, this is a pretty controversial rule.
+
+///
+/// Provides attached dependency properties for the type.
+///
+public static partial class FrameworkElementExtensions
+{
+ #region Public properties
+
+ #region CanDragElement
+
+ ///
+ /// Attached for repositions the element
+ /// in response to mouse drag gestures on the element.
+ ///
+ public static readonly DependencyProperty CanDragElementProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(CanDragElementProperty).Replace("Property", string.Empty),
+ typeof(bool),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(false, OnCanDragElementChanged));
+
+ ///
+ /// Gets the that enables/disables repositions the element
+ /// in response to mouse drag gestures on the element.
+ ///
+ /// The to get the associated from
+ /// The associated with the
+ public static bool GetCanDragElement(FrameworkElement obj)
+ {
+ return (bool)obj.GetValue(CanDragElementProperty);
+ }
+
+ ///
+ /// Sets the that enables/disables repositions the element
+ /// in response to mouse drag gestures on the element.
+ ///
+ /// The to associate the with
+ /// The for binding to the
+ public static void SetCanDragElement(FrameworkElement obj, bool value)
+ {
+ obj.SetValue(CanDragElementProperty, value);
+ }
+
+ private static void OnCanDragElementChanged(
+ DependencyObject element,
+ DependencyPropertyChangedEventArgs args)
+ {
+ if (element is not FrameworkElement frameworkElement)
+ {
+ throw new ArgumentException($"Element should be {nameof(FrameworkElement)}.");
+ }
+
+ if (args.OldValue is true)
+ {
+ frameworkElement.RemoveHandler(UIElement.PointerPressedEvent, new PointerEventHandler(OnPointerPressed));
+ }
+
+ if (args.NewValue is true)
+ {
+ frameworkElement.AddHandler(UIElement.PointerPressedEvent, new PointerEventHandler(OnPointerPressed), handledEventsToo: false);
+ }
+ }
+
+ #endregion
+
+ #region DragX
+
+ ///
+ /// Dependency property for the X position of the dragged element, relative to the left of the root element.
+ ///
+ public static readonly DependencyProperty DragXProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(DragXProperty).Replace("Property", string.Empty),
+ typeof(double),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(double.NaN, OnDragXChanged));
+
+ ///
+ /// Gets the X position of the dragged element, relative to the left of the root element. This is a dependency property.
+ ///
+ /// The to get the associated from
+ /// The associated with the
+ public static double GetDragX(FrameworkElement obj)
+ {
+ return (double)obj.GetValue(DragXProperty);
+ }
+
+ ///
+ /// Sets the X position of the dragged element, relative to the left of the root element. This is a dependency property.
+ ///
+ /// The to associate the with
+ /// The for binding to the
+ public static void SetDragX(FrameworkElement obj, double value)
+ {
+ obj.SetValue(DragXProperty, value);
+ }
+
+ private static void OnDragXChanged(
+ DependencyObject element,
+ DependencyPropertyChangedEventArgs args)
+ {
+ if (element is not FrameworkElement frameworkElement)
+ {
+ throw new ArgumentException($"Element should be {nameof(FrameworkElement)}.");
+ }
+
+ UpdatePosition(frameworkElement, new Point((double)args.NewValue, GetDragY(frameworkElement)));
+ }
+
+ #endregion
+
+ #region DragY
+
+ ///
+ /// Dependency property for the Y position of the dragged element, relative to the top of the root element.
+ ///
+ public static readonly DependencyProperty DragYProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(DragYProperty).Replace("Property", string.Empty),
+ typeof(double),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(double.NaN, OnDragYChanged));
+
+ ///
+ /// Gets or sets the Y position of the dragged element, relative to the top of the root element. This is a dependency property.
+ ///
+ /// The to get the associated from
+ /// The associated with the
+ public static double GetDragY(FrameworkElement obj)
+ {
+ return (double)obj.GetValue(DragYProperty);
+ }
+
+ ///
+ /// Gets or sets the Y position of the dragged element, relative to the top of the root element. This is a dependency property.
+ ///
+ /// The to associate the with
+ /// The for binding to the
+ public static void SetDragY(FrameworkElement obj, double value)
+ {
+ obj.SetValue(DragYProperty, value);
+ }
+
+ private static void OnDragYChanged(
+ DependencyObject element,
+ DependencyPropertyChangedEventArgs args)
+ {
+ if (element is not FrameworkElement frameworkElement)
+ {
+ throw new ArgumentException($"Element should be {nameof(FrameworkElement)}.");
+ }
+
+ UpdatePosition(frameworkElement, new Point(GetDragX(frameworkElement), (double)args.NewValue));
+ }
+
+ #endregion
+
+ #region ConstrainDragToParentBounds
+
+ ///
+ /// Dependency property for the ConstrainDragToParentBounds property. If true, the dragged element will be constrained to stay within the bounds of its parent container.
+ ///
+ public static readonly DependencyProperty ConstrainDragToParentBoundsProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(ConstrainDragToParentBoundsProperty).Replace("Property", string.Empty),
+ typeof(bool),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(false, OnConstrainDragToParentBoundsChanged));
+
+ ///
+ /// Gets or sets a value indicating whether the dragged element is constrained to stay within the bounds of its parent container. This is a dependency property.
+ ///
+ ///
+ /// True if the dragged element should be constrained to its parents bounds; otherwise, False.
+ ///
+ /// The to get the associated from
+ /// The associated with the
+ public static bool GetConstrainDragToParentBounds(FrameworkElement obj)
+ {
+ return (bool)obj.GetValue(ConstrainDragToParentBoundsProperty);
+ }
+
+ ///
+ /// Gets or sets a value indicating whether the dragged element is constrained to stay within the bounds of its parent container. This is a dependency property.
+ ///
+ ///
+ /// True if the dragged element should be constrained to its parents bounds; otherwise, False.
+ ///
+ /// The to associate the with
+ /// The for binding to the
+ public static void SetConstrainDragToParentBounds(FrameworkElement obj, bool value)
+ {
+ obj.SetValue(ConstrainDragToParentBoundsProperty, value);
+ }
+
+ private static void OnConstrainDragToParentBoundsChanged(
+ DependencyObject element,
+ DependencyPropertyChangedEventArgs args)
+ {
+ if (element is not FrameworkElement frameworkElement)
+ {
+ throw new ArgumentException($"Element should be {nameof(FrameworkElement)}.");
+ }
+
+ UpdatePosition(frameworkElement, new Point(GetDragX(frameworkElement), GetDragY(frameworkElement)));
+ }
+
+ #endregion
+
+ #endregion
+
+ #region Private properties
+
+ #region SettingPosition
+
+ private static readonly DependencyProperty SettingPositionProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(SettingPositionProperty).Replace("Property", string.Empty),
+ typeof(bool),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(false));
+
+ private static bool GetSettingPosition(FrameworkElement element)
+ {
+ return (bool)element.GetValue(SettingPositionProperty);
+ }
+
+ private static void SetSettingPosition(FrameworkElement element, bool value)
+ {
+ element.SetValue(SettingPositionProperty, value);
+ }
+
+ #endregion
+
+ #region RelativePosition
+
+ private static readonly DependencyProperty RelativePositionProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(RelativePositionProperty).Replace("Property", string.Empty),
+ typeof(Point),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(default(Point)));
+
+ private static Point GetRelativePosition(FrameworkElement element)
+ {
+ return (Point)element.GetValue(RelativePositionProperty);
+ }
+
+ private static void SetRelativePosition(FrameworkElement element, Point value)
+ {
+ element.SetValue(RelativePositionProperty, value);
+ }
+
+ #endregion
+
+ #region CachedRenderTransform
+
+ private static readonly DependencyProperty CachedRenderTransformProperty =
+ DependencyProperty.RegisterAttached(
+ nameof(CachedRenderTransformProperty).Replace("Property", string.Empty),
+ typeof(Transform),
+ typeof(FrameworkElementExtensions),
+ new PropertyMetadata(null));
+
+ private static Transform GetCachedRenderTransform(FrameworkElement element)
+ {
+ return (Transform)element.GetValue(CachedRenderTransformProperty);
+ }
+
+ private static void SetCachedRenderTransform(FrameworkElement element, Transform value)
+ {
+ element.SetValue(CachedRenderTransformProperty, value);
+ }
+
+ #endregion
+
+ ///
+ /// Gets the on-screen position of the associated element in root coordinates.
+ ///
+ /// The on-screen position of the associated element in root coordinates.
+ private static Point GetActualPosition(FrameworkElement element)
+ {
+ GeneralTransform elementToRoot = element.TransformToVisual(GetRootElement(element));
+ Point translation = GetTransformOffset(elementToRoot);
+ return new Point(translation.X, translation.Y);
+ }
+
+ ///
+ /// Gets the element bounds in element coordinates.
+ ///
+ /// The element bounds in element coordinates.
+ private static Rect GetElementBounds(FrameworkElement element)
+ {
+ Rect layoutRect = GetLayoutRect(element);
+ return new Rect(new Point(0, 0), new Size(layoutRect.Width, layoutRect.Height));
+ }
+
+ ///
+ /// Get the layout rectangle of an element, by getting the layout slot and then computing which portion of the slot is being used.
+ ///
+ /// The element whose layout Rect will be retrieved.
+ /// The layout Rect of that element.
+ private static Rect GetLayoutRect(FrameworkElement element)
+ {
+ double actualWidth = element.ActualWidth;
+ double actualHeight = element.ActualHeight;
+
+ // Use RenderSize here because that works for SL Image and MediaElement - the other uses fo ActualWidth/Height are correct even for these element types
+ if (element is Image)
+ {
+ if (element.Parent is Canvas)
+ {
+ actualWidth = double.IsNaN(element.Width) ? actualWidth : element.Width;
+ actualHeight = double.IsNaN(element.Height) ? actualHeight : element.Height;
+ }
+ else
+ {
+ actualWidth = element.RenderSize.Width;
+ actualHeight = element.RenderSize.Height;
+ }
+ }
+
+ actualWidth = element.Visibility == Visibility.Collapsed ? 0 : actualWidth;
+ actualHeight = element.Visibility == Visibility.Collapsed ? 0 : actualHeight;
+ Thickness margin = element.Margin;
+
+ Rect slotRect = LayoutInformation.GetLayoutSlot(element);
+
+ double left = 0.0;
+ double top = 0.0;
+
+ switch (element.HorizontalAlignment)
+ {
+ case HorizontalAlignment.Left:
+ left = slotRect.Left + margin.Left;
+ break;
+
+ case HorizontalAlignment.Center:
+ left = ((((slotRect.Left + margin.Left) + slotRect.Right) - margin.Right) / 2.0) - (actualWidth / 2.0);
+ break;
+
+ case HorizontalAlignment.Right:
+ left = (slotRect.Right - margin.Right) - actualWidth;
+ break;
+
+ case HorizontalAlignment.Stretch:
+ left = Math.Max((double)(slotRect.Left + margin.Left), (double)(((((slotRect.Left + margin.Left) + slotRect.Right) - margin.Right) / 2.0) - (actualWidth / 2.0)));
+ break;
+ }
+
+ switch (element.VerticalAlignment)
+ {
+ case VerticalAlignment.Top:
+ top = slotRect.Top + margin.Top;
+ break;
+
+ case VerticalAlignment.Center:
+ top = ((((slotRect.Top + margin.Top) + slotRect.Bottom) - margin.Bottom) / 2.0) - (actualHeight / 2.0);
+ break;
+
+ case VerticalAlignment.Bottom:
+ top = (slotRect.Bottom - margin.Bottom) - actualHeight;
+ break;
+
+ case VerticalAlignment.Stretch:
+ top = Math.Max((double)(slotRect.Top + margin.Top), (double)(((((slotRect.Top + margin.Top) + slotRect.Bottom) - margin.Bottom) / 2.0) - (actualHeight / 2.0)));
+ break;
+ }
+
+ return new Rect(left, top, actualWidth, actualHeight);
+ }
+
+ ///
+ /// Gets the parent element of the associated object.
+ ///
+ /// The parent element of the associated object.
+ private static FrameworkElement? GetParentElement(FrameworkElement element)
+ {
+ return element.Parent as FrameworkElement;
+ }
+
+ ///
+ /// Gets the root element of the scene in which the associated object is located.
+ ///
+ /// The root element of the scene in which the associated object is located.
+ private static UIElement GetRootElement(FrameworkElement element)
+ {
+ DependencyObject child = element;
+ DependencyObject parent = child;
+ while (parent != null)
+ {
+ child = parent;
+ parent = VisualTreeHelper.GetParent(child);
+ }
+
+ return (UIElement)child;
+ }
+
+ ///
+ /// Gets and sets the RenderTransform of the associated element.
+ ///
+ private static Transform GetRenderTransform(FrameworkElement element)
+ {
+ var cachedRenderTransform = GetCachedRenderTransform(element);
+ if (cachedRenderTransform == null ||
+ !object.ReferenceEquals(cachedRenderTransform, element.RenderTransform))
+ {
+ Transform clonedTransform = CloneTransform(element.RenderTransform);
+ SetRenderTransform(element, clonedTransform);
+ cachedRenderTransform = clonedTransform;
+ }
+
+ return cachedRenderTransform;
+ }
+
+ ///
+ /// Gets and sets the RenderTransform of the associated element.
+ ///
+ private static void SetRenderTransform(FrameworkElement element, Transform value)
+ {
+ var cachedRenderTransform = GetCachedRenderTransform(element);
+ if (cachedRenderTransform != value)
+ {
+ SetCachedRenderTransform(element, value);
+ element.RenderTransform = value;
+ }
+ }
+
+ #endregion
+
+ #region Private methods
+
+ ///
+ /// Attempts to update the position of the associated element to the specified coordinates.
+ ///
+ /// The associated element.
+ /// The desired position of the element in root coordinates.
+ private static void UpdatePosition(FrameworkElement element, Point point)
+ {
+ if (!GetSettingPosition(element) && element != null)
+ {
+ GeneralTransform elementToRoot = element.TransformToVisual(GetRootElement(element));
+ Point translation = GetTransformOffset(elementToRoot);
+ double xChange = double.IsNaN(point.X) ? 0 : point.X - translation.X;
+ double yChange = double.IsNaN(point.Y) ? 0 : point.Y - translation.Y;
+ ApplyTranslation(element, xChange, yChange);
+ }
+ }
+
+ ///
+ /// Applies a relative position translation to the associated element.
+ ///
+ /// The associated element.
+ /// The X component of the desired translation in root coordinates.
+ /// The Y component of the desired translation in root coordinates.
+ private static void ApplyTranslation(FrameworkElement element, double x, double y)
+ {
+ var parentElement = GetParentElement(element);
+ if (parentElement != null)
+ {
+ GeneralTransform rootToParent = GetRootElement(element).TransformToVisual(parentElement);
+ Point transformedPoint = TransformAsVector(rootToParent, x, y);
+ x = transformedPoint.X;
+ y = transformedPoint.Y;
+
+ if (GetConstrainDragToParentBounds(element))
+ {
+ Rect parentBounds = new Rect(0, 0, parentElement.ActualWidth, parentElement.ActualHeight);
+
+ GeneralTransform objectToParent = element.TransformToVisual(parentElement);
+ Rect objectBoundingBox = GetElementBounds(element);
+ objectBoundingBox = objectToParent.TransformBounds(objectBoundingBox);
+
+ Rect endPosition = objectBoundingBox;
+ endPosition.X += x;
+ endPosition.Y += y;
+
+ if (!RectContainsRect(parentBounds, endPosition))
+ {
+ if (endPosition.X < parentBounds.Left)
+ {
+ double diff = endPosition.X - parentBounds.Left;
+ x -= diff;
+ }
+ else if (endPosition.Right > parentBounds.Right)
+ {
+ double diff = endPosition.Right - parentBounds.Right;
+ x -= diff;
+ }
+
+ if (endPosition.Y < parentBounds.Top)
+ {
+ double diff = endPosition.Y - parentBounds.Top;
+ y -= diff;
+ }
+ else if (endPosition.Bottom > parentBounds.Bottom)
+ {
+ double diff = endPosition.Bottom - parentBounds.Bottom;
+ y -= diff;
+ }
+ }
+ }
+
+ ApplyTranslationTransform(element, x, y);
+ }
+ }
+
+ ///
+ /// Applies the given translation to the RenderTransform of the selected element.
+ ///
+ /// The associated element.
+ /// The X component of the translation in parent coordinates.
+ /// The Y component of the translation in parent coordinates.
+ private static void ApplyTranslationTransform(FrameworkElement element, double x, double y)
+ {
+ Transform renderTransform = GetRenderTransform(element);
+
+ // todo jekelly: what if its frozen?
+ TranslateTransform? translateTransform = renderTransform as TranslateTransform;
+
+ if (translateTransform == null)
+ {
+ TransformGroup? renderTransformGroup = renderTransform as TransformGroup;
+ MatrixTransform? renderMatrixTransform = renderTransform as MatrixTransform;
+ if (renderTransformGroup != null)
+ {
+ if (renderTransformGroup.Children.Count > 0)
+ {
+ translateTransform = renderTransformGroup.Children[renderTransformGroup.Children.Count - 1] as TranslateTransform;
+ }
+
+ if (translateTransform == null)
+ {
+ translateTransform = new TranslateTransform();
+ renderTransformGroup.Children.Add(translateTransform);
+ }
+ }
+ else if (renderMatrixTransform != null)
+ {
+ Matrix matrix = renderMatrixTransform.Matrix;
+ matrix.OffsetX += x;
+ matrix.OffsetY += y;
+ MatrixTransform matrixTransform = new MatrixTransform();
+ matrixTransform.Matrix = matrix;
+ SetRenderTransform(element, matrixTransform);
+ return;
+ }
+ else
+ {
+ TransformGroup transformGroup = new TransformGroup();
+ translateTransform = new TranslateTransform();
+
+ // this will break multi-step animations that target the render transform
+ if (renderTransform != null)
+ {
+ transformGroup.Children.Add(renderTransform);
+ }
+
+ transformGroup.Children.Add(translateTransform);
+ SetRenderTransform(element, transformGroup);
+ }
+ }
+
+ Debug.Assert(translateTransform != null, "TranslateTransform should not be null by this point.");
+ if (translateTransform != null)
+ {
+ translateTransform.X += x;
+ translateTransform.Y += y;
+ }
+ }
+
+ ///
+ /// Does a recursive deep copy of the specified transform.
+ ///
+ /// The transform to clone.
+ /// A deep copy of the specified transform, or null if the specified transform is null.
+ /// Thrown if the type of the Transform is not recognized.
+ private static Transform CloneTransform(Transform transform)
+ {
+ transform = transform ?? throw new ArgumentNullException(nameof(transform));
+
+ ScaleTransform? scaleTransform = null;
+ RotateTransform? rotateTransform = null;
+ SkewTransform? skewTransform = null;
+ TranslateTransform? translateTransform = null;
+ MatrixTransform? matrixTransform = null;
+ TransformGroup? transformGroup = null;
+
+ Type transformType = transform.GetType();
+ if ((scaleTransform = transform as ScaleTransform) != null)
+ {
+ return new ScaleTransform()
+ {
+ CenterX = scaleTransform.CenterX,
+ CenterY = scaleTransform.CenterY,
+ ScaleX = scaleTransform.ScaleX,
+ ScaleY = scaleTransform.ScaleY,
+ };
+ }
+ else if ((rotateTransform = transform as RotateTransform) != null)
+ {
+ return new RotateTransform()
+ {
+ Angle = rotateTransform.Angle,
+ CenterX = rotateTransform.CenterX,
+ CenterY = rotateTransform.CenterY,
+ };
+ }
+ else if ((skewTransform = transform as SkewTransform) != null)
+ {
+ return new SkewTransform()
+ {
+ AngleX = skewTransform.AngleX,
+ AngleY = skewTransform.AngleY,
+ CenterX = skewTransform.CenterX,
+ CenterY = skewTransform.CenterY,
+ };
+ }
+ else if ((translateTransform = transform as TranslateTransform) != null)
+ {
+ return new TranslateTransform()
+ {
+ X = translateTransform.X,
+ Y = translateTransform.Y,
+ };
+ }
+ else if ((matrixTransform = transform as MatrixTransform) != null)
+ {
+ return new MatrixTransform()
+ {
+ Matrix = matrixTransform.Matrix,
+ };
+ }
+ else if ((transformGroup = transform as TransformGroup) != null)
+ {
+ TransformGroup group = new TransformGroup();
+ foreach (Transform childTransform in transformGroup.Children)
+ {
+ group.Children.Add(CloneTransform(childTransform));
+ }
+
+ return group;
+ }
+
+ throw new InvalidOperationException("Unexpected Transform type encountered");
+ }
+
+ ///
+ /// Updates the X and Y properties based on the current rendered position of the associated element.
+ ///
+ private static void UpdatePosition(FrameworkElement element)
+ {
+ GeneralTransform elementToRoot = element.TransformToVisual(GetRootElement(element));
+ Point translation = GetTransformOffset(elementToRoot);
+ SetDragX(element, translation.X);
+ SetDragY(element, translation.Y);
+ }
+
+ private static void StartDrag(FrameworkElement element, PointerRoutedEventArgs e)
+ {
+ SetRelativePosition(element, e.GetCurrentPoint(element).Position);
+ element.CapturePointer(e.Pointer);
+
+ element.PointerMoved += OnPointerMoved;
+ element.PointerCaptureLost += OnPointerCaptureLost;
+ element.AddHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(OnPointerReleased), handledEventsToo: false);
+ }
+
+ private static void HandleDrag(FrameworkElement element, Point newPositionInElementCoordinates)
+ {
+ var relativePosition = GetRelativePosition(element);
+ double relativeXDiff = newPositionInElementCoordinates.X - relativePosition.X;
+ double relativeYDiff = newPositionInElementCoordinates.Y - relativePosition.Y;
+
+ GeneralTransform elementToRoot = element.TransformToVisual(GetRootElement(element));
+ Point relativeDifferenceInRootCoordinates = TransformAsVector(elementToRoot, relativeXDiff, relativeYDiff);
+
+ SetSettingPosition(element, true);
+ ApplyTranslation(element, relativeDifferenceInRootCoordinates.X, relativeDifferenceInRootCoordinates.Y);
+ UpdatePosition(element);
+ SetSettingPosition(element, false);
+ }
+
+ private static void EndDrag(FrameworkElement element)
+ {
+ element.PointerMoved -= OnPointerMoved;
+ element.PointerCaptureLost -= OnPointerCaptureLost;
+ element.RemoveHandler(UIElement.PointerReleasedEvent, new PointerEventHandler(OnPointerReleased));
+ }
+
+ private static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
+ {
+ StartDrag((FrameworkElement)sender, e);
+ }
+
+ private static void OnPointerCaptureLost(object sender, PointerRoutedEventArgs e)
+ {
+ EndDrag((FrameworkElement)sender);
+ }
+
+ private static void OnPointerReleased(object sender, PointerRoutedEventArgs e)
+ {
+ (sender as FrameworkElement)?.ReleasePointerCapture(e.Pointer);
+ }
+
+ private static void OnPointerMoved(object sender, PointerRoutedEventArgs e)
+ {
+ var relativePosition = e.GetCurrentPoint(sender as UIElement).Position;
+ HandleDrag((FrameworkElement)sender, relativePosition);
+ }
+
+ #endregion
+
+ #region Linear algebra helper methods
+
+ ///
+ /// Check if one Rect is contained by another.
+ ///
+ /// The containing Rect.
+ /// The contained Rect.
+ /// True if rect1 contains rect2; otherwise, False.
+ private static bool RectContainsRect(Rect rect1, Rect rect2)
+ {
+ if (rect1.IsEmpty || rect2.IsEmpty)
+ {
+ return false;
+ }
+
+ return
+ (rect1.X <= rect2.X) &&
+ (rect1.Y <= rect2.Y) &&
+ ((rect1.X + rect1.Width) >= (rect2.X + rect2.Width)) &&
+ ((rect1.Y + rect1.Height) >= (rect2.Y + rect2.Height));
+ }
+
+ ///
+ /// Transforms as vector.
+ ///
+ /// The transform.
+ /// The X component of the vector.
+ /// The Y component of the vector.
+ /// A point containing the values of X and Y transformed by transform as a vector.
+ private static Point TransformAsVector(GeneralTransform transform, double x, double y)
+ {
+ Point origin = transform.TransformPoint(new Point(0, 0));
+ Point transformedPoint = transform.TransformPoint(new Point(x, y));
+
+ return new Point(transformedPoint.X - origin.X, transformedPoint.Y - origin.Y);
+ }
+
+ ///
+ /// Gets the transform offset.
+ ///
+ /// The transform.
+ /// The offset of the transform.
+ private static Point GetTransformOffset(GeneralTransform transform)
+ {
+ return transform.TransformPoint(new Point(0, 0));
+ }
+
+ #endregion
+}
\ No newline at end of file