Przeglądaj źródła

Implement animation for scrolling the image galleries

Ruben 2 lat temu
rodzic
commit
3603e1699f

+ 17 - 0
src/PicView/ChangeImage/Navigation.cs

@@ -117,6 +117,23 @@ internal static class Navigation
 
         if (fastPic) await FastPic.Run(next).ConfigureAwait(false);
         else await LoadPic.LoadPicAtIndexAsync(next).ConfigureAwait(false);
+
+        if (Settings.Default.FullscreenGallery)
+        {
+            await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(() =>
+            {
+                UC.GetPicGallery.Scroller.CanContentScroll = true; // Disable animations
+                // Deselect current item
+                GalleryNavigation.SetSelected(GalleryNavigation.SelectedGalleryItem, false);
+                GalleryNavigation.SetSelected(FolderIndex, false);
+
+                // Select next item
+                GalleryNavigation.SetSelected(next, true);
+                GalleryNavigation.SelectedGalleryItem = next;
+                UC.GetPicGallery.Scroller.ScrollToHorizontalOffset(GalleryNavigation.CenterScrollPosition);
+            });
+        }
+            
     }
 
     /// <summary>

+ 0 - 3
src/PicView/ChangeImage/UpdateImage.cs

@@ -81,9 +81,6 @@ internal static class UpdateImage
             ConfigureWindows.GetMainWindow.MainImage.LayoutTransform = null;
         }, DispatcherPriority.Send);
 
-        if (Settings.Default.FullscreenGallery)
-            GalleryNavigation.FullscreenGalleryNavigation();
-
         if (GetToolTipMessage is { IsVisible: true })
             ConfigureWindows.GetMainWindow.Dispatcher.Invoke(() => GetToolTipMessage.Visibility = Visibility.Hidden);
 

+ 2 - 0
src/PicView/PicGallery/GalleryClick.cs

@@ -141,6 +141,7 @@ internal static class GalleryClick
     {
         await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(() =>
         {
+            GetPicGallery.Scroller.CanContentScroll = false; // Enable animations
             // Deselect current item
             GalleryNavigation.SetSelected(GalleryNavigation.SelectedGalleryItem, false);
             GalleryNavigation.SetSelected(FolderIndex, false);
@@ -155,6 +156,7 @@ internal static class GalleryClick
             // Select next item
             GalleryNavigation.SetSelected(id, true);
             GalleryNavigation.SelectedGalleryItem = id;
+            GetPicGallery.Scroller.ScrollToHorizontalOffset(GalleryNavigation.CenterScrollPosition);
         });
         // Change image
         await LoadPic.LoadPicAtIndexAsync(id).ConfigureAwait(false);

+ 0 - 4
src/PicView/PicGallery/GalleryLoad.cs

@@ -22,7 +22,6 @@ internal static class GalleryLoad
     internal static void PicGallery_Loaded(object sender, RoutedEventArgs e)
     {
         // Add events and set fields, when it's loaded.
-        UC.GetPicGallery.Scroller.PreviewMouseWheel += (_, e) => GalleryNavigation.ScrollTo(e.Delta > 0);
         UC.GetPicGallery.Scroller.ScrollChanged += (_, _) => ConfigureWindows.GetMainWindow.Focus(); // Maintain window focus when scrolling manually
         UC.GetPicGallery.grid.MouseLeftButtonDown += (_, _) => ConfigureWindows.GetMainWindow.Focus();
         UC.GetPicGallery.x2.MouseLeftButtonDown += (_, _) => GalleryToggle.CloseHorizontalGallery();
@@ -217,9 +216,6 @@ internal static class GalleryLoad
             };
 
             UC.GetPicGallery.Container.Children.Add(item);
-            if (!selected) { return; }
-            GalleryNavigation.SelectedGalleryItem = i;
-            GalleryNavigation.ScrollTo();
         });
     }
 

+ 23 - 13
src/PicView/PicGallery/GalleryNavigation.cs

@@ -43,7 +43,7 @@ internal static class GalleryNavigation
         }
     }
 
-    private static double CenterScrollPosition
+    internal static double CenterScrollPosition
     {
         get
         {
@@ -69,6 +69,8 @@ internal static class GalleryNavigation
         if (GetPicGallery == null || PicGalleryItemSize < 1) { return; }
         if (!GalleryFunctions.IsGalleryOpen) return;
 
+        GetPicGallery.Scroller.CanContentScroll = false; // Enable animations
+
         if (Settings.Default.FullscreenGallery)
         {
             GetPicGallery.Scroller.ScrollToHorizontalOffset(CenterScrollPosition);
@@ -92,6 +94,7 @@ internal static class GalleryNavigation
     /// <param name="speedUp"></param>
     internal static void ScrollTo(bool next, bool end = false, bool speedUp = false)
     {
+        GetPicGallery.Scroller.CanContentScroll = false; // Enable animations
         if (end)
         {
             if (next)
@@ -105,11 +108,24 @@ internal static class GalleryNavigation
         }
         else
         {
-            var speed = speedUp ? PicGalleryItemSize * 4.7 : PicGalleryItemSize / 2;
-            var offset = next ? -speed : speed;
+            if (Settings.Default.FullscreenGallery)
+            {
+                GetPicGallery.Scroller.CanContentScroll = true; // Disable animations
+                var speed = speedUp ? PicGalleryItemSize * HorizontalItems * 1.2 : PicGalleryItemSize * HorizontalItems * 0.2;
+                var offset = next ? -speed : speed;
+
+                var direction = GetPicGallery.Scroller.HorizontalOffset + offset;
+                GetPicGallery.Scroller.ScrollToHorizontalOffset(direction);
+            }
+            else
+            {
+                var speed = speedUp ? PicGalleryItemSize * HorizontalItems * 1.2 : PicGalleryItemSize * HorizontalItems / 1.7;
+                var offset = next ? -speed : speed;
+
+                var newOffset = GetPicGallery.Scroller.HorizontalOffset + offset;
 
-            var direction = GetPicGallery.Scroller.HorizontalOffset + offset;
-            GetPicGallery.Scroller.ScrollToHorizontalOffset(direction);
+                GetPicGallery.Scroller.ScrollToHorizontalOffset(newOffset);
+            }
         }
     }
 
@@ -218,14 +234,8 @@ internal static class GalleryNavigation
 
         ConfigureWindows.GetMainWindow.Dispatcher.Invoke(() =>
         {
-            if (Settings.Default.FullscreenGallery)
-            {
-                GetPicGallery.Scroller.ScrollToHorizontalOffset(CenterScrollPosition);
-            }
-            else
-            {
-                GetPicGallery.Scroller.ScrollToVerticalOffset(CenterScrollPosition);
-            }
+            GetPicGallery.Scroller.CanContentScroll = true; // Disable animations
+            GetPicGallery.Scroller.ScrollToHorizontalOffset(CenterScrollPosition);
         });
 
         Tooltip.CloseToolTipMessage();

+ 1 - 1
src/PicView/Shortcuts/MainKeyboardShortcuts.cs

@@ -137,7 +137,7 @@ internal static class MainKeyboardShortcuts
 
             case Key.PageUp when GetPicGallery != null && GalleryFunctions.IsGalleryOpen:
                 {
-                    GalleryNavigation.ScrollTo(true, ctrlDown);
+                    GalleryNavigation.ScrollTo(true, ctrlDown, shiftDown);
                     return;
                 }
             case Key.PageUp:

+ 1 - 1
src/PicView/Shortcuts/MainMouseKeys.cs

@@ -221,7 +221,7 @@ internal static class MainMouseKeys
         if (GetPicGallery is not null && GetPicGallery.IsMouseOver)
         {
             await GetMainWindow.Dispatcher.BeginInvoke(DispatcherPriority.Normal, () =>
-                GalleryNavigation.ScrollTo(direction, false, true));
+                GalleryNavigation.ScrollTo(direction, false, (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift));
         }
         else
         {

+ 1 - 4
src/PicView/Themes/Styles/ScrollBar.xaml

@@ -219,10 +219,7 @@
     </ControlTemplate>
 
     <ControlTemplate x:Key="HorizontalScrollBar" TargetType="{x:Type ScrollBar}">
-        <Track
-            Name="PART_Track"
-            Grid.Column="1"
-            IsDirectionReversed="False">
+        <Track Name="PART_Track" IsDirectionReversed="False">
             <Track.Thumb>
                 <Thumb Style="{StaticResource HorizontalScrollBarThumbStyle}" />
             </Track.Thumb>

+ 346 - 0
src/PicView/UILogic/ScrollInfoAdapter.cs

@@ -0,0 +1,346 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace PicView.UILogic
+{
+    public class SmoothScrollViewer : ScrollViewer
+    {
+        public SmoothScrollViewer()
+        {
+            Loaded += ScrollViewer_Loaded;
+        }
+
+        private void ScrollViewer_Loaded(object sender, RoutedEventArgs e)
+        {
+            ScrollInfo = new ScrollInfoAdapter(ScrollInfo);
+        }
+    }
+
+    public class ScrollInfoAdapter : UIElement, IScrollInfo
+    {
+        internal const double _scrollLineDelta = 16.0;
+        internal const double _mouseWheelDelta = 48.0;
+        private readonly IScrollInfo _child;
+        private double _computedHorizontalOffset;
+        private double _computedVerticalOffset;
+
+        public ScrollInfoAdapter(IScrollInfo child)
+        {
+            _child = child;
+        }
+
+        public bool CanVerticallyScroll
+        {
+            get => _child.CanVerticallyScroll;
+            set => _child.CanVerticallyScroll = value;
+        }
+
+        public bool CanHorizontallyScroll
+        {
+            get => _child.CanHorizontallyScroll;
+            set => _child.CanHorizontallyScroll = value;
+        }
+
+        public double ExtentWidth => _child.ExtentWidth;
+
+        public double ExtentHeight => _child.ExtentHeight;
+
+        public double ViewportWidth => _child.ViewportWidth;
+
+        public double ViewportHeight => _child.ViewportHeight;
+
+        public double HorizontalOffset => _child.HorizontalOffset;
+        public double VerticalOffset => _child.VerticalOffset;
+
+        public ScrollViewer ScrollOwner
+        {
+            get => _child.ScrollOwner;
+            set => _child.ScrollOwner = value;
+        }
+
+        public void LineUp()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.LineUp();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset - _scrollLineDelta);
+            }
+        }
+
+        public void LineDown()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.LineDown();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset + _scrollLineDelta);
+            }
+        }
+
+        public void LineLeft()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.LineLeft();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset - _scrollLineDelta);
+            }
+        }
+
+        public void LineRight()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.LineRight();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset + _scrollLineDelta);
+            }
+        }
+
+        public void MouseWheelUp()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.MouseWheelUp();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset - _mouseWheelDelta);
+            }
+        }
+
+        public void MouseWheelDown()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.MouseWheelDown();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset + _mouseWheelDelta);
+            }
+        }
+
+        public void MouseWheelLeft()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.MouseWheelLeft();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset - _mouseWheelDelta);
+            }
+        }
+
+        public void MouseWheelRight()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.MouseWheelRight();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset + _mouseWheelDelta);
+            }
+        }
+
+        public void PageUp()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.PageUp();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset - ViewportHeight);
+            }
+        }
+
+        public void PageDown()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.PageDown();
+            }
+            else
+            {
+                VerticalScroll(_computedVerticalOffset + ViewportHeight);
+            }
+        }
+
+        public void PageLeft()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.PageLeft();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset - ViewportWidth);
+            }
+        }
+
+        public void PageRight()
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.PageRight();
+            }
+            else
+            {
+                HorizontalScroll(_computedHorizontalOffset + ViewportWidth);
+            }
+        }
+
+        public void SetHorizontalOffset(double offset)
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.SetHorizontalOffset(offset);
+            }
+            else
+            {
+                _computedHorizontalOffset = offset;
+                Animate(HorizontalScrollOffsetProperty, offset, 0);
+            }
+        }
+
+        public void SetVerticalOffset(double offset)
+        {
+            if (_child.ScrollOwner.CanContentScroll)
+            {
+                _child.SetVerticalOffset(offset);
+            }
+            else
+            {
+                _computedVerticalOffset = offset;
+                Animate(VerticalScrollOffsetProperty, offset, 0);
+            }
+        }
+
+        #region not exposed methods
+
+        private void Animate(DependencyProperty property, double targetValue, int duration = 700)
+        {
+            //make a smooth animation that starts and ends slowly
+            var keyFramesAnimation = new DoubleAnimationUsingKeyFrames
+            {
+                Duration = TimeSpan.FromSeconds(.8)
+            };
+            keyFramesAnimation.KeyFrames.Add(
+                new SplineDoubleKeyFrame(
+                    targetValue,
+                    KeyTime.FromTimeSpan(TimeSpan.FromSeconds(.8)),
+                    new KeySpline(0.5, 0.0, 0.5, 1.0)
+                )
+            );
+
+            BeginAnimation(property, keyFramesAnimation);
+        }
+
+        private void VerticalScroll(double val)
+        {
+            if (!(Math.Abs(_computedVerticalOffset - ValidateVerticalOffset(val)) >
+                  0.1)) //prevent restart of animation in case of frequent event fire
+            {
+                return;
+            }
+
+            _computedVerticalOffset = ValidateVerticalOffset(val);
+            Animate(VerticalScrollOffsetProperty, _computedVerticalOffset);
+        }
+
+        private void HorizontalScroll(double val)
+        {
+            if (!(Math.Abs(_computedHorizontalOffset - ValidateHorizontalOffset(val)) >
+                  0.1)) //prevent restart of animation in case of frequent event fire
+            {
+                return;
+            }
+
+            _computedHorizontalOffset = ValidateHorizontalOffset(val);
+            Animate(HorizontalScrollOffsetProperty, _computedHorizontalOffset);
+        }
+
+        private double ValidateVerticalOffset(double verticalOffset)
+        {
+            if (verticalOffset < 0)
+            {
+                return 0;
+            }
+
+            return verticalOffset > _child.ScrollOwner.ScrollableHeight ? _child.ScrollOwner.ScrollableHeight : verticalOffset;
+        }
+
+        private double ValidateHorizontalOffset(double horizontalOffset)
+        {
+            if (horizontalOffset < 0)
+            {
+                return 0;
+            }
+
+            if (horizontalOffset > _child.ScrollOwner.ScrollableWidth)
+            {
+                return _child.ScrollOwner.ScrollableWidth;
+            }
+
+            return horizontalOffset;
+        }
+
+        #endregion not exposed methods
+
+        #region helper dependency properties as scrollbars are not animatable by default
+
+        internal double VerticalScrollOffset
+        {
+            get => (double)GetValue(VerticalScrollOffsetProperty);
+            set => SetValue(VerticalScrollOffsetProperty, value);
+        }
+
+        internal static readonly DependencyProperty VerticalScrollOffsetProperty =
+            DependencyProperty.Register("VerticalScrollOffset", typeof(double), typeof(ScrollInfoAdapter),
+                new PropertyMetadata(0.0, OnVerticalScrollOffsetChanged));
+
+        private static void OnVerticalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var smoothScrollViewer = (ScrollInfoAdapter)d;
+            smoothScrollViewer._child.SetVerticalOffset((double)e.NewValue);
+        }
+
+        internal double HorizontalScrollOffset
+        {
+            get => (double)GetValue(HorizontalScrollOffsetProperty);
+            set => SetValue(HorizontalScrollOffsetProperty, value);
+        }
+
+        internal static readonly DependencyProperty HorizontalScrollOffsetProperty =
+            DependencyProperty.Register("HorizontalScrollOffset", typeof(double), typeof(ScrollInfoAdapter),
+                new PropertyMetadata(0.0, OnHorizontalScrollOffsetChanged));
+
+        private static void OnHorizontalScrollOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var smoothScrollViewer = (ScrollInfoAdapter)d;
+            smoothScrollViewer._child.SetHorizontalOffset((double)e.NewValue);
+        }
+
+        public Rect MakeVisible(Visual visual, Rect rectangle)
+        {
+            throw new NotImplementedException();
+        }
+
+        #endregion helper dependency properties as scrollbars are not animatable by default
+    }
+}

+ 6 - 4
src/PicView/Views/UserControls/Gallery/PicGallery.xaml

@@ -2,6 +2,7 @@
     x:Class="PicView.Views.UserControls.Gallery.PicGallery"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:Anim="clr-namespace:PicView.UILogic"
     xmlns:buttons="clr-namespace:PicView.Views.UserControls.Buttons"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -19,7 +20,7 @@
                 HorizontalAlignment="Right"
                 VerticalAlignment="Top"
                 Panel.ZIndex="1" />
-            <ScrollViewer
+            <Anim:SmoothScrollViewer
                 x:Name="Scroller"
                 CanContentScroll="False"
                 FocusVisualStyle="{x:Null}"
@@ -27,7 +28,7 @@
                 HorizontalScrollBarVisibility="Visible"
                 IsDeferredScrollingEnabled="False"
                 VerticalScrollBarVisibility="Disabled">
-                <ScrollViewer.Style>
+                <Anim:SmoothScrollViewer.Style>
                     <Style TargetType="{x:Type ScrollViewer}">
                         <Setter Property="OverridesDefaultStyle" Value="True" />
                         <Setter Property="Template">
@@ -57,6 +58,7 @@
                                             VerticalAlignment="Bottom"
                                             Maximum="{TemplateBinding ScrollableWidth}"
                                             Orientation="Horizontal"
+                                            PreviewMouseLeftButtonDown="PART_HorizontalScrollBar_OnPreviewMouseLeftButtonDown"
                                             ViewportSize="{TemplateBinding ViewportWidth}"
                                             Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"
                                             Value="{TemplateBinding HorizontalOffset}" />
@@ -65,7 +67,7 @@
                             </Setter.Value>
                         </Setter>
                     </Style>
-                </ScrollViewer.Style>
+                </Anim:SmoothScrollViewer.Style>
                 <WrapPanel
                     x:Name="Container"
                     Background="Transparent"
@@ -73,7 +75,7 @@
                     Focusable="False"
                     Orientation="Vertical"
                     VirtualizingPanel.VirtualizationMode="Recycling" />
-            </ScrollViewer>
+            </Anim:SmoothScrollViewer>
         </Grid>
     </Border>
 </UserControl>

+ 8 - 2
src/PicView/Views/UserControls/Gallery/PicGallery.xaml.cs

@@ -1,4 +1,5 @@
-using PicView.PicGallery;
+using System.Windows.Input;
+using PicView.PicGallery;
 
 namespace PicView.Views.UserControls.Gallery;
 
@@ -10,7 +11,12 @@ public partial class PicGallery
     public PicGallery()
     {
         InitializeComponent();
-
         Loaded += GalleryLoad.PicGallery_Loaded;
     }
+
+    private void PART_HorizontalScrollBar_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+    {
+        // Disable animation for scrollbar
+        Scroller.CanContentScroll = true;
+    }
 }

+ 4 - 5
src/PicView/Views/Windows/SettingsWindow.xaml.cs

@@ -108,11 +108,10 @@ public partial class SettingsWindow
                 Settings.Default.BottomGalleryItems = (int)e.NewValue;
                 SetBottomGalleryText.Text = e.NewValue.ToString("0.#", CultureInfo.CurrentCulture);
                 Settings.Default.Save();
-                if (GalleryFunctions.IsGalleryOpen)
-                {
-                    GalleryLoad.LoadLayout();
-                    ScaleImage.TryFitImage();
-                }
+                if (!Settings.Default.FullscreenGallery) return;
+
+                GalleryLoad.LoadLayout();
+                ScaleImage.TryFitImage();
             };
 
             // Themes