diff --git a/Bloxstrap/Bootstrapper.cs b/Bloxstrap/Bootstrapper.cs index 8cd32f49..a71827da 100644 --- a/Bloxstrap/Bootstrapper.cs +++ b/Bloxstrap/Bootstrapper.cs @@ -17,6 +17,8 @@ using Microsoft.Win32; using Bloxstrap.AppData; +using System.Windows.Shell; +using Bloxstrap.UI.Elements.Bootstrapper.Base; namespace Bloxstrap { @@ -24,7 +26,10 @@ public class Bootstrapper { #region Properties private const int ProgressBarMaximum = 10000; - + + private const double TaskbarProgressMaximumWpf = 1; // this can not be changed. keep it at 1. + private const int TaskbarProgressMaximumWinForms = WinFormsDialogBase.TaskbarProgressMaximum; + private const string AppSettings = "\r\n" + "\r\n" + @@ -43,6 +48,8 @@ public class Bootstrapper private bool _isInstalling = false; private double _progressIncrement; + private double _taskbarProgressIncrement; + private double _taskbarProgressMaximum; private long _totalDownloadedBytes = 0; private bool _mustUpgrade => String.IsNullOrEmpty(AppData.State.VersionGuid) || File.Exists(AppData.LockFilePath) || !File.Exists(AppData.ExecutablePath); @@ -74,6 +81,7 @@ private void UpdateProgressBar() if (Dialog is null) return; + // UI progress int progressValue = (int)Math.Floor(_progressIncrement * _totalDownloadedBytes); // bugcheck: if we're restoring a file from a package, it'll incorrectly increment the progress beyond 100 @@ -81,6 +89,12 @@ private void UpdateProgressBar() progressValue = Math.Clamp(progressValue, 0, ProgressBarMaximum); Dialog.ProgressValue = progressValue; + + // taskbar progress + double taskbarProgressValue = _taskbarProgressIncrement * _totalDownloadedBytes; + taskbarProgressValue = Math.Clamp(taskbarProgressValue, 0, _taskbarProgressMaximum); + + Dialog.TaskbarProgressValue = taskbarProgressValue; } private void HandleConnectionError(Exception exception) @@ -584,11 +598,20 @@ private async Task UpgradeRoblox() // TODO: cancelling needs to always be enabled Dialog.CancelEnabled = true; Dialog.ProgressStyle = ProgressBarStyle.Continuous; + Dialog.TaskbarProgressState = TaskbarItemProgressState.Normal; Dialog.ProgressMaximum = ProgressBarMaximum; // compute total bytes to download - _progressIncrement = (double)ProgressBarMaximum / _versionPackageManifest.Sum(package => package.PackedSize); + int totalPackedSize = _versionPackageManifest.Sum(package => package.PackedSize); + _progressIncrement = (double)ProgressBarMaximum / totalPackedSize; + + if (Dialog is WinFormsDialogBase) + _taskbarProgressMaximum = (double)TaskbarProgressMaximumWinForms; + else + _taskbarProgressMaximum = (double)TaskbarProgressMaximumWpf; + + _taskbarProgressIncrement = _taskbarProgressMaximum / (double)totalPackedSize; } var extractionTasks = new List(); @@ -619,6 +642,7 @@ private async Task UpgradeRoblox() await Task.Delay(1000); Dialog.ProgressStyle = ProgressBarStyle.Marquee; + Dialog.TaskbarProgressState = TaskbarItemProgressState.Indeterminate; SetStatus(Strings.Bootstrapper_Status_Configuring); } diff --git a/Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs b/Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs index 33974f1f..40c3002a 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs @@ -1,4 +1,5 @@ using System.Windows.Forms; +using System.Windows.Shell; using Bloxstrap.UI.Utility; @@ -6,6 +7,8 @@ namespace Bloxstrap.UI.Elements.Bootstrapper.Base { public class WinFormsDialogBase : Form, IBootstrapperDialog { + public const int TaskbarProgressMaximum = 100; + public Bloxstrap.Bootstrapper? Bootstrapper { get; set; } private bool _isClosing; @@ -15,6 +18,8 @@ public class WinFormsDialogBase : Form, IBootstrapperDialog protected virtual ProgressBarStyle _progressStyle { get; set; } protected virtual int _progressValue { get; set; } protected virtual int _progressMaximum { get; set; } + protected virtual TaskbarItemProgressState _taskbarProgressState { get; set; } + protected virtual double _taskbarProgressValue { get; set; } protected virtual bool _cancelEnabled { get; set; } public string Message @@ -65,6 +70,26 @@ public int ProgressValue } } + public TaskbarItemProgressState TaskbarProgressState + { + get => _taskbarProgressState; + set + { + _taskbarProgressState = value; + TaskbarProgress.SetProgressState(Process.GetCurrentProcess().MainWindowHandle, value); + } + } + + public double TaskbarProgressValue + { + get => _taskbarProgressValue; + set + { + _taskbarProgressValue = value; + TaskbarProgress.SetProgressValue(Process.GetCurrentProcess().MainWindowHandle, (int)value, TaskbarProgressMaximum); + } + } + public bool CancelEnabled { get => _cancelEnabled; diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml index 200dbf0c..b1ef6b9e 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml +++ b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml @@ -12,6 +12,11 @@ AllowsTransparency="True" Background="Transparent" Closing="Window_Closing"> + + + + + diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs index 67575a85..5afcc431 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using System.Windows.Media; using System.Windows.Media.Imaging; +using System.Windows.Shell; using Bloxstrap.UI.Elements.Bootstrapper.Base; using Bloxstrap.UI.ViewModels.Bootstrapper; @@ -65,6 +66,26 @@ public int ProgressValue } } + public TaskbarItemProgressState TaskbarProgressState + { + get => _viewModel.TaskbarProgressState; + set + { + _viewModel.TaskbarProgressState = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressState)); + } + } + + public double TaskbarProgressValue + { + get => _viewModel.TaskbarProgressValue; + set + { + _viewModel.TaskbarProgressValue = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressValue)); + } + } + public bool CancelEnabled { get => _viewModel.CancelEnabled; diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml index 2cf25e77..76cda5ee 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml +++ b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml @@ -19,6 +19,10 @@ WindowStartupLocation="CenterScreen" mc:Ignorable="d"> + + + + diff --git a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs index a007e31a..524241c3 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs @@ -1,9 +1,6 @@ using System.ComponentModel; using System.Windows.Forms; - -using Wpf.Ui.Appearance; -using Wpf.Ui.Mvvm.Contracts; -using Wpf.Ui.Mvvm.Services; +using System.Windows.Shell; using Bloxstrap.UI.ViewModels.Bootstrapper; using Bloxstrap.UI.Elements.Bootstrapper.Base; @@ -62,6 +59,26 @@ public int ProgressValue } } + public TaskbarItemProgressState TaskbarProgressState + { + get => _viewModel.TaskbarProgressState; + set + { + _viewModel.TaskbarProgressState = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressState)); + } + } + + public double TaskbarProgressValue + { + get => _viewModel.TaskbarProgressValue; + set + { + _viewModel.TaskbarProgressValue = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressValue)); + } + } + public bool CancelEnabled { get => _viewModel.CancelEnabled; diff --git a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml index 881692d9..5f49a73d 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml +++ b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml @@ -22,6 +22,10 @@ WindowStyle="None" mc:Ignorable="d"> + + + + diff --git a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs index 18a28715..7dc97c70 100644 --- a/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs +++ b/Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs @@ -16,6 +16,7 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; +using System.Windows.Shell; using System.Windows.Threading; namespace Bloxstrap.UI.Elements.Bootstrapper @@ -72,6 +73,26 @@ public int ProgressValue } } + public TaskbarItemProgressState TaskbarProgressState + { + get => _viewModel.TaskbarProgressState; + set + { + _viewModel.TaskbarProgressState = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressState)); + } + } + + public double TaskbarProgressValue + { + get => _viewModel.TaskbarProgressValue; + set + { + _viewModel.TaskbarProgressValue = value; + _viewModel.OnPropertyChanged(nameof(_viewModel.TaskbarProgressValue)); + } + } + public bool CancelEnabled { get => _viewModel.CancelEnabled; diff --git a/Bloxstrap/UI/IBootstrapperDialog.cs b/Bloxstrap/UI/IBootstrapperDialog.cs index e15f7fb0..95339aa1 100644 --- a/Bloxstrap/UI/IBootstrapperDialog.cs +++ b/Bloxstrap/UI/IBootstrapperDialog.cs @@ -1,4 +1,5 @@ using System.Windows.Forms; +using System.Windows.Shell; namespace Bloxstrap.UI { @@ -10,6 +11,8 @@ public interface IBootstrapperDialog ProgressBarStyle ProgressStyle { get; set; } int ProgressValue { get; set; } int ProgressMaximum { get; set; } + TaskbarItemProgressState TaskbarProgressState { get; set; } + double TaskbarProgressValue { get; set; } bool CancelEnabled { get; set; } void ShowBootstrapper(); diff --git a/Bloxstrap/UI/Utility/TaskbarProgress.cs b/Bloxstrap/UI/Utility/TaskbarProgress.cs new file mode 100644 index 00000000..7cc7a698 --- /dev/null +++ b/Bloxstrap/UI/Utility/TaskbarProgress.cs @@ -0,0 +1,90 @@ +using System.Runtime.InteropServices; +using System.Windows.Shell; + +namespace Bloxstrap.UI.Utility +{ + // Modified from https://github.com/PowerShell/PSReadLine/blob/e9122d38e932614393ff61faf57d6518990d7226/PSReadLine/PlatformWindows.cs#L704 + internal static class TaskbarProgress + { + private enum TaskbarStates + { + NoProgress = 0, + Indeterminate = 0x1, + Normal = 0x2, + Error = 0x4, + Paused = 0x8, + } + + [ComImport()] + [Guid("ea1afb91-9e28-4b86-90e9-9e9f8a5eefaf")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + private interface ITaskbarList3 + { + // ITaskbarList + [PreserveSig] + int HrInit(); + + [PreserveSig] + int AddTab(IntPtr hwnd); + + [PreserveSig] + int DeleteTab(IntPtr hwnd); + + [PreserveSig] + int ActivateTab(IntPtr hwnd); + + [PreserveSig] + int SetActiveAlt(IntPtr hwnd); + + // ITaskbarList2 + [PreserveSig] + int MarkFullscreenWindow(IntPtr hwnd, [MarshalAs(UnmanagedType.Bool)] bool fFullscreen); + + // ITaskbarList3 + [PreserveSig] + int SetProgressValue(IntPtr hwnd, UInt64 ullCompleted, UInt64 ullTotal); + + [PreserveSig] + int SetProgressState(IntPtr hwnd, TaskbarStates state); + + // N.B. for copy/pasters: we've left out the rest of the ITaskbarList3 methods... + } + + [ComImport()] + [Guid("56fdf344-fd6d-11d0-958a-006097c9a090")] + [ClassInterface(ClassInterfaceType.None)] + private class TaskbarInstance + { + } + + private static Lazy _taskbarInstance = new Lazy(() => (ITaskbarList3)new TaskbarInstance()); + + private static TaskbarStates ConvertEnum(TaskbarItemProgressState state) + { + return state switch + { + TaskbarItemProgressState.None => TaskbarStates.NoProgress, + TaskbarItemProgressState.Indeterminate => TaskbarStates.Indeterminate, + TaskbarItemProgressState.Normal => TaskbarStates.Normal, + TaskbarItemProgressState.Error => TaskbarStates.Error, + TaskbarItemProgressState.Paused => TaskbarStates.Paused, + _ => throw new Exception($"Unrecognised TaskbarItemProgressState: {state}") + }; + } + + private static int SetProgressState(IntPtr windowHandle, TaskbarStates taskbarState) + { + return _taskbarInstance.Value.SetProgressState(windowHandle, taskbarState); + } + + public static int SetProgressState(IntPtr windowHandle, TaskbarItemProgressState taskbarState) + { + return SetProgressState(windowHandle, ConvertEnum(taskbarState)); + } + + public static int SetProgressValue(IntPtr windowHandle, int progressValue, int progressMax) + { + return _taskbarInstance.Value.SetProgressValue(windowHandle, (ulong)progressValue, (ulong)progressMax); + } + } +} diff --git a/Bloxstrap/UI/ViewModels/Bootstrapper/BootstrapperDialogViewModel.cs b/Bloxstrap/UI/ViewModels/Bootstrapper/BootstrapperDialogViewModel.cs index 9d35dac8..4a78da0a 100644 --- a/Bloxstrap/UI/ViewModels/Bootstrapper/BootstrapperDialogViewModel.cs +++ b/Bloxstrap/UI/ViewModels/Bootstrapper/BootstrapperDialogViewModel.cs @@ -1,6 +1,7 @@ using System.Windows; using System.Windows.Input; using System.Windows.Media; +using System.Windows.Shell; using CommunityToolkit.Mvvm.Input; @@ -19,6 +20,9 @@ public class BootstrapperDialogViewModel : NotifyPropertyChangedViewModel public int ProgressMaximum { get; set; } = 0; public int ProgressValue { get; set; } = 0; + public TaskbarItemProgressState TaskbarProgressState { get; set; } = TaskbarItemProgressState.Indeterminate; + public double TaskbarProgressValue { get; set; } = 0; + public bool CancelEnabled { get; set; } = false; public Visibility CancelButtonVisibility => CancelEnabled ? Visibility.Visible : Visibility.Collapsed;