XiangTan_DM/DM_Weight/util/TabTip/AnimationHelper.cs

329 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Point = System.Windows.Point;
namespace DM_Weight.util.TabTip
{
internal static class AnimationHelper
{
private static readonly Dictionary<FrameworkElement, Storyboard> MoveRootVisualStoryboards = new Dictionary<FrameworkElement, Storyboard>();
private static Point GetCurrentUIElementPoint(Visual element) => element.PointToScreen(new Point(0, 0)).ToPointInLogicalUnits(element);
internal static event Action<Exception> ExceptionCatched;
private static Rectangle ToRectangleInLogicalUnits(this Rectangle rectangleToConvert, DependencyObject element)
{
const float logicalUnitDpi = 96.0f;
// ReSharper disable once AssignNullToNotNullAttribute
IntPtr windowHandle = new WindowInteropHelper(Window.GetWindow(element)).EnsureHandle();
using (Graphics graphics = Graphics.FromHwnd(windowHandle))
return Rectangle.FromLTRB(
left: (int) (rectangleToConvert.Left * logicalUnitDpi / graphics.DpiX),
top: (int) (rectangleToConvert.Top * logicalUnitDpi / graphics.DpiY),
right: (int) (rectangleToConvert.Right * logicalUnitDpi / graphics.DpiX),
bottom: (int) (rectangleToConvert.Bottom * logicalUnitDpi / graphics.DpiY));
}
private static Point ToPointInLogicalUnits(this Point point, DependencyObject element)
{
const float logicalUnitDpi = 96.0f;
// ReSharper disable once AssignNullToNotNullAttribute
IntPtr windowHandle = new WindowInteropHelper(Window.GetWindow(element)).EnsureHandle();
using (Graphics graphics = Graphics.FromHwnd(windowHandle))
return new Point(x: point.X * logicalUnitDpi / graphics.DpiX, y: point.Y * logicalUnitDpi / graphics.DpiY);
}
// ReSharper disable once UnusedMember.Local
private static Point GetCurrentUIElementPointRelativeToRoot(UIElement element)
{
return element.TransformToAncestor(GetRootVisualForAnimation(element)).Transform(new Point(0, 0));
}
private static Rectangle GetUIElementRect(UIElement element)
{
Rect rect = element.RenderTransform.TransformBounds(new Rect(GetCurrentUIElementPoint(element), element.RenderSize));
return Rectangle.FromLTRB(
left: (int)rect.Left,
top: (int)rect.Top,
right: (int)rect.Right,
bottom: (int)rect.Bottom);
}
private static Rectangle GetCurrentScreenBounds(DependencyObject element) =>
new Screen(Window.GetWindow(element)).Bounds.ToRectangleInLogicalUnits(element);
private static Rectangle GetWorkAreaWithTabTipOpened(DependencyObject element)
{
Rectangle workAreaWithTabTipClosed = GetWorkAreaWithTabTipClosed(element);
int tabTipRectangleTop = TabTip.GetWouldBeTabTipRectangle().ToRectangleInLogicalUnits(element).Top;
int bottom = (tabTipRectangleTop == 0) ? workAreaWithTabTipClosed.Bottom / 2 : tabTipRectangleTop; // in case TabTip is not yet opened
return Rectangle.FromLTRB(
left: workAreaWithTabTipClosed.Left,
top: workAreaWithTabTipClosed.Top,
right: workAreaWithTabTipClosed.Right,
bottom: bottom);
}
private static Rectangle GetWorkAreaWithTabTipClosed(DependencyObject element)
{
Rectangle currentScreenBounds = GetCurrentScreenBounds(element);
Taskbar taskbar = new Taskbar();
Rectangle taskbarBounds = taskbar.Bounds.ToRectangleInLogicalUnits(element);
switch (taskbar.Position)
{
case TaskbarPosition.Bottom:
return Rectangle.FromLTRB(
left: currentScreenBounds.Left,
top: currentScreenBounds.Top,
right: currentScreenBounds.Right,
bottom: taskbarBounds.Top);
case TaskbarPosition.Top:
return Rectangle.FromLTRB(
left: currentScreenBounds.Left,
top: taskbarBounds.Bottom,
right: currentScreenBounds.Right,
bottom: currentScreenBounds.Bottom);
default:
return currentScreenBounds;
}
}
// ReSharper disable once UnusedMember.Local
private static bool IsUIElementInWorkAreaWithTabTipOpened(UIElement element)
{
return GetWorkAreaWithTabTipOpened(element).Contains(GetUIElementRect(element));
}
// ReSharper disable once UnusedMember.Local
private static bool IsUIElementInWorkArea(UIElement element, Rectangle workAreaRectangle)
{
return workAreaRectangle.Contains(GetUIElementRect(element));
}
private static FrameworkElement GetRootVisualForAnimation(DependencyObject element)
{
Window rootWindow = Window.GetWindow(element);
if (rootWindow?.WindowState != WindowState.Maximized)
return rootWindow;
else
return VisualTreeHelper.GetChild(rootWindow, 0) as FrameworkElement;
}
private static double GetYOffsetToMoveUIElementInToWorkArea(Rectangle uiElementRectangle, Rectangle workAreaRectangle)
{
const double noOffset = 0;
const int paddingTop = 30;
const int paddingBottom = 10;
if (uiElementRectangle.Top >= workAreaRectangle.Top &&
uiElementRectangle.Bottom <= workAreaRectangle.Bottom) // UIElement is in work area
return noOffset;
if (uiElementRectangle.Top < workAreaRectangle.Top) // Top of UIElement higher than work area
return workAreaRectangle.Top - uiElementRectangle.Top + paddingTop; // positive value to move down
else // Botom of UIElement lower than work area
{
int offset = workAreaRectangle.Bottom - uiElementRectangle.Bottom - paddingBottom; // negative value to move up
if (uiElementRectangle.Top > (workAreaRectangle.Top - offset)) // will Top of UIElement be in work area if offset applied?
return offset; // negative value to move up
else
return workAreaRectangle.Top - uiElementRectangle.Top + paddingTop; // negative value to move up, but only to the point, where top
// of UIElement is just below top bound of work area
}
}
private static Storyboard GetOrCreateMoveRootVisualStoryboard(FrameworkElement visualRoot)
{
if (MoveRootVisualStoryboards.ContainsKey(visualRoot))
return MoveRootVisualStoryboards[visualRoot];
else
return CreateMoveRootVisualStoryboard(visualRoot);
}
private static Storyboard CreateMoveRootVisualStoryboard(FrameworkElement visualRoot)
{
Storyboard moveRootVisualStoryboard = new Storyboard
{
Duration = new Duration(TimeSpan.FromSeconds(0.35))
};
DoubleAnimation moveAnimation = new DoubleAnimation
{
EasingFunction = new CircleEase {EasingMode = EasingMode.EaseOut},
Duration = new Duration(TimeSpan.FromSeconds(0.35)),
FillBehavior = (visualRoot is Window) ? FillBehavior.Stop : FillBehavior.HoldEnd
};
moveRootVisualStoryboard.Children.Add(moveAnimation);
if (!(visualRoot is Window))
visualRoot.RenderTransform = new TranslateTransform();
Storyboard.SetTarget(moveAnimation, visualRoot);
Storyboard.SetTargetProperty(
element: moveAnimation,
path: (visualRoot is Window) ? new PropertyPath("Top") : new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
MoveRootVisualStoryboards.Add(visualRoot, moveRootVisualStoryboard);
SubscribeToWindowStateChangedToMoveRootVisual(visualRoot);
return moveRootVisualStoryboard;
}
private static void SubscribeToWindowStateChangedToMoveRootVisual(FrameworkElement visualRoot)
{
// ReSharper disable once CanBeReplacedWithTryCastAndCheckForNull
if (visualRoot is Window)
{
Window window = (Window)visualRoot;
window.StateChanged += (sender, args) =>
{
if (window.WindowState == WindowState.Normal)
MoveRootVisualBy(
rootVisual: window,
moveBy: GetYOffsetToMoveUIElementInToWorkArea(
uiElementRectangle: GetWindowRectangle(window),
workAreaRectangle: GetWorkAreaWithTabTipClosed(window)));
};
}
else
{
Window window = Window.GetWindow(visualRoot);
if (window != null)
window.StateChanged += (sender, args) =>
{
if (window.WindowState == WindowState.Normal)
MoveRootVisualTo(visualRoot, 0);
};
}
}
private static void MoveRootVisualBy(FrameworkElement rootVisual, double moveBy)
{
if (moveBy == 0)
return;
Storyboard moveRootVisualStoryboard = GetOrCreateMoveRootVisualStoryboard(rootVisual);
DoubleAnimation doubleAnimation = moveRootVisualStoryboard.Children.First() as DoubleAnimation;
if (doubleAnimation != null)
{
Window window = rootVisual as Window;
if (window != null)
{
doubleAnimation.From = window.Top;
doubleAnimation.To = window.Top + moveBy;
}
else
{
doubleAnimation.From = doubleAnimation.To ?? 0;
doubleAnimation.To = (doubleAnimation.To ?? 0) + moveBy;
}
}
moveRootVisualStoryboard.Begin();
}
private static void MoveRootVisualTo(FrameworkElement rootVisual, double moveTo)
{
Storyboard moveRootVisualStoryboard = GetOrCreateMoveRootVisualStoryboard(rootVisual);
DoubleAnimation doubleAnimation = moveRootVisualStoryboard.Children.First() as DoubleAnimation;
if (doubleAnimation != null)
{
Window window = rootVisual as Window;
if (window != null)
{
doubleAnimation.From = window.Top;
doubleAnimation.To = moveTo;
}
else
{
doubleAnimation.From = doubleAnimation.To ?? 0;
doubleAnimation.To = moveTo;
}
}
moveRootVisualStoryboard.Begin();
}
internal static void GetUIElementInToWorkAreaWithTabTipOpened(UIElement element)
{
try
{
FrameworkElement rootVisualForAnimation = GetRootVisualForAnimation(element);
Rectangle workAreaWithTabTipOpened = GetWorkAreaWithTabTipOpened(element);
Rectangle uiElementRectangle;
Window window = rootVisualForAnimation as Window;
if (window != null && workAreaWithTabTipOpened.Height >= window.Height)
uiElementRectangle = GetWindowRectangle(window);
else
uiElementRectangle = GetUIElementRect(element);
MoveRootVisualBy(
rootVisual: rootVisualForAnimation,
moveBy: GetYOffsetToMoveUIElementInToWorkArea(
uiElementRectangle: uiElementRectangle,
workAreaRectangle: workAreaWithTabTipOpened));
}
catch (Exception ex)
{
ExceptionCatched?.Invoke(ex);
}
}
private static Rectangle GetWindowRectangle(Window window)
{
return Rectangle.FromLTRB(
left: (int)window.Left,
top: (int)window.Top,
right: (int)(window.Left + window.Width),
bottom: (int)(window.Top + window.Height));
}
internal static void GetEverythingInToWorkAreaWithTabTipClosed()
{
try
{
foreach (KeyValuePair<FrameworkElement, Storyboard> moveRootVisualStoryboard in MoveRootVisualStoryboards)
{
Window window = moveRootVisualStoryboard.Key as Window;
// if window exist also check if it has not been closed
if (window != null && new WindowInteropHelper(window).Handle != IntPtr.Zero)
MoveRootVisualBy(
rootVisual: window,
moveBy: GetYOffsetToMoveUIElementInToWorkArea(
uiElementRectangle: GetWindowRectangle(window),
workAreaRectangle: GetWorkAreaWithTabTipClosed(window)));
else
MoveRootVisualTo(rootVisual: moveRootVisualStoryboard.Key, moveTo: 0);
}
}
catch (Exception ex)
{
ExceptionCatched?.Invoke(ex);
}
}
}
}