Last active
December 25, 2015 20:18
-
-
Save sgraf812/7033464 to your computer and use it in GitHub Desktop.
Pan and zoom behavior for Windows Phone 7 + toolkit.
No pan inertia/squish, just correct matrix math stuff.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Interactivity; | |
using System.Windows.Media; | |
using Microsoft.Phone.Controls; | |
namespace PhoneApp1 | |
{ | |
public class PanAndZoomBehavior : Behavior<FrameworkElement> | |
{ | |
private const double MinZoom = 1.0; | |
private readonly CompositeTransform _old = new CompositeTransform(); | |
private double _initialScale; | |
private GestureListener _listener; | |
public PanAndZoomBehavior() | |
{ | |
MaxZoom = 10.0; | |
} | |
/// <summary> | |
/// This does not enforce zoom bounds on setting. | |
/// </summary> | |
public double MaxZoom { get; set; } | |
protected override void OnAttached() | |
{ | |
base.OnAttached(); | |
AssociatedObject.RenderTransform = new CompositeTransform(); | |
_listener = GestureService.GetGestureListener(AssociatedObject); | |
_listener.PinchDelta += OnPinchDelta; | |
_listener.PinchStarted += OnPinchStarted; | |
_listener.DragDelta += OnDragDelta; | |
// wait for the RootVisual to be initialized | |
Dispatcher.BeginInvoke(() => | |
((PhoneApplicationFrame)Application.Current.RootVisual).OrientationChanged += OrientationChanged); | |
} | |
protected override void OnDetaching() | |
{ | |
((PhoneApplicationPage)Application.Current.RootVisual).OrientationChanged -= OrientationChanged; | |
_listener.PinchDelta -= OnPinchDelta; | |
_listener.PinchStarted -= OnPinchStarted; | |
_listener.DragDelta -= OnDragDelta; | |
_listener = null; | |
base.OnDetaching(); | |
} | |
private void OnPinchDelta(object sender, PinchGestureEventArgs e) | |
{ | |
var img = sender as Image; | |
var transform = img.RenderTransform as CompositeTransform; | |
var a = transform.Transform(e.GetPosition(img, 0)); // we need the points to be relative to the current transform | |
var b = transform.Transform(e.GetPosition(img, 1)); | |
var scale = new CompositeTransform | |
{ | |
CenterX = (a.X + b.X) / 2, | |
CenterY = (a.Y + b.Y) / 2, | |
ScaleX = Clamp(e.DistanceRatio * _initialScale / _old.ScaleX, | |
MinZoom / _old.ScaleX, | |
MaxZoom / _old.ScaleX), | |
ScaleY = Clamp(e.DistanceRatio * _initialScale / _old.ScaleY, | |
MinZoom / _old.ScaleY, | |
MaxZoom / _old.ScaleY) | |
}; | |
ConstrainToParentBounds(img, scale); | |
transform = ComposeScaleTranslate(transform, scale); | |
img.RenderTransform = transform; | |
_old.CenterX = transform.CenterX; | |
_old.CenterY = transform.CenterY; | |
_old.TranslateX = transform.TranslateX; | |
_old.TranslateY = transform.TranslateY; | |
_old.ScaleX = transform.ScaleX; | |
_old.ScaleY = transform.ScaleY; | |
} | |
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e) | |
{ | |
var img = sender as Image; | |
var transform = img.RenderTransform as CompositeTransform; | |
_old.CenterX = transform.CenterX; | |
_old.CenterY = transform.CenterY; | |
_old.TranslateX = transform.TranslateX; | |
_old.TranslateY = transform.TranslateY; | |
_old.ScaleX = transform.ScaleX; | |
_old.ScaleY = transform.ScaleY; | |
_initialScale = transform.ScaleX; | |
} | |
private static void OnDragDelta(object sender, DragDeltaGestureEventArgs e) | |
{ | |
// Translation is done as the last operation, so no need to move the operation up in composition order | |
var img = sender as Image; | |
var transform = img.RenderTransform as CompositeTransform; | |
var translate = new CompositeTransform | |
{ | |
TranslateX = e.HorizontalChange, | |
TranslateY = e.VerticalChange | |
}; | |
ConstrainToParentBounds(img, translate); | |
img.RenderTransform = ComposeScaleTranslate(transform, translate); | |
} | |
private static void ConstrainToParentBounds(FrameworkElement elm, CompositeTransform transform) | |
{ | |
var p = (FrameworkElement)elm.Parent; | |
var canvas = p.TransformToVisual(elm).TransformBounds(new Rect(0, 0, p.ActualWidth, p.ActualHeight)); | |
// Now compute the new viewport relative to the previous | |
var newViewport = transform.TransformBounds(new Rect(0, 0, elm.ActualWidth, elm.ActualHeight)); | |
var top = newViewport.Top - canvas.Top; | |
var bottom = canvas.Bottom - newViewport.Bottom; | |
var left = newViewport.Left - canvas.Left; | |
var right = canvas.Right - newViewport.Right; | |
if (top > 0) | |
if (top + bottom > 0) | |
transform.TranslateY += (bottom - top) / 2; | |
else | |
transform.TranslateY -= top; | |
else if (bottom > 0) | |
if (top + bottom > 0) | |
transform.TranslateY += (bottom - top) / 2; | |
else | |
transform.TranslateY += bottom; | |
if (left > 0) | |
if (left + right > 0) | |
transform.TranslateX += (right - left) / 2; | |
else | |
transform.TranslateX -= left; | |
else if (right > 0) | |
if (left + right > 0) | |
transform.TranslateX += (right - left) / 2; | |
else | |
transform.TranslateX += right; | |
} | |
private static CompositeTransform ComposeScaleTranslate(CompositeTransform fst, CompositeTransform snd) | |
{ | |
// See http://stackoverflow.com/a/19439099/388010 on why this works | |
return new CompositeTransform | |
{ | |
ScaleX = fst.ScaleX * snd.ScaleX, | |
ScaleY = fst.ScaleY * snd.ScaleY, | |
CenterX = fst.CenterX, | |
CenterY = fst.CenterY, | |
TranslateX = snd.TranslateX + snd.ScaleX * fst.TranslateX + (snd.ScaleX - 1) * (fst.CenterX - snd.CenterX), | |
TranslateY = snd.TranslateY + snd.ScaleY * fst.TranslateY + (snd.ScaleY - 1) * (fst.CenterY - snd.CenterY), | |
}; | |
} | |
private static double Clamp(double val, double min, double max) | |
{ | |
return val > min ? val < max ? val : max : min; | |
} | |
private void OrientationChanged(object sender, OrientationChangedEventArgs e) | |
{ | |
// Handling orientation change is a heck more involved than I initially thought | |
AssociatedObject.RenderTransform = new CompositeTransform(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment