Browse Source

[Avalonia] Sizing and navigation updates

Ruben 1 year ago
parent
commit
7398eb380d

+ 138 - 83
src/PicView.Avalonia/CustomControls/PicBox.cs

@@ -1,25 +1,30 @@
 using Avalonia;
+using Avalonia.Automation.Peers;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Controls.Automation.Peers;
 using Avalonia.Media;
 using Avalonia.Metadata;
-using Avalonia.Threading;
-using PicView.Avalonia.Gallery;
-using PicView.Avalonia.Helpers;
+using Avalonia.Utilities;
 using PicView.Avalonia.Navigation;
-using PicView.Core.Calculations;
-using PicView.Core.Config;
+using PicView.Avalonia.Views;
 
 namespace PicView.Avalonia.CustomControls;
 
 public class PicBox : Control
 {
+    static PicBox()
+    {
+        // Registers the SourceProperty to render when the source changes
+        AffectsRender<PicBox>(SourceProperty);
+    }
+    
     #region Properties
     /// <summary>
     /// Defines the <see cref="Source"/> property.
     /// </summary>
     public static readonly StyledProperty<IImage?> SourceProperty =
-        AvaloniaProperty.Register<Image, IImage?>(nameof(Source));
+        AvaloniaProperty.Register<PicBox, IImage?>(nameof(Source));
     
     /// <summary>
     /// Gets or sets the image that will be displayed.
@@ -31,11 +36,27 @@ public class PicBox : Control
         set => SetValue(SourceProperty, value);
     }
     
+    /// <summary>
+    /// Defines the <see cref="SecondSource"/> property.
+    /// </summary>
+    public static readonly StyledProperty<IImage?> SecondSourceProperty =
+        AvaloniaProperty.Register<PicBox, IImage?>(nameof(SecondSource));
+    
+    /// <summary>
+    /// Gets or sets the second image that will be displayed, when side by side view is enabled
+    /// </summary>
+    [Content]
+    public IImage? SecondSource
+    {
+        get => GetValue(SecondSourceProperty);
+        set => SetValue(SecondSourceProperty, value);
+    }
+    
     /// <summary>
     /// Defines the <see cref="ImageType"/> property.
     /// </summary>
     public static readonly AvaloniaProperty<ImageType> ImageTypeProperty =
-        AvaloniaProperty.Register<AnimatedMenu, ImageType>(nameof(ImageType));
+        AvaloniaProperty.Register<PicBox, ImageType>(nameof(ImageType));
 
     /// <summary>
     /// Gets or sets the image type.
@@ -48,12 +69,6 @@ public class PicBox : Control
     }
     
     #endregion
-    
-    static PicBox()
-    {
-        // Registers the SourceProperty to render when the source changes
-        AffectsRender<PicBox>(SourceProperty);
-    }
 
     #region Rendering
     
@@ -63,99 +78,139 @@ public class PicBox : Control
     /// <param name="context">The drawing context.</param>
     public sealed override void Render(DrawingContext context)
     {
+        base.Render(context);
+        
         var source = Source;
-
-        if (source == null || Bounds is not { Width: > 0, Height: > 0 })
+        if (source == null)
         {
             return;
         }
-        var viewPort = new Rect(Bounds.Size);
+        
         var sourceSize = source.Size;
+        var viewPort = DetermineViewPort();
         
         var is1To1 = false; // TODO: replace with settings value
-
+        var isSideBySide = false; // TODO: replace with settings value
         if (is1To1)
         {
-            var scale = 1; 
-            var scaledSize = sourceSize * scale;
-            var destRect = viewPort
-                .CenterRect(new Rect(scaledSize))
-                .Intersect(viewPort);
-            var sourceRect = new Rect(sourceSize)
-                .CenterRect(new Rect(destRect.Size / scale));
-
-            context.DrawImage(source, sourceRect, destRect);
+            RenderImage1To1(context, source, viewPort, sourceSize);
+        }
+        else if (isSideBySide)
+        {
+            RenderImageSideBySide(context, source, viewPort, sourceSize);
         }
         else
         {
-            if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
-            {
-                return;
-            }
-            double desktopMinWidth = 0, desktopMinHeight = 0, containerWidth = 0, containerHeight = 0;
-            var uiTopSize = SettingsHelper.Settings.UIProperties.ShowInterface ? SizeDefaults.TitlebarHeight : 0;
-            var uiBottomSize = SettingsHelper.Settings.WindowProperties.Fullscreen || !SettingsHelper.Settings.UIProperties.ShowInterface
-                || !SettingsHelper.Settings.UIProperties.ShowBottomNavBar 
-                    ? 0 : SizeDefaults.BottombarHeight + SizeDefaults.ScrollbarSize;
-            var galleryHeight = GalleryFunctions.IsBottomGalleryOpen
-                ? SettingsHelper.Settings.Gallery.BottomGalleryItemSize
-                : 0;
-            if (Dispatcher.UIThread.CheckAccess())
-            {
-                desktopMinWidth = desktop.MainWindow.MinWidth;
-                desktopMinHeight = desktop.MainWindow.MinHeight;
-                containerWidth = desktop.MainWindow.Width;
-                containerHeight = desktop.MainWindow.Height - (uiTopSize + uiBottomSize);
-            }
-            else
-            {
-                Dispatcher.UIThread.InvokeAsync(() =>
-                {
-                    desktopMinWidth = desktop.MainWindow.MinWidth;
-                    desktopMinHeight = desktop.MainWindow.MinHeight;
-                    containerWidth = desktop.MainWindow.Width;
-                    containerHeight = desktop.MainWindow.Height - (uiTopSize + uiBottomSize);
-                }, DispatcherPriority.Normal).Wait();
-            }
-            var size = ImageSizeCalculationHelper.GetImageSize(
-                source.Size.Width,
-                source.Size.Height,
-                ScreenHelper.ScreenSize.Width,
-                ScreenHelper.ScreenSize.Height,
-                desktopMinWidth,
-                desktopMinHeight,
-                ImageSizeCalculationHelper.GetInterfaceSize(),
-                rotationAngle: 0,
-                75,
-                ScreenHelper.ScreenSize.Scaling,
-                uiTopSize,
-                uiBottomSize,
-                galleryHeight,
-                containerWidth,
-                containerHeight);
-            
-            var destRect = viewPort
-                .CenterRect(new Rect(0,0,size.Width, size.Height))
-                .Intersect(viewPort);
-            var sourceRect = new Rect(sourceSize)
-                .CenterRect(new Rect(destRect.Size / size.AspectRatio));
-
-            context.DrawImage(source, sourceRect, destRect);
+            RenderImage(context, source, viewPort, sourceSize);
         }
-        // TODO: Implement other size modes
     }
+    
+    private Rect DetermineViewPort()
+    {
+        if (!(Bounds.Width <= 0) && !(Bounds.Height <= 0))
+        {
+            return new Rect(Bounds.Size);
+        }
 
-    /// <inheritdoc/>
+        if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            return new Rect();
+        }
+
+        var mainView = desktop.MainWindow?.GetControl<MainView>("MainView");
+        return mainView == null ? new Rect() : new Rect(Bounds.X, Bounds.Y, mainView.Bounds.Width, mainView.Bounds.Height);
+    }
+    
+    private void RenderImage1To1(DrawingContext context, IImage source, Rect viewPort, Size sourceSize)
+    {
+        var scale = 1.0;
+        var scaledSize = sourceSize * scale;
+        var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort);
+        var sourceRect = new Rect(sourceSize).CenterRect(new Rect(destRect.Size / scale));
+
+        context.DrawImage(source, sourceRect, destRect);
+    }
+
+    private void RenderImage(DrawingContext context, IImage source, Rect viewPort, Size sourceSize)
+    {
+        var scale = CalculateScaling(viewPort.Size, sourceSize);
+        var scaledSize = sourceSize * scale;
+        var destRect = viewPort.CenterRect(new Rect(scaledSize)).Intersect(viewPort);
+        var sourceRect = new Rect(sourceSize).CenterRect(new Rect(destRect.Size / scale));
+
+        context.DrawImage(source, sourceRect, destRect);
+    }
+    
+    private void RenderImageSideBySide(DrawingContext context, IImage source, Rect viewPort, Size sourceSize)
+    {
+        // TODO Add side by side viewing mode
+    }
+    
+    private static Vector CalculateScaling(Size destinationSize, Size sourceSize)
+    {
+        var isConstrainedWidth = !double.IsPositiveInfinity(destinationSize.Width);
+        var isConstrainedHeight = !double.IsPositiveInfinity(destinationSize.Height);
+
+        // Compute scaling factors for both axes
+        var scaleX = MathUtilities.IsZero(sourceSize.Width) ? 0.0 : destinationSize.Width / sourceSize.Width;
+        var scaleY = MathUtilities.IsZero(sourceSize.Height) ? 0.0 : destinationSize.Height / sourceSize.Height;
+
+        if (!isConstrainedWidth)
+        {
+            scaleX = scaleY;
+        }
+        else if (!isConstrainedHeight)
+        {
+            scaleY = scaleX;
+        }
+
+        return new Vector(scaleX, scaleY);
+    }
+    
+    public static Size CalculateSize(Size destinationSize, Size sourceSize)
+    {
+        return sourceSize * CalculateScaling(destinationSize, sourceSize);
+    }
+
+    /// <summary>
+    /// Measures the control.
+    /// </summary>
+    /// <param name="availableSize">The available size.</param>
+    /// <returns>The desired size of the control.</returns>
     protected override Size MeasureOverride(Size availableSize)
     {
-        return Source?.Size ?? availableSize;
+        return Source != null ? CalculateSize(availableSize, Source.Size) : new Size();
     }
     
     /// <inheritdoc/>
     protected override Size ArrangeOverride(Size finalSize)
     {
-        return Source?.Size ?? finalSize;
+        UpdateLayout();
+        return base.ArrangeOverride(finalSize);
     }
     
+    protected override AutomationPeer OnCreateAutomationPeer()
+    {
+        return new ImageAutomationPeer(this);
+    }
+
+    #endregion
+    
+    #region Pan and Zoom
+    
+    // TODO: Add Pan and Zoom
+    
+    #endregion 
+    
+    #region Rotation
+    
+    // TODO: Add Rotation
+    
+    #endregion
+
+    #region Animated Pic
+
+    // TODO: Add Animation behavior
+
     #endregion
 }

+ 4 - 4
src/PicView.Avalonia/Helpers/WindowHelper.cs

@@ -466,20 +466,20 @@ public static class WindowHelper
             desktopMinHeight,
             ImageSizeCalculationHelper.GetInterfaceSize(),
             rotation,
-            75,
+            65,
             screenSize.Scaling,
             uiTopSize,
             uiBottomSize,
             vm.GalleryHeight,
             containerWidth,
             containerHeight);
-
+        
         vm.TitleMaxWidth = size.TitleMaxWidth;
         vm.ImageWidth = size.Width;
         vm.ImageHeight = size.Height;
         vm.ImageMargin = new Thickness(0, 0, 0, size.Margin);
-        
-        vm.GalleryWidth = SettingsHelper.Settings.WindowProperties.AutoFit ? size.Width : double.NaN;
+
+        vm.GalleryWidth = SettingsHelper.Settings.WindowProperties.AutoFit ? size.Width : double.NaN;;
     }
     
     public static void SaveSize(Window window)

+ 19 - 5
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -67,7 +67,7 @@ public class ImageIterator
         _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
         _watcher.Created += async (_, e) => await OnFileAdded(e).ConfigureAwait(false);
         _watcher.Deleted += (_, e) => OnFileDeleted(e);
-        _watcher.Renamed += (_, e) => OnFileRenamed(e);
+        _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
     }
 
     public async Task Preload()
@@ -586,20 +586,34 @@ public class ImageIterator
         using var image = new MagickImage();
         image.Ping(vm.ImageIterator.Pics[index]);
         var thumb = image.GetExifProfile()?.CreateThumbnail();
-        if (thumb is null && index == Index)
+        if (thumb is null)
         {
-            vm.IsLoading = true;
-            await Dispatcher.UIThread.InvokeAsync(() =>  vm.ImageViewer.MainImage.Source = null);
+            await Set();
             return;
         }
 
         var byteArray = await Task.FromResult(thumb.ToByteArray());
+        if (byteArray is null)
+        {
+            await Set();
+            return;
+        }
         var stream = new MemoryStream(byteArray);
         if (index != Index)
         {
             return;
         }
         vm.ImageViewer.SetImage(new Bitmap(stream), ImageType.Bitmap);
-        WindowHelper.SetSize(image.Width, image.Height, 0, vm);
+        WindowHelper.SetSize(image?.Width ?? 0, image?.Height ?? 0, 0, vm);
+        return;
+
+        async Task Set()
+        {
+            if (index == Index)
+            {
+                vm.IsLoading = true;
+                await Dispatcher.UIThread.InvokeAsync(() =>  vm.ImageViewer.MainImage.Source = null);
+            }
+        }
     }
 }

+ 0 - 2
src/PicView.Avalonia/Navigation/NavigationHelper.cs

@@ -1,9 +1,7 @@
 using Avalonia;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls;
-using Avalonia.Media.Imaging;
 using Avalonia.Threading;
-using ImageMagick;
 using PicView.Avalonia.Gallery;
 using PicView.Avalonia.Helpers;
 using PicView.Avalonia.ViewModels;

+ 2 - 0
src/PicView.Avalonia/Views/ImageViewer.axaml

@@ -28,8 +28,10 @@
                     VerticalAlignment="Center"
                     x:Name="ImageZoomBorder">
                     <customControls:PicBox
+                        Height="{CompiledBinding ImageHeight}"
                         RenderOptions.BitmapInterpolationMode="HighQuality"
                         UseLayoutRounding="True"
+                        Width="{CompiledBinding ImageWidth}"
                         x:Name="MainImage" />
                 </Border>
             </customControls:AutoScrollViewer>

+ 1 - 0
src/PicView.Avalonia/Views/ImageViewer.axaml.cs

@@ -80,6 +80,7 @@ public partial class ImageViewer : UserControl
         {
             //ImageHelper.SetImage(image, MainImage, imageType);
             MainImage.Source = image as IImage;
+            MainImage.ImageType = imageType;
             Reset();
             if (SettingsHelper.Settings.Zoom.ScrollEnabled)
             {

+ 0 - 1
src/PicView.Core/Calculations/ImageSizeCalculationHelper.cs

@@ -64,7 +64,6 @@ public static class ImageSizeCalculationHelper
         else
         {
             maxWidth = stretch ? containerWidth : Math.Min(containerWidth, width);
-
             
             if (SettingsHelper.Settings.Zoom.ScrollEnabled)
             {