1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
8
using System.Windows.Controls;
9
using System.Windows.Controls.Primitives;
10
using System.Windows.Data;
11
using System.Windows.Documents;
12
using System.Windows.Input;
13
using System.Windows.Media;
15
namespace ICSharpCode.SharpDevelop.Widgets
17
public class ZoomScrollViewer : ScrollViewer
19
static ZoomScrollViewer()
21
DefaultStyleKeyProperty.OverrideMetadata(typeof(ZoomScrollViewer),
22
new FrameworkPropertyMetadata(typeof(ZoomScrollViewer)));
25
public static readonly DependencyProperty CurrentZoomProperty =
26
DependencyProperty.Register("CurrentZoom", typeof(double), typeof(ZoomScrollViewer),
27
new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, CalculateZoomButtonCollapsed, CoerceZoom));
29
public double CurrentZoom {
30
get { return (double)GetValue(CurrentZoomProperty); }
31
set { SetValue(CurrentZoomProperty, value); }
34
static object CoerceZoom(DependencyObject d, object baseValue)
36
var zoom = (double)baseValue;
37
ZoomScrollViewer sv = (ZoomScrollViewer)d;
38
return Math.Max(sv.MinimumZoom, Math.Min(sv.MaximumZoom, zoom));
41
public static readonly DependencyProperty MinimumZoomProperty =
42
DependencyProperty.Register("MinimumZoom", typeof(double), typeof(ZoomScrollViewer),
43
new FrameworkPropertyMetadata(0.2));
45
public double MinimumZoom {
46
get { return (double)GetValue(MinimumZoomProperty); }
47
set { SetValue(MinimumZoomProperty, value); }
50
public static readonly DependencyProperty MaximumZoomProperty =
51
DependencyProperty.Register("MaximumZoom", typeof(double), typeof(ZoomScrollViewer),
52
new FrameworkPropertyMetadata(5.0));
54
public double MaximumZoom {
55
get { return (double)GetValue(MaximumZoomProperty); }
56
set { SetValue(MaximumZoomProperty, value); }
59
public static readonly DependencyProperty MouseWheelZoomProperty =
60
DependencyProperty.Register("MouseWheelZoom", typeof(bool), typeof(ZoomScrollViewer),
61
new FrameworkPropertyMetadata(true));
63
public bool MouseWheelZoom {
64
get { return (bool)GetValue(MouseWheelZoomProperty); }
65
set { SetValue(MouseWheelZoomProperty, value); }
68
public static readonly DependencyProperty AlwaysShowZoomButtonsProperty =
69
DependencyProperty.Register("AlwaysShowZoomButtons", typeof(bool), typeof(ZoomScrollViewer),
70
new FrameworkPropertyMetadata(false, CalculateZoomButtonCollapsed));
72
public bool AlwaysShowZoomButtons {
73
get { return (bool)GetValue(AlwaysShowZoomButtonsProperty); }
74
set { SetValue(AlwaysShowZoomButtonsProperty, value); }
77
static readonly DependencyPropertyKey ComputedZoomButtonCollapsedPropertyKey =
78
DependencyProperty.RegisterReadOnly("ComputedZoomButtonCollapsed", typeof(bool), typeof(ZoomScrollViewer),
79
new FrameworkPropertyMetadata(true));
81
public static readonly DependencyProperty ComputedZoomButtonCollapsedProperty = ComputedZoomButtonCollapsedPropertyKey.DependencyProperty;
83
public bool ComputedZoomButtonCollapsed {
84
get { return (bool)GetValue(ComputedZoomButtonCollapsedProperty); }
85
private set { SetValue(ComputedZoomButtonCollapsedPropertyKey, value); }
88
static void CalculateZoomButtonCollapsed(DependencyObject d, DependencyPropertyChangedEventArgs e)
90
ZoomScrollViewer z = d as ZoomScrollViewer;
92
z.ComputedZoomButtonCollapsed = (z.AlwaysShowZoomButtons == false) && (z.CurrentZoom == 1.0);
95
protected override void OnMouseWheel(MouseWheelEventArgs e)
97
if (!e.Handled && Keyboard.Modifiers == ModifierKeys.Control && MouseWheelZoom) {
98
double oldZoom = CurrentZoom;
99
double newZoom = RoundToOneIfClose(CurrentZoom * Math.Pow(1.001, e.Delta));
100
newZoom = Math.Max(this.MinimumZoom, Math.Min(this.MaximumZoom, newZoom));
102
// adjust scroll position so that mouse stays over the same virtual coordinate
103
ContentPresenter presenter = Template.FindName("PART_Presenter", this) as ContentPresenter;
105
if (presenter != null) {
106
Point mousePos = e.GetPosition(presenter);
107
relMousePos = new Vector(mousePos.X / presenter.ActualWidth, mousePos.Y / presenter.ActualHeight);
109
relMousePos = new Vector(0.5, 0.5);
112
Point scrollOffset = new Point(this.HorizontalOffset, this.VerticalOffset);
113
Vector oldHalfViewport = new Vector(this.ViewportWidth / 2, this.ViewportHeight / 2);
114
Vector newHalfViewport = oldHalfViewport / newZoom * oldZoom;
115
Point oldCenter = scrollOffset + oldHalfViewport;
116
Point virtualMousePos = scrollOffset + new Vector(relMousePos.X * this.ViewportWidth, relMousePos.Y * this.ViewportHeight);
118
// As newCenter, we want to choose a point between oldCenter and virtualMousePos. The more we zoom in, the closer
119
// to virtualMousePos. We'll create the line x = oldCenter + lambda * (virtualMousePos-oldCenter).
120
// On this line, we need to choose lambda between -1 and 1:
121
// -1 = zoomed out completely
122
// 0 = zoom unchanged
123
// +1 = zoomed in completely
124
// But the zoom factor (newZoom/oldZoom) we have is in the range [0,+Infinity].
126
// Basically, I just played around until I found a function that maps this to [-1,1] and works well.
127
// "f" is squared because otherwise the mouse simply stays over virtualMousePos, but I wanted virtualMousePos
128
// to move towards the middle -> squaring f causes lambda to be closer to 1, giving virtualMousePos more weight
131
double f = Math.Min(newZoom, oldZoom) / Math.Max(newZoom, oldZoom);
132
double lambda = 1 - f*f;
133
if (oldZoom > newZoom)
136
Point newCenter = oldCenter + lambda * (virtualMousePos - oldCenter);
137
scrollOffset = newCenter - newHalfViewport;
139
SetCurrentValue(CurrentZoomProperty, newZoom);
141
this.ScrollToHorizontalOffset(scrollOffset.X);
142
this.ScrollToVerticalOffset(scrollOffset.Y);
146
base.OnMouseWheel(e);
149
internal static double RoundToOneIfClose(double val)
151
if (Math.Abs(val - 1.0) < 0.0001)
158
sealed class IsNormalZoomConverter : IValueConverter
160
public static readonly IsNormalZoomConverter Instance = new IsNormalZoomConverter();
162
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
164
if (parameter is bool && (bool)parameter)
166
return ((double)value) == 1.0;
169
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
171
throw new NotImplementedException();