Browse Source

[Avalonia] Slideshow implementation

Ruben 1 year ago
parent
commit
7065158c54

+ 10 - 0
src/PicView.Avalonia.MacOS/App.axaml.cs

@@ -294,4 +294,14 @@ public void ShowAboutWindow()
         // TODO: Implement ExtractWithLocalSoftwareAsync
         return Task.FromResult(false);
     }
+    
+    public void DisableScreensaver()
+    {
+        // TODO: Implement DisableScreensaver
+    }
+    
+    public void EnableScreensaver()
+    {
+        // TODO: Implement EnableScreensaver
+    }
 }

+ 10 - 0
src/PicView.Avalonia.Win32/App.axaml.cs

@@ -329,5 +329,15 @@ public class App : Application, IPlatformSpecificService
         return await ArchiveExtractionHelper.ExtractWithLocalSoftwareAsync(path, tempDirectory);
     }
     
+    public void DisableScreensaver()
+    {
+        NativeMethods.DisableScreensaver();
+    }
+    
+    public void EnableScreensaver()
+    {
+        NativeMethods.EnableScreensaver();
+    }
+    
     #endregion
 }

+ 3 - 0
src/PicView.Avalonia/Interfaces/IPlatformSpecificService.cs

@@ -5,6 +5,9 @@ public interface IPlatformSpecificService
     void SetTaskbarProgress(ulong progress, ulong maximum);
     void StopTaskbarProgress();
     void SetCursorPos(int x, int y);
+    
+    void DisableScreensaver();
+    void EnableScreensaver();
 
     List<string> GetFiles(FileInfo fileInfo);
 

+ 102 - 0
src/PicView.Avalonia/Navigation/Slideshow.cs

@@ -0,0 +1,102 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using PicView.Avalonia.Keybindings;
+using PicView.Avalonia.UI;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.Config;
+using PicView.Core.Navigation;
+using Timer = System.Timers.Timer;
+
+namespace PicView.Avalonia.Navigation;
+
+public static class Slideshow
+{
+    public static bool IsRunning => _timer is not null && _timer.Enabled;
+    
+    private static Timer? _timer;
+    public static async Task StartSlideshow(MainViewModel vm)
+    {
+        if (!InitiateAndStart(vm))
+        {
+            return;
+        }
+        
+        await Start(vm, TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.SlideShowTimer).TotalMilliseconds);
+    }
+
+    public static async Task StartSlideshow(MainViewModel vm, int milliseconds)
+    {
+        if (!InitiateAndStart(vm))
+        {
+            return;
+        }
+
+        await Start(vm, milliseconds);
+    }
+    
+    public static void StopSlideshow(MainViewModel vm)
+    {
+        if (_timer is null)
+        {
+            return;
+        }
+
+        if (!SettingsHelper.Settings.WindowProperties.Fullscreen)
+        {
+            WindowHelper.Restore(vm, Application.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime);
+            if (SettingsHelper.Settings.WindowProperties.AutoFit)
+            {
+                WindowHelper.CenterWindowOnScreen();
+            }
+        }
+        
+        _timer.Stop();
+        _timer = null;
+        vm.PlatformService.EnableScreensaver();
+    }
+    
+    static bool InitiateAndStart(MainViewModel vm)
+    {
+        if (!NavigationHelper.CanNavigate(vm))
+        {
+            return false;
+        }
+        
+        if (_timer is null)
+        {
+            _timer = new Timer
+            {
+                Enabled = true,
+            };
+            _timer.Elapsed += async (_, _) =>
+                await vm.ImageIterator.NextIteration(NavigateTo.Next);
+        }
+        else if (_timer.Enabled)
+        {
+            if (!MainKeyboardShortcuts.IsKeyHeldDown)
+            {
+                _timer = null;
+            }
+
+            return false;
+        }
+        
+        return true;
+    }
+
+    private static async Task Start(MainViewModel vm, double seconds)
+    {
+        _timer.Interval = seconds;
+        _timer.Start();
+        vm.PlatformService.DisableScreensaver();
+
+        UIHelper.CloseMenus(vm);
+
+        if (!SettingsHelper.Settings.WindowProperties.Fullscreen)
+        {
+            await WindowHelper.ToggleFullscreen(vm, false);
+        }
+        // TODO: add animation
+        await vm.ImageIterator.NextIteration(NavigateTo.Next);
+    }
+}

+ 8 - 2
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -412,6 +412,12 @@ public static class FunctionsHelper
             UIHelper.CloseMenus(Vm);
             return;
         }
+
+        if (Navigation.Slideshow.IsRunning)
+        {
+            Navigation.Slideshow.StopSlideshow(Vm);
+            return;
+        }
         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
         {
             return;
@@ -777,9 +783,9 @@ public static class FunctionsHelper
         SetTitleHelper.RefreshTitle(Vm);
     }
 
-    public static Task Slideshow()
+    public static async Task Slideshow()
     {
-        return Task.CompletedTask;
+        await Navigation.Slideshow.StartSlideshow(Vm);
     }
 
     public static Task ColorPicker()

+ 10 - 4
src/PicView.Avalonia/UI/WindowHelper.cs

@@ -231,7 +231,7 @@ public static class WindowHelper
         await SettingsHelper.SaveSettingsAsync().ConfigureAwait(false);
     }
 
-    public static async Task ToggleFullscreen(MainViewModel vm)
+    public static async Task ToggleFullscreen(MainViewModel vm, bool saveSettings = true)
     {
         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
         {
@@ -243,7 +243,11 @@ public static class WindowHelper
             vm.IsFullscreen = false;
             await Dispatcher.UIThread.InvokeAsync(() => 
                 desktop.MainWindow.WindowState = WindowState.Normal);
-            SettingsHelper.Settings.WindowProperties.Fullscreen = false;
+            if (saveSettings)
+            {
+                SettingsHelper.Settings.WindowProperties.Fullscreen = false;
+            }
+            
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
             {
                 RestoreSize(desktop.MainWindow);
@@ -254,6 +258,10 @@ public static class WindowHelper
         {
             SaveSize(desktop.MainWindow);
             Fullscreen(vm, desktop);
+            if (saveSettings)
+            {
+                SettingsHelper.Settings.WindowProperties.Fullscreen = true;
+            }
         }
 
         await SettingsHelper.SaveSettingsAsync().ConfigureAwait(false);
@@ -363,8 +371,6 @@ public static class WindowHelper
         {
             Dispatcher.UIThread.Invoke(() => desktop.MainWindow.WindowState = WindowState.FullScreen);
         }
-
-        SettingsHelper.Settings.WindowProperties.Fullscreen = true;
             
         if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
         {

+ 14 - 2
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -507,7 +507,7 @@ public class MainViewModel : ViewModelBase
 
     public ReactiveCommand<Unit, Unit>? ColorPickerCommand { get; }
 
-    public ReactiveCommand<Unit, Unit>? SlideshowCommand { get; }
+    public ReactiveCommand<int, Unit>? SlideshowCommand { get; }
     
     public ReactiveCommand<string, Unit>? SetAsWallpaperCommand { get; }
     
@@ -1647,6 +1647,18 @@ public class MainViewModel : ViewModelBase
         }
     }
 
+    public async Task StartSlideShowTask(int milliseconds)
+    {
+        if (milliseconds <= 0)
+        {
+            await Avalonia.Navigation.Slideshow.StartSlideshow(this);
+        }
+        else
+        {
+            await Avalonia.Navigation.Slideshow.StartSlideshow(this, milliseconds);
+        }
+    }
+
     #endregion Methods
 
     public MainViewModel(IPlatformSpecificService? platformSpecificService)
@@ -1848,7 +1860,7 @@ public class MainViewModel : ViewModelBase
         
         ColorPickerCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.ColorPicker);
         
-        SlideshowCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.Slideshow);
+        SlideshowCommand = ReactiveCommand.CreateFromTask<int>(StartSlideShowTask);
         
         ToggleTaskbarProgressCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.ToggleTaskbarProgress);
 

+ 1 - 2
src/PicView.Avalonia/Views/GeneralSettingsView.axaml

@@ -210,8 +210,7 @@
             Minimum="1"
             TickFrequency="1"
             TickPlacement="BottomRight"
-            Value="{CompiledBinding GetSlideshowSpeed,
-                                    Mode=OneWay}"
+            Value="{CompiledBinding GetSlideshowSpeed}"
             Width="270" />
         <TextBlock
             Classes="txt"

+ 85 - 35
src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml

@@ -261,10 +261,12 @@
                     Canvas.Left="7"
                     Canvas.Top="99"
                     Classes="ButtonBorder altHover"
-                    IsEnabled="False"
+                    Command="{CompiledBinding StartSlideShowTask}"
+                    CommandParameter="0"
                     ToolTip.Placement="Top"
                     ToolTip.Tip="{CompiledBinding Slideshow,
-                                                  Mode=OneWay}">
+                                                  Mode=OneWay}"
+                    x:Name="SlideShowButton">
                     <StackPanel Orientation="Horizontal">
                         <Path
                             Data="M20 16v16l10-8-10-8zm18-10h-28c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-28c0-2.21-1.79-4-4-4zm0 32h-28v-28h28v28z"
@@ -281,39 +283,87 @@
                                                    Mode=OneWay}" />
                     </StackPanel>
                     <SplitButton.Flyout>
-                        <MenuFlyout Placement="Top">
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="5 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="10 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="20 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="30 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="60 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="120 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="180 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
-                            <TextBlock Classes="txt" Width="86">
-                                <Run Text="300 " />
-                                <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
-                            </TextBlock>
+                        <MenuFlyout Placement="Top" ShowMode="Transient">
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="5000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="5 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="10000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="10 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="20000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="20 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="30000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="30 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="60000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="60 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="120000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="120 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="180000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="180 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
+                            <Button
+                                Background="Transparent"
+                                Command="{CompiledBinding StartSlideShowTask}"
+                                CommandParameter="300000"
+                                Width="86">
+                                <TextBlock Classes="txt">
+                                    <Run Text="300 " />
+                                    <Run Text="{CompiledBinding SecAbbreviation, Mode=OneWay}" />
+                                </TextBlock>
+                            </Button>
                         </MenuFlyout>
                     </SplitButton.Flyout>
                 </SplitButton>

+ 7 - 1
src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml.cs

@@ -1,5 +1,8 @@
+using System.Reactive.Linq;
 using Avalonia.Input;
 using PicView.Avalonia.CustomControls;
+using PicView.Avalonia.ViewModels;
+using ReactiveUI;
 
 namespace PicView.Avalonia.Views.UC.Menus;
 
@@ -9,13 +12,16 @@ public partial class ImageMenu  : AnimatedMenu
     {
         InitializeComponent();
         GoToPicBox.KeyDown += async (_, e) => await GoToPicBox_OnKeyDown(e);
+        this.WhenAnyValue(x => x.IsVisible)
+            .Where(isVisible => !isVisible)
+            .Subscribe(_ => SlideShowButton.Flyout.Hide());
     }
 
     private async Task GoToPicBox_OnKeyDown(KeyEventArgs e)
     {
         if (e.Key == Key.Enter)
         {
-            if (DataContext is not ViewModels.MainViewModel vm)
+            if (DataContext is not MainViewModel vm)
             {
                 return;
             }

+ 24 - 0
src/PicView.WindowsNT/NativeMethods.cs

@@ -12,4 +12,28 @@ public static partial class NativeMethods
     [LibraryImport("User32.dll")]
     [return: MarshalAs(UnmanagedType.Bool)]
     public static partial bool SetCursorPos(int x, int y);    
+    
+    
+    #region Disable Screensaver and Power options
+
+    private const uint ES_CONTINUOUS = 0x80000000;
+    private const uint ES_SYSTEM_REQUIRED = 0x00000001;
+    private const uint ES_DISPLAY_REQUIRED = 0x00000002;
+
+    [LibraryImport("kernel32.dll", SetLastError = true)]
+    public static partial uint SetThreadExecutionState(uint esFlags);
+    
+    public static void DisableScreensaver()
+    {
+        _ = SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED);
+    }
+    
+    public static void EnableScreensaver()
+    {
+        _ = SetThreadExecutionState(ES_CONTINUOUS);
+    }
+
+    #endregion Disable Screensaver and Power options
+
+
 }