329 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			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);
 | 
						|
            }
 | 
						|
        } 
 | 
						|
 | 
						|
    }
 | 
						|
}
 |