Skip to content

Commit

Permalink
Add taskbar progress bar (#2757)
Browse files Browse the repository at this point in the history
* add taskbar progress bar

does not support winform bootstrappers

* add winforms taskbar progress bar

* fix build
  • Loading branch information
bluepilledgreat authored Oct 3, 2024
1 parent 75f8be2 commit 4f1b4f2
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 6 deletions.
28 changes: 26 additions & 2 deletions Bloxstrap/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
using Microsoft.Win32;

using Bloxstrap.AppData;
using System.Windows.Shell;
using Bloxstrap.UI.Elements.Bootstrapper.Base;

using ICSharpCode.SharpZipLib.Zip;

Expand All @@ -26,7 +28,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 =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<Settings>\r\n" +
Expand All @@ -46,6 +51,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);
Expand Down Expand Up @@ -91,13 +98,20 @@ 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
// too lazy to fix properly so lol
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)
Expand Down Expand Up @@ -627,11 +641,20 @@ private async Task UpgradeRoblox()
if (Dialog is not null)
{
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<Task>();
Expand All @@ -658,6 +681,7 @@ private async Task UpgradeRoblox()
if (Dialog is not null)
{
Dialog.ProgressStyle = ProgressBarStyle.Marquee;
Dialog.TaskbarProgressState = TaskbarItemProgressState.Indeterminate;
SetStatus(Strings.Bootstrapper_Status_Configuring);
}

Expand Down
25 changes: 25 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/Base/WinFormsDialogBase.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using System.Windows.Forms;
using System.Windows.Shell;

using Bloxstrap.UI.Utility;

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;
Expand All @@ -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
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
AllowsTransparency="True"
Background="Transparent"
Closing="Window_Closing">

<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressState="{Binding Path=TaskbarProgressState}" ProgressValue="{Binding Path=TaskbarProgressValue}" />
</Window.TaskbarItemInfo>

<Border CornerRadius="10" BorderBrush="#33393B3D" Background="{Binding Background}" BorderThickness="{Binding DialogBorder}">
<Grid>
<Image Source="{Binding ByfronLogoLocation}" RenderOptions.BitmapScalingMode="HighQuality" Width="114" Height="108" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="17,13,0,0" />
Expand Down
21 changes: 21 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/ByfronDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">

<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressState="{Binding Path=TaskbarProgressState}" ProgressValue="{Binding Path=TaskbarProgressValue}" />
</Window.TaskbarItemInfo>

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
Expand Down
25 changes: 21 additions & 4 deletions Bloxstrap/UI/Elements/Bootstrapper/ClassicFluentDialog.xaml.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
WindowStyle="None"
mc:Ignorable="d">

<Window.TaskbarItemInfo>
<TaskbarItemInfo ProgressState="{Binding Path=TaskbarProgressState}" ProgressValue="{Binding Path=TaskbarProgressValue}" />
</Window.TaskbarItemInfo>

<!-- Background is for Aero theme only -->
<Grid Background="{Binding Path=BackgroundColourBrush, Mode=OneTime}">
<!-- Allow for drag -->
Expand Down
21 changes: 21 additions & 0 deletions Bloxstrap/UI/Elements/Bootstrapper/FluentDialog.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions Bloxstrap/UI/IBootstrapperDialog.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Windows.Forms;
using System.Windows.Shell;

namespace Bloxstrap.UI
{
Expand All @@ -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();
Expand Down
90 changes: 90 additions & 0 deletions Bloxstrap/UI/Utility/TaskbarProgress.cs
Original file line number Diff line number Diff line change
@@ -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<ITaskbarList3> _taskbarInstance = new Lazy<ITaskbarList3>(() => (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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shell;

using CommunityToolkit.Mvvm.Input;

Expand All @@ -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;

Expand Down

0 comments on commit 4f1b4f2

Please sign in to comment.