diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt
index 5148a0f3df..5b24af3dee 100644
--- a/.github/actions/spell-check/expect.txt
+++ b/.github/actions/spell-check/expect.txt
@@ -752,6 +752,7 @@ hxx
Hyperlink
IAction
IActivated
+IAnimatable
IApp
IApplication
IAppx
@@ -796,6 +797,7 @@ IDrop
idx
IDXGI
IDYES
+IEasing
IEnum
IEnumerable
IEnumerator
diff --git a/src/modules/colorPicker/ColorPickerUI/Behaviors/MoveWindowBehavior.cs b/src/modules/colorPicker/ColorPickerUI/Behaviors/MoveWindowBehavior.cs
deleted file mode 100644
index 6a3f096d0c..0000000000
--- a/src/modules/colorPicker/ColorPickerUI/Behaviors/MoveWindowBehavior.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (c) Microsoft Corporation
-// The Microsoft Corporation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System;
-using System.Windows;
-using System.Windows.Media.Animation;
-using Microsoft.Xaml.Behaviors;
-
-namespace ColorPicker.Behaviors
-{
- public class MoveWindowBehavior : Behavior
- {
- public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(double), typeof(MoveWindowBehavior), new PropertyMetadata(new PropertyChangedCallback(LeftPropertyChanged)));
-
- private static void LeftPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var sender = ((MoveWindowBehavior)d).AssociatedObject;
- var move = new DoubleAnimation(sender.Left, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
- move.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
- sender.BeginAnimation(Window.LeftProperty, move, HandoffBehavior.Compose);
- }
-
- public static readonly DependencyProperty TopProperty = DependencyProperty.Register("Top", typeof(double), typeof(MoveWindowBehavior), new PropertyMetadata(new PropertyChangedCallback(TopPropertyChanged)));
-
- private static void TopPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- var sender = ((MoveWindowBehavior)d).AssociatedObject;
- var move = new DoubleAnimation(sender.Top, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
- move.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseInOut };
- sender.BeginAnimation(Window.TopProperty, move, HandoffBehavior.Compose);
- }
-
- public double Left
- {
- get
- {
- return (double)GetValue(LeftProperty);
- }
-
- set
- {
- SetValue(LeftProperty, value);
- }
- }
-
- public double Top
- {
- get
- {
- return (double)GetValue(TopProperty);
- }
-
- set
- {
- SetValue(TopProperty, value);
- }
- }
- }
-}
diff --git a/src/modules/colorPicker/ColorPickerUI/Behaviors/ResizeBehavior.cs b/src/modules/colorPicker/ColorPickerUI/Behaviors/ResizeBehavior.cs
index 1f7c3b5857..5424f44acd 100644
--- a/src/modules/colorPicker/ColorPickerUI/Behaviors/ResizeBehavior.cs
+++ b/src/modules/colorPicker/ColorPickerUI/Behaviors/ResizeBehavior.cs
@@ -11,20 +11,55 @@ namespace ColorPicker.Behaviors
{
public class ResizeBehavior : Behavior
{
+ // animation behavior variables
+ // used when size is getting bigger
+ private static readonly TimeSpan _animationTime = TimeSpan.FromMilliseconds(200);
+ private static readonly IEasingFunction _easeFunction = new SineEase() { EasingMode = EasingMode.EaseOut };
+
+ // used when size is getting smaller
+ private static readonly TimeSpan _animationTimeSmaller = _animationTime;
+ private static readonly IEasingFunction _easeFunctionSmaller = new QuadraticEase() { EasingMode = EasingMode.EaseIn };
+
+ private static void CustomAnimation(DependencyProperty prop, IAnimatable sender, double fromValue, double toValue)
+ {
+ // if the animation is to/from a value of 0, it will cancel the current animation
+ DoubleAnimation move = null;
+ if (toValue > 0 && fromValue > 0)
+ {
+ // if getting bigger
+ if (fromValue < toValue)
+ {
+ move = new DoubleAnimation(fromValue, toValue, new Duration(_animationTime), FillBehavior.Stop)
+ {
+ EasingFunction = _easeFunction,
+ };
+ }
+ else
+ {
+ move = new DoubleAnimation(fromValue, toValue, new Duration(_animationTimeSmaller), FillBehavior.Stop)
+ {
+ EasingFunction = _easeFunctionSmaller,
+ };
+ }
+ }
+
+ // HandoffBehavior must be SnapshotAndReplace
+ // Compose does not allow cancellation
+ sender.BeginAnimation(prop, move, HandoffBehavior.SnapshotAndReplace);
+ }
+
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register("Width", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(WidthPropertyChanged)));
private static void WidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = ((ResizeBehavior)d).AssociatedObject;
- var move = new DoubleAnimation(sender.Width, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
- move.Completed += (s, e1) =>
- {
- sender.BeginAnimation(FrameworkElement.WidthProperty, null);
- sender.Width = (double)e.NewValue;
- };
- move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
- sender.BeginAnimation(FrameworkElement.WidthProperty, move, HandoffBehavior.Compose);
+ var fromValue = sender.Width;
+ var toValue = (double)e.NewValue;
+
+ // setting Width before animation prevents jumping
+ sender.Width = toValue;
+ CustomAnimation(FrameworkElement.WidthProperty, sender, fromValue, toValue);
}
public static readonly DependencyProperty HeightProperty = DependencyProperty.Register("Height", typeof(double), typeof(ResizeBehavior), new PropertyMetadata(new PropertyChangedCallback(HeightPropertyChanged)));
@@ -32,15 +67,13 @@ namespace ColorPicker.Behaviors
private static void HeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sender = ((ResizeBehavior)d).AssociatedObject;
- var move = new DoubleAnimation(sender.Height, (double)e.NewValue, new Duration(TimeSpan.FromMilliseconds(150)), FillBehavior.Stop);
- move.Completed += (s, e1) =>
- {
- sender.BeginAnimation(FrameworkElement.HeightProperty, null);
- sender.Height = (double)e.NewValue;
- };
- move.EasingFunction = new QuadraticEase() { EasingMode = EasingMode.EaseOut };
- sender.BeginAnimation(FrameworkElement.HeightProperty, move, HandoffBehavior.Compose);
+ var fromValue = sender.Height;
+ var toValue = (double)e.NewValue;
+
+ // setting Height before animation prevents jumping
+ sender.Height = toValue;
+ CustomAnimation(FrameworkElement.HeightProperty, sender, fromValue, toValue);
}
public double Width
diff --git a/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs b/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs
index ffd61da6ff..ad9564260d 100644
--- a/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs
+++ b/src/modules/colorPicker/ColorPickerUI/Helpers/ZoomWindowHelper.cs
@@ -18,33 +18,27 @@ namespace ColorPicker.Helpers
[Export(typeof(ZoomWindowHelper))]
public class ZoomWindowHelper
{
- private const int ZoomWindowChangeDelayInMS = 50;
private const int ZoomFactor = 2;
private const int BaseZoomImageSize = 50;
private const int MaxZoomLevel = 4;
private const int MinZoomLevel = 0;
+ private static readonly Bitmap _bmp = new Bitmap(BaseZoomImageSize, BaseZoomImageSize, PixelFormat.Format32bppArgb);
+ private static readonly Graphics _graphics = Graphics.FromImage(_bmp);
+
private readonly IZoomViewModel _zoomViewModel;
private readonly AppStateHandler _appStateHandler;
- private readonly IThrottledActionInvoker _throttledActionInvoker;
private int _currentZoomLevel;
private int _previousZoomLevel;
private ZoomWindow _zoomWindow;
- private double _lastLeft;
- private double _lastTop;
-
- private double _previousScaledX;
- private double _previousScaledY;
-
[ImportingConstructor]
- public ZoomWindowHelper(IZoomViewModel zoomViewModel, AppStateHandler appStateHandler, IThrottledActionInvoker throttledActionInvoker)
+ public ZoomWindowHelper(IZoomViewModel zoomViewModel, AppStateHandler appStateHandler)
{
_zoomViewModel = zoomViewModel;
_appStateHandler = appStateHandler;
- _throttledActionInvoker = throttledActionInvoker;
_appStateHandler.AppClosed += AppStateHandler_AppClosed;
_appStateHandler.AppHidden += AppStateHandler_AppClosed;
}
@@ -73,7 +67,7 @@ namespace ColorPicker.Helpers
{
_currentZoomLevel = 0;
_previousZoomLevel = 0;
- HideZoomWindow();
+ HideZoomWindow(true);
}
private void SetZoomImage(System.Windows.Point point)
@@ -89,28 +83,17 @@ namespace ColorPicker.Helpers
{
var x = (int)point.X - (BaseZoomImageSize / 2);
var y = (int)point.Y - (BaseZoomImageSize / 2);
- var rect = new Rectangle(x, y, BaseZoomImageSize, BaseZoomImageSize);
- using (var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb))
- {
- var g = Graphics.FromImage(bmp);
- g.CopyFromScreen(rect.Left, rect.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
+ _graphics.CopyFromScreen(x, y, 0, 0, _bmp.Size, CopyPixelOperation.SourceCopy);
- var bitmapImage = BitmapToImageSource(bmp);
-
- _zoomViewModel.ZoomArea = bitmapImage;
- _zoomViewModel.ZoomFactor = 1;
- }
+ _zoomViewModel.ZoomArea = BitmapToImageSource(_bmp);
}
else
{
- var enlarge = (_currentZoomLevel - _previousZoomLevel) > 0 ? true : false;
- var currentZoomFactor = enlarge ? ZoomFactor : 1.0 / ZoomFactor;
-
- _zoomViewModel.ZoomFactor *= currentZoomFactor;
+ _zoomViewModel.ZoomFactor = Math.Pow(ZoomFactor, _currentZoomLevel - 1);
}
- ShowZoomWindow((int)point.X, (int)point.Y);
+ ShowZoomWindow(point);
}
private static BitmapSource BitmapToImageSource(Bitmap bitmap)
@@ -130,84 +113,67 @@ namespace ColorPicker.Helpers
}
}
- private void HideZoomWindow()
+ private void HideZoomWindow(bool fully = false)
{
if (_zoomWindow != null)
{
- _zoomWindow.Hide();
+ _zoomWindow.Opacity = 0;
+ _zoomViewModel.DesiredWidth = 0;
+ _zoomViewModel.DesiredHeight = 0;
+
+ if (fully)
+ {
+ _zoomWindow.Hide();
+ }
}
}
- private void ShowZoomWindow(int x, int y)
+ private void ShowZoomWindow(System.Windows.Point point)
{
- if (_zoomWindow == null)
+ _zoomWindow ??= new ZoomWindow
{
- _zoomWindow = new ZoomWindow();
- _zoomWindow.Content = _zoomViewModel;
- _zoomWindow.Loaded += ZoomWindow_Loaded;
- _zoomWindow.IsVisibleChanged += ZoomWindow_IsVisibleChanged;
- }
+ Content = _zoomViewModel,
+ Opacity = 0,
+ };
- // we just started zooming, remember where we opened zoom window
- if (_currentZoomLevel == 1 && _previousZoomLevel == 0)
- {
- var dpi = MonitorResolutionHelper.GetCurrentMonitorDpi();
- _previousScaledX = x / dpi.DpiScaleX;
- _previousScaledY = y / dpi.DpiScaleY;
- }
-
- _lastLeft = Math.Floor(_previousScaledX - (BaseZoomImageSize * Math.Pow(ZoomFactor, _currentZoomLevel - 1) / 2));
- _lastTop = Math.Floor(_previousScaledY - (BaseZoomImageSize * Math.Pow(ZoomFactor, _currentZoomLevel - 1) / 2));
-
- var justShown = false;
if (!_zoomWindow.IsVisible)
{
- _zoomWindow.Left = _lastLeft;
- _zoomWindow.Top = _lastTop;
- _zoomViewModel.Height = BaseZoomImageSize;
- _zoomViewModel.Width = BaseZoomImageSize;
_zoomWindow.Show();
- justShown = true;
+ }
+
+ if (_zoomWindow.Opacity < 0.5)
+ {
+ var halfWidth = _zoomWindow.Width / 2;
+ var halfHeight = _zoomWindow.Height / 2;
+
+ // usually takes 1-3 iterations to converge
+ // 5 is just an arbitrary limit to prevent infinite loops
+ for (var i = 0; i < 5; i++)
+ {
+ // mouse position relative to top left of _zoomWindow
+ var scaledPoint = _zoomWindow.PointFromScreen(point);
+
+ var diffX = scaledPoint.X - halfWidth;
+ var diffY = scaledPoint.Y - halfHeight;
+
+ // minimum difference that is considered important
+ const double minDiff = 0.05;
+ if (Math.Abs(diffX) < minDiff && Math.Abs(diffY) < minDiff)
+ {
+ break;
+ }
+
+ _zoomWindow.Left += diffX;
+ _zoomWindow.Top += diffY;
+ }
// make sure color picker window is on top of just opened zoom window
AppStateHandler.SetTopMost();
+ _zoomWindow.Opacity = 1;
}
- // dirty hack - sometimes when we just show a window on a second monitor with different DPI,
- // window position is not set correctly on a first time, we need to "ping" it again to make it appear on the proper location
- if (justShown)
- {
- _zoomWindow.Left = _lastLeft + 1;
- _zoomWindow.Top = _lastTop + 1;
- SessionEventHelper.Event.ZoomUsed = true;
- }
-
- _throttledActionInvoker.ScheduleAction(
- () =>
- {
- _zoomWindow.DesiredLeft = _lastLeft;
- _zoomWindow.DesiredTop = _lastTop;
- _zoomViewModel.DesiredHeight = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
- _zoomViewModel.DesiredWidth = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
- },
- ZoomWindowChangeDelayInMS);
- }
-
- private void ZoomWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- // need to set at this point again, to avoid issues moving between screens with different scaling
- if ((bool)e.NewValue)
- {
- _zoomWindow.Left = _lastLeft;
- _zoomWindow.Top = _lastTop;
- }
- }
-
- private void ZoomWindow_Loaded(object sender, RoutedEventArgs e)
- {
- // need to call it again at load time, because it does was not dpi aware at the first time of Show() call
- _zoomWindow.Left = _lastLeft;
- _zoomWindow.Top = _lastTop;
+ _zoomViewModel.DesiredHeight = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
+ _zoomViewModel.DesiredWidth = BaseZoomImageSize * _zoomViewModel.ZoomFactor;
}
private void AppStateHandler_AppClosed(object sender, EventArgs e)
diff --git a/src/modules/colorPicker/ColorPickerUI/Views/ZoomView.xaml b/src/modules/colorPicker/ColorPickerUI/Views/ZoomView.xaml
index 1c5c0214cb..5c6c617f6f 100644
--- a/src/modules/colorPicker/ColorPickerUI/Views/ZoomView.xaml
+++ b/src/modules/colorPicker/ColorPickerUI/Views/ZoomView.xaml
@@ -11,6 +11,8 @@
Focusable="False">
-
diff --git a/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs b/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs
index 2fac62c8b3..8434b8879d 100644
--- a/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs
+++ b/src/modules/colorPicker/ColorPickerUI/ZoomWindow.xaml.cs
@@ -10,50 +10,16 @@ namespace ColorPicker
///
/// Interaction logic for ZoomWindow.xaml
///
- public partial class ZoomWindow : Window, INotifyPropertyChanged
+ public partial class ZoomWindow : Window
{
- private double _left;
- private double _top;
-
public ZoomWindow()
{
InitializeComponent();
DataContext = this;
- }
- public double DesiredLeft
- {
- get
- {
- return _left;
- }
-
- set
- {
- _left = value;
- NotifyPropertyChanged(nameof(DesiredLeft));
- }
- }
-
- public double DesiredTop
- {
- get
- {
- return _top;
- }
-
- set
- {
- _top = value;
- NotifyPropertyChanged(nameof(DesiredTop));
- }
- }
-
- public event PropertyChangedEventHandler PropertyChanged;
-
- private void NotifyPropertyChanged(string propertyName)
- {
- PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ // must be large enough to fit max zoom
+ Width = 500;
+ Height = 500;
}
}
}