diff --git a/BetterGenshinImpact.sln b/BetterGenshinImpact.sln
index 92b305cc..3cd75161 100644
--- a/BetterGenshinImpact.sln
+++ b/BetterGenshinImpact.sln
@@ -17,44 +17,100 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.HotkeyCapture", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.KeyboardCapture", "Fischless.KeyboardCapture\Fischless.KeyboardCapture.csproj", "{10A48327-7E58-4B51-B1FC-55506C703C8F}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Deploy", "Deploy", "{458E1106-43A4-47E6-B11B-D243035D4C76}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicaSetup", "Build\MicaSetup\MicaSetup.csproj", "{AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicaSetup.Uninst", "Build\MicaSetup\MicaSetup.Uninst.csproj", "{673344BC-B860-44AE-AD88-D33465BDE25B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
+ Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|Any CPU.Build.0 = Debug|x64
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|x64.ActiveCfg = Debug|x64
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|x64.Build.0 = Debug|x64
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|Any CPU.ActiveCfg = Release|x64
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|Any CPU.Build.0 = Release|x64
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|x64.ActiveCfg = Release|x64
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|x64.Build.0 = Release|x64
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|Any CPU.Build.0 = Debug|x64
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|x64.ActiveCfg = Debug|x64
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|x64.Build.0 = Debug|x64
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|Any CPU.ActiveCfg = Release|x64
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|Any CPU.Build.0 = Release|x64
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|x64.ActiveCfg = Release|x64
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|x64.Build.0 = Release|x64
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|Any CPU.Build.0 = Debug|x64
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|x64.ActiveCfg = Debug|x64
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|x64.Build.0 = Debug|x64
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|Any CPU.ActiveCfg = Release|x64
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|Any CPU.Build.0 = Release|x64
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|x64.ActiveCfg = Release|x64
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|x64.Build.0 = Release|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|Any CPU.Build.0 = Debug|x64
{6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|x64.ActiveCfg = Debug|x64
{6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|x64.Build.0 = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|Any CPU.ActiveCfg = Release|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|Any CPU.Build.0 = Release|x64
{6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|x64.ActiveCfg = Release|x64
{6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|x64.Build.0 = Release|x64
+ {D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Debug|Any CPU.Build.0 = Debug|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Debug|x64.ActiveCfg = Debug|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Debug|x64.Build.0 = Debug|x64
+ {D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|Any CPU.ActiveCfg = Release|x64
+ {D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|Any CPU.Build.0 = Release|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|x64.ActiveCfg = Release|x64
{D35CB953-C666-4E57-9A9A-3AAE5BF78402}.Release|x64.Build.0 = Release|x64
+ {08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|Any CPU.Build.0 = Debug|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|x64.ActiveCfg = Debug|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Debug|x64.Build.0 = Debug|x64
+ {08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|Any CPU.ActiveCfg = Release|x64
+ {08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|Any CPU.Build.0 = Release|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|x64.ActiveCfg = Release|x64
{08152E44-2564-46C5-B5B2-54DD43C01A79}.Release|x64.Build.0 = Release|x64
+ {10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|Any CPU.Build.0 = Debug|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|x64.ActiveCfg = Debug|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Debug|x64.Build.0 = Debug|x64
+ {10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|Any CPU.ActiveCfg = Release|x64
+ {10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|Any CPU.Build.0 = Release|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|x64.ActiveCfg = Release|x64
{10A48327-7E58-4B51-B1FC-55506C703C8F}.Release|x64.Build.0 = Release|x64
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Debug|x64.ActiveCfg = Debug|x64
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Debug|x64.Build.0 = Debug|x64
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Release|x64.ActiveCfg = Release|x64
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15}.Release|x64.Build.0 = Release|x64
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Debug|x64.ActiveCfg = Debug|x64
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Debug|x64.Build.0 = Debug|x64
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|x64.ActiveCfg = Release|x64
+ {673344BC-B860-44AE-AD88-D33465BDE25B}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {AB85DA23-EB8F-4FBF-A7FA-35CE05B23C15} = {458E1106-43A4-47E6-B11B-D243035D4C76}
+ {673344BC-B860-44AE-AD88-D33465BDE25B} = {458E1106-43A4-47E6-B11B-D243035D4C76}
+ EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {352D8B78-9DE3-4E58-985F-FADD22594DB4}
EndGlobalSection
diff --git a/BetterGenshinImpact/app.manifest b/BetterGenshinImpact/app.manifest
deleted file mode 100644
index 2dd0dace..00000000
--- a/BetterGenshinImpact/app.manifest
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
-
-
-
-
-
-
-
-
diff --git a/Build/.gitignore b/Build/.gitignore
new file mode 100644
index 00000000..42475343
--- /dev/null
+++ b/Build/.gitignore
@@ -0,0 +1,5 @@
+.vs/
+dist/
+/*.exe
+/*.7z
+/*.zip
diff --git a/Build/LICENSE b/Build/LICENSE
new file mode 100644
index 00000000..781844a0
--- /dev/null
+++ b/Build/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Lemutec Contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Build/MicaSetup.Tools/7-Zip/7z.dll b/Build/MicaSetup.Tools/7-Zip/7z.dll
new file mode 100644
index 00000000..7113c59b
Binary files /dev/null and b/Build/MicaSetup.Tools/7-Zip/7z.dll differ
diff --git a/Build/MicaSetup.Tools/7-Zip/7z.exe b/Build/MicaSetup.Tools/7-Zip/7z.exe
new file mode 100644
index 00000000..69946dbc
Binary files /dev/null and b/Build/MicaSetup.Tools/7-Zip/7z.exe differ
diff --git a/Build/MicaSetup/.gitignore b/Build/MicaSetup/.gitignore
new file mode 100644
index 00000000..11b8c04f
--- /dev/null
+++ b/Build/MicaSetup/.gitignore
@@ -0,0 +1,4 @@
+bin/
+obj/
+
+*.user
diff --git a/Build/MicaSetup/App.xaml b/Build/MicaSetup/App.xaml
new file mode 100644
index 00000000..b32e93cb
--- /dev/null
+++ b/Build/MicaSetup/App.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/App.xaml.cs b/Build/MicaSetup/App.xaml.cs
new file mode 100644
index 00000000..c08820f1
--- /dev/null
+++ b/Build/MicaSetup/App.xaml.cs
@@ -0,0 +1,17 @@
+using MicaSetup.Helper;
+using System.Windows;
+
+namespace MicaSetup;
+
+public partial class App : Application, IApp
+{
+ public App()
+ {
+ InitializeComponent();
+ }
+}
+
+public interface IApp
+{
+ public int Run();
+}
diff --git a/Build/MicaSetup/AssemblyInfo.cs b/Build/MicaSetup/AssemblyInfo.cs
new file mode 100644
index 00000000..f79fc71c
--- /dev/null
+++ b/Build/MicaSetup/AssemblyInfo.cs
@@ -0,0 +1,16 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Markup;
+using System.Windows.Media;
+
+[assembly: DisableDpiAwareness]
+[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
+[assembly: ObfuscateAssembly(true)]
+[assembly: ComVisible(false)]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: XmlnsPrefix("urn:MicaSetup", "mica")]
+[assembly: XmlnsDefinition("urn:MicaSetup", "MicaSetup.Design.Controls")]
+[assembly: XmlnsDefinition("urn:MicaSetup", "MicaSetup.Design.Converters")]
diff --git a/Build/MicaSetup/Core/IsExternalInit.cs b/Build/MicaSetup/Core/IsExternalInit.cs
new file mode 100644
index 00000000..f0392094
--- /dev/null
+++ b/Build/MicaSetup/Core/IsExternalInit.cs
@@ -0,0 +1,11 @@
+using System.ComponentModel;
+
+namespace System.Runtime.CompilerServices;
+
+///
+/// Workaround class for records and "init" keyword
+///
+[EditorBrowsable(EditorBrowsableState.Never)]
+internal class IsExternalInit
+{
+}
diff --git a/Build/MicaSetup/Core/Logger.cs b/Build/MicaSetup/Core/Logger.cs
new file mode 100644
index 00000000..85ab67e6
--- /dev/null
+++ b/Build/MicaSetup/Core/Logger.cs
@@ -0,0 +1,104 @@
+using MicaSetup.Helper;
+using System;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using DebugOut = System.Diagnostics.Debug;
+
+namespace MicaSetup.Core;
+
+public static class Logger
+{
+ private static readonly string ApplicationLogPath = SpecialPathHelper.GetFolder();
+ private static readonly TextWriterTraceListener TraceListener = null!;
+
+ static Logger()
+ {
+ if (!Directory.Exists(ApplicationLogPath))
+ {
+ _ = Directory.CreateDirectory(ApplicationLogPath);
+ }
+
+ string logFilePath = Path.Combine(ApplicationLogPath, DateTime.Now.ToString(@"yyyyMMdd", CultureInfo.InvariantCulture) + ".log");
+ TraceListener = new TextWriterTraceListener(logFilePath);
+#if LEGACY
+ Trace.AutoFlush = true;
+ Trace.Listeners.Clear();
+ Trace.Listeners.Add(TraceListener);
+#endif
+ }
+
+ [SuppressMessage("Style", "IDE0060:")]
+ public static void Ignore(params object[] values)
+ {
+ }
+
+ [Conditional("DEBUG")]
+ public static void Debug(params object[] values)
+ {
+ Log("DEBUG", string.Join(" ", values));
+ }
+
+ public static void Info(params object[] values)
+ {
+ Log("INFO", string.Join(" ", values));
+ }
+
+ public static void Warn(params object[] values)
+ {
+ Log("ERROR", string.Join(" ", values));
+ }
+
+ public static void Error(params object[] values)
+ {
+ Log("ERROR", string.Join(" ", values));
+ }
+
+ public static void Fatal(params object[] values)
+ {
+ Log("FATAL", string.Join(" ", values));
+ }
+
+ public static void Exception(Exception e, string message = null!)
+ {
+ Log(
+ (message ?? string.Empty) + Environment.NewLine +
+ e?.Message + Environment.NewLine +
+ "Inner exception: " + Environment.NewLine +
+ e?.InnerException?.Message + Environment.NewLine +
+ "Stack trace: " + Environment.NewLine +
+ e?.StackTrace,
+ "ERROR");
+#if DEBUG
+ Debugger.Break();
+#endif
+ }
+
+ private static void Log(string type, string message)
+ {
+ StringBuilder sb = new();
+
+ sb.Append(type + "|" + DateTime.Now.ToString(@"yyyy-MM-dd|HH:mm:ss.fff", CultureInfo.InvariantCulture))
+ .Append("|" + GetCallerInfo())
+ .Append("|" + message);
+
+ DebugOut.WriteLine(sb.ToString());
+ if (Option.Current.Logging)
+ {
+ TraceListener.WriteLine(sb.ToString());
+ TraceListener.Flush();
+ }
+ }
+
+ private static string GetCallerInfo()
+ {
+ StackTrace stackTrace = new();
+
+ MethodBase methodName = stackTrace.GetFrame(3)?.GetMethod()!;
+ string? className = methodName?.DeclaringType?.Name;
+ return className + "|" + methodName?.Name;
+ }
+}
diff --git a/Build/MicaSetup/Core/MuiLanguage.cs b/Build/MicaSetup/Core/MuiLanguage.cs
new file mode 100644
index 00000000..291256d9
--- /dev/null
+++ b/Build/MicaSetup/Core/MuiLanguage.cs
@@ -0,0 +1,183 @@
+using MicaSetup.Helper;
+using MicaSetup.Services;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Windows;
+using System.Windows.Baml2006;
+using System.Xaml;
+
+namespace MicaSetup.Core;
+
+public static class MuiLanguage
+{
+ ///
+ /// https://learn.microsoft.com/en-us/typography/fonts/windows_11_font_list
+ /// https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list
+ /// https://learn.microsoft.com/en-us/typography/fonts/windows_81_font_list
+ /// https://learn.microsoft.com/en-us/typography/fonts/windows_8_font_list
+ /// https://learn.microsoft.com/en-us/typography/fonts/windows_7_font_list
+ ///
+ public static List FontSelector { get; } = new();
+
+ public static void SetupLanguage()
+ {
+ _ = SetLanguage();
+ }
+
+ public static bool SetLanguage() => CultureInfo.CurrentUICulture.TwoLetterISOLanguageName switch
+ {
+ "zh" => SetLanguage("zh"),
+ "ja" => SetLanguage("ja"),
+ "en" or _ => SetLanguage("en"),
+ };
+
+ public static bool SetLanguage(string name = "en")
+ {
+ if (Application.Current == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ foreach (ResourceDictionary dictionary in Application.Current.Resources.MergedDictionaries)
+ {
+ if (dictionary.Source != null && dictionary.Source.OriginalString.Equals($"/Resources/Languages/{name}.xaml", StringComparison.Ordinal))
+ {
+ Application.Current.Resources.MergedDictionaries.Remove(dictionary);
+ Application.Current.Resources.MergedDictionaries.Add(dictionary);
+ return true;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _ = e;
+ }
+ return false;
+ }
+
+ public static string Mui(string key)
+ {
+ try
+ {
+ if (Application.Current == null)
+ {
+ return MuiBaml(key);
+ }
+ if (Application.Current!.FindResource(key) is string value)
+ {
+ return value;
+ }
+ }
+ catch (Exception e)
+ {
+ _ = e;
+ }
+ return null!;
+ }
+
+ public static string Mui(string key, params object[] args)
+ {
+ return string.Format(Mui(key)?.ToString(), args);
+ }
+
+ private static string MuiBaml(string key)
+ {
+ try
+ {
+ using Stream resourceXaml = ResourceHelper.GetStream(new MuiLanguageService().GetXamlUriString());
+ if (BamlHelper.LoadBaml(resourceXaml) is ResourceDictionary resourceDictionary)
+ {
+ return (resourceDictionary[key] as string)!;
+ }
+ }
+ catch (Exception e)
+ {
+ _ = e;
+ }
+ return null!;
+ }
+}
+
+file static class BamlHelper
+{
+ public static object LoadBaml(Stream stream)
+ {
+ using Baml2006Reader reader = new(stream);
+ using XamlObjectWriter writer = new(reader.SchemaContext);
+
+ while (reader.Read())
+ {
+ writer.WriteNode(reader);
+ }
+ return writer.Result;
+ }
+}
+
+public class MuiLanguageFont
+{
+ public string? Name { get; set; }
+ public string? TwoName { get; set; }
+ public string? ThreeName { get; set; }
+
+ public string? ResourceFontFileName { get; set; }
+ public string? ResourceFontFamilyName { get; set; }
+ public string? ResourceFamilyName => !string.IsNullOrWhiteSpace(ResourceFontFileName) && !string.IsNullOrWhiteSpace(ResourceFontFamilyName) ? $"./{ResourceFontFileName}#{ResourceFontFamilyName}" : null!;
+
+ public string? SystemFamilyName { get; set; }
+ public string? SystemFamilyNameBackup { get; set; }
+}
+
+public static class MuiLanguageFontExtension
+{
+ public static MuiLanguageFont OnNameOf(this MuiLanguageFont self, string name)
+ {
+ self.Name = name ?? throw new ArgumentNullException(nameof(name));
+ self.TwoName = null!;
+ self.ThreeName = null!;
+ return self;
+ }
+
+ public static MuiLanguageFont OnTwoNameOf(this MuiLanguageFont self, string twoName)
+ {
+ self.Name = null!;
+ self.TwoName = twoName ?? throw new ArgumentNullException(nameof(twoName));
+ self.ThreeName = null!;
+ return self;
+ }
+
+ public static MuiLanguageFont OnThreeNameOf(this MuiLanguageFont self, string threeName)
+ {
+ self.Name = null!;
+ self.TwoName = null!;
+ self.ThreeName = threeName ?? throw new ArgumentNullException(nameof(threeName));
+ return self;
+ }
+
+ public static MuiLanguageFont OnAnyName(this MuiLanguageFont self)
+ {
+ self.Name = null!;
+ self.TwoName = null!;
+ self.ThreeName = null!;
+ return self;
+ }
+
+ public static MuiLanguageFont ForResourceFont(this MuiLanguageFont self, string fontFileName, string familyName)
+ {
+ self.ResourceFontFileName = fontFileName ?? throw new ArgumentNullException(nameof(fontFileName));
+ self.ResourceFontFamilyName = familyName ?? throw new ArgumentNullException(nameof(familyName));
+ return self;
+ }
+
+ public static MuiLanguageFont ForSystemFont(this MuiLanguageFont self, string familyName, string familyNameBackup = null!)
+ {
+ self.SystemFamilyName = familyName ?? throw new ArgumentNullException(nameof(familyName));
+ self.SystemFamilyNameBackup = familyNameBackup;
+ _ = !new Regex("^[a-zA-Z ]+$").IsMatch(familyName) ? throw new ArgumentException(nameof(familyName)) : default(object);
+ return self;
+ }
+}
diff --git a/Build/MicaSetup/Core/ServiceManager.cs b/Build/MicaSetup/Core/ServiceManager.cs
new file mode 100644
index 00000000..459b3022
--- /dev/null
+++ b/Build/MicaSetup/Core/ServiceManager.cs
@@ -0,0 +1,15 @@
+using Microsoft.Extensions.DependencyInjection;
+
+namespace MicaSetup.Services;
+
+#pragma warning disable CS8618
+
+public class ServiceManager
+{
+ public static ServiceProvider Services { get; set; }
+
+ public static T GetService()
+ {
+ return Services.GetService()!;
+ }
+}
diff --git a/Build/MicaSetup/Design/Animations/DoubleEasingAnimation.cs b/Build/MicaSetup/Design/Animations/DoubleEasingAnimation.cs
new file mode 100644
index 00000000..2ff605d4
--- /dev/null
+++ b/Build/MicaSetup/Design/Animations/DoubleEasingAnimation.cs
@@ -0,0 +1,372 @@
+using System;
+
+namespace MicaSetup.Controls.Animations;
+
+public delegate double DoubleEasingAnimation(double t, double b, double c, double d);
+
+public static class DoubleEasingAnimations
+{
+ public static double EaseInOutQuad(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d / 2d) < 1d)
+ {
+ return c / 2d * t * t + b;
+ }
+ return -c / 2d * ((t -= 1d) * (t - 2d) - 1d) + b;
+ }
+
+ public static double EaseInQuad(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * (t /= d) * t + b;
+ }
+
+ public static double EaseOutQuad(double t, double b, double c, double d)
+ {
+ c -= b;
+ return -c * (t /= d) * (t - 2d) + b;
+ }
+
+ public static double EaseInCubic(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * (t /= d) * t * t + b;
+ }
+
+ public static double EaseOutCubic(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * ((t = t / d - 1d) * t * t + 1d) + b;
+ }
+
+ public static double EaseInOutCubic(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d / 2d) < 1d)
+ {
+ return c / 2d * t * t * t + b;
+ }
+ return c / 2d * ((t -= 2d) * t * t + 2d) + b;
+ }
+
+ public static double EaseInQuart(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * (t /= d) * t * t * t + b;
+ }
+
+ public static double EaseOutQuart(double t, double b, double c, double d)
+ {
+ c -= b;
+ return -c * ((t = t / d - 1d) * t * t * t - 1d) + b;
+ }
+
+ public static double EaseInOutQuart(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d / 2d) < 1d)
+ {
+ return c / 2d * t * t * t * t + b;
+ }
+ return -c / 2d * ((t -= 2d) * t * t * t - 2d) + b;
+ }
+
+ public static double EaseInQuint(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * (t /= d) * t * t * t * t + b;
+ }
+
+ public static double EaseOutQuint(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * ((t = t / d - 1d) * t * t * t * t + 1d) + b;
+ }
+
+ public static double EaseInOutQuint(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d / 2d) < 1d)
+ {
+ return c / 2d * t * t * t * t * t + b;
+ }
+ return c / 2d * ((t -= 2d) * t * t * t * t + 2d) + b;
+ }
+
+ public static double EaseInSine(double t, double b, double c, double d)
+ {
+ c -= b;
+ return -c * Math.Cos(t / d * 1.5707963267948966d) + c + b;
+ }
+
+ public static double EaseOutSine(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * Math.Sin(t / d * 1.5707963267948966d) + b;
+ }
+
+ public static double EaseInOutSine(double t, double b, double c, double d)
+ {
+ c -= b;
+ return -c / 2d * (Math.Cos(3.141592653589793d * t / d) - 1d) + b;
+ }
+
+ public static double EaseInExpo(double t, double b, double c, double d)
+ {
+ c -= b;
+ if (t != 0d)
+ {
+ return c * Math.Pow(2d, 10d * (t / d - 1d)) + b;
+ }
+ return b;
+ }
+
+ public static double EaseOutExpo(double t, double b, double c, double d)
+ {
+ c -= b;
+ if (t != d)
+ {
+ return c * (-Math.Pow(2d, -10d * t / d) + 1d) + b;
+ }
+ return b + c;
+ }
+
+ public static double EaseInOutExpo(double t, double b, double c, double d)
+ {
+ c -= b;
+ if (t == 0d)
+ {
+ return b;
+ }
+ if (t == d)
+ {
+ return b + c;
+ }
+ if ((t /= d / 2d) < 1d)
+ {
+ return c / 2d * Math.Pow(2d, 10d * (t - 1d)) + b;
+ }
+ return c / 2d * (-Math.Pow(2d, -10d * (t -= 1d)) + 2d) + b;
+ }
+
+ public static double EaseInCirc(double t, double b, double c, double d)
+ {
+ c -= b;
+ return -c * (Math.Sqrt(1d - (t /= d) * t) - 1d) + b;
+ }
+
+ public static double EaseOutCirc(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c * Math.Sqrt(1d - (t = t / d - 1d) * t) + b;
+ }
+
+ public static double EaseInOutCirc(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d / 2d) < 1d)
+ {
+ return -c / 2d * (Math.Sqrt(1d - t * t) - 1d) + b;
+ }
+ return c / 2d * (Math.Sqrt(1d - (t -= 2d) * t) + 1d) + b;
+ }
+
+ public static double EaseInElastic(double t, double b, double c, double d)
+ {
+ c -= b;
+ double num = 0d;
+ double num2 = c;
+ if (t == 0d)
+ {
+ return b;
+ }
+ if ((t /= d) == 1d)
+ {
+ return b + c;
+ }
+ if (num == 0d)
+ {
+ num = d * 0.3d;
+ }
+ double num3;
+ if (num2 < Math.Abs(c))
+ {
+ num2 = c;
+ num3 = num / 4d;
+ }
+ else
+ {
+ num3 = num / 6.283185307179586d * Math.Asin(c / num2);
+ }
+ return -(num2 * Math.Pow(2d, 10d * (t -= 1d)) * Math.Sin((t * d - num3) * 6.283185307179586d / num)) + b;
+ }
+
+ public static double EaseOutElastic(double t, double b, double c, double d)
+ {
+ c -= b;
+ double num = 0d;
+ double num2 = c;
+ if (t == 0d)
+ {
+ return b;
+ }
+ if ((t /= d) == 1d)
+ {
+ return b + c;
+ }
+ if (num == 0d)
+ {
+ num = d * 0.3d;
+ }
+ double num3;
+ if (num2 < Math.Abs(c))
+ {
+ num2 = c;
+ num3 = num / 4d;
+ }
+ else
+ {
+ num3 = num / 6.283185307179586d * Math.Asin(c / num2);
+ }
+ return num2 * Math.Pow(2d, -10d * t) * Math.Sin((t * d - num3) * 6.283185307179586d / num) + c + b;
+ }
+
+ public static double EaseInOutElastic(double t, double b, double c, double d)
+ {
+ c -= b;
+ double num = 0d;
+ double num2 = c;
+ if (t == 0d)
+ {
+ return b;
+ }
+ if ((t /= d / 2d) == 2d)
+ {
+ return b + c;
+ }
+ if (num == 0d)
+ {
+ num = d * 0.44999999999999996d;
+ }
+ double num3;
+ if (num2 < Math.Abs(c))
+ {
+ num2 = c;
+ num3 = num / 4d;
+ }
+ else
+ {
+ num3 = num / 6.283185307179586d * Math.Asin(c / num2);
+ }
+ if (t < 1d)
+ {
+ return -0.5d * (num2 * Math.Pow(2d, 10d * (t -= 1d)) * Math.Sin((t * d - num3) * 6.283185307179586d / num)) + b;
+ }
+ return num2 * Math.Pow(2d, -10d * (t -= 1d)) * Math.Sin((t * d - num3) * 6.283185307179586d / num) * 0.5d + c + b;
+ }
+
+ public static double EaseInBounce(double t, double b, double c, double d)
+ {
+ c -= b;
+ return c - EaseOutBounce(d - t, 0d, c, d) + b;
+ }
+
+ public static double EaseOutBounce(double t, double b, double c, double d)
+ {
+ c -= b;
+ if ((t /= d) < 0.36363636363636365d)
+ {
+ return c * (7.5625d * t * t) + b;
+ }
+ if (t < 0.7272727272727273d)
+ {
+ return c * (7.5625d * (t -= 0.5454545454545454d) * t + 0.75d) + b;
+ }
+ if (t < 0.9090909090909091d)
+ {
+ return c * (7.5625d * (t -= 0.8181818181818182d) * t + 0.9375d) + b;
+ }
+ return c * (7.5625d * (t -= 0.9545454545454546d) * t + 0.984375d) + b;
+ }
+
+ public static double EaseInOutBounce(double t, double b, double c, double d)
+ {
+ c -= b;
+ if (t < d / 2d)
+ {
+ return EaseInBounce(t * 2d, 0d, c, d) * 0.5d + b;
+ }
+ return EaseOutBounce(t * 2d - d, 0d, c, d) * 0.5d + c * 0.5d + b;
+ }
+
+ public static double Linear(double t, double b, double c, double d)
+ {
+ c -= b;
+ return t / d * c + b;
+ }
+
+ public static DoubleEasingAnimation[] Function { get; } = new DoubleEasingAnimation[]
+ {
+ EaseInOutQuad,
+ EaseInQuad,
+ EaseOutQuad,
+ EaseInCubic,
+ EaseOutCubic,
+ EaseInOutCubic,
+ EaseInQuart,
+ EaseOutQuart,
+ EaseInOutQuart,
+ EaseInQuint,
+ EaseOutQuint,
+ EaseInOutQuint,
+ EaseInSine,
+ EaseOutSine,
+ EaseInOutSine,
+ EaseInExpo,
+ EaseOutExpo,
+ EaseInOutExpo,
+ EaseInCirc,
+ EaseOutCirc,
+ EaseInOutCirc,
+ EaseInElastic,
+ EaseOutElastic,
+ EaseInOutElastic,
+ EaseInBounce,
+ EaseOutBounce,
+ EaseInOutBounce,
+ Linear,
+ };
+}
+
+public enum DoubleEasingAnimationType
+{
+ InOutQuad,
+ InQuad,
+ OutQuad,
+ InCubic,
+ OutCubic,
+ InOutCubic,
+ InQuart,
+ OutQuart,
+ InOutQuart,
+ InQuint,
+ OutQuint,
+ InOutQuint,
+ InSine,
+ OutSine,
+ InOutSine,
+ InExpo,
+ OutExpo,
+ InOutExpo,
+ InCirc,
+ OutCirc,
+ InOutCirc,
+ InElastic,
+ OutElastic,
+ InOutElastic,
+ InBounce,
+ OutBounce,
+ InOutBounce,
+ Linear,
+}
diff --git a/Build/MicaSetup/Design/Animations/ProgressAccumulator.cs b/Build/MicaSetup/Design/Animations/ProgressAccumulator.cs
new file mode 100644
index 00000000..bcdd76df
--- /dev/null
+++ b/Build/MicaSetup/Design/Animations/ProgressAccumulator.cs
@@ -0,0 +1,88 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MicaSetup.Controls.Animations;
+
+public partial class ProgressAccumulator : ObservableObject, IDisposable
+{
+ public Action? Handler;
+ public DoubleEasingAnimation? Animation;
+
+ [ObservableProperty]
+ private double duration = 3000d;
+
+ [ObservableProperty]
+ private double current = 0d;
+
+ [ObservableProperty]
+ private double from = 0d;
+
+ [ObservableProperty]
+ private double to = 100d;
+
+ private Task task = null!;
+ private bool isRunning = false;
+ private DateTime startTime = default;
+ private double durationTime = default;
+
+ public ProgressAccumulator(double from = 0d, double to = 100d, double duration = 3000d, Action handler = null!, DoubleEasingAnimation anime = null!)
+ {
+ Reset(from, to, duration, handler);
+ Animation = anime;
+ }
+
+ public void Dispose()
+ {
+ Reset();
+ }
+
+ public ProgressAccumulator Start()
+ {
+ isRunning = true;
+ startTime = DateTime.Now;
+ durationTime = default;
+ task = Task.Run(Handle);
+ return this;
+ }
+
+ public ProgressAccumulator Stop()
+ {
+ isRunning = false;
+ return this;
+ }
+
+ public ProgressAccumulator Reset(double from = 0d, double to = 100d, double duration = 3000d, Action handler = null!)
+ {
+ Stop();
+
+ Current = From = from;
+ To = to;
+ Duration = duration;
+ Handler = handler;
+ return this;
+ }
+
+ private void Handle()
+ {
+ while (isRunning)
+ {
+ if (!SpinWait.SpinUntil(() => !isRunning, 50))
+ {
+ Calc();
+ Handler?.Invoke(Current);
+ }
+ }
+ }
+
+ private double Calc()
+ {
+ if (durationTime <= Duration)
+ {
+ Current = (Animation ?? DoubleEasingAnimations.EaseOutCirc).Invoke(durationTime, From, To, Duration);
+ durationTime = DateTime.Now.Subtract(startTime).TotalMilliseconds;
+ }
+ return Current;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/BitmapIcon/BitmapIcon.cs b/Build/MicaSetup/Design/Controls/BitmapIcon/BitmapIcon.cs
new file mode 100644
index 00000000..8dead176
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/BitmapIcon/BitmapIcon.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace MicaSetup.Design.Controls;
+
+#pragma warning disable CS8618
+
+public class BitmapIcon : IconElement
+{
+ static BitmapIcon()
+ {
+ ForegroundProperty.OverrideMetadata(typeof(BitmapIcon), new FrameworkPropertyMetadata(OnForegroundChanged));
+ }
+
+ public BitmapIcon()
+ {
+ }
+
+ public static readonly DependencyProperty UriSourceProperty =
+ BitmapImage.UriSourceProperty.AddOwner(
+ typeof(BitmapIcon),
+ new FrameworkPropertyMetadata(OnUriSourceChanged));
+
+ public Uri UriSource
+ {
+ get => (Uri)GetValue(UriSourceProperty);
+ set => SetValue(UriSourceProperty, value);
+ }
+
+ private static void OnUriSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((BitmapIcon)d).ApplyUriSource();
+ }
+
+ public static readonly DependencyProperty ShowAsMonochromeProperty =
+ DependencyProperty.Register(
+ nameof(ShowAsMonochrome),
+ typeof(bool),
+ typeof(BitmapIcon),
+ new PropertyMetadata(true, OnShowAsMonochromeChanged));
+
+ public bool ShowAsMonochrome
+ {
+ get => (bool)GetValue(ShowAsMonochromeProperty);
+ set => SetValue(ShowAsMonochromeProperty, value);
+ }
+
+ private static void OnShowAsMonochromeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((BitmapIcon)d).ApplyShowAsMonochrome();
+ }
+
+ private protected override void InitializeChildren()
+ {
+ _image = new Image
+ {
+ Visibility = Visibility.Hidden
+ };
+
+ _opacityMask = new ImageBrush();
+ _foreground = new Rectangle
+ {
+ OpacityMask = _opacityMask
+ };
+
+ ApplyForeground();
+ ApplyUriSource();
+
+ Children.Add(_image);
+
+ ApplyShowAsMonochrome();
+ }
+
+ private protected override void OnShouldInheritForegroundFromVisualParentChanged()
+ {
+ ApplyForeground();
+ }
+
+ private protected override void OnVisualParentForegroundPropertyChanged(DependencyPropertyChangedEventArgs args)
+ {
+ if (ShouldInheritForegroundFromVisualParent)
+ {
+ ApplyForeground();
+ }
+ }
+
+ private static void OnForegroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((BitmapIcon)d).ApplyForeground();
+ }
+
+ private void ApplyForeground()
+ {
+ if (_foreground != null)
+ {
+ _foreground.Fill = ShouldInheritForegroundFromVisualParent ? VisualParentForeground : Foreground;
+ }
+ }
+
+ private void ApplyUriSource()
+ {
+ if (_image != null && _opacityMask != null)
+ {
+ var uriSource = UriSource;
+ if (uriSource != null)
+ {
+ var imageSource = new BitmapImage(uriSource);
+ _image.Source = imageSource;
+ _opacityMask.ImageSource = imageSource;
+ }
+ else
+ {
+ _image.ClearValue(Image.SourceProperty);
+ _opacityMask.ClearValue(ImageBrush.ImageSourceProperty);
+ }
+ }
+ }
+
+ private void ApplyShowAsMonochrome()
+ {
+ bool showAsMonochrome = ShowAsMonochrome;
+
+ if (_image != null)
+ {
+ _image.Visibility = showAsMonochrome ? Visibility.Hidden : Visibility.Visible;
+ }
+
+ if (_foreground != null)
+ {
+ if (showAsMonochrome)
+ {
+ if (!Children.Contains(_foreground))
+ {
+ Children.Add(_foreground);
+ }
+ }
+ else
+ {
+ Children.Remove(_foreground);
+ }
+ }
+ }
+
+ private Image _image;
+ private Rectangle _foreground;
+ private ImageBrush _opacityMask;
+}
diff --git a/Build/MicaSetup/Design/Controls/BitmapIcon/IconElement.cs b/Build/MicaSetup/Design/Controls/BitmapIcon/IconElement.cs
new file mode 100644
index 00000000..4489c829
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/BitmapIcon/IconElement.cs
@@ -0,0 +1,172 @@
+using System;
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+#pragma warning disable CS8618
+
+public abstract class IconElement : FrameworkElement
+{
+ private protected IconElement()
+ {
+ }
+
+ public static readonly DependencyProperty ForegroundProperty =
+ TextElement.ForegroundProperty.AddOwner(
+ typeof(IconElement),
+ new FrameworkPropertyMetadata(SystemColors.ControlTextBrush,
+ FrameworkPropertyMetadataOptions.Inherits,
+ OnForegroundPropertyChanged));
+
+ private static void OnForegroundPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
+ {
+ ((IconElement)sender).OnForegroundPropertyChanged(args);
+ }
+
+ private void OnForegroundPropertyChanged(DependencyPropertyChangedEventArgs args)
+ {
+ var baseValueSource = DependencyPropertyHelper.GetValueSource(this, args.Property).BaseValueSource;
+ _isForegroundDefaultOrInherited = baseValueSource <= BaseValueSource.Inherited;
+ UpdateShouldInheritForegroundFromVisualParent();
+ }
+
+ [Bindable(true), Category("Appearance")]
+ public Brush Foreground
+ {
+ get { return (Brush)GetValue(ForegroundProperty); }
+ set { SetValue(ForegroundProperty, value); }
+ }
+
+ private static readonly DependencyProperty VisualParentForegroundProperty =
+ DependencyProperty.Register(
+ nameof(VisualParentForeground),
+ typeof(Brush),
+ typeof(IconElement),
+ new PropertyMetadata(null, OnVisualParentForegroundPropertyChanged));
+
+ protected Brush VisualParentForeground
+ {
+ get => (Brush)GetValue(VisualParentForegroundProperty);
+ set => SetValue(VisualParentForegroundProperty, value);
+ }
+
+ private static void OnVisualParentForegroundPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
+ {
+ ((IconElement)sender).OnVisualParentForegroundPropertyChanged(args);
+ }
+
+ private protected virtual void OnVisualParentForegroundPropertyChanged(DependencyPropertyChangedEventArgs args)
+ {
+ }
+
+ protected bool ShouldInheritForegroundFromVisualParent
+ {
+ get => _shouldInheritForegroundFromVisualParent;
+ private set
+ {
+ if (_shouldInheritForegroundFromVisualParent != value)
+ {
+ _shouldInheritForegroundFromVisualParent = value;
+
+ if (_shouldInheritForegroundFromVisualParent)
+ {
+ SetBinding(VisualParentForegroundProperty,
+ new Binding
+ {
+ Path = new PropertyPath(TextElement.ForegroundProperty),
+ Source = VisualParent
+ });
+ }
+ else
+ {
+ ClearValue(VisualParentForegroundProperty);
+ }
+
+ OnShouldInheritForegroundFromVisualParentChanged();
+ }
+ }
+ }
+
+ private protected virtual void OnShouldInheritForegroundFromVisualParentChanged()
+ {
+ }
+
+ private void UpdateShouldInheritForegroundFromVisualParent()
+ {
+ ShouldInheritForegroundFromVisualParent =
+ _isForegroundDefaultOrInherited &&
+ Parent != null &&
+ VisualParent != null &&
+ Parent != VisualParent;
+ }
+
+ protected UIElementCollection Children
+ {
+ get
+ {
+ EnsureLayoutRoot();
+ return _layoutRoot.Children;
+ }
+ }
+
+ private protected abstract void InitializeChildren();
+
+ protected override int VisualChildrenCount => 1;
+
+ protected override Visual GetVisualChild(int index)
+ {
+ if (index == 0)
+ {
+ EnsureLayoutRoot();
+ return _layoutRoot;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(index));
+ }
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ EnsureLayoutRoot();
+ _layoutRoot.Measure(availableSize);
+ return _layoutRoot.DesiredSize;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ EnsureLayoutRoot();
+ _layoutRoot.Arrange(new Rect(new Point(), finalSize));
+ return finalSize;
+ }
+
+ protected override void OnVisualParentChanged(DependencyObject oldParent)
+ {
+ base.OnVisualParentChanged(oldParent);
+ UpdateShouldInheritForegroundFromVisualParent();
+ }
+
+ private void EnsureLayoutRoot()
+ {
+ if (_layoutRoot != null)
+ return;
+
+ _layoutRoot = new Grid
+ {
+ Background = Brushes.Transparent,
+ SnapsToDevicePixels = true,
+ };
+ InitializeChildren();
+
+ AddVisualChild(_layoutRoot);
+ }
+
+ private Grid _layoutRoot;
+ private bool _isForegroundDefaultOrInherited = true;
+ private bool _shouldInheritForegroundFromVisualParent;
+}
diff --git a/Build/MicaSetup/Design/Controls/Button/Button.xaml b/Build/MicaSetup/Design/Controls/Button/Button.xaml
new file mode 100644
index 00000000..8b75791c
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Button/Button.xaml
@@ -0,0 +1,129 @@
+
+
+ 11,5,11,6
+ 1
+ 0,0,8,0
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/CheckBox/CheckBox.xaml b/Build/MicaSetup/Design/Controls/CheckBox/CheckBox.xaml
new file mode 100644
index 00000000..2dede484
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/CheckBox/CheckBox.xaml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml
new file mode 100644
index 00000000..8a371e95
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml.cs b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml.cs
new file mode 100644
index 00000000..9e1f613b
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxDialog.xaml.cs
@@ -0,0 +1,109 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using System.Windows;
+
+namespace MicaSetup.Design.Controls;
+
+[INotifyPropertyChanged]
+public partial class MessageBoxDialog : Window
+{
+ [ObservableProperty]
+ private string message = null!;
+
+ [ObservableProperty]
+ protected bool okayVisiable = true;
+
+ [ObservableProperty]
+ protected bool yesVisiable = false;
+
+ [ObservableProperty]
+ protected bool noVisiable = false;
+
+ [ObservableProperty]
+ protected WindowDialogResult result = WindowDialogResult.None;
+
+ partial void OnResultChanged(WindowDialogResult value)
+ {
+ _ = value;
+ Close();
+ }
+
+ [ObservableProperty]
+ private string iconString = "\xe915";
+
+ [ObservableProperty]
+ private MessageBoxType type = MessageBoxType.Info;
+
+ partial void OnTypeChanged(MessageBoxType value)
+ {
+ IconString = value switch
+ {
+ MessageBoxType.Question => "\xe918",
+ MessageBoxType.Info or _ => "\xe915",
+ };
+
+ OkayVisiable = value switch
+ {
+ MessageBoxType.Question => false,
+ MessageBoxType.Info or _ => true,
+ };
+
+ YesVisiable = value switch
+ {
+ MessageBoxType.Question => true,
+ MessageBoxType.Info or _ => false,
+ };
+
+ NoVisiable = value switch
+ {
+ MessageBoxType.Question => true,
+ MessageBoxType.Info or _ => false,
+ };
+ }
+
+ public MessageBoxDialog()
+ {
+ DataContext = this;
+ InitializeComponent();
+ }
+
+ [RelayCommand]
+ private void Okay()
+ {
+ Result = WindowDialogResult.OK;
+ }
+
+ [RelayCommand]
+ private void Yes()
+ {
+ Result = WindowDialogResult.Yes;
+ }
+
+ [RelayCommand]
+ private void No()
+ {
+ Result = WindowDialogResult.No;
+ }
+
+ public WindowDialogResult ShowDialog(Window owner)
+ {
+ Owner = owner;
+ ShowDialog();
+ return Result;
+ }
+}
+
+public enum MessageBoxType
+{
+ Info,
+ Question,
+}
+
+public enum WindowDialogResult
+{
+ None = 0,
+ OK = 1,
+ Cancel = 2,
+ Yes = 6,
+ No = 7
+}
diff --git a/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxX.cs b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxX.cs
new file mode 100644
index 00000000..6acc9853
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/MessageBox/MessageBoxX.cs
@@ -0,0 +1,29 @@
+using MicaSetup.Helper;
+using System.Windows;
+
+namespace MicaSetup.Design.Controls;
+
+public static class MessageBoxX
+{
+ public static WindowDialogResult Info(DependencyObject dependencyObject, string message)
+ {
+ Window owner = (dependencyObject is Window win ? win : dependencyObject == null ? UIDispatcherHelper.MainWindow : Window.GetWindow(dependencyObject)) ?? UIDispatcherHelper.MainWindow;
+
+ return new MessageBoxDialog()
+ {
+ Type = MessageBoxType.Info,
+ Message = message,
+ }.ShowDialog(owner);
+ }
+
+ public static WindowDialogResult Question(DependencyObject dependencyObject, string message)
+ {
+ Window owner = (dependencyObject is Window win ? win : dependencyObject == null ? UIDispatcherHelper.MainWindow : Window.GetWindow(dependencyObject)) ?? UIDispatcherHelper.MainWindow;
+
+ return new MessageBoxDialog()
+ {
+ Type = MessageBoxType.Question,
+ Message = message,
+ }.ShowDialog(owner);
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBar.xaml b/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBar.xaml
new file mode 100644
index 00000000..6ef08d82
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBar.xaml
@@ -0,0 +1,102 @@
+
+
+
+
+ #73EBF3
+ #238EFA
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBarHelper.cs b/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBarHelper.cs
new file mode 100644
index 00000000..e71cfba6
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ProgressBar/ProgressBarHelper.cs
@@ -0,0 +1,31 @@
+using System.Windows;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+public class ProgressBarHelper
+{
+ public static Color GetForeground1(DependencyObject obj)
+ {
+ return (Color)obj.GetValue(Foreground1Property);
+ }
+
+ public static void SetForeground1(DependencyObject obj, Color value)
+ {
+ obj.SetValue(Foreground1Property, value);
+ }
+
+ public static readonly DependencyProperty Foreground1Property = DependencyProperty.RegisterAttached("Foreground1", typeof(Color), typeof(ProgressBarHelper), new((Color)ColorConverter.ConvertFromString("#73EBF3")));
+
+ public static Color GetForeground2(DependencyObject obj)
+ {
+ return (Color)obj.GetValue(Foreground2Property);
+ }
+
+ public static void SetForeground2(DependencyObject obj, Color value)
+ {
+ obj.SetValue(Foreground2Property, value);
+ }
+
+ public static readonly DependencyProperty Foreground2Property = DependencyProperty.RegisterAttached("Foreground2", typeof(Color), typeof(ProgressBarHelper), new((Color)ColorConverter.ConvertFromString("#238EFA")));
+}
diff --git a/Build/MicaSetup/Design/Controls/ProgressBar/SetupProgressBar.cs b/Build/MicaSetup/Design/Controls/ProgressBar/SetupProgressBar.cs
new file mode 100644
index 00000000..53161866
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ProgressBar/SetupProgressBar.cs
@@ -0,0 +1,51 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Shell;
+
+namespace MicaSetup.Design.Controls;
+
+public class SetupProgressBar : ProgressBar
+{
+ public bool SyncToWindowTaskbar
+ {
+ get => (bool)GetValue(LinkToWindowTaskbarProperty);
+ set => SetValue(LinkToWindowTaskbarProperty, value);
+ }
+
+ public static readonly DependencyProperty LinkToWindowTaskbarProperty = DependencyProperty.Register("SyncToWindowTaskbar", typeof(bool), typeof(SetupProgressBar), new PropertyMetadata(true));
+
+ public SetupProgressBar()
+ {
+ ValueChanged += OnValueChanged;
+ }
+
+ private void OnValueChanged(object sender, RoutedPropertyChangedEventArgs e)
+ {
+ if (!SyncToWindowTaskbar)
+ {
+ return;
+ }
+
+ if (this.FindTaskbar() is TaskbarItemInfo taskbar)
+ {
+ if (Value >= 0d)
+ {
+ taskbar.ProgressValue = Value / 100d;
+
+ if (Value >= 100d)
+ {
+ taskbar.ProgressState = TaskbarItemProgressState.None;
+ }
+ else
+ {
+ taskbar.ProgressState = TaskbarItemProgressState.Normal;
+ }
+ }
+ else
+ {
+ taskbar.ProgressValue = 0d;
+ taskbar.ProgressState = TaskbarItemProgressState.Indeterminate;
+ }
+ }
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/ProgressBar/TaskbarExtension.cs b/Build/MicaSetup/Design/Controls/ProgressBar/TaskbarExtension.cs
new file mode 100644
index 00000000..294ea01e
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ProgressBar/TaskbarExtension.cs
@@ -0,0 +1,27 @@
+using System.Windows;
+using System.Windows.Shell;
+
+namespace MicaSetup.Design.Controls;
+
+public static class TaskbarExtension
+{
+ public static TaskbarItemInfo FindTaskbar(this FrameworkElement owner)
+ {
+ if ((owner is Window ? owner as Window : Window.GetWindow(owner)) is Window win)
+ {
+ return win.TaskbarItemInfo ??= new();
+ }
+ return null!;
+ }
+
+ public static TaskbarItemInfo FindTaskbar(this Window owner)
+ {
+ return owner.TaskbarItemInfo ??= new();
+ }
+
+ public static void SetProgress(this TaskbarItemInfo taskbarItem, double value, TaskbarItemProgressState state = TaskbarItemProgressState.Normal)
+ {
+ taskbarItem.ProgressState = state;
+ taskbarItem.ProgressValue = value;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewer.xaml b/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewer.xaml
new file mode 100644
index 00000000..3d75133e
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewer.xaml
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewerHelper.cs b/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewerHelper.cs
new file mode 100644
index 00000000..f80ac076
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ScrollViewer/ScrollViewerHelper.cs
@@ -0,0 +1,110 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+public class ScrollViewerHelper
+{
+ public static Brush GetTrackBrush(DependencyObject obj)
+ {
+ return (Brush)obj.GetValue(TrackBrushProperty);
+ }
+
+ public static void SetTrackBrush(DependencyObject obj, Brush value)
+ {
+ obj.SetValue(TrackBrushProperty, value);
+ }
+
+ public static readonly DependencyProperty TrackBrushProperty = DependencyProperty.RegisterAttached("TrackBrush", typeof(Brush), typeof(ScrollViewerHelper));
+
+ public static Brush GetThumbBrush(DependencyObject obj)
+ {
+ return (Brush)obj.GetValue(ThumbBrushProperty);
+ }
+
+ public static void SetThumbBrush(DependencyObject obj, Brush value)
+ {
+ obj.SetValue(ThumbBrushProperty, value);
+ }
+
+ public static readonly DependencyProperty ThumbBrushProperty = DependencyProperty.RegisterAttached("ThumbBrush", typeof(Brush), typeof(ScrollViewerHelper));
+
+ public static CornerRadius GetScrollBarCornerRadius(DependencyObject obj)
+ {
+ return (CornerRadius)obj.GetValue(ScrollBarCornerRadiusProperty);
+ }
+
+ public static void SetScrollBarCornerRadius(DependencyObject obj, CornerRadius value)
+ {
+ obj.SetValue(ScrollBarCornerRadiusProperty, value);
+ }
+
+ public static readonly DependencyProperty ScrollBarCornerRadiusProperty = DependencyProperty.RegisterAttached("ScrollBarCornerRadius", typeof(CornerRadius), typeof(ScrollViewerHelper));
+
+ public static double GetScrollBarThickness(DependencyObject obj)
+ {
+ return (double)obj.GetValue(ScrollBarThicknessProperty);
+ }
+
+ public static void SetScrollBarThickness(DependencyObject obj, double value)
+ {
+ obj.SetValue(ScrollBarThicknessProperty, value);
+ }
+
+ public static readonly DependencyProperty ScrollBarThicknessProperty = DependencyProperty.RegisterAttached("ScrollBarThickness", typeof(double), typeof(ScrollViewerHelper));
+
+ internal static bool GetScrollViewerHook(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(ScrollViewerHookProperty);
+ }
+
+ internal static void SetScrollViewerHook(DependencyObject obj, bool value)
+ {
+ obj.SetValue(ScrollViewerHookProperty, value);
+ }
+
+ internal static readonly DependencyProperty ScrollViewerHookProperty = DependencyProperty.RegisterAttached("ScrollViewerHook", typeof(bool), typeof(ScrollViewerHelper), new PropertyMetadata(OnScrollViewerHookChanged));
+
+ private static void OnScrollViewerHookChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var scrollViewer = d as ScrollViewer;
+ scrollViewer!.PreviewMouseWheel += ScrollViewer_PreviewMouseWheel;
+ }
+
+ private static void ScrollViewer_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
+ {
+ var scrollViewer = sender as ScrollViewer;
+ var handle = true;
+
+ if (e.Delta > 0)
+ {
+ if (scrollViewer!.ComputedVerticalScrollBarVisibility == Visibility.Visible)
+ { handle = false; }
+ else if (scrollViewer.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
+ scrollViewer.LineLeft();
+ else if (scrollViewer.VerticalScrollBarVisibility != ScrollBarVisibility.Disabled)
+ { handle = false; }
+ else if (scrollViewer.HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled)
+ scrollViewer.LineLeft();
+ else
+ return;
+ }
+ else
+ {
+ if (scrollViewer!.ComputedVerticalScrollBarVisibility == Visibility.Visible)
+ { handle = false; }
+ else if (scrollViewer.ComputedHorizontalScrollBarVisibility == Visibility.Visible)
+ scrollViewer.LineRight();
+ else if (scrollViewer.VerticalScrollBarVisibility != ScrollBarVisibility.Disabled)
+ { handle = false; }
+ else if (scrollViewer.HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled)
+ scrollViewer.LineRight();
+ else
+ return;
+ }
+
+ if (handle)
+ e.Handled = true;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/ScrollViewer/SmoothScrollViewer.cs b/Build/MicaSetup/Design/Controls/ScrollViewer/SmoothScrollViewer.cs
new file mode 100644
index 00000000..eaab786d
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/ScrollViewer/SmoothScrollViewer.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace MicaSetup.Design.Controls;
+
+public class SmoothScrollViewer : ScrollViewer
+{
+ private double _totalVerticalOffset;
+ private double _totalHorizontalOffset;
+ private bool _isRunning;
+
+ public Orientation Orientation
+ {
+ get => (Orientation)GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+
+ public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(SmoothScrollViewer), new PropertyMetadata(Orientation.Vertical));
+
+ public bool CanMouseWheel
+ {
+ get => (bool)GetValue(CanMouseWheelProperty);
+ set => SetValue(CanMouseWheelProperty, value);
+ }
+
+ public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(nameof(CanMouseWheel), typeof(bool), typeof(SmoothScrollViewer), new PropertyMetadata(true));
+
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ if (ViewportHeight + VerticalOffset >= ExtentHeight && e.Delta <= 0)
+ {
+ return;
+ }
+
+ if (VerticalOffset == 0 && e.Delta >= 0)
+ {
+ return;
+ }
+
+ if (!CanMouseWheel)
+ {
+ return;
+ }
+
+ if (!IsInertiaEnabled)
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ base.OnMouseWheel(e);
+ }
+ else
+ {
+ _totalHorizontalOffset = HorizontalOffset;
+ CurrentHorizontalOffset = HorizontalOffset;
+ _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
+ CurrentHorizontalOffset = _totalHorizontalOffset;
+ }
+
+ return;
+ }
+
+ e.Handled = true;
+
+ if (Orientation == Orientation.Vertical)
+ {
+ if (!_isRunning)
+ {
+ _totalVerticalOffset = VerticalOffset;
+ CurrentVerticalOffset = VerticalOffset;
+ }
+
+ _totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
+ ScrollToVerticalOffsetWithAnimation(_totalVerticalOffset);
+ }
+ else
+ {
+ if (!_isRunning)
+ {
+ _totalHorizontalOffset = HorizontalOffset;
+ CurrentHorizontalOffset = HorizontalOffset;
+ }
+
+ _totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
+ ScrollToHorizontalOffsetWithAnimation(_totalHorizontalOffset);
+ }
+ }
+
+ internal void ScrollToTopInternal(double milliseconds = 500)
+ {
+ if (!_isRunning)
+ {
+ _totalVerticalOffset = VerticalOffset;
+ CurrentVerticalOffset = VerticalOffset;
+ }
+
+ ScrollToVerticalOffsetWithAnimation(0, milliseconds);
+ }
+
+ public void ScrollToVerticalOffsetWithAnimation(double offset, double milliseconds = 500)
+ {
+ DoubleAnimation animation = AnimationHelper.CreateAnimation(offset, milliseconds);
+ animation.EasingFunction = new CubicEase
+ {
+ EasingMode = EasingMode.EaseOut
+ };
+ animation.FillBehavior = FillBehavior.Stop;
+ animation.Completed += (s, e1) =>
+ {
+ CurrentVerticalOffset = offset;
+ _isRunning = false;
+ };
+ _isRunning = true;
+
+ BeginAnimation(CurrentVerticalOffsetProperty, animation, HandoffBehavior.Compose);
+ }
+
+ public void ScrollToHorizontalOffsetWithAnimation(double offset, double milliseconds = 500)
+ {
+ DoubleAnimation animation = AnimationHelper.CreateAnimation(offset, milliseconds);
+ animation.EasingFunction = new CubicEase
+ {
+ EasingMode = EasingMode.EaseOut
+ };
+ animation.FillBehavior = FillBehavior.Stop;
+ animation.Completed += (s, e1) =>
+ {
+ CurrentHorizontalOffset = offset;
+ _isRunning = false;
+ };
+ _isRunning = true;
+
+ BeginAnimation(CurrentHorizontalOffsetProperty, animation, HandoffBehavior.Compose);
+ }
+
+ protected override HitTestResult? HitTestCore(PointHitTestParameters hitTestParameters)
+ {
+ return IsPenetrating ? null : base.HitTestCore(hitTestParameters);
+ }
+
+ public static void SetIsInertiaEnabled(DependencyObject element, bool value)
+ {
+ element.SetValue(IsInertiaEnabledProperty, value);
+ }
+
+ public static bool GetIsInertiaEnabled(DependencyObject element)
+ {
+ return (bool)element.GetValue(IsInertiaEnabledProperty);
+ }
+
+ public bool IsInertiaEnabled
+ {
+ get => (bool)GetValue(IsInertiaEnabledProperty);
+ set => SetValue(IsInertiaEnabledProperty, value);
+ }
+
+ public static readonly DependencyProperty IsInertiaEnabledProperty = DependencyProperty.RegisterAttached(nameof(IsInertiaEnabled), typeof(bool), typeof(SmoothScrollViewer), new PropertyMetadata(true));
+
+ public bool IsPenetrating
+ {
+ get => (bool)GetValue(IsPenetratingProperty);
+ set => SetValue(IsPenetratingProperty, value);
+ }
+
+ public static void SetIsPenetrating(DependencyObject element, bool value)
+ {
+ element.SetValue(IsPenetratingProperty, value);
+ }
+
+ public static bool GetIsPenetrating(DependencyObject element)
+ {
+ return (bool)element.GetValue(IsPenetratingProperty);
+ }
+
+ public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(nameof(IsPenetrating), typeof(bool), typeof(SmoothScrollViewer), new PropertyMetadata(false));
+
+ internal double CurrentVerticalOffset
+ {
+ get => (double)GetValue(CurrentVerticalOffsetProperty);
+ set => SetValue(CurrentVerticalOffsetProperty, value);
+ }
+
+ internal static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(nameof(CurrentVerticalOffset), typeof(double), typeof(SmoothScrollViewer), new PropertyMetadata(0d, OnCurrentVerticalOffsetChanged));
+
+ private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is SmoothScrollViewer ctl && e.NewValue is double v)
+ {
+ ctl.ScrollToVerticalOffset(v);
+ }
+ }
+
+ internal double CurrentHorizontalOffset
+ {
+ get => (double)GetValue(CurrentHorizontalOffsetProperty);
+ set => SetValue(CurrentHorizontalOffsetProperty, value);
+ }
+
+ internal static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(nameof(CurrentHorizontalOffset), typeof(double), typeof(SmoothScrollViewer), new PropertyMetadata(0d, OnCurrentHorizontalOffsetChanged));
+
+ private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is SmoothScrollViewer ctl && e.NewValue is double v)
+ {
+ ctl.ScrollToHorizontalOffset(v);
+ }
+ }
+}
+
+file sealed class AnimationHelper
+{
+ public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200)
+ {
+ return new(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds)))
+ {
+ EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut }
+ };
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Selection.cs b/Build/MicaSetup/Design/Controls/Selection.cs
new file mode 100644
index 00000000..d7118d5b
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Selection.cs
@@ -0,0 +1,104 @@
+namespace MicaSetup.Design.Controls;
+
+public static class Selection
+{
+ public const string CeliakeyboardEnassociate = "\xe900";
+ public const string CeliakeyboardMenuLanguage = "\xe901";
+ public const string ContactsDelete = "\xe902";
+ public const string DeviceMateBook = "\xe903";
+ public const string FilesNameDrive = "\xe904";
+ public const string GalleryCollage = "\xe905";
+ public const string GalleryCreate = "\xe906";
+ public const string GalleryMaterialSelectCheckbo = "\xe907";
+ public const string GalleryMoveIn10 = "\xe908";
+ public const string GalleryMoveOut = "\xe909";
+ public const string GalleryPhotoEditMore = "\xe90a";
+ public const string GalleryRectify = "\xe90b";
+ public const string GalleryRename = "\xe90c";
+ public const string GallerySet = "\xe90d";
+ public const string GallerySortOrder = "\xe90e";
+ public const string GallerySortReverse = "\xe90f";
+ public const string MessageForwarding = "\xe910";
+ public const string MessageTheme = "\xe911";
+ public const string PowerOff = "\xe912";
+ public const string PublicAdd = "\xe913";
+ public const string PublicAddFilled = "\xe914";
+ public const string PublicAddNorm = "\xe915";
+ public const string PublicApp = "\xe916";
+ public const string PublicArrowLeft = "\xe917";
+ public const string PublicArrowRight = "\xe918";
+ public const string PublicArrowUp0 = "\xe919";
+ public const string PublicArrowUp1 = "\xe91a";
+ public const string PublicBack = "\xe91b";
+ public const string PublicBackToTop = "\xe91c";
+ public const string PublicBrightness = "\xe91d";
+ public const string PublicCancel = "\xe91e";
+ public const string PublicCancelFilled = "\xe91f";
+ public const string PublicClose = "\xe920";
+ public const string PublicCloudDownload = "\xe921";
+ public const string PublicCloudUpload = "\xe922";
+ public const string PublicConnection = "\xe923";
+ public const string PublicContacts = "\xe924";
+ public const string PublicCopy = "\xe925";
+ public const string PublicDelete = "\xe926";
+ public const string PublicDoNotDisturb = "\xe927";
+ public const string PublicDownload = "\xe928";
+ public const string PublicEdit = "\xe929";
+ public const string PublicEmail = "\xe92a";
+ public const string PublicEmailSend = "\xe92b";
+ public const string PublicEnlarge = "\xe92c";
+ public const string PublicFail = "\xe92d";
+ public const string PublicFolder = "\xe92e";
+ public const string PublicForbid = "\xe92f";
+ public const string PublicHelp = "\xe930";
+ public const string PublicHighLights = "\xe931";
+ public const string PublicHome = "\xe932";
+ public const string PublicInputSearch = "\xe933";
+ public const string PublicKeyboard = "\xe934";
+ public const string PublicListCycle = "\xe935";
+ public const string PubliClock = "\xe936";
+ public const string PublicMessage = "\xe937";
+ public const string PublicMobileData = "\xe938";
+ public const string PublicMove = "\xe939";
+ public const string PublicNavigation = "\xe93a";
+ public const string PublicOk = "\xe93b";
+ public const string PublicOkFilled = "\xe93c";
+ public const string PublicPicture = "\xe93d";
+ public const string PublicPrinter = "\xe93e";
+ public const string PublicQuit = "\xe93f";
+ public const string PublicRandom = "\xe940";
+ public const string PublicReduce = "\xe941";
+ public const string PublicRefresh = "\xe942";
+ public const string PublicRemove = "\xe943";
+ public const string PublicReset = "\xe944";
+ public const string PublicRing = "\xe945";
+ public const string PublicRingOff = "\xe946";
+ public const string PublicRotate = "\xe947";
+ public const string PublicSave = "\xe948";
+ public const string PublicScan = "\xe949";
+ public const string PublicSearch = "\xe94a";
+ public const string PublicSecurity = "\xe94b";
+ public const string PublicSend = "\xe94c";
+ public const string PublicSettings = "\xe94d";
+ public const string PublicShare = "\xe94e";
+ public const string PublicSift = "\xe94f";
+ public const string PublicSound = "\xe950";
+ public const string PublicText = "\xe951";
+ public const string PublicThemes = "\xe952";
+ public const string PublicThumbsup = "\xe953";
+ public const string PublicTodo = "\xe954";
+ public const string PublicTopping = "\xe955";
+ public const string PublicUnlock = "\xe956";
+ public const string PublicUpgrade = "\xe957";
+ public const string PublicUpload = "\xe958";
+ public const string PublicVolumeDown = "\xe959";
+ public const string PublicWirelessProjection = "\xe95b";
+ public const string PublicWorldClock = "\xe95c";
+ public const string QuickReply = "\xe95d";
+ public const string Reboot = "\xe95e";
+ public const string SearchThings = "\xe95f";
+ public const string ContactSencryptionCalls = "\xe960";
+ public const string Circle = "\xe961";
+ public const string Squircle = "\xe962";
+ public const string IcoMoon = "\xe95a";
+}
diff --git a/Build/MicaSetup/Design/Controls/Shell/Routing.cs b/Build/MicaSetup/Design/Controls/Shell/Routing.cs
new file mode 100644
index 00000000..3ac4f1b1
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Shell/Routing.cs
@@ -0,0 +1,112 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows;
+
+namespace MicaSetup.Design.Controls;
+
+public static class Routing
+{
+ public static ServiceProvider Provider { get; internal set; } = null!;
+ public static WeakReference Shell { get; internal set; } = null!;
+
+ public static void RegisterRoute()
+ {
+ ServiceCollection serviceCollection = new();
+
+ foreach (var pageItem in ShellPageSetting.PageDict)
+ {
+ serviceCollection.Register(pageItem.Key, pageItem.Value);
+ }
+ Provider = serviceCollection.BuildServiceProvider();
+ }
+
+ public static FrameworkElement ResolveRoute(string route)
+ {
+ if (string.IsNullOrEmpty(route))
+ {
+ return null!;
+ }
+ return Provider?.Resolve(route)!;
+ }
+
+ public static void GoTo(string route)
+ {
+ if (Shell != null)
+ {
+ if (Shell.TryGetTarget(out ShellControl shell))
+ {
+ OnGoTo(route);
+ shell.Content = ResolveRoute(route);
+ shell.Route = route;
+ }
+ }
+ }
+
+ public static void GoToNext()
+ {
+ if (Shell != null)
+ {
+ if (Shell.TryGetTarget(out ShellControl shell))
+ {
+ if (ShellPageSetting.PageDict.ContainsKey(shell.Route))
+ {
+ bool found = false;
+ foreach (var item in ShellPageSetting.PageDict)
+ {
+ if (found)
+ {
+ OnGoTo(item.Key);
+ shell.Content = ResolveRoute(item.Key);
+ shell.Route = item.Key;
+ break;
+ }
+ if (item.Key == shell.Route)
+ {
+ found = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private static void OnGoTo(string route)
+ {
+ }
+}
+
+file class RoutingServiceInfo
+{
+ public string Name { get; set; }
+ public Type Type { get; set; }
+
+ public RoutingServiceInfo(string name, Type type)
+ {
+ Name = name;
+ Type = type;
+ }
+}
+
+file static class RoutingExtension
+{
+ public static IServiceCollection Register(this IServiceCollection services, string name, Type type)
+ {
+ services.AddSingleton(type);
+ services.AddSingleton(new RoutingServiceInfo(name, type));
+ return services;
+ }
+
+ public static T Resolve(this IServiceProvider serviceProvider, string name)
+ {
+ var serviceInfo = serviceProvider.GetRequiredService>()
+ .FirstOrDefault(x => x.Name == name);
+
+ if (serviceInfo == null)
+ {
+ throw new InvalidOperationException($"Service '{name}' not found");
+ }
+ return (T)serviceProvider.GetRequiredService(serviceInfo.Type);
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Shell/ShellControl.cs b/Build/MicaSetup/Design/Controls/Shell/ShellControl.cs
new file mode 100644
index 00000000..b0508786
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Shell/ShellControl.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MicaSetup.Design.Controls;
+
+public class ShellControl : ContentControl
+{
+ public string Route
+ {
+ get => (string)GetValue(RouteProperty);
+ set => SetCurrentValue(RouteProperty, value);
+ }
+
+ public static readonly DependencyProperty RouteProperty = DependencyProperty.Register(nameof(Route), typeof(string), typeof(ShellControl), new(string.Empty));
+
+ public ShellControl()
+ {
+ Routing.Shell = new WeakReference(this);
+ FocusVisualStyle = null!;
+ Loaded += (_, _) => Routing.GoTo(Route);
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Shell/ShellPageSetting.cs b/Build/MicaSetup/Design/Controls/Shell/ShellPageSetting.cs
new file mode 100644
index 00000000..cbd6b775
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Shell/ShellPageSetting.cs
@@ -0,0 +1,9 @@
+using System;
+using System.Collections.Generic;
+
+namespace MicaSetup.Design.Controls;
+
+public class ShellPageSetting
+{
+ public static Dictionary PageDict = new();
+}
diff --git a/Build/MicaSetup/Design/Controls/TextBox/TextBox.cs b/Build/MicaSetup/Design/Controls/TextBox/TextBox.cs
new file mode 100644
index 00000000..9003c1cd
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/TextBox/TextBox.cs
@@ -0,0 +1,102 @@
+using CommunityToolkit.Mvvm.Input;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace MicaSetup.Design.Controls;
+
+public class TextBoxEx : TextBox
+{
+ public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
+ nameof(PlaceholderText),
+ typeof(string),
+ typeof(TextBoxEx),
+ new PropertyMetadata(string.Empty)
+ );
+
+ public static readonly DependencyProperty PlaceholderEnabledProperty = DependencyProperty.Register(
+ nameof(PlaceholderEnabled),
+ typeof(bool),
+ typeof(TextBoxEx),
+ new PropertyMetadata(true)
+ );
+
+ public static readonly DependencyProperty IsTextSelectionEnabledProperty = DependencyProperty.Register(
+ nameof(IsTextSelectionEnabled),
+ typeof(bool),
+ typeof(TextBoxEx),
+ new PropertyMetadata(false)
+ );
+
+ public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
+ nameof(TemplateButtonCommand),
+ typeof(IRelayCommand),
+ typeof(TextBoxEx),
+ new PropertyMetadata(null)
+ );
+
+ public string PlaceholderText
+ {
+ get => (string)GetValue(PlaceholderTextProperty);
+ set => SetValue(PlaceholderTextProperty, value);
+ }
+
+ public bool PlaceholderEnabled
+ {
+ get => (bool)GetValue(PlaceholderEnabledProperty);
+ set => SetValue(PlaceholderEnabledProperty, value);
+ }
+
+ ///
+ /// TODO
+ ///
+ public bool IsTextSelectionEnabled
+ {
+ get => (bool)GetValue(IsTextSelectionEnabledProperty);
+ set => SetValue(IsTextSelectionEnabledProperty, value);
+ }
+
+ public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
+
+ public TextBoxEx()
+ {
+ SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick));
+ }
+
+ protected override void OnTextChanged(TextChangedEventArgs e)
+ {
+ base.OnTextChanged(e);
+
+ if (PlaceholderEnabled && Text.Length > 0)
+ PlaceholderEnabled = false;
+
+ if (!PlaceholderEnabled && Text.Length < 1)
+ PlaceholderEnabled = true;
+ }
+
+ protected override void OnGotFocus(RoutedEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ CaretIndex = Text.Length;
+ }
+
+ protected override void OnLostFocus(RoutedEventArgs e)
+ {
+ base.OnLostFocus(e);
+ }
+
+ protected virtual void OnClearButtonClick()
+ {
+ if (Text.Length > 0)
+ {
+ Text = string.Empty;
+ }
+ }
+
+ protected virtual void OnTemplateButtonClick(string? parameter)
+ {
+ Debug.WriteLine($"INFO: {typeof(TextBoxEx)} button clicked");
+ OnClearButtonClick();
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/TextBox/TextBox.xaml b/Build/MicaSetup/Design/Controls/TextBox/TextBox.xaml
new file mode 100644
index 00000000..4588b1e8
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/TextBox/TextBox.xaml
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+ 1,1,1,0
+ 0,0,0,1
+ 10,0,0,0
+ 0,0,10,0
+ 0,0,4,0
+ 0,0,0,0
+ 24
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/Window/ApplicationThemeManager.cs b/Build/MicaSetup/Design/Controls/Window/ApplicationThemeManager.cs
new file mode 100644
index 00000000..d9a4ff6d
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/ApplicationThemeManager.cs
@@ -0,0 +1,22 @@
+using MicaSetup.Natives;
+using Microsoft.Win32;
+
+namespace MicaSetup.Design.Controls;
+
+public class ApplicationThemeManager
+{
+ public static ApplicationTheme GetAppTheme()
+ {
+ using RegistryKey? key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
+ object? registryValueObject = key?.GetValue("AppsUseLightTheme");
+
+ if (registryValueObject == null)
+ {
+ return ApplicationTheme.Light;
+ }
+
+ var registryValue = (int)registryValueObject;
+
+ return registryValue > 0 ? ApplicationTheme.Light : ApplicationTheme.Dark;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowClearOwnerOnClosingBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowClearOwnerOnClosingBehavior.cs
new file mode 100644
index 00000000..d9c050ff
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowClearOwnerOnClosingBehavior.cs
@@ -0,0 +1,44 @@
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Windows;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowClearOwnerOnClosingBehavior : Behavior
+{
+ protected override void OnAttached()
+ {
+ AssociatedObject.Closing += OnWindowClosing;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.Closing -= OnWindowClosing;
+ base.OnDetaching();
+ }
+
+ private void OnWindowClosing(object? sender, CancelEventArgs e)
+ {
+ if (!e.Cancel)
+ {
+ try
+ {
+ if (AssociatedObject.Owner != null)
+ {
+ AssociatedObject.Owner = null!;
+ }
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"Don't attach this {nameof(WindowClearOwnerOnClosingBehavior)} for {nameof(Window)} called from {nameof(Window.ShowDialog)}.", ex);
+ if (Debugger.IsAttached)
+ {
+ Debugger.Break();
+ }
+ }
+ }
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowDragMoveBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowDragMoveBehavior.cs
new file mode 100644
index 00000000..e4d82372
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowDragMoveBehavior.cs
@@ -0,0 +1,28 @@
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.Windows;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowDragMoveBehavior : Behavior
+{
+ protected override void OnAttached()
+ {
+ AssociatedObject.MouseLeftButtonDown += MouseLeftButtonDown;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.MouseLeftButtonDown -= MouseLeftButtonDown;
+ base.OnDetaching();
+ }
+
+ private void MouseLeftButtonDown(object sender, EventArgs e)
+ {
+ if (sender is UIElement ui)
+ {
+ Window.GetWindow(ui)?.DragMove();
+ }
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowHideTitleButtonBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowHideTitleButtonBehavior.cs
new file mode 100644
index 00000000..b68882e3
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowHideTitleButtonBehavior.cs
@@ -0,0 +1,27 @@
+using MicaSetup.Natives;
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.Windows;
+using System.Windows.Interop;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowHideTitleButtonBehavior : Behavior
+{
+ protected override void OnAttached()
+ {
+ AssociatedObject.SourceInitialized += OnSourceInitialized;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.Loaded -= OnSourceInitialized;
+ base.OnDetaching();
+ }
+
+ private void OnSourceInitialized(object? sender, EventArgs e)
+ {
+ NativeMethods.HideAllWindowButton(new WindowInteropHelper(AssociatedObject).Handle);
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowRestorePathBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowRestorePathBehavior.cs
new file mode 100644
index 00000000..2c2ba05e
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowRestorePathBehavior.cs
@@ -0,0 +1,49 @@
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Shapes;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowRestorePathBehavior : Behavior
+{
+ private WindowState windowStatePrev = WindowState.Normal;
+
+ protected override void OnAttached()
+ {
+ AssociatedObject.Loaded += Loaded;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.Loaded -= Loaded;
+ base.OnDetaching();
+ }
+
+ private void Loaded(object sender, EventArgs e)
+ {
+ if (sender is UIElement maximizeButtonContent && Window.GetWindow(maximizeButtonContent) is Window window)
+ {
+ window.SizeChanged += (s, e) =>
+ {
+ if (windowStatePrev != window.WindowState)
+ {
+ if (maximizeButtonContent is Path path)
+ {
+ if (window.WindowState == WindowState.Maximized)
+ {
+ path.Data = Geometry.Parse("M0 0.2 L0.8 0.2 M0 01 L0.8 1 M0.8 1 L0.8 0.2 M0 0.2 L0 1 M0.3 0 L0.95 0 M01 0.05 L1 0.7");
+ }
+ else
+ {
+ path.Data = Geometry.Parse("M0.025 0 L0.975 0 M0.025 1 L0.975 1 M1 0.975 L1 0.025 M0 0.025 L0 0.975");
+ }
+ }
+ windowStatePrev = window.WindowState;
+ }
+ };
+ }
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleHeaderBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleHeaderBehavior.cs
new file mode 100644
index 00000000..15313a50
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleHeaderBehavior.cs
@@ -0,0 +1,83 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.Windows;
+using System.Windows.Input;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowTitleHeaderBehavior : Behavior
+{
+ protected override void OnAttached()
+ {
+ AssociatedObject.Loaded += Loaded;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.Loaded -= Loaded;
+ base.OnDetaching();
+ }
+
+ private void Loaded(object sender, EventArgs e)
+ {
+ AssociatedObject.RegisterAsTitleHeader();
+ }
+}
+
+file static class RegisterAsTitleHeaderBehaviorExtension
+{
+ public static void RegisterAsTitleHeader(this UIElement self)
+ {
+ self.MouseLeftButtonDown += (s, e) =>
+ {
+ if (s is UIElement titleHeader && Window.GetWindow(titleHeader) is Window window)
+ {
+ if (e.ClickCount == 2)
+ {
+ if (window.ResizeMode == ResizeMode.CanResize || window.ResizeMode == ResizeMode.CanResizeWithGrip)
+ {
+ switch (window.WindowState)
+ {
+ case WindowState.Normal:
+ WindowSystemCommands.MaximizeWindow(window);
+ break;
+
+ case WindowState.Maximized:
+ WindowSystemCommands.RestoreWindow(window);
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ WindowSystemCommands.FastRestoreWindow(window);
+ }
+ }
+ }
+ };
+
+ self.MouseRightButtonDown += (s, e) =>
+ {
+ if (s is UIElement titleHeader && Window.GetWindow(titleHeader) is Window window)
+ {
+ if (window.Cursor != null && window.Cursor.ToString() == Cursors.None.ToString())
+ {
+ return;
+ }
+ if (e.RightButton == MouseButtonState.Pressed)
+ {
+ if (User32.GetCursorPos(out POINT pt))
+ {
+ e.Handled = true;
+ SystemCommands.ShowSystemMenu(window, new Point(DpiHelper.CalcDPiX(pt.X), DpiHelper.CalcDPiY(pt.Y)));
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleIconBehavior.cs b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleIconBehavior.cs
new file mode 100644
index 00000000..c414e691
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/Behaviors/WindowTitleIconBehavior.cs
@@ -0,0 +1,48 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using Microsoft.Xaml.Behaviors;
+using System;
+using System.Windows;
+using System.Windows.Input;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowTitleIconBehavior : Behavior
+{
+ protected override void OnAttached()
+ {
+ AssociatedObject.Loaded += Loaded;
+ base.OnAttached();
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.Loaded -= Loaded;
+ base.OnDetaching();
+ }
+
+ private void Loaded(object sender, EventArgs e)
+ {
+ AssociatedObject.RegisterAsTitleIcon();
+ }
+}
+
+public static class RegisterAsTitleIconBehaviorExtension
+{
+ public static void RegisterAsTitleIcon(this UIElement self)
+ {
+ self.MouseLeftButtonDown += (s, e) =>
+ {
+ if (s is UIElement titleHeader && Window.GetWindow(titleHeader) is Window window)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ if (User32.GetCursorPos(out POINT pt))
+ {
+ SystemCommands.ShowSystemMenu(window, new Point(DpiHelper.CalcDPiX(pt.X), DpiHelper.CalcDPiY(pt.Y)));
+ }
+ }
+ }
+ };
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/SnapLayout.cs b/Build/MicaSetup/Design/Controls/Window/SnapLayout.cs
new file mode 100644
index 00000000..4ece450c
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/SnapLayout.cs
@@ -0,0 +1,174 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using Microsoft.Win32;
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Automation.Peers;
+using System.Windows.Automation.Provider;
+using System.Windows.Controls;
+using System.Windows.Media;
+using Point = System.Windows.Point;
+using Size = System.Windows.Size;
+
+namespace MicaSetup.Design.Controls;
+
+///
+/// https://learn.microsoft.com/zh-cn/windows/apps/desktop/modernize/apply-snap-layout-menu
+///
+public sealed class SnapLayout
+{
+ public bool IsRegistered { get; private set; } = false;
+ public bool IsSupported => Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version.Build > 20000;
+ public static bool IsEnabled { get; } = IsSnapLayoutEnabled();
+
+ private Button? button;
+ private bool isButtonFocused;
+
+ private Window? window;
+
+ public void Register(Button button)
+ {
+ isButtonFocused = false;
+ this.button = button;
+ IsRegistered = true;
+ }
+
+ public nint WndProc(nint hWnd, int msg, nint wParam, nint lParam, ref bool handled)
+ {
+ if (!IsRegistered) return 0;
+ switch ((User32.WindowMessage)msg)
+ {
+ case User32.WindowMessage.WM_NCLBUTTONDOWN:
+ if (IsOverButton(lParam))
+ {
+ window ??= Window.GetWindow(button);
+ if (window != null)
+ {
+ WindowSystemCommands.MaximizeOrRestoreWindow(window);
+ }
+ else
+ {
+ if (new ButtonAutomationPeer(button).GetPattern(PatternInterface.Invoke) is IInvokeProvider invokeProv)
+ {
+ invokeProv?.Invoke();
+ }
+ }
+ handled = true;
+ }
+ break;
+
+ case User32.WindowMessage.WM_NCMOUSELEAVE:
+ DefocusButton();
+ break;
+
+ case User32.WindowMessage.WM_NCHITTEST:
+ if (IsEnabled)
+ {
+ if (IsOverButton(lParam))
+ {
+ FocusButton();
+ handled = true;
+ }
+ else
+ {
+ DefocusButton();
+ }
+ }
+ return (int)User32.HitTestValues.HTMAXBUTTON;
+
+ case User32.WindowMessage.WM_SETCURSOR:
+ if (isButtonFocused)
+ {
+ handled = true;
+ }
+ break;
+
+ default:
+ handled = false;
+ break;
+ }
+ return (int)User32.HitTestValues.HTCLIENT;
+ }
+
+ private void FocusButton()
+ {
+ if (isButtonFocused) return;
+
+ if (button != null)
+ {
+ button.Background = (Brush)Application.Current.FindResource("ButtonBackgroundPointerOver");
+ button.BorderBrush = (Brush)Application.Current.FindResource("ButtonBorderBrushPointerOver");
+ button.Foreground = (Brush)Application.Current.FindResource("ButtonForegroundPointerOver");
+ }
+ isButtonFocused = true;
+ }
+
+ private void DefocusButton()
+ {
+ if (!isButtonFocused) return;
+
+ button?.ClearValue(Control.BackgroundProperty);
+ button?.ClearValue(Control.BorderBrushProperty);
+ button?.ClearValue(Control.ForegroundProperty);
+ isButtonFocused = false;
+ }
+
+ private bool IsOverButton(nint lParam)
+ {
+ if (button == null)
+ {
+ return false;
+ }
+
+ try
+ {
+ int x = Macro.GET_X_LPARAM(lParam);
+ int y = Macro.GET_Y_LPARAM(lParam);
+
+ Rect rect = new(button.PointToScreen(new Point()), new Size(DpiHelper.CalcDPIX(button.ActualWidth), DpiHelper.CalcDPIY(button.ActualHeight)));
+
+ if (rect.Contains(new Point(x, y)))
+ {
+ return true;
+ }
+ }
+ catch (OverflowException)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ private static bool IsSnapLayoutEnabled()
+ {
+ try
+ {
+ using RegistryKey? key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced", true);
+ object? registryValueObject = key?.GetValue("EnableSnapAssistFlyout");
+
+ if (registryValueObject == null)
+ {
+ return true;
+ }
+ int registryValue = (int)registryValueObject;
+ return registryValue > 0;
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return true;
+ }
+}
+
+file static class Macro
+{
+ public static ushort LOWORD(nint value) => (ushort)((long)value & 0xFFFF);
+
+ public static ushort HIWORD(nint value) => (ushort)((((long)value) >> 0x10) & 0xFFFF);
+
+ public static int GET_X_LPARAM(nint wParam) => LOWORD(wParam);
+
+ public static int GET_Y_LPARAM(nint wParam) => HIWORD(wParam);
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowBackdrop.cs b/Build/MicaSetup/Design/Controls/Window/WindowBackdrop.cs
new file mode 100644
index 00000000..a2330363
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowBackdrop.cs
@@ -0,0 +1,187 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using System;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+public static class WindowBackdrop
+{
+ private static bool IsSupported(WindowBackdropType backdropType)
+ {
+ return backdropType switch
+ {
+ WindowBackdropType.Auto => OsVersionHelper.IsWindows11_22523,
+ WindowBackdropType.Tabbed => OsVersionHelper.IsWindows11_22523,
+ WindowBackdropType.Mica => OsVersionHelper.IsWindows11_OrGreater,
+ WindowBackdropType.Acrylic => OsVersionHelper.IsWindows7_OrGreater,
+ WindowBackdropType.None => OsVersionHelper.IsWindows11_OrGreater,
+ _ => false
+ };
+ }
+
+ public static bool ApplyBackdrop(Window window, WindowBackdropType backdropType = WindowBackdropType.Mica, ApplicationTheme theme = ApplicationTheme.Unknown)
+ {
+ if (!IsSupported(backdropType))
+ {
+ return false;
+ }
+
+ if (window is null)
+ {
+ return false;
+ }
+
+ if (window.IsLoaded)
+ {
+ nint windowHandle = new WindowInteropHelper(window).Handle;
+
+ if (windowHandle == 0x00)
+ {
+ return false;
+ }
+
+ return ApplyBackdrop(windowHandle, backdropType, theme);
+ }
+
+ window.Loaded += (sender, _) =>
+ {
+ nint windowHandle =
+ new WindowInteropHelper(sender as Window ?? null)?.Handle
+ ?? IntPtr.Zero;
+
+ if (windowHandle == 0x00)
+ return;
+
+ ApplyBackdrop(windowHandle, backdropType, theme);
+ };
+
+ return true;
+ }
+
+ public static bool ApplyBackdrop(nint hWnd, WindowBackdropType backdropType = WindowBackdropType.Mica, ApplicationTheme theme = ApplicationTheme.Unknown)
+ {
+ if (!IsSupported(backdropType))
+ {
+ return false;
+ }
+
+ if (hWnd == 0x00 || !User32.IsWindow(hWnd))
+ {
+ return false;
+ }
+
+ if (backdropType != WindowBackdropType.Auto)
+ {
+ WindowDarkMode.RemoveBackground(hWnd);
+ }
+
+ switch (theme)
+ {
+ case ApplicationTheme.Unknown:
+ if (ApplicationThemeManager.GetAppTheme() == ApplicationTheme.Dark)
+ {
+ WindowDarkMode.ApplyWindowDarkMode(hWnd);
+ }
+ break;
+
+ case ApplicationTheme.Dark:
+ WindowDarkMode.ApplyWindowDarkMode(hWnd);
+ break;
+
+ case ApplicationTheme.Light:
+ case ApplicationTheme.HighContrast:
+ WindowDarkMode.RemoveWindowDarkMode(hWnd);
+ break;
+ }
+
+ var wtaOptions = new UxTheme.WTA_OPTIONS()
+ {
+ Flags = UxTheme.WTNCA.WTNCA_NODRAWCAPTION,
+ Mask = (uint)UxTheme.ThemeDialogTextureFlags.ETDT_VALIDBITS,
+ };
+
+ UxTheme.SetWindowThemeAttribute(
+ hWnd,
+ UxTheme.WINDOWTHEMEATTRIBUTETYPE.WTA_NONCLIENT,
+ wtaOptions,
+ (uint)Marshal.SizeOf(typeof(UxTheme.WTA_OPTIONS))
+ );
+
+ var dwmApiResult = DwmApi.DwmSetWindowAttribute(
+ hWnd,
+ DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
+ (int)(backdropType switch
+ {
+ WindowBackdropType.Auto => DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_AUTO,
+ WindowBackdropType.Mica => DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW,
+ WindowBackdropType.Acrylic => DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW,
+ WindowBackdropType.Tabbed => DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TABBEDWINDOW,
+ _ => DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_NONE,
+ }),
+ Marshal.SizeOf(typeof(int))
+ );
+
+ return dwmApiResult == HRESULT.S_OK;
+ }
+
+ public static bool RemoveBackdrop(Window window)
+ {
+ if (window == null)
+ {
+ return false;
+ }
+
+ nint hWnd = new WindowInteropHelper(window).Handle;
+
+ return RemoveBackdrop(hWnd);
+ }
+
+ public static bool RemoveBackdrop(nint hWnd)
+ {
+ if (hWnd == 0x00 || !User32.IsWindow(hWnd))
+ {
+ return false;
+ }
+
+ var windowSource = HwndSource.FromHwnd(hWnd);
+
+ if (windowSource?.Handle.ToInt32() != 0x00 && windowSource?.CompositionTarget != null)
+ {
+ windowSource.CompositionTarget.BackgroundColor = SystemColors.WindowColor;
+ }
+
+ if (windowSource?.RootVisual is Window window)
+ {
+ var backgroundBrush = window.Resources["ApplicationBackgroundBrush"];
+
+ if (backgroundBrush is not SolidColorBrush)
+ {
+ backgroundBrush = ApplicationThemeManager.GetAppTheme() == ApplicationTheme.Dark
+ ? new SolidColorBrush(Color.FromArgb(0xFF, 0x20, 0x20, 0x20))
+ : new SolidColorBrush(Color.FromArgb(0xFF, 0xFA, 0xFA, 0xFA));
+ }
+
+ window.Background = (SolidColorBrush)backgroundBrush;
+ }
+
+ _ = DwmApi.DwmSetWindowAttribute(
+ hWnd,
+ DWMWINDOWATTRIBUTE.DWMWA_MICA_EFFECT,
+ 0x0,
+ Marshal.SizeOf(typeof(int))
+ );
+
+ _ = DwmApi.DwmSetWindowAttribute(
+ hWnd,
+ DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE,
+ (int)DwmApi.DWM_SYSTEMBACKDROP_TYPE.DWMSBT_NONE,
+ Marshal.SizeOf(typeof(int))
+ );
+
+ return true;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowDarkMode.cs b/Build/MicaSetup/Design/Controls/Window/WindowDarkMode.cs
new file mode 100644
index 00000000..8ef48e57
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowDarkMode.cs
@@ -0,0 +1,100 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Interop;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+public static class WindowDarkMode
+{
+ public static bool ApplyWindowDarkMode(nint hWnd)
+ {
+ if (hWnd == 0x00 || !User32.IsWindow(hWnd))
+ {
+ return false;
+ }
+
+ var dwAttribute = DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE;
+
+ if (!OsVersionHelper.IsWindows11_22523_OrGreater)
+ {
+ dwAttribute = DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE_OLD;
+ }
+
+ _ = DwmApi.DwmSetWindowAttribute(
+ hWnd,
+ dwAttribute,
+ 0x1,
+ Marshal.SizeOf(typeof(int))
+ );
+
+ return true;
+ }
+
+ public static bool RemoveWindowDarkMode(nint handle)
+ {
+ if (handle == 0x00 || !User32.IsWindow(handle))
+ {
+ return false;
+ }
+
+ var dwAttribute = DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE;
+
+ if (!OsVersionHelper.IsWindows11_22523_OrGreater)
+ {
+ dwAttribute = DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE_OLD;
+ }
+
+ _ = DwmApi.DwmSetWindowAttribute(
+ handle,
+ dwAttribute,
+ 0x0,
+ Marshal.SizeOf(typeof(int))
+ );
+ return true;
+ }
+
+ public static bool RemoveBackground(nint hWnd)
+ {
+ if (hWnd == 0x00 || !User32.IsWindow(hWnd))
+ {
+ return false;
+ }
+
+ var windowSource = HwndSource.FromHwnd(hWnd);
+
+ if (windowSource?.RootVisual is Window window)
+ {
+ return RemoveBackground(window);
+ }
+ return false;
+ }
+
+ public static bool RemoveBackground(Window window)
+ {
+ if (window == null)
+ {
+ return false;
+ }
+
+ window.Background = Brushes.Transparent;
+
+ nint windowHandle = new WindowInteropHelper(window).Handle;
+
+ if (windowHandle == 0x00)
+ {
+ return false;
+ }
+
+ var windowSource = HwndSource.FromHwnd(windowHandle);
+
+ if (windowSource?.Handle.ToInt32() != 0x00 && windowSource?.CompositionTarget != null)
+ {
+ windowSource.CompositionTarget.BackgroundColor = Colors.Transparent;
+ }
+
+ return true;
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowExtension.cs b/Build/MicaSetup/Design/Controls/Window/WindowExtension.cs
new file mode 100644
index 00000000..b006bb06
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowExtension.cs
@@ -0,0 +1,9 @@
+namespace MicaSetup.Design.Controls;
+
+public static class WindowExtension
+{
+ //public static void EnableBackdrop(this Window window, BackdropType backdropType = BackdropType.Mica)
+ //{
+ // ThemeService.Current.EnableBackdrop(window, backdropType);
+ //}
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowSystemCommands.cs b/Build/MicaSetup/Design/Controls/Window/WindowSystemCommands.cs
new file mode 100644
index 00000000..537790bf
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowSystemCommands.cs
@@ -0,0 +1,71 @@
+using MicaSetup.Helper;
+using MicaSetup.Natives;
+using System.Security;
+using System.Windows;
+using System.Windows.Interop;
+
+namespace MicaSetup.Design.Controls;
+
+internal static class WindowSystemCommands
+{
+ [SecuritySafeCritical]
+ public static void ShowSystemMenu(Window window, Point? screenLocation = null!)
+ {
+ if (screenLocation == null)
+ {
+ if (User32.GetCursorPos(out POINT pt))
+ {
+ SystemCommands.ShowSystemMenu(window, new Point(DpiHelper.CalcDPiX(pt.X), DpiHelper.CalcDPiY(pt.Y)));
+ }
+ }
+ else
+ {
+ SystemCommands.ShowSystemMenu(window, new Point(DpiHelper.CalcDPiX(screenLocation.Value.X), DpiHelper.CalcDPiY(screenLocation.Value.Y)));
+ }
+ }
+
+ [SecuritySafeCritical]
+ public static void CloseWindow(Window window)
+ {
+ SystemCommands.CloseWindow(window);
+ }
+
+ [SecuritySafeCritical]
+ public static void MaximizeWindow(Window window)
+ {
+ SystemCommands.MaximizeWindow(window);
+ }
+
+ [SecuritySafeCritical]
+ public static void MinimizeWindow(Window window)
+ {
+ SystemCommands.MinimizeWindow(window);
+ }
+
+ [SecuritySafeCritical]
+ public static void RestoreWindow(Window window)
+ {
+ SystemCommands.RestoreWindow(window);
+ window.WindowStyle = WindowStyle.SingleBorderWindow;
+ }
+
+ [SecuritySafeCritical]
+ public static void FastRestoreWindow(Window window, bool force = false)
+ {
+ _ = User32.PostMessage(new WindowInteropHelper(window).Handle, (int)User32.WindowMessage.WM_NCLBUTTONDOWN, (int)User32.HitTestValues.HTCAPTION, 0);
+ window.WindowStyle = WindowStyle.SingleBorderWindow;
+ }
+
+ [SecuritySafeCritical]
+ public static void MaximizeOrRestoreWindow(Window window)
+ {
+ if (window.WindowState == WindowState.Maximized)
+ {
+ RestoreWindow(window);
+ }
+ else
+ {
+ MaximizeWindow(window);
+ }
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowX.cs b/Build/MicaSetup/Design/Controls/Window/WindowX.cs
new file mode 100644
index 00000000..c90fa020
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowX.cs
@@ -0,0 +1,149 @@
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using MicaSetup.Natives;
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Interop;
+using System.Windows.Shell;
+
+namespace MicaSetup.Design.Controls;
+
+[INotifyPropertyChanged]
+public partial class WindowX : Window
+{
+ public HwndSource? HwndSource { get; private set; }
+ public nint? Handle { get; private set; }
+ public SnapLayout SnapLayout { get; } = new();
+
+ public bool IsActivated
+ {
+ get => (bool)GetValue(IsActivatedProperty);
+ set => SetValue(IsActivatedProperty, value);
+ }
+
+ public static readonly DependencyProperty IsActivatedProperty = DependencyProperty.Register(nameof(IsActivated), typeof(bool), typeof(WindowX), new PropertyMetadata(false));
+
+ public WindowX()
+ {
+ Loaded += (s, e) =>
+ {
+ HwndSource = (PresentationSource.FromVisual(this) as HwndSource)!;
+ HwndSource.AddHook(new HwndSourceHook(WndProc));
+ Handle = new WindowInteropHelper(this).Handle;
+
+ if (GetTemplateChild("PART_BtnMaximize") is Button buttonMaximize)
+ {
+ if (SnapLayout.IsSupported)
+ {
+ SnapLayout.Register(buttonMaximize);
+ }
+ }
+ };
+ }
+
+ protected private virtual nint WndProc(nint hWnd, int msg, nint wParam, nint lParam, ref bool handled)
+ {
+ return SnapLayout.WndProc(hWnd, msg, wParam, lParam, ref handled);
+ }
+
+ protected override void OnActivated(EventArgs e)
+ {
+ IsActivated = true;
+ _ = WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.Mica, ThemeService.Current.CurrentTheme switch
+ {
+ WindowsTheme.Dark => ApplicationTheme.Dark,
+ WindowsTheme.Light => ApplicationTheme.Light,
+ WindowsTheme.Auto or _ => ApplicationTheme.Unknown,
+ });
+ base.OnActivated(e);
+ }
+
+ protected override void OnDeactivated(EventArgs e)
+ {
+ IsActivated = false;
+ base.OnDeactivated(e);
+ }
+
+ protected override void OnSourceInitialized(EventArgs e)
+ {
+ base.OnSourceInitialized(e);
+ WindowBackdrop.ApplyBackdrop(this, WindowBackdropType.None, ApplicationTheme.Unknown);
+ NativeMethods.HideAllWindowButton(new WindowInteropHelper(this).Handle);
+ }
+
+ public override void EndInit()
+ {
+ ApplyResizeBorderThickness(WindowState);
+ base.EndInit();
+ }
+
+ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
+ {
+ if (e.Property.Name is nameof(WindowState))
+ {
+ ApplyResizeBorderThickness((WindowState)e.NewValue);
+ }
+ base.OnPropertyChanged(e);
+ }
+
+ private void ApplyResizeBorderThickness(WindowState windowsState)
+ {
+ if (windowsState == WindowState.Maximized || ResizeMode == ResizeMode.NoResize || ResizeMode == ResizeMode.CanMinimize)
+ {
+ WindowChrome.SetWindowChrome(this, new WindowChrome()
+ {
+ CaptionHeight = 0d,
+ CornerRadius = new CornerRadius(8d),
+ GlassFrameThickness = new Thickness(-1d),
+ ResizeBorderThickness = new Thickness(0d),
+ });
+ }
+ else
+ {
+ WindowChrome.SetWindowChrome(this, new WindowChrome()
+ {
+ CaptionHeight = 0d,
+ CornerRadius = new CornerRadius(8d),
+ GlassFrameThickness = new Thickness(-1d),
+ ResizeBorderThickness = new Thickness(3d),
+ });
+ }
+ }
+
+ [RelayCommand]
+ private void CloseWindow()
+ {
+ WindowSystemCommands.CloseWindow(this);
+ }
+
+ [RelayCommand]
+ private void MinimizeWindow()
+ {
+ WindowSystemCommands.MinimizeWindow(this);
+ }
+
+ [RelayCommand]
+ private void MaximizeWindow()
+ {
+ WindowSystemCommands.MaximizeWindow(this);
+ }
+
+ [RelayCommand]
+ private void RestoreWindow()
+ {
+ WindowSystemCommands.RestoreWindow(this);
+ }
+
+ [RelayCommand]
+ private void MaximizeOrRestoreWindow()
+ {
+ WindowSystemCommands.MaximizeOrRestoreWindow(this);
+ }
+
+ [RelayCommand]
+ private void ShowSystemMenu()
+ {
+ WindowSystemCommands.ShowSystemMenu(this);
+ }
+}
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowX.xaml b/Build/MicaSetup/Design/Controls/Window/WindowX.xaml
new file mode 100644
index 00000000..7d1dcfad
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowX.xaml
@@ -0,0 +1,370 @@
+
+
+ #FF000000
+ #33000000
+ #99000000
+ #CC000000
+ #66000000
+ #FFFFFFFF
+ #33FFFFFF
+ #99FFFFFF
+ #CCFFFFFF
+ #66FFFFFF
+ #FFF2F2F2
+ #FF000000
+ #33000000
+ #66000000
+ #CC000000
+ #FF333333
+ #FF858585
+ #FF767676
+ #FF171717
+ #FF1F1F1F
+ #FF323232
+ #FF2B2B2B
+ #FFFFFFFF
+ #FF767676
+ #19FFFFFF
+ #33FFFFFF
+ #FFF000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Build/MicaSetup/Design/Controls/Window/WindowXCaption.cs b/Build/MicaSetup/Design/Controls/Window/WindowXCaption.cs
new file mode 100644
index 00000000..8962c95e
--- /dev/null
+++ b/Build/MicaSetup/Design/Controls/Window/WindowXCaption.cs
@@ -0,0 +1,189 @@
+using System.Windows;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Controls;
+
+public sealed class WindowXCaption
+{
+ public static ImageSource GetIcon(DependencyObject obj)
+ {
+ return (ImageSource)obj.GetValue(IconProperty);
+ }
+
+ public static void SetIcon(DependencyObject obj, ImageSource value)
+ {
+ obj.SetValue(IconProperty, value);
+ }
+
+ public static readonly DependencyProperty IconProperty =
+ DependencyProperty.RegisterAttached("Icon", typeof(ImageSource), typeof(WindowXCaption));
+
+ public static Thickness GetPadding(DependencyObject obj)
+ {
+ return (Thickness)obj.GetValue(PaddingProperty);
+ }
+
+ public static void SetPadding(DependencyObject obj, Thickness value)
+ {
+ obj.SetValue(PaddingProperty, value);
+ }
+
+ public static readonly DependencyProperty PaddingProperty =
+ DependencyProperty.RegisterAttached("Padding", typeof(Thickness), typeof(WindowXCaption));
+
+ public static double GetHeight(DependencyObject obj)
+ {
+ return (double)obj.GetValue(HeightProperty);
+ }
+
+ public static void SetHeight(DependencyObject obj, double value)
+ {
+ obj.SetValue(HeightProperty, value);
+ }
+
+ public static readonly DependencyProperty HeightProperty =
+ DependencyProperty.RegisterAttached("Height", typeof(double), typeof(WindowXCaption));
+
+ public static Brush GetForeground(DependencyObject obj)
+ {
+ return (Brush)obj.GetValue(ForegroundProperty);
+ }
+
+ public static void SetForeground(DependencyObject obj, Brush value)
+ {
+ obj.SetValue(ForegroundProperty, value);
+ }
+
+ public static readonly DependencyProperty ForegroundProperty =
+ DependencyProperty.RegisterAttached("Foreground", typeof(Brush), typeof(WindowXCaption));
+
+ public static Brush GetBackground(DependencyObject obj)
+ {
+ return (Brush)obj.GetValue(BackgroundProperty);
+ }
+
+ public static void SetBackground(DependencyObject obj, Brush value)
+ {
+ obj.SetValue(BackgroundProperty, value);
+ }
+
+ public static readonly DependencyProperty BackgroundProperty =
+ DependencyProperty.RegisterAttached("Background", typeof(Brush), typeof(WindowXCaption));
+
+ public static Style GetMinimizeButtonStyle(DependencyObject obj)
+ {
+ return (Style)obj.GetValue(MinimizeButtonStyleProperty);
+ }
+
+ public static void SetMinimizeButtonStyle(DependencyObject obj, Style value)
+ {
+ obj.SetValue(MinimizeButtonStyleProperty, value);
+ }
+
+ public static readonly DependencyProperty MinimizeButtonStyleProperty =
+ DependencyProperty.RegisterAttached("MinimizeButtonStyle", typeof(Style), typeof(WindowXCaption));
+
+ public static Style GetMaximizeButtonStyle(DependencyObject obj)
+ {
+ return (Style)obj.GetValue(MaximizeButtonStyleProperty);
+ }
+
+ public static void SetMaximizeButtonStyle(DependencyObject obj, Style value)
+ {
+ obj.SetValue(MaximizeButtonStyleProperty, value);
+ }
+
+ public static readonly DependencyProperty MaximizeButtonStyleProperty =
+ DependencyProperty.RegisterAttached("MaximizeButtonStyle", typeof(Style), typeof(WindowXCaption));
+
+ public static Style GetCloseButtonStyle(DependencyObject obj)
+ {
+ return (Style)obj.GetValue(CloseButtonStyleProperty);
+ }
+
+ public static void SetCloseButtonStyle(DependencyObject obj, Style value)
+ {
+ obj.SetValue(CloseButtonStyleProperty, value);
+ }
+
+ public static readonly DependencyProperty CloseButtonStyleProperty =
+ DependencyProperty.RegisterAttached("CloseButtonStyle", typeof(Style), typeof(WindowXCaption));
+
+ public static Style GetFullScreenButtonStyle(DependencyObject obj)
+ {
+ return (Style)obj.GetValue(FullScreenButtonStyleProperty);
+ }
+
+ public static void SetFullScreenButtonStyle(DependencyObject obj, Style value)
+ {
+ obj.SetValue(FullScreenButtonStyleProperty, value);
+ }
+
+ public static readonly DependencyProperty FullScreenButtonStyleProperty =
+ DependencyProperty.RegisterAttached("FullScreenButtonStyle", typeof(Style), typeof(WindowXCaption));
+
+ public static object GetHeader(DependencyObject obj)
+ {
+ return obj.GetValue(HeaderProperty);
+ }
+
+ public static void SetHeader(DependencyObject obj, object value)
+ {
+ obj.SetValue(HeaderProperty, value);
+ }
+
+ public static readonly DependencyProperty HeaderProperty =
+ DependencyProperty.RegisterAttached("Header", typeof(object), typeof(WindowXCaption));
+
+ public static UIElement GetExtendControl(DependencyObject obj)
+ {
+ return (UIElement)obj.GetValue(ExtendControlProperty);
+ }
+
+ public static void SetExtendControl(DependencyObject obj, UIElement value)
+ {
+ obj.SetValue(ExtendControlProperty, value);
+ }
+
+ public static readonly DependencyProperty ExtendControlProperty =
+ DependencyProperty.RegisterAttached("ExtendControl", typeof(UIElement), typeof(WindowXCaption));
+
+ public static bool GetDisableCloseButton(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(DisableCloseButtonProperty);
+ }
+
+ public static void SetDisableCloseButton(DependencyObject obj, bool value)
+ {
+ obj.SetValue(DisableCloseButtonProperty, value);
+ }
+
+ public static readonly DependencyProperty DisableCloseButtonProperty =
+ DependencyProperty.RegisterAttached("DisableCloseButton", typeof(bool), typeof(WindowXCaption));
+
+ public static bool GetHideBasicButtons(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(HideBasicButtonsProperty);
+ }
+
+ public static void SetHideBasicButtons(DependencyObject obj, bool value)
+ {
+ obj.SetValue(HideBasicButtonsProperty, value);
+ }
+
+ public static readonly DependencyProperty HideBasicButtonsProperty =
+ DependencyProperty.RegisterAttached("HideBasicButtons", typeof(bool), typeof(WindowXCaption));
+
+ public static bool GetShowFullScreenButton(DependencyObject obj)
+ {
+ return (bool)obj.GetValue(ShowFullScreenButtonProperty);
+ }
+
+ public static void SetShowFullScreenButton(DependencyObject obj, bool value)
+ {
+ obj.SetValue(ShowFullScreenButtonProperty, value);
+ }
+
+ public static readonly DependencyProperty ShowFullScreenButtonProperty =
+ DependencyProperty.RegisterAttached("ShowFullScreenButton", typeof(bool), typeof(WindowXCaption), new(false));
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/BoolToValueConverterBase.cs b/Build/MicaSetup/Design/Converters/Abstraction/BoolToValueConverterBase.cs
new file mode 100644
index 00000000..4817befe
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/BoolToValueConverterBase.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Globalization;
+
+namespace MicaSetup.Design.Converters;
+
+public abstract class BoolToValueConverterBase : SingletonConverterBase where TConverter : new()
+{
+ public abstract T TrueValue { get; set; }
+
+ public abstract T FalseValue { get; set; }
+
+ public abstract bool IsInverted { get; set; }
+
+ protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var returnValue = this.FalseValue;
+
+ if (value is bool boolValue)
+ {
+ if (this.IsInverted)
+ {
+ returnValue = boolValue ? this.FalseValue : this.TrueValue;
+ }
+ else
+ {
+ returnValue = boolValue ? this.TrueValue : this.FalseValue;
+ }
+ }
+
+ return returnValue!;
+ }
+
+ protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ bool returnValue = false;
+
+ if (value != null)
+ {
+ if (this.IsInverted)
+ {
+ returnValue = value.Equals(this.FalseValue);
+ }
+ else
+ {
+ returnValue = value.Equals(this.TrueValue);
+ }
+ }
+
+ return returnValue;
+ }
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/ConverterBase.cs b/Build/MicaSetup/Design/Converters/Abstraction/ConverterBase.cs
new file mode 100644
index 00000000..4dd0b3d4
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/ConverterBase.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+public abstract class ConverterBase : DependencyObject, IValueConverter
+{
+ public PreferredCulture PreferredCulture { get; set; } = ValueConvertersConfig.DefaultPreferredCulture;
+
+ protected abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
+
+ protected virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ throw new NotSupportedException($"Converter '{this.GetType().Name}' does not support backward conversion.");
+ }
+
+ object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return this.Convert(value, targetType, parameter, this.SelectCulture(() => culture));
+ }
+
+ object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return this.ConvertBack(value, targetType, parameter, this.SelectCulture(() => culture));
+ }
+
+ private CultureInfo SelectCulture(Func converterCulture)
+ {
+ return PreferredCulture switch
+ {
+ PreferredCulture.CurrentCulture => CultureInfo.CurrentCulture,
+ PreferredCulture.CurrentUICulture => CultureInfo.CurrentUICulture,
+ _ => converterCulture(),
+ };
+ }
+
+ public static readonly object UnsetValue = DependencyProperty.UnsetValue;
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/PreferredCulture.cs b/Build/MicaSetup/Design/Converters/Abstraction/PreferredCulture.cs
new file mode 100644
index 00000000..60dca35c
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/PreferredCulture.cs
@@ -0,0 +1,21 @@
+using System.Globalization;
+
+namespace MicaSetup.Design.Converters;
+
+public enum PreferredCulture
+{
+ ///
+ /// Uses the default culture provided by .
+ ///
+ ConverterCulture,
+
+ ///
+ /// Overrides the default converter culture with .
+ ///
+ CurrentCulture,
+
+ ///
+ /// Overrides the default converter culture with .
+ ///
+ CurrentUICulture,
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/PropertyHelper.cs b/Build/MicaSetup/Design/Converters/Abstraction/PropertyHelper.cs
new file mode 100644
index 00000000..957cc0c5
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/PropertyHelper.cs
@@ -0,0 +1,15 @@
+using System.Windows;
+using Property = System.Windows.DependencyProperty;
+
+namespace MicaSetup.Design.Converters;
+
+#pragma warning disable WPF0011
+#pragma warning disable WPF0015
+
+internal static class PropertyHelper
+{
+ public static Property Create(string name, T defaultValue) =>
+ Property.Register(name, typeof(T), typeof(TParent), new PropertyMetadata(defaultValue));
+
+ public static Property Create(string name) => Create(name, default!);
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/ReversibleValueToBoolConverterBase.cs b/Build/MicaSetup/Design/Converters/Abstraction/ReversibleValueToBoolConverterBase.cs
new file mode 100644
index 00000000..897e6c17
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/ReversibleValueToBoolConverterBase.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Property = System.Windows.DependencyProperty;
+
+namespace MicaSetup.Design.Converters;
+
+public abstract class ReversibleValueToBoolConverterBase : ValueToBoolConverterBase
+ where TConverter : new()
+{
+ public abstract T FalseValue { get; set; }
+
+ public bool BaseOnFalseValue
+ {
+ get { return (bool)this.GetValue(BaseOnFalseValueProperty); }
+ set { this.SetValue(BaseOnFalseValueProperty, value); }
+ }
+
+ protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (!this.BaseOnFalseValue)
+ {
+ return base.Convert(value, targetType, parameter, culture);
+ }
+
+ var falseValue = this.FalseValue;
+ return !Equals(value, falseValue) ^ this.IsInverted;
+ }
+
+ protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ return true.Equals(value) ^ this.IsInverted ? this.TrueValue! : this.FalseValue!;
+ }
+
+ public static readonly Property BaseOnFalseValueProperty = PropertyHelper.Create>(nameof(BaseOnFalseValueProperty));
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/SingletonConverterBase.cs b/Build/MicaSetup/Design/Converters/Abstraction/SingletonConverterBase.cs
new file mode 100644
index 00000000..12df2cfa
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/SingletonConverterBase.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading;
+
+namespace MicaSetup.Design.Converters;
+
+public abstract class SingletonConverterBase : ConverterBase
+ where TConverter : new()
+{
+ private static readonly Lazy InstanceConstructor = new(() =>
+ {
+ return new TConverter();
+ }, LazyThreadSafetyMode.PublicationOnly);
+
+ public static TConverter Instance => InstanceConstructor.Value;
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/ValueConverterGroup.cs b/Build/MicaSetup/Design/Converters/Abstraction/ValueConverterGroup.cs
new file mode 100644
index 00000000..22e01e73
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/ValueConverterGroup.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace MicaSetup.Design.Converters;
+
+[ContentProperty(nameof(Converters))]
+[ValueConversion(typeof(object), typeof(object))]
+public class ValueConverterGroup : SingletonConverterBase
+{
+ public List Converters { get; set; } = new List();
+
+ protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (this.Converters is IEnumerable converters)
+ {
+ var language = culture;
+ return converters.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, language));
+ }
+
+ return UnsetValue;
+ }
+
+ protected override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (this.Converters is IEnumerable converters)
+ {
+ var language = culture;
+ return converters.Reverse().Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, language));
+ }
+
+ return UnsetValue;
+ }
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/ValueConvertersConfig.cs b/Build/MicaSetup/Design/Converters/Abstraction/ValueConvertersConfig.cs
new file mode 100644
index 00000000..08f87f11
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/ValueConvertersConfig.cs
@@ -0,0 +1,6 @@
+namespace MicaSetup.Design.Converters;
+
+public static class ValueConvertersConfig
+{
+ public static PreferredCulture DefaultPreferredCulture { get; set; } = PreferredCulture.ConverterCulture;
+}
diff --git a/Build/MicaSetup/Design/Converters/Abstraction/ValueToBoolConverterBase.cs b/Build/MicaSetup/Design/Converters/Abstraction/ValueToBoolConverterBase.cs
new file mode 100644
index 00000000..5a09b67c
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/Abstraction/ValueToBoolConverterBase.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Globalization;
+using Property = System.Windows.DependencyProperty;
+
+namespace MicaSetup.Design.Converters;
+
+public abstract class ValueToBoolConverterBase : ConverterBase
+ where TConverter : new()
+{
+ public abstract T TrueValue { get; set; }
+
+ public bool IsInverted
+ {
+ get { return (bool)this.GetValue(IsInvertedProperty); }
+ set { this.SetValue(IsInvertedProperty, value); }
+ }
+
+ protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ var trueValue = this.TrueValue;
+ return Equals(value, trueValue) ^ this.IsInverted;
+ }
+
+ public static readonly Property IsInvertedProperty = PropertyHelper.Create>(nameof(IsInverted));
+}
diff --git a/Build/MicaSetup/Design/Converters/AddConverter.cs b/Build/MicaSetup/Design/Converters/AddConverter.cs
new file mode 100644
index 00000000..1c37e1ac
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/AddConverter.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(double), typeof(double))]
+public class AddConverter : SingletonConverterBase
+{
+ protected override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ if (double.TryParse(value?.ToString(), NumberStyles.Any, culture, out var basis)
+ && double.TryParse(parameter?.ToString(), NumberStyles.Any, culture, out var subtract))
+ {
+ return basis + subtract;
+ }
+
+ return UnsetValue;
+ }
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolInverter.cs b/Build/MicaSetup/Design/Converters/BoolInverter.cs
new file mode 100644
index 00000000..5c128b09
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolInverter.cs
@@ -0,0 +1,8 @@
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(bool))]
+public class BoolInverter : BoolNegationConverter
+{
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolNegationConverter.cs b/Build/MicaSetup/Design/Converters/BoolNegationConverter.cs
new file mode 100644
index 00000000..279c22c2
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolNegationConverter.cs
@@ -0,0 +1,14 @@
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(bool))]
+public class BoolNegationConverter : BoolToValueConverter
+{
+ public BoolNegationConverter()
+ {
+ this.TrueValue = true;
+ this.FalseValue = false;
+ this.IsInverted = true;
+ }
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolToBrushConverter.cs b/Build/MicaSetup/Design/Converters/BoolToBrushConverter.cs
new file mode 100644
index 00000000..76ac700e
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolToBrushConverter.cs
@@ -0,0 +1,9 @@
+using System.Windows.Data;
+using System.Windows.Media;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(Brush))]
+public class BoolToBrushConverter : BoolToValueConverter
+{
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolToDoubleConverter.cs b/Build/MicaSetup/Design/Converters/BoolToDoubleConverter.cs
new file mode 100644
index 00000000..f5006351
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolToDoubleConverter.cs
@@ -0,0 +1,8 @@
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(double))]
+public class BoolToDoubleConverter : BoolToValueConverter
+{
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolToFontWeightConverter.cs b/Build/MicaSetup/Design/Converters/BoolToFontWeightConverter.cs
new file mode 100644
index 00000000..d7c8c003
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolToFontWeightConverter.cs
@@ -0,0 +1,14 @@
+using System.Windows;
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(FontWeight))]
+public class BoolToFontWeightConverter : BoolToValueConverter
+{
+ public BoolToFontWeightConverter()
+ {
+ this.TrueValue = FontWeights.Bold;
+ this.FalseValue = FontWeights.Normal;
+ }
+}
diff --git a/Build/MicaSetup/Design/Converters/BoolToObjectConverter.cs b/Build/MicaSetup/Design/Converters/BoolToObjectConverter.cs
new file mode 100644
index 00000000..b951bc23
--- /dev/null
+++ b/Build/MicaSetup/Design/Converters/BoolToObjectConverter.cs
@@ -0,0 +1,8 @@
+using System.Windows.Data;
+
+namespace MicaSetup.Design.Converters;
+
+[ValueConversion(typeof(bool), typeof(object))]
+public class BoolToObjectConverter : BoolToValueConverter