Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
50078ab
PD2Launcherv2: Remove bogus Services directory from the project
Slacker86 May 29, 2026
9874111
PD2Launcherv2: Decouple IsDisableUpdates from Messages.ConfigurationC…
Slacker86 May 29, 2026
8ea8715
PD2Launcherv2: Colors: Introduce Red* set of colors and derived brush…
Slacker86 May 29, 2026
9a98031
PD2Launcherv2: Views.OptionsView: Put the D2GL/3dfx note under the ri…
Slacker86 May 29, 2026
9a34984
PD2Launcherv2, PD2Shared: Add ForceSoftwareRenderer to LauncherOptions
Slacker86 May 29, 2026
bebb02f
PD2Shared: ILocalStorage: Introduce LoadSectionIfExists<T>()
Slacker86 May 29, 2026
b7ecfa1
PD2Launcherv2: Introduce Utils.MsgBox
Slacker86 May 29, 2026
6dcbace
PD2Shared: Introduce Utils.Env
Slacker86 May 29, 2026
558391a
PD2Shared: Add Serilog and its selected sinks as dependencies
Slacker86 May 29, 2026
c44b029
PD2Shared: Introduce a strongly typed Constants.Version
Slacker86 May 29, 2026
6069915
eqpublish.ps1: Update accordingly after introducing Constants.Version
Slacker86 Jun 29, 2026
c6d3fc4
PD2Shared: Introduce Logging namespace and incorporate logging into t…
Slacker86 May 29, 2026
a2d0168
PD2Launcherv2: Improve unhandled exception handlers
Slacker86 May 29, 2026
5014ea6
PD2Launcherv2: Perform sanity checks on boot
Slacker86 May 29, 2026
b134314
PD2Shared: Introduce Utils.Wine
Slacker86 May 29, 2026
1214977
PD2Shared: Make LocalStorage.Update() more robust
Slacker86 May 29, 2026
5a38653
PD2Launcherv2: MainWindow: Hide MainFrame in the designer
Slacker86 May 29, 2026
54d0bfa
PD2Launcherv2: MainWindow: Show an indicator if running under Wine
Slacker86 May 29, 2026
bc58fd4
PD2Launcherv2: MainWindow: Offer applying launcher options for Wine c…
Slacker86 May 29, 2026
b73b9d3
PD2Shared: Utils.Wine: Add means of configuring Wine for PD2
Slacker86 May 29, 2026
8215f08
PD2Launcherv2: OptionsView: Remove dead code
Slacker86 May 29, 2026
e28c5ad
PD2Launcherv2: View(Model)s/OptionsView(Model): Allow applying/removi…
Slacker86 May 29, 2026
8be4b86
PD2Launcherv2: MainWindow: Offer applying PD2-specific Wine configura…
Slacker86 May 29, 2026
acd28fc
PD2Launcherv2, PD2Shared: Add UseHttp2 to LauncherOptions
Slacker86 May 29, 2026
8035af6
PD2Launcherv2: MainWindow: Remove an odd callback trigger in CloseBut…
Slacker86 May 29, 2026
151c518
PD2Launcherv2: Update Resources/Images/empty_button.png
Slacker86 May 29, 2026
bb9fac4
PD2Launcherv2: Resources/Images: Update image exif tags with explicit…
Slacker86 May 29, 2026
dc6c899
PD2Launcherv2: Resources/Images: Remove bogus PhysicalPixel (PixelsPe…
Slacker86 May 29, 2026
3c7493f
PD2Launcherv2: ControlStyles: Update ImageFillProgressBarStyle visuals
Slacker86 May 29, 2026
bf1323e
PD2Launcherv2: CustomImageButton: Add DisabledImageSource
Slacker86 May 29, 2026
46ce0c0
PD2Launcherv2: CustomControl: Introduce BlankButtonWithText
Slacker86 May 29, 2026
d33f083
PD2Launcherv2: ControlStyles: Introduce the PixelPerfectWindow style
Slacker86 May 29, 2026
bebfff2
PD2Launcherv2: ControlStyles: CustomImageButton: Remove any Stretch p…
Slacker86 May 29, 2026
f08f2e8
PD2Launcherv2: ControlStyles: CustomVerticalScrollBar: Resize subcont…
Slacker86 May 29, 2026
c949505
PD2Launcherv2: ControlStyles: ScrollBar: Resize subcontrols to their …
Slacker86 May 29, 2026
2d5c77a
PD2Launcherv2: ControlStyles: CustomCheckBoxStyle, CustomRadioButtonS…
Slacker86 May 29, 2026
dac6834
PD2Launcherv2: ControlStyles: Pick a darker color for CustomVerticalS…
Slacker86 May 29, 2026
5edd2ea
PD2Shared: Add BouncyCastle.Cryptography as a dependency
Slacker86 May 29, 2026
4e18fdc
PD2Shared: Introduce Extensions.GeneralDigestEx
Slacker86 May 29, 2026
eab712d
PD2Shared: Remove excessive exception handling from LaunchGameHelpers…
Slacker86 May 29, 2026
221d92c
PD2Shared: Introduce Extensions.HttpResponseMessageEx
Slacker86 Jun 27, 2026
ca2a33e
PD2Shared: Replace Helpers.GameFileUpdateHelpers with GameFileUpdate …
Slacker86 May 29, 2026
2d9cc7b
PD2Launcherv2: Introduce Utils.ProgressWithCookie
Slacker86 Jun 17, 2026
3f30c45
PD2Launcherv2: MainWindow: Utilize ProgressWithCookie
Slacker86 Jun 17, 2026
cf4a23a
PD2Launcherv2: MainWindow: Redo the 'Donate here' button as a BlankBu…
Slacker86 May 29, 2026
8d3553e
PD2Launcherv2: MainWindow: Add offline indicator
Slacker86 May 29, 2026
fe745a9
PD2Launcherv2: MainWindow: Toggle offline indicator in place of 'Upda…
Slacker86 May 29, 2026
f9458cb
PD2Launcherv2: Introduce Utils.Shell
Slacker86 May 29, 2026
d059c63
PD2Launcherv2: MainWindow: Add GoToLogButton
Slacker86 May 29, 2026
f171cba
PD2Launcherv2: Add the Resources/Icons/icon.ico as a Resource and exp…
Slacker86 May 29, 2026
18098b3
PD2Shared: Add System.IO.Hashing as a dependency
Slacker86 May 31, 2026
6e6b22f
PD2Shared: GameFileUpdate: Abstract out all hashing into a Hash and D…
Slacker86 May 31, 2026
b78900f
PD2Shared: Remove now obsolete Extensions.GeneralDigestEx
Slacker86 May 31, 2026
9fd80dd
PD2Shared: GameFileUpdater: Add LogManifestStats()
Slacker86 Jun 4, 2026
529b86b
PD2Launcherv2: Rename Resources/Fonts/exocet-blizzard-heavy.otf -> .ttf
Slacker86 Jun 6, 2026
f07f591
PD2Launcherv2: BlankButtonWithText: Add ButtonKind
Slacker86 Jun 6, 2026
e21118e
PD2Launcherv2: MainWindow: Revamp OptionsButton and LootButton into B…
Slacker86 Jun 6, 2026
febeffc
PD2Launcherv2: MainWindow: Disable OptionsButton and LootButton when …
Slacker86 Jun 6, 2026
501f015
PD2Launcherv2: Revamp sanity checks
Slacker86 Jun 19, 2026
7d15fe8
PD2Shared: Introduce the Mpq namespace
Slacker86 Jun 12, 2026
8f54887
PD2Shared: Logging: Improve exceptions in Logging.SetUp()
Slacker86 Jun 20, 2026
f5a8c75
PD2Launcherv2: Sanity checks: Add MPQ-related checks
Slacker86 Jun 20, 2026
5b282cd
PD2Launcherv2: MainWindow: Wire up MPQ-related functionality
Slacker86 Jun 20, 2026
781ede9
Introduce the mkmpqmanifest project
Slacker86 Jun 20, 2026
af47831
PD2Launcherv2: MainWindow: Add a slight delay after launching the gam…
Slacker86 Jun 22, 2026
71e983c
PD2Shared: GameFileUpdater: Avoid locks in DownloadFilesAsync()
Slacker86 Jul 3, 2026
24e1024
PD2Shared: GameFileUpdater: Avoid locks in QueryDownloadsAsync()
Slacker86 Jul 3, 2026
fa221f6
PD2Shared: GameFileUpdater: Avoid locks in ValidateFilesAsync()
Slacker86 Jul 3, 2026
d6346c2
PD2Launcherv2, PD2Shared: SanityChecks: Add CheckGameDirPathEncoding()
Slacker86 Jul 5, 2026
7e00bdb
PD2Shared: GameFileUpdater: Improve throughput estimator in DownloadF…
Slacker86 Jul 5, 2026
33dd9d5
[DO NOT MERGE] PD2Shared: Add custom build of StormLib.dll as project…
Slacker86 Jun 20, 2026
a530fcd
[FIXUP] PD2Shared: Introduce Logging namespace and incorporate loggin…
Slacker86 Jul 11, 2026
1bd61fe
PD2Shared, PD2Launcherv2: Update Utils.Wine slightly and log detected…
Slacker86 Jul 11, 2026
9940128
PD2Launcherv2: Make Messages.LauncherOptionsChangeMessage init-only
Slacker86 Jul 9, 2026
233be56
PD2Shared: Utils.Win32: Add more GetLastException()/GetLastErrorMessa…
Slacker86 Jul 10, 2026
703a3eb
PD2Shared: Logging: Put the new Utils.Win32.GetLastException() routin…
Slacker86 Jul 10, 2026
9e7cb90
PD2Launcherv2: Introduce the Utils.Gl namespace
Slacker86 Jul 10, 2026
57539d2
PD2Launcherv2: Sanity checks: Add a TestGlContext "non-check"
Slacker86 Jul 10, 2026
3bd8956
PD2Launcherv2: MainWindow: Prompt the user depending on GlTest.BestCt…
Slacker86 Jul 10, 2026
d097bce
[FIXUP] PD2Shared: Introduce the Mpq namespace
Slacker86 Jul 12, 2026
167f9e9
PD2Launcherv2: Log how long it took to reach MainWindow.ContentRendered
Slacker86 Jul 12, 2026
326b1a9
PD2Shared, PD2Launcherv2: Collect and display more Wine-related versi…
Slacker86 Jul 12, 2026
cf595bb
[FIXUP] PD2Launcherv2: Introduce Utils.MsgBox
Slacker86 Jul 14, 2026
1bbf0b0
PD2Launcherv2: MainWindow, ControlStyles: Remove Hyperlink line artif…
Slacker86 Jul 14, 2026
01c2b6c
[FIXUP] PD2Shared: Replace Helpers.GameFileUpdateHelpers with GameFil…
Slacker86 Jul 14, 2026
e0d97cf
PD2Launcherv2: MainWindow: Focus frames after navigation
Slacker86 Jul 14, 2026
077505b
PD2Launcherv2: MainWindow: Remove any attempts to nullify FocusVisual…
Slacker86 Jul 14, 2026
e2be566
PD2Launcherv2: ControlStyles: Match CloseButtonStyle's regular Border…
Slacker86 Jul 14, 2026
b824cae
PD2Launcherv2: ControlStyles: CustomColorChangeButton: Remove border …
Slacker86 Jul 14, 2026
b634e13
PD2Launcherv2: FiltersView: Better align headings with their buttons
Slacker86 Jul 14, 2026
68b3a73
PD2Launcherv2: FiltersView: Shift the right-hand side buttons slightly
Slacker86 Jul 14, 2026
9c647cb
PD2Launcherv2: Views: Merge the common ControlStyles into each Resour…
Slacker86 Jul 14, 2026
1ffb58e
PD2Launcherv2: ControlStyles: Change all StaticResource -> DynamicRes…
Slacker86 Jul 14, 2026
dbaf1ca
PD2Shared: Wine.ApplyWineConfiguration(): Also enable 'EmulateModeset'
Slacker86 Jul 14, 2026
1fb0cd7
PD2Shared: Utils.Wine: Improve exception messages
Slacker86 Jul 15, 2026
c136d92
[FIXUP] PD2Shared: Replace Helpers.GameFileUpdateHelpers with GameFil…
Slacker86 Jul 16, 2026
7a04f64
[FIXUP] PD2Launcherv2: MainWindow: Prompt the user depending on GlTes…
Slacker86 Jul 16, 2026
5a80634
PD2Launcherv2: Add 'Close after launch' checkbox to the launcher
Slacker86 Jul 15, 2026
0efe665
PD2Shared: Add LaunchGameHelpers.IsGameRunning
Slacker86 Jul 16, 2026
0c01bdd
PD2Launcherv2: MainWindow: Make CenterWindowOnScreen() actually work
Slacker86 Jul 16, 2026
fa21d65
[FIXUP] PD2Launcherv2: MainWindow: Wire up MPQ-related functionality
Slacker86 Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions PD2Launcherv2.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PD2Shared", "PD2Shared\PD2S
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamPD2", "SteamPD2\SteamPD2.csproj", "{6EA7B7D5-E28C-411E-973D-9B80946D3FBF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mkmpqmanifest", "mkmpqmanifest\mkmpqmanifest.csproj", "{D10451DF-BED1-4081-B9C7-04E0EA5A8EF5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -43,6 +45,10 @@ Global
{6EA7B7D5-E28C-411E-973D-9B80946D3FBF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6EA7B7D5-E28C-411E-973D-9B80946D3FBF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6EA7B7D5-E28C-411E-973D-9B80946D3FBF}.Release|Any CPU.Build.0 = Release|Any CPU
{D10451DF-BED1-4081-B9C7-04E0EA5A8EF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D10451DF-BED1-4081-B9C7-04E0EA5A8EF5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D10451DF-BED1-4081-B9C7-04E0EA5A8EF5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D10451DF-BED1-4081-B9C7-04E0EA5A8EF5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
6 changes: 1 addition & 5 deletions PD2Launcherv2/App.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,13 @@
<ResourceDictionary Source="Resources/Styles/ControlStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>

<!-- Global Style to remove the focus visual style from all controls -->
<Style TargetType="{x:Type Control}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
</Style>
<conv:InverseBooleanConverter x:Key="InverseBool"/>
<conv:BooleanToVisibilityConverter x:Key="BoolToVisConverter"/>
<conv:StringToIntConverter x:Key="StringToIntConverter"/>
<local:ViewModelLocator x:Key="ViewModelLocator"/>

<!-- Font Family Resources -->
<FontFamily x:Key="BlizzHeavy">pack://application:,,,/Resources/Fonts/exocet-blizzard-heavy.otf#Exocet Blizzard OT Light Bold</FontFamily>
<FontFamily x:Key="BlizzHeavy">pack://application:,,,/Resources/Fonts/exocet-blizzard-heavy.ttf#Exocet Blizzard OT Light Bold</FontFamily>
<FontFamily x:Key="BlizzLight">pack://application:,,,/Resources/Fonts/exocet-blizzard-light.ttf#Exocet Blizzard OT Light Regular</FontFamily>
<FontFamily x:Key="BlizzMedium">pack://application:,,,/Resources/Fonts/exocet-blizzard-medium.ttf#Exocet Blizzard OT Medium Regular</FontFamily>

Expand Down
149 changes: 101 additions & 48 deletions PD2Launcherv2/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
using System.Net.Http;
using System.Text;
using System.Windows;
using PD2Launcherv2.Utils;
using PD2Shared.GameFileUpdate;
using PD2Shared.Logging;
using static PD2Shared.Logging.LoggingStatic;
using PD2Shared.Mpq;
using PD2Shared.Utils;

[assembly: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute(WrapNonExceptionThrows = true)]

namespace PD2Launcherv2
{
Expand All @@ -19,6 +27,8 @@ namespace PD2Launcherv2
/// </summary>
public partial class App : Application
{
private readonly Stopwatch _bootStopwatch = Stopwatch.StartNew();

/// <summary>
/// Holds the service provider for dependency injection.
/// </summary>
Expand Down Expand Up @@ -58,7 +68,7 @@ private static void ConfigureServices(ServiceCollection services)
services.AddSingleton<LaunchGameHelpers>();
services.AddSingleton<NewsHelpers>();
services.AddSingleton<DDrawHelpers>();
services.AddSingleton<GameFileUpdateHelpers>();
services.AddSingleton<GameFileUpdater>();
services.AddSingleton<FileUpdateHelpers>(provider =>
new FileUpdateHelpers( provider.GetRequiredService<HttpClient>()));
services.AddTransient<OptionsViewModel>();
Expand Down Expand Up @@ -98,7 +108,7 @@ protected override async void OnStartup(StartupEventArgs e)

try
{
if (Process.GetProcessesByName("Game").Any())
if (LaunchGameHelpers.IsGameRunning)
{
Debug.WriteLine("Game already running.");
return;
Expand All @@ -110,8 +120,8 @@ protected override async void OnStartup(StartupEventArgs e)
await filterHelpers.CheckAndUpdateFilterAsync(selected);
}

var launcherArgs = localStorage.LoadSection<LauncherArgs>(StorageKey.LauncherArgs);
if (!launcherArgs.disableAutoUpdate)
var launcherOptions = localStorage.LoadSection<LauncherOptions>(StorageKey.LauncherOptions);
if (!launcherOptions.DisableAutoUpdate)
{
try
{
Expand All @@ -136,23 +146,108 @@ protected override async void OnStartup(StartupEventArgs e)
return;
}

// This is a bit meh, but let's keep the convention and make this case-insensitive
var createConsole = e.Args.Any(arg => arg.Equals("--console", StringComparison.OrdinalIgnoreCase));

// This is not expected to throw
Logging.SetUp(createConsole);

if (Wine.IsRunningUnderWine)
{
if (Wine.Version != null)
{
string wineVersionStr = Wine.Version.ToString();

if (Wine.BuildId != null)
{
wineVersionStr += $" ({Wine.BuildId})";
}

if (Wine.OsName != null)
{
wineVersionStr += $" on {Wine.OsName}";

if (Wine.OsRelease != null)
{
wineVersionStr += $" {Wine.OsRelease}";
}
}

L.CallerInformation($"Running under Wine {wineVersionStr}");
}
else
{
L.CallerInformation($"Running under an undetermined Wine version");
}
}

L.CallerInformation($"Using StormLib {Mpq.StormLibVersionString}");
L.CallerInformation($"Using up to {Environment.ProcessorCount} concurrent task(s)");

if (!SanityChecks.Run())
{
L.CallerInformation($"Sanity checks failed and user declined to continue.");

this.Shutdown(1);
return;
}

// Normal UI mode
base.OnStartup(e);
var mainWindow = _serviceProvider.GetService<MainWindow>();

if (mainWindow != null)
{
mainWindow.ContentRendered += MainWindow_ContentRendered;
}

mainWindow?.Show();
}

private void MainWindow_ContentRendered(object? sender, EventArgs e)
{
((MainWindow)sender!).ContentRendered -= MainWindow_ContentRendered;

L.CallerDebug($"Time till {nameof(MainWindow)} rendered: {_bootStopwatch.Elapsed}.");
}

protected override void OnExit(ExitEventArgs e)
{
Logging.ShutDown(e.ApplicationExitCode);

base.OnExit(e);
}

// Handle non-UI thread exceptions
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
LogException(e.ExceptionObject as Exception);
// Blindly cast the object to Exception thanks to RuntimeCompatibilityAttribute (https://learn.microsoft.com/en-us/dotnet/api/system.unhandledexceptioneventargs.exceptionobject#remarks)
var ex = (Exception)e.ExceptionObject;

L.Fatal(ex, "Unhandled exception");

this.Dispatcher.Invoke(() =>
{
MsgBox.Exception(ex, "Unhandled exception:");

// Unlike DispatcherUnhandledException, WER will still kick in.
// However, due to reliance on task asynchronous programming model, it is very unlikely that this handler will ever be used.
this.Shutdown(1);
});
}

// Handle UI thread exceptions
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
LogException(e.Exception);
var ex = e.Exception;

L.Fatal(ex, "Unhandled exception");
MsgBox.Exception(ex, "Unhandled exception:");

e.Handled = true; // Prevent application from crashing

// ...yet shut it down on our own terms now, knowing that we have prevented Windows Error Reporting from kicking in.
this.Shutdown(1);
}

private void CleanUpTempStorageFiles()
Expand All @@ -174,47 +269,5 @@ private void CleanUpTempStorageFiles()
}
}
}

private void LogException(Exception ex)
{
if (ex == null) return;

string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory);

string logFile = Path.Combine(logPath, $"pd2launcher_error__{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt");

// Use StackTrace to get more detailed info about where the exception occurred
var stackTrace = new StackTrace(ex, true);
var frame = stackTrace.GetFrames()?.FirstOrDefault();
var method = frame?.GetMethod();
var declaringType = method?.DeclaringType;
var methodName = method?.Name;

// Prepare the log entry
var sb = new StringBuilder();
sb.AppendLine("==============================================================================");
sb.AppendLine($"Timestamp: {DateTime.Now}");
sb.AppendLine("Exception Class:");
sb.AppendLine(declaringType?.FullName ?? "N/A");
sb.AppendLine("Exception Method:");
sb.AppendLine($"{methodName ?? "N/A"}");
sb.AppendLine("Exception Message:");
sb.AppendLine(ex.Message);
sb.AppendLine("Stack Trace:");
sb.AppendLine(ex.StackTrace);

// Include inner exception details if available
if (ex.InnerException != null)
{
sb.AppendLine("Inner Exception Message:");
sb.AppendLine(ex.InnerException.Message);
sb.AppendLine("Inner Exception Stack Trace:");
sb.AppendLine(ex.InnerException.StackTrace);
}
sb.AppendLine("==============================================================================");

// Append the log entry to the file
File.AppendAllText(logFile, sb.ToString());
}
}
}
2 changes: 2 additions & 0 deletions PD2Launcherv2/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace ProjectDiablo2Launcherv2
{
public static class Constants
{
public const string DefaultDialogTitle = "Project Diablo 2 Launcher";

public static readonly List<string> excludedFiles = new List<string>
{ "D2.LNG", "BnetLog.txt", "ProjectDiablo.cfg", "ddraw.ini", "default.filter", "loot.filter", "UI.ini", "d2gl.yaml"};

Expand Down
44 changes: 44 additions & 0 deletions PD2Launcherv2/CustomControl/BlankButtonWithText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Windows;
using System.Windows.Controls;

namespace PD2Launcherv2.CustomControl
{
public class BlankButtonWithText : Button
{
public enum ButtonKindEnum
{
Normal,
SplitTop,
SplitBottom
}

static BlankButtonWithText()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BlankButtonWithText), new FrameworkPropertyMetadata(typeof(BlankButtonWithText)));
}

public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(BlankButtonWithText),
new PropertyMetadata("..."));

public static readonly DependencyProperty ButtonKindProperty = DependencyProperty.Register(
"ButtonKind",
typeof(ButtonKindEnum),
typeof(BlankButtonWithText),
new PropertyMetadata(ButtonKindEnum.Normal));

public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}

public ButtonKindEnum ButtonKind
{
get => (ButtonKindEnum)GetValue(ButtonKindProperty);
set => SetValue(ButtonKindProperty, value);
}
}
}
37 changes: 37 additions & 0 deletions PD2Launcherv2/CustomControl/CustomImageButton.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace PD2Launcherv2.CustomControl
{
public class CustomImageButton : Button
{
private static RenderTargetBitmap _missingImage = null!;
private static BitmapSource MissingImage
{
get
{
if (_missingImage == null)
{
Rect imageSize = new(new Size(100, 100));

_missingImage = new RenderTargetBitmap((int)imageSize.Width, (int)imageSize.Height, 96, 96, PixelFormats.Pbgra32);

DrawingVisual visual = new();
using (DrawingContext context = visual.RenderOpen())
{
context.DrawRectangle(Brushes.Magenta, pen: null, imageSize);
}

_missingImage.Render(visual);
}

return _missingImage;
}
}

static CustomImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomImageButton), new FrameworkPropertyMetadata(typeof(CustomImageButton)));
Expand All @@ -23,6 +48,12 @@ static CustomImageButton()
typeof(CustomImageButton),
new PropertyMetadata(default(ImageSource)));

public static readonly DependencyProperty DisabledImageSourceProperty = DependencyProperty.Register(
"DisabledImageSource",
typeof(ImageSource),
typeof(CustomImageButton),
new PropertyMetadata(MissingImage));

public ImageSource NormalImageSource
{
get => (ImageSource)GetValue(NormalImageSourceProperty);
Expand All @@ -34,5 +65,11 @@ public ImageSource PressedImageSource
get => (ImageSource)GetValue(PressedImageSourceProperty);
set => SetValue(PressedImageSourceProperty, value);
}

public ImageSource DisabledImageSource
{
get => (ImageSource)GetValue(DisabledImageSourceProperty);
set => SetValue(DisabledImageSourceProperty, value);
}
}
}
Loading