diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/TypeConverters/WindowOptionColorConverter.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/TypeConverters/WindowOptionColorConverter.cs
new file mode 100644
index 0000000000..bd902c02a9
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/TypeConverters/WindowOptionColorConverter.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+
+namespace EleCho.WpfSuite
+{
+ ///
+ /// converter
+ ///
+ public class WindowOptionColorConverter : TypeConverter
+ {
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
+ {
+ if (sourceType == typeof(string) ||
+ sourceType == typeof(Color))
+ return true;
+
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is string stringValue)
+ {
+ if (stringValue.Equals(nameof(WindowOptionColor.Default), StringComparison.OrdinalIgnoreCase))
+ {
+ return WindowOptionColor.Default;
+ }
+ else if (stringValue.Equals(nameof(WindowOptionColor.None), StringComparison.OrdinalIgnoreCase))
+ {
+ return WindowOptionColor.None;
+ }
+ else if (ColorConverter.ConvertFromString(stringValue) is Color color)
+ {
+ color.A = 0xFF;
+ return (WindowOptionColor)color;
+ }
+ }
+ else if (value is Color colorValue)
+ {
+ colorValue.A = 0xFF;
+ return (WindowOptionColor)colorValue;
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowAccentState.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowAccentState.cs
new file mode 100644
index 0000000000..8ba60ae729
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowAccentState.cs
@@ -0,0 +1,38 @@
+namespace EleCho.WpfSuite
+{
+ ///
+ /// Window accent state
+ ///
+ public enum WindowAccentState
+ {
+ ///
+ /// None
+ ///
+ None,
+
+ ///
+ /// Gradient
+ ///
+ Gradient = 1,
+
+ ///
+ /// Transparent
+ ///
+ Transparent = 2,
+
+ ///
+ /// Blur behind
+ ///
+ BlurBehind = 3,
+
+ ///
+ /// Acrylic blur behind
+ ///
+ AcrylicBlurBehind = 4,
+
+ ///
+ /// Host backdrop
+ ///
+ HostBackdrop = 5,
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowBackdrop.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowBackdrop.cs
new file mode 100644
index 0000000000..b70c7d225c
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowBackdrop.cs
@@ -0,0 +1,35 @@
+using System.Windows.Media;
+
+namespace EleCho.WpfSuite
+{
+ ///
+ /// Window backdrop
+ ///
+ public enum WindowBackdrop
+ {
+ ///
+ /// Automatic
+ ///
+ Auto = 0,
+
+ ///
+ /// None
+ ///
+ None = 1,
+
+ ///
+ /// Mica
+ ///
+ Mica = 2,
+
+ ///
+ /// Acrylic
+ ///
+ Acrylic = 3,
+
+ ///
+ /// Mica alternate
+ ///
+ MicaAlt = 4
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowCorner.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowCorner.cs
new file mode 100644
index 0000000000..13850c1299
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowCorner.cs
@@ -0,0 +1,28 @@
+namespace EleCho.WpfSuite
+{
+ ///
+ /// Window corner
+ ///
+ public enum WindowCorner
+ {
+ ///
+ /// Default
+ ///
+ Default = 0,
+
+ ///
+ /// Do not round
+ ///
+ DoNotRound = 1,
+
+ ///
+ /// Round
+ ///
+ Round = 2,
+
+ ///
+ /// Round small
+ ///
+ RoundSmall = 3
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs
new file mode 100644
index 0000000000..2e4080bf07
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.NativeMethods.cs
@@ -0,0 +1,178 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace EleCho.WpfSuite {
+ public partial class WindowOption {
+ internal static class NativeDefinition {
+ public const int GWL_STYLE = -16;
+
+ public const nint WS_CAPTION = 0x00C00000;
+ public const nint WS_SYSMENU = 0x00080000;
+
+ public const nint WM_NCHITTEST = 0x0084;
+ public const nint WM_NCMOUSELEAVE = 0x02A2;
+ public const nint WM_NCLBUTTONDOWN = 0x00A1;
+ public const nint WM_NCLBUTTONUP = 0x00A2;
+ public const nint WM_MOUSEMOVE = 0x0200;
+
+ public const nint HTCLOSE = 20;
+ public const nint HTMAXBUTTON = 9;
+ public const nint HTMINBUTTON = 8;
+
+ [DllImport("DWMAPI")]
+ public static extern nint DwmSetWindowAttribute(nint hwnd, DwmWindowAttribute attribute, nint dataPointer, uint dataSize);
+
+ [DllImport("DWMAPI")]
+ public static extern nint DwmExtendFrameIntoClientArea(nint hwnd, ref Margins margins);
+
+ [DllImport("User32")]
+ public static extern bool SetWindowCompositionAttribute(nint hwnd, ref WindowCompositionAttributeData data);
+
+ [DllImport("User32")]
+ public static extern bool GetWindowCompositionAttribute(nint hwnd, ref WindowCompositionAttributeData data);
+
+ [DllImport("User32")]
+ public static extern bool SetWindowPos(nint hwnd, nint hwndInsertAfter, int x, int y, int width, int height, SetWindowPosFlags flags);
+
+ [DllImport("User32")]
+ public static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
+
+ [DllImport("User32")]
+ public static extern bool UpdateWindow(nint hwnd);
+
+ [DllImport("User32")]
+ public extern static nint GetWindowLongPtr(nint hwnd, int index);
+
+ [DllImport("User32")]
+ public extern static nint SetWindowLongPtr(nint hwnd, int index, nint newLong);
+
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WindowCompositionAttributeData {
+ ///
+ /// A flag describing which value to get or set, specified as a value of the enumeration.
+ /// This parameter specifies which attribute to get or set, and the pvData member points to an object containing the attribute value.
+ ///
+ public WindowCompositionAttribute Attribute;
+
+ ///
+ /// When used with the GetWindowCompositionAttribute function, this member contains a pointer to a variable that will hold the value of the requested attribute when the function returns.
+ /// When used with the SetWindowCompositionAttribute function, it points an object containing the attribute value to set.
+ /// The type of the value set depends on the value of the Attrib member.
+ ///
+ public nint DataPointer;
+
+ ///
+ /// The size of the object pointed to by the pvData member, in bytes.
+ ///
+ public uint DataSize;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct AccentPolicy {
+ public AccentState AccentState;
+ public AccentFlags AccentFlags;
+ public int GradientColor;
+ public int AnimationId;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Margins {
+ public int LeftWidth;
+ public int RightWidth;
+ public int TopHeight;
+ public int BottomHeight;
+ }
+
+ public enum AccentState {
+ Disabled,
+ EnableGradient = 1,
+ EnableTransparent = 2,
+ EnableBlurBehind = 3,
+ EnableAcrylicBlurBehind = 4,
+ EnableHostBackdrop = 5,
+ InvalidState = 6,
+ }
+
+ [Flags]
+ public enum AccentFlags {
+ None = 0,
+ ExtendSize = 0x4,
+ LeftBorder = 0x20,
+ TopBorder = 0x40,
+ RightBorder = 0x80,
+ BottomBorder = 0x100,
+ AllBorder = LeftBorder | TopBorder | RightBorder | BottomBorder,
+ }
+
+ public enum WindowCompositionAttribute {
+ // 省略其他未使用的字段
+ WcaAccentPolicy = 19,
+ // 省略其他未使用的字段
+ }
+
+ public enum DwmWindowAttribute {
+ NCRENDERING_ENABLED,
+ NCRENDERING_POLICY,
+ TRANSITIONS_FORCEDISABLED,
+ ALLOW_NCPAINT,
+ CAPTION_BUTTON_BOUNDS,
+ NONCLIENT_RTL_LAYOUT,
+ FORCE_ICONIC_REPRESENTATION,
+ FLIP3D_POLICY,
+ EXTENDED_FRAME_BOUNDS,
+ HAS_ICONIC_BITMAP,
+ DISALLOW_PEEK,
+ EXCLUDED_FROM_PEEK,
+ CLOAK,
+ CLOAKED,
+ FREEZE_REPRESENTATION,
+ PASSIVE_UPDATE_MODE,
+ USE_HOSTBACKDROPBRUSH,
+ USE_IMMERSIVE_DARK_MODE = 20,
+ WINDOW_CORNER_PREFERENCE = 33,
+ BORDER_COLOR,
+ CAPTION_COLOR,
+ TEXT_COLOR,
+ VISIBLE_FRAME_BORDER_THICKNESS,
+ SYSTEMBACKDROP_TYPE,
+ LAST
+ }
+
+ // Token: 0x0200002E RID: 46
+ [Flags]
+ public enum SetWindowPosFlags {
+ // Token: 0x04000368 RID: 872
+ ASYNCWINDOWPOS = 16384,
+ // Token: 0x04000369 RID: 873
+ DEFERERASE = 8192,
+ // Token: 0x0400036A RID: 874
+ DRAWFRAME = 32,
+ // Token: 0x0400036B RID: 875
+ FRAMECHANGED = 32,
+ // Token: 0x0400036C RID: 876
+ HIDEWINDOW = 128,
+ // Token: 0x0400036D RID: 877
+ NOACTIVATE = 16,
+ // Token: 0x0400036E RID: 878
+ NOCOPYBITS = 256,
+ // Token: 0x0400036F RID: 879
+ NOMOVE = 2,
+ // Token: 0x04000370 RID: 880
+ NOOWNERZORDER = 512,
+ // Token: 0x04000371 RID: 881
+ NOREDRAW = 8,
+ // Token: 0x04000372 RID: 882
+ NOREPOSITION = 512,
+ // Token: 0x04000373 RID: 883
+ NOSENDCHANGING = 1024,
+ // Token: 0x04000374 RID: 884
+ NOSIZE = 1,
+ // Token: 0x04000375 RID: 885
+ NOZORDER = 4,
+ // Token: 0x04000376 RID: 886
+ SHOWWINDOW = 64
+ }
+ }
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs
new file mode 100644
index 0000000000..c2defd56a2
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOption.cs
@@ -0,0 +1,1392 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Interop;
+using System.Windows.Media;
+using System.Windows.Shell;
+using System.Xml.Linq;
+using static EleCho.WpfSuite.WindowOption.NativeDefinition;
+
+namespace EleCho.WpfSuite {
+ ///
+ /// Window options
+ ///
+ public partial class WindowOption {
+ const int BackdropTypeNone = 1;
+ const int BackdropTypeMica = 2;
+ const int BackdropTypeAcrylic = 3;
+
+ static readonly Version s_versionWindows10_1809 = new Version(10, 0, 17763);
+ static readonly Version s_versionWindows10 = new Version(10, 0);
+
+ ///
+ /// DWM 支持 Corner, BorderColor, CaptionColor, TextColor
+ ///
+ static readonly Version s_versionWindows11_22000 = new Version(10, 0, 22000);
+
+ ///
+ /// DWM 支持暗色模式与 Backdrop 属性
+ ///
+ static readonly Version s_versionWindows11_22621 = new Version(10, 0, 22621);
+
+ static readonly Version s_versionCurrentWindows = Environment.OSVersion.Version;
+
+ static Dictionary? s_maximumButtons;
+ static Dictionary? s_minimumButtons;
+ static Dictionary? s_closeButtons;
+
+ static DependencyPropertyKey s_uiElementIsMouseOverPropertyKey =
+ (DependencyPropertyKey)typeof(UIElement).GetField("IsMouseOverPropertyKey", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!;
+
+ static DependencyPropertyKey s_buttonIsPressedPropertyKey =
+ (DependencyPropertyKey)typeof(ButtonBase).GetField("IsPressedPropertyKey", BindingFlags.NonPublic | BindingFlags.Static)!.GetValue(null)!;
+
+ ///
+ /// Check whether the current platform can set backdrop property
+ ///
+ public static bool CanSetBackdrop => s_versionCurrentWindows >= s_versionWindows11_22621;
+
+ ///
+ /// Check whether the current platform can set accent properties
+ ///
+ public static bool CanSetAccent => s_versionCurrentWindows >= s_versionWindows10_1809;
+
+
+
+ ///
+ /// Get value of Backdrop property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowBackdrop GetBackdrop(DependencyObject obj) {
+ return (WindowBackdrop)obj.GetValue(BackdropProperty);
+ }
+
+ ///
+ /// Set value of Backdrop property
+ ///
+ ///
+ ///
+ public static void SetBackdrop(DependencyObject obj, WindowBackdrop value) {
+ obj.SetValue(BackdropProperty, value);
+ }
+
+
+ ///
+ /// Get value of Corner property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowCorner GetCorner(DependencyObject obj) {
+ return (WindowCorner)obj.GetValue(CornerProperty);
+ }
+
+ ///
+ /// Set value of Corner property
+ ///
+ ///
+ ///
+ public static void SetCorner(DependencyObject obj, WindowCorner value) {
+ obj.SetValue(CornerProperty, value);
+ }
+
+
+ ///
+ /// Get value of CaptionColor property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowOptionColor GetCaptionColor(DependencyObject obj) {
+ return (WindowOptionColor)obj.GetValue(CaptionColorProperty);
+ }
+
+ ///
+ /// Set value of CaptionColor property
+ ///
+ ///
+ ///
+ public static void SetCaptionColor(DependencyObject obj, WindowOptionColor value) {
+ obj.SetValue(CaptionColorProperty, value);
+ }
+
+
+ ///
+ /// Get value of TextColor property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowOptionColor GetTextColor(DependencyObject obj) {
+ return (WindowOptionColor)obj.GetValue(TextColorProperty);
+ }
+
+ ///
+ /// Set value of TextColor property
+ ///
+ ///
+ ///
+ public static void SetTextColor(DependencyObject obj, WindowOptionColor value) {
+ obj.SetValue(TextColorProperty, value);
+ }
+
+
+ ///
+ /// Get value of BorderColor property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowOptionColor GetBorderColor(DependencyObject obj) {
+ return (WindowOptionColor)obj.GetValue(BorderColorProperty);
+ }
+
+ ///
+ /// Set value of BorderColor property
+ ///
+ ///
+ ///
+ public static void SetBorderColor(DependencyObject obj, WindowOptionColor value) {
+ obj.SetValue(BorderColorProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsDarkMode property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static bool GetIsDarkMode(DependencyObject obj) {
+ return (bool)obj.GetValue(IsDarkModeProperty);
+ }
+
+ ///
+ /// Set value of IsDarkMode property
+ ///
+ ///
+ ///
+ public static void SetIsDarkMode(DependencyObject obj, bool value) {
+ obj.SetValue(IsDarkModeProperty, value);
+ }
+
+
+ ///
+ /// Get value of AccentState property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static WindowAccentState GetAccentState(DependencyObject obj) {
+ return (WindowAccentState)obj.GetValue(AccentStateProperty);
+ }
+
+ ///
+ /// Set value of AccentState property
+ ///
+ ///
+ ///
+ public static void SetAccentState(DependencyObject obj, WindowAccentState value) {
+ obj.SetValue(AccentStateProperty, value);
+ }
+
+
+ ///
+ /// Get value of AccentBorder property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static bool GetAccentBorder(DependencyObject obj) {
+ return (bool)obj.GetValue(AccentBorderProperty);
+ }
+
+ ///
+ /// Set value of AccentBorder property
+ ///
+ ///
+ ///
+ public static void SetAccentBorder(DependencyObject obj, bool value) {
+ obj.SetValue(AccentBorderProperty, value);
+ }
+
+
+ ///
+ /// Get value of AccentGradientColor property
+ ///
+ ///
+ ///
+ [AttachedPropertyBrowsableForType(typeof(Window))]
+ public static Color GetAccentGradientColor(DependencyObject obj) {
+ return (Color)obj.GetValue(AccentGradientColorProperty);
+ }
+
+ ///
+ /// Set value of AccentGradientColor property
+ ///
+ ///
+ ///
+ public static void SetAccentGradientColor(DependencyObject obj, Color value) {
+ obj.SetValue(AccentGradientColorProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsCaptionVisible property
+ ///
+ ///
+ ///
+ public static bool GetIsCaptionVisible(DependencyObject obj) {
+ return (bool)obj.GetValue(IsCaptionVisibleProperty);
+ }
+
+ ///
+ /// Set value of IsCaptionVisible property
+ ///
+ ///
+ ///
+ public static void SetIsCaptionVisible(DependencyObject obj, bool value) {
+ obj.SetValue(IsCaptionVisibleProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsCaptionMenuVisible property
+ ///
+ ///
+ ///
+ public static bool GetIsCaptionMenuVisible(DependencyObject obj) {
+ return (bool)obj.GetValue(IsCaptionMenuVisibleProperty);
+ }
+
+ ///
+ /// Set value of IsCaptionMenuVisible property
+ ///
+ ///
+ ///
+ public static void SetIsCaptionMenuVisible(DependencyObject obj, bool value) {
+ obj.SetValue(IsCaptionMenuVisibleProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsMaximumButton property
+ ///
+ ///
+ ///
+ public static bool GetIsMaximumButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsMaximumButtonProperty);
+ }
+
+ ///
+ /// Set value of IsMaximumButton property
+ ///
+ ///
+ ///
+ public static void SetIsMaximumButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsMaximumButtonProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsMinimumButton property
+ ///
+ ///
+ ///
+ public static bool GetIsMinimumButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsMinimumButtonProperty);
+ }
+
+ ///
+ /// Set value of IsMinimumButton property
+ ///
+ ///
+ ///
+ public static void SetIsMinimumButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsMinimumButtonProperty, value);
+ }
+
+
+ ///
+ /// Get value of IsCloseButton property
+ ///
+ ///
+ ///
+ public static bool GetIsCloseButton(DependencyObject obj) {
+ return (bool)obj.GetValue(IsCloseButtonProperty);
+ }
+
+ ///
+ /// Set value of IsCloseButton property
+ ///
+ ///
+ ///
+ public static void SetIsCloseButton(DependencyObject obj, bool value) {
+ obj.SetValue(IsCloseButtonProperty, value);
+ }
+
+
+
+ ///
+ /// The DependencyProperty of Backdrop property
+ ///
+ public static readonly DependencyProperty BackdropProperty =
+ DependencyProperty.RegisterAttached("Backdrop", typeof(WindowBackdrop), typeof(WindowOption), new FrameworkPropertyMetadata(WindowBackdrop.Auto, OnBackdropChanged));
+
+ ///
+ /// The DependencyProperty of Corner property
+ ///
+ public static readonly DependencyProperty CornerProperty =
+ DependencyProperty.RegisterAttached("Corner", typeof(WindowCorner), typeof(WindowOption), new PropertyMetadata(WindowCorner.Default, OnCornerChanged));
+
+ ///
+ /// The DependencyProperty of CaptionColor property
+ ///
+ public static readonly DependencyProperty CaptionColorProperty =
+ DependencyProperty.RegisterAttached("CaptionColor", typeof(WindowOptionColor), typeof(WindowOption), new PropertyMetadata(WindowOptionColor.Default, OnCaptionColorChanged));
+
+ ///
+ /// The DependencyProperty of TextColor property
+ ///
+ public static readonly DependencyProperty TextColorProperty =
+ DependencyProperty.RegisterAttached("TextColor", typeof(WindowOptionColor), typeof(WindowOption), new PropertyMetadata(WindowOptionColor.Default, OnTextColorChanged));
+
+ ///
+ /// The DependencyProperty of BorderColor property
+ ///
+ public static readonly DependencyProperty BorderColorProperty =
+ DependencyProperty.RegisterAttached("BorderColor", typeof(WindowOptionColor), typeof(WindowOption), new PropertyMetadata(WindowOptionColor.Default, OnBorderColorChanged));
+
+ ///
+ /// The DependencyProperty of IsDarkMode property
+ ///
+ public static readonly DependencyProperty IsDarkModeProperty =
+ DependencyProperty.RegisterAttached("IsDarkMode", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsDarkModeChanged));
+
+ ///
+ /// The DependencyProperty of AccentState property
+ ///
+ public static readonly DependencyProperty AccentStateProperty =
+ DependencyProperty.RegisterAttached("AccentState", typeof(WindowAccentState), typeof(WindowOption), new FrameworkPropertyMetadata(WindowAccentState.None, OnAccentChanged));
+
+ ///
+ /// The DependencyProperty of AccentBorder property
+ ///
+ public static readonly DependencyProperty AccentBorderProperty =
+ DependencyProperty.RegisterAttached("AccentBorder", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(true, OnAccentChanged));
+
+ ///
+ /// The DependencyProperty of AccentGradientColor property
+ ///
+ public static readonly DependencyProperty AccentGradientColorProperty =
+ DependencyProperty.RegisterAttached("AccentGradientColor", typeof(Color), typeof(WindowOption), new FrameworkPropertyMetadata(Color.FromScRgb(0.25f, 1, 1, 1), OnAccentChanged));
+
+ ///
+ /// The DependencyProperty of IsCaptionVisible property
+ ///
+ public static readonly DependencyProperty IsCaptionVisibleProperty =
+ DependencyProperty.RegisterAttached("IsCaptionVisible", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(true, OnIsCaptionVisibleChanged));
+
+ ///
+ /// The DependencyProperty of IsCaptionMenuVisible property
+ ///
+ public static readonly DependencyProperty IsCaptionMenuVisibleProperty =
+ DependencyProperty.RegisterAttached("IsCaptionMenuVisible", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(true, OnIsCaptionMenuVisibleChanged));
+
+ ///
+ /// The DependencyProperty of IsMaximumButton property
+ ///
+ public static readonly DependencyProperty IsMaximumButtonProperty =
+ DependencyProperty.RegisterAttached("IsMaximumButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsMaximumButtonChanged));
+
+ ///
+ /// The DependencyProperty of IsMinimumButton property
+ ///
+ public static readonly DependencyProperty IsMinimumButtonProperty =
+ DependencyProperty.RegisterAttached("IsMinimumButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsMinimumButtonChanged));
+
+ ///
+ /// The DependencyProperty of IsCloseButton property
+ ///
+ public static readonly DependencyProperty IsCloseButtonProperty =
+ DependencyProperty.RegisterAttached("IsCloseButton", typeof(bool), typeof(WindowOption), new FrameworkPropertyMetadata(false, OnIsCloseButtonChanged));
+
+
+ #region DependencyProperty Callbacks
+
+ private static void OnBackdropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ // check
+ if (GetAccentState(d) is not WindowAccentState.None &&
+ GetBackdrop(d) is not WindowBackdrop.Auto &&
+ GetBackdrop(d) is not WindowBackdrop.None) {
+ throw new InvalidOperationException("Backdrop and AccentState can't both be set");
+ }
+
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyBackdrop(window, hwndSource, GetBackdrop(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyBackdrop(window, hwndSource, GetBackdrop(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyBackdrop;
+ popup.Opened += EventHandlerApplyBackdrop;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyBackdrop;
+ element.Loaded += EventHandlerApplyBackdrop;
+ }
+ }
+
+ private static void OnCornerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyCorner(window, hwndSource, GetCorner(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyCorner(window, hwndSource, GetCorner(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyCorner;
+ popup.Opened += EventHandlerApplyCorner;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyCorner;
+ element.Loaded += EventHandlerApplyCorner;
+ }
+ }
+
+ private static void OnCaptionColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyCaptionColor(window, hwndSource, GetCaptionColor(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyCaptionColor(window, hwndSource, GetCaptionColor(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyCaptionColor;
+ popup.Opened += EventHandlerApplyCaptionColor;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyCaptionColor;
+ element.Loaded += EventHandlerApplyCaptionColor;
+ }
+ }
+
+ private static void OnTextColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyTextColor(window, hwndSource, GetTextColor(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyTextColor(window, hwndSource, GetTextColor(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyTextColor;
+ popup.Opened += EventHandlerApplyTextColor;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyTextColor;
+ element.Loaded += EventHandlerApplyTextColor;
+ }
+ }
+
+ private static void OnBorderColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyBorderColor(window, hwndSource, GetBorderColor(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyBorderColor(window, hwndSource, GetBorderColor(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyBorderColor;
+ popup.Opened += EventHandlerApplyBorderColor;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyBorderColor;
+ element.Loaded += EventHandlerApplyBorderColor;
+ }
+ }
+
+ private static void OnIsDarkModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyDarkMode(window, hwndSource, GetIsDarkMode(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyDarkMode(window, hwndSource, GetIsDarkMode(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyDarkMode;
+ popup.Opened += EventHandlerApplyDarkMode;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyDarkMode;
+ element.Loaded += EventHandlerApplyDarkMode;
+ }
+ }
+
+ private static void OnAccentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ // check
+ if (GetAccentState(d) is not WindowAccentState.None &&
+ GetBackdrop(d) is not WindowBackdrop.Auto &&
+ GetBackdrop(d) is not WindowBackdrop.None) {
+ throw new InvalidOperationException("Backdrop and AccentState can't both be set");
+ }
+
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyAccent(window, hwndSource, GetAccentState(d), GetAccentGradientColor(d), GetAccentBorder(d));
+ }
+ else if (e.Property == AccentStateProperty) {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyAccent(window, hwndSource, GetAccentState(d), GetAccentGradientColor(d), GetAccentBorder(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyAccent;
+ popup.Opened += EventHandlerApplyAccent;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyAccent;
+ element.Loaded += EventHandlerApplyAccent;
+ }
+ }
+
+ private static void OnIsCaptionVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyIsCaptionVisible(window, hwndSource, GetIsCaptionVisible(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyIsCaptionVisible(window, hwndSource, GetIsCaptionVisible(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyIsCaptionVisible;
+ popup.Opened += EventHandlerApplyIsCaptionVisible;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyIsCaptionVisible;
+ element.Loaded += EventHandlerApplyIsCaptionVisible;
+ }
+ }
+
+ private static void OnIsCaptionMenuVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is Window window) {
+ if (GetWindowHwndSource(d) is HwndSource hwndSource) {
+ ApplyIsCaptionMenuVisible(window, hwndSource, GetIsCaptionMenuVisible(d));
+ }
+ else {
+ DoAfterHandleOk(d, (d, hwndSource) => {
+ ApplyIsCaptionMenuVisible(window, hwndSource, GetIsCaptionMenuVisible(d));
+ });
+ }
+ }
+ else if (d is Popup popup) {
+ popup.Opened -= EventHandlerApplyIsCaptionMenuVisible;
+ popup.Opened += EventHandlerApplyIsCaptionMenuVisible;
+ }
+ else if (d is FrameworkElement element) {
+ element.Loaded -= EventHandlerApplyIsCaptionMenuVisible;
+ element.Loaded += EventHandlerApplyIsCaptionMenuVisible;
+ }
+ }
+
+ private static void OnIsMaximumButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsMaximumButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsMaximumButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static void OnIsMinimumButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsMinimumButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsMinimumButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static void OnIsCloseButtonChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ if (d is not FrameworkElement frameworkElement) {
+ throw new InvalidOperationException("Target DependencyObject is not FrameworkElement");
+ }
+
+ if (DesignerProperties.GetIsInDesignMode(d)) {
+ return;
+ }
+
+ if (Window.GetWindow(d) is Window window) {
+ DoAfterWindowSourceInitialized(window, () => {
+ ApplyIsCloseButton(window, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ DoAfterElementLoaded(frameworkElement, () => {
+ if (Window.GetWindow(frameworkElement) is Window loadedWindow) {
+ DoAfterWindowSourceInitialized(loadedWindow, () => {
+ ApplyIsCloseButton(loadedWindow, frameworkElement, (bool)e.NewValue);
+ });
+ }
+ else {
+ throw new InvalidOperationException("Cannot find Window of Visual");
+ }
+ });
+ }
+ }
+
+ private static IntPtr WindowCaptionButtonsInteropHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
+ if (handled) {
+ return IntPtr.Zero;
+ }
+
+ switch ((nint)msg) {
+ case NativeDefinition.WM_NCHITTEST: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+ var result = default(IntPtr);
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ var relativePoint = maximumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(maximumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTMAXBUTTON;
+ }
+ else {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ var relativePoint = minimumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(minimumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTMINBUTTON;
+ }
+ else {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ var relativePoint = closeButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(closeButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, true);
+
+ handled = true;
+ result = NativeDefinition.HTCLOSE;
+ }
+ else {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ case NativeDefinition.WM_NCLBUTTONDOWN: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ var relativePoint = maximumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(maximumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ var relativePoint = minimumButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(minimumButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ var relativePoint = closeButtonVisual.PointFromScreen(new Point(x, y));
+ var hitResult = VisualTreeHelper.HitTest(closeButtonVisual, relativePoint);
+
+ if (hitResult is not null) {
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, true);
+ }
+
+ handled = true;
+ }
+ }
+
+ break;
+ }
+
+ case NativeDefinition.WM_NCLBUTTONUP: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ if (maximumButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ if (minimumButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ if (closeButtonVisual is ButtonBase button) {
+ bool shouldClick = false;
+ if ((bool)button.GetValue(s_buttonIsPressedPropertyKey.DependencyProperty)) {
+ shouldClick = true;
+ }
+
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+
+ if (shouldClick) {
+ button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
+ button.Command?.Execute(button.CommandParameter);
+ }
+
+ handled = true;
+ }
+ }
+
+ break;
+ }
+
+ case NativeDefinition.WM_NCMOUSELEAVE: {
+ var x = (int)((ulong)lParam & 0x0000FFFF);
+ var y = (int)((ulong)lParam & 0xFFFF0000) >> 16;
+
+ if (s_maximumButtons is not null &&
+ s_maximumButtons.TryGetValue(hwnd, out var maximumButtonVisual)) {
+ maximumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (maximumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ if (s_minimumButtons is not null &&
+ s_minimumButtons.TryGetValue(hwnd, out var minimumButtonVisual)) {
+ minimumButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (minimumButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ if (s_closeButtons is not null &&
+ s_closeButtons.TryGetValue(hwnd, out var closeButtonVisual)) {
+ closeButtonVisual.SetValue(s_uiElementIsMouseOverPropertyKey, false);
+
+ if (closeButtonVisual is ButtonBase button) {
+ button.SetValue(s_buttonIsPressedPropertyKey, false);
+ }
+ }
+
+ break;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ #endregion
+
+
+ #region Utilities
+
+ private static int CreateColorInteger(Color color) {
+ return
+ color.R << 0 |
+ color.G << 8 |
+ color.B << 16 |
+ color.A << 24;
+ }
+
+ private static HwndSource? GetWindowHwndSource(DependencyObject dependencyObject) {
+ if (dependencyObject is Window window) {
+ return PresentationSource.FromVisual(window) as HwndSource;
+ }
+ else if (dependencyObject is ToolTip tooltip) {
+ return PresentationSource.FromVisual(tooltip) as HwndSource;
+ }
+ else if (dependencyObject is Popup popup) {
+ if (popup.Child is null)
+ return null;
+
+ return PresentationSource.FromVisual(popup.Child) as HwndSource;
+ }
+ else if (dependencyObject is Visual visual) {
+ return PresentationSource.FromVisual(visual) as HwndSource;
+ }
+
+ return null;
+ }
+
+ private static void DoAfterHandleOk(DependencyObject dependencyObject, Action action) {
+ if (dependencyObject is Window window) {
+ var eventHandler = default(EventHandler);
+
+ eventHandler = (s, e) => {
+ if (GetWindowHwndSource(window) is HwndSource hwndSource) {
+ action?.Invoke(dependencyObject, hwndSource);
+ }
+
+ window.SourceInitialized -= eventHandler;
+ };
+
+ window.SourceInitialized += eventHandler;
+ }
+ else if (dependencyObject is Popup popup) {
+ var eventHandler = default(EventHandler);
+
+ eventHandler = (s, e) => {
+ if (GetWindowHwndSource(popup) is HwndSource hwndSource) {
+ action?.Invoke(dependencyObject, hwndSource);
+ }
+
+ popup.Opened -= eventHandler;
+ };
+
+ popup.Opened += eventHandler;
+ }
+ else if (dependencyObject is ToolTip tooltip) {
+ var eventHandler = default(RoutedEventHandler);
+
+ eventHandler = (s, e) => {
+ if (GetWindowHwndSource(tooltip) is HwndSource hwndSource) {
+ action?.Invoke(dependencyObject, hwndSource);
+ }
+
+ tooltip.Opened -= eventHandler;
+ };
+
+ tooltip.Opened += eventHandler;
+ }
+ else {
+ throw new NotSupportedException("Invalid dependency object");
+ }
+ }
+
+ private static void DoAfterWindowSourceInitialized(Window window, Action action) {
+ var eventHandler = default(EventHandler);
+
+ eventHandler = (s, e) => {
+ action?.Invoke();
+ window.SourceInitialized -= eventHandler;
+ };
+
+ window.SourceInitialized += eventHandler;
+ }
+
+ private static void DoAfterElementLoaded(FrameworkElement element, Action action) {
+ var eventHandler = default(RoutedEventHandler);
+
+ eventHandler = (s, e) => {
+ action?.Invoke();
+ element.Loaded -= eventHandler;
+ };
+
+ element.Loaded += eventHandler;
+ }
+
+ private static bool HasWindowCaptionButton(nint hwnd) {
+ if (s_minimumButtons is not null && s_minimumButtons.ContainsKey(hwnd))
+ return true;
+ if (s_maximumButtons is not null && s_maximumButtons.ContainsKey(hwnd))
+ return true;
+ if (s_closeButtons is not null && s_closeButtons.ContainsKey(hwnd))
+ return true;
+
+ return false;
+ }
+
+ #endregion
+
+
+ #region Final Logic
+
+ private static unsafe void ApplyBackdrop(Window? window, HwndSource hwndSource, WindowBackdrop backdrop) {
+ // this api is only available on windows 11 22621
+ if (s_versionCurrentWindows < s_versionWindows11_22621)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ // prepare composition background
+ if (backdrop != WindowBackdrop.None &&
+ backdrop != WindowBackdrop.Auto) {
+ hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
+ }
+ else {
+ hwndSource.CompositionTarget.BackgroundColor = Colors.Black;
+ }
+
+ // prepare glass frame
+ if (window is null ||
+ WindowChrome.GetWindowChrome(window) is null) {
+ // TODO: 实现脱离 WindowChrome 的 Blur backdrop 效果
+ var margins = new Margins() {
+ LeftWidth = -1,
+ RightWidth = -1,
+ TopHeight = -1,
+ BottomHeight = -1
+ };
+
+ DwmExtendFrameIntoClientArea(handle, ref margins);
+ }
+
+ // set backdrop
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.SYSTEMBACKDROP_TYPE, (nint)(void*)&backdrop, (uint)sizeof(WindowBackdrop));
+
+ // draw
+ SetWindowPos(
+ handle, IntPtr.Zero, 0, 0, 0, 0,
+ SetWindowPosFlags.DRAWFRAME | SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOOWNERZORDER | SetWindowPosFlags.NOSIZE | SetWindowPosFlags.NOZORDER);
+ }
+
+ private static unsafe void ApplyCorner(Window? window, HwndSource hwndSource, WindowCorner corner) {
+ // this api is only available on windows 11 22000
+ if (s_versionCurrentWindows < s_versionWindows11_22000)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.WINDOW_CORNER_PREFERENCE, (nint)(void*)&corner, (uint)sizeof(WindowCorner));
+ }
+
+ private static unsafe void ApplyCaptionColor(Window? window, HwndSource hwndSource, WindowOptionColor color) {
+ // this api is only available on windows 11 22000
+ if (s_versionCurrentWindows < s_versionWindows11_22000)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.CAPTION_COLOR, (nint)(void*)&color, (uint)sizeof(WindowOptionColor));
+ }
+
+ private static unsafe void ApplyTextColor(Window? window, HwndSource hwndSource, WindowOptionColor color) {
+ // this api is only available on windows 11 22000
+ if (s_versionCurrentWindows < s_versionWindows11_22000)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.TEXT_COLOR, (nint)(void*)&color, (uint)sizeof(WindowOptionColor));
+ }
+
+ private static unsafe void ApplyBorderColor(Window? window, HwndSource hwndSource, WindowOptionColor color) {
+ // this api is only available on windows 11 22000
+ if (s_versionCurrentWindows < s_versionWindows11_22000)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.BORDER_COLOR, (nint)(void*)&color, (uint)sizeof(WindowOptionColor));
+ }
+
+ private static unsafe void ApplyDarkMode(Window? window, HwndSource hwndSource, bool isDarkMode) {
+ // this api is only available on windows 11
+ if (s_versionCurrentWindows < s_versionWindows11_22000)
+ return;
+
+ var handle = hwndSource.Handle;
+
+ DwmSetWindowAttribute(handle, DwmWindowAttribute.USE_IMMERSIVE_DARK_MODE, (nint)(void*)&isDarkMode, (uint)sizeof(WindowBackdrop));
+ }
+
+ private static unsafe void ApplyAccent(Window? window, HwndSource hwndSource, WindowAccentState accentState, Color gradientColor, bool addBorder) {
+ var handle = hwndSource.Handle;
+
+ var accentPolicy = new AccentPolicy() {
+ AccentState = (AccentState)accentState,
+ AccentFlags = addBorder ? AccentFlags.AllBorder : 0,
+ GradientColor = CreateColorInteger(gradientColor)
+ };
+
+ var windowCompositionAttributeData = new WindowCompositionAttributeData() {
+ Attribute = WindowCompositionAttribute.WcaAccentPolicy,
+ DataPointer = (nint)(void*)&accentPolicy,
+ DataSize = (uint)sizeof(AccentPolicy),
+ };
+
+ // prepare composition background
+ if (accentState != WindowAccentState.None) {
+ hwndSource.CompositionTarget.BackgroundColor = Colors.Transparent;
+ }
+ else {
+ hwndSource.CompositionTarget.BackgroundColor = Colors.Black;
+ }
+
+ // prepare glass frame thickness
+ if (window is null ||
+ WindowChrome.GetWindowChrome(window) is null) {
+ var margins = new Margins() {
+ LeftWidth = 0,
+ RightWidth = 0,
+ TopHeight = 0,
+ BottomHeight = 0
+ };
+
+ // clear margins, composition accent only visible when margin is 0
+ DwmExtendFrameIntoClientArea(handle, ref margins);
+ }
+
+ // set composition
+ SetWindowCompositionAttribute(handle, ref windowCompositionAttributeData);
+
+ // refresh
+ SetWindowPos(
+ handle, IntPtr.Zero, 0, 0, 0, 0,
+ SetWindowPosFlags.DRAWFRAME | SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOOWNERZORDER | SetWindowPosFlags.NOSIZE | SetWindowPosFlags.NOZORDER);
+ }
+
+ private static unsafe void ApplyIsCaptionVisible(Window? window, HwndSource hwndSource, bool isCaptionVisible) {
+ var handle = hwndSource.Handle;
+
+ var oldPtr = NativeDefinition.GetWindowLongPtr(handle, NativeDefinition.GWL_STYLE);
+ var newPtr = isCaptionVisible ?
+ oldPtr | NativeDefinition.WS_CAPTION :
+ oldPtr & ~NativeDefinition.WS_CAPTION;
+
+ NativeDefinition.SetWindowLongPtr(handle, NativeDefinition.GWL_STYLE, newPtr);
+
+ // refresh
+ SetWindowPos(
+ handle, IntPtr.Zero, 0, 0, 0, 0,
+ SetWindowPosFlags.DRAWFRAME | SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOOWNERZORDER | SetWindowPosFlags.NOSIZE | SetWindowPosFlags.NOZORDER);
+ }
+
+ private static unsafe void ApplyIsCaptionMenuVisible(Window? window, HwndSource hwndSource, bool isCaptionMenuVisible) {
+ var handle = hwndSource.Handle;
+
+ var oldPtr = NativeDefinition.GetWindowLongPtr(handle, NativeDefinition.GWL_STYLE);
+ var newPtr = isCaptionMenuVisible ?
+ oldPtr | NativeDefinition.WS_SYSMENU :
+ oldPtr & ~NativeDefinition.WS_SYSMENU;
+
+ NativeDefinition.SetWindowLongPtr(handle, NativeDefinition.GWL_STYLE, newPtr);
+
+ // refresh
+ SetWindowPos(
+ handle, IntPtr.Zero, 0, 0, 0, 0,
+ SetWindowPosFlags.DRAWFRAME | SetWindowPosFlags.NOACTIVATE | SetWindowPosFlags.NOMOVE | SetWindowPosFlags.NOOWNERZORDER | SetWindowPosFlags.NOSIZE | SetWindowPosFlags.NOZORDER);
+ }
+
+ private static unsafe void ApplyIsMaximumButton(Window window, Visual visual, bool isMaximumButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isMaximumButton) {
+ if (s_maximumButtons is null) {
+ s_maximumButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_maximumButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MaximumButton is already set to another Visual");
+ }
+
+ s_maximumButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_maximumButtons is null) {
+ return;
+ }
+
+ s_maximumButtons.Remove(windowHandle);
+
+ if (s_maximumButtons.Count == 0) {
+ s_maximumButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ private static unsafe void ApplyIsMinimumButton(Window window, Visual visual, bool isMinimumButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isMinimumButton) {
+ if (s_minimumButtons is null) {
+ s_minimumButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_minimumButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MinimumButton is already set to another Visual");
+ }
+
+ s_minimumButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_minimumButtons is null) {
+ return;
+ }
+
+ s_minimumButtons.Remove(windowHandle);
+
+ if (s_minimumButtons.Count == 0) {
+ s_minimumButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ private static unsafe void ApplyIsCloseButton(Window window, Visual visual, bool isCloseButton) {
+ var windowInteropHelper = new WindowInteropHelper(window);
+ var windowHandle = windowInteropHelper.EnsureHandle();
+
+ var hwndSource = HwndSource.FromHwnd(windowHandle);
+
+ if (isCloseButton) {
+ if (s_closeButtons is null) {
+ s_closeButtons = new();
+ }
+
+ if (HasWindowCaptionButton(windowHandle)) {
+ hwndSource.AddHook(WindowCaptionButtonsInteropHook);
+ }
+
+ if (s_closeButtons.ContainsKey(windowHandle)) {
+ throw new InvalidOperationException("MinimumButton is already set to another Visual");
+ }
+
+ s_closeButtons[windowHandle] = visual;
+ }
+ else {
+ if (s_closeButtons is null) {
+ return;
+ }
+
+ s_closeButtons.Remove(windowHandle);
+
+ if (s_closeButtons.Count == 0) {
+ s_closeButtons = null;
+ }
+
+ if (!HasWindowCaptionButton(windowHandle)) {
+ hwndSource.RemoveHook(WindowCaptionButtonsInteropHook);
+ }
+ }
+ }
+
+ private static void EventHandlerApplyBackdrop(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyBackdrop(sender as Window, hwndSource, GetBackdrop(d));
+ }
+
+ private static void EventHandlerApplyCorner(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyCorner(sender as Window, hwndSource, GetCorner(d));
+ }
+
+ private static void EventHandlerApplyCaptionColor(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyCaptionColor(sender as Window, hwndSource, GetCaptionColor(d));
+ }
+
+ private static void EventHandlerApplyTextColor(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyTextColor(sender as Window, hwndSource, GetTextColor(d));
+ }
+
+ private static void EventHandlerApplyBorderColor(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyBorderColor(sender as Window, hwndSource, GetBorderColor(d));
+ }
+
+ private static void EventHandlerApplyDarkMode(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyDarkMode(sender as Window, hwndSource, GetIsDarkMode(d));
+ }
+
+ private static void EventHandlerApplyAccent(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyAccent(sender as Window, hwndSource, GetAccentState(d), GetAccentGradientColor(d), GetAccentBorder(d));
+ }
+
+ private static void EventHandlerApplyIsCaptionVisible(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyIsCaptionVisible(sender as Window, hwndSource, GetIsCaptionVisible(d));
+ }
+
+ private static void EventHandlerApplyIsCaptionMenuVisible(object? sender, EventArgs e) {
+ if (sender is DependencyObject d &&
+ GetWindowHwndSource(d) is HwndSource hwndSource)
+ ApplyIsCaptionMenuVisible(sender as Window, hwndSource, GetIsCaptionMenuVisible(d));
+ }
+
+ #endregion
+ }
+}
diff --git a/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOptionColor.cs b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOptionColor.cs
new file mode 100644
index 0000000000..c0e8ccac93
--- /dev/null
+++ b/dnSpy/dnSpy/Lib/EleCho.WpfSuite/Utilities/WindowOptionColor.cs
@@ -0,0 +1,122 @@
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Windows.Media;
+
+namespace EleCho.WpfSuite
+{
+ ///
+ /// Color for WindowOption
+ ///
+ [TypeConverter(typeof(WindowOptionColorConverter))]
+ [StructLayout(LayoutKind.Sequential)]
+ public struct WindowOptionColor
+ {
+ private int _value;
+
+ ///
+ /// Integer value
+ ///
+ public int Value
+ {
+ get => _value;
+ set => _value = value;
+ }
+
+ ///
+ /// Red channel value
+ ///
+ public byte R
+ {
+ get => (byte)(_value);
+ set => _value = (_value & ~0xFF) + value;
+ }
+
+ ///
+ /// Green channel value
+ ///
+ public byte G
+ {
+ get => (byte)(_value >> 8);
+ set => _value = (_value & ~0xFF00) + (value << 8);
+ }
+
+ ///
+ /// Blue channel value
+ ///
+ public byte B
+ {
+ get => (byte)(_value >> 16);
+ set => _value = (_value & ~0xFF0000) + (value << 16);
+ }
+
+ ///
+ /// Extra byte
+ ///
+ public byte Extra
+ {
+ get => (byte)(_value >> 24);
+ set => _value = (int)(_value & ~0xFF000000) + (value << 24);
+ }
+
+ ///
+ /// Create an empty window option color
+ ///
+ public WindowOptionColor()
+ {
+
+ }
+
+ ///
+ /// Create a window option color with specified channel values
+ ///
+ /// Red channel value
+ /// Green channel value
+ /// Blue channel value
+ public WindowOptionColor(byte r, byte g, byte b) : this(r, g, b, 0xFF)
+ { }
+
+ ///
+ /// Create a window option color with specified channel values and extra byte
+ ///
+ /// Red channel value
+ /// Green channel value
+ /// Blue channel value
+ /// Extra byte
+ public WindowOptionColor(byte r, byte g, byte b, byte extra)
+ {
+ _value = r | g << 8 | b << 16 | extra << 24;
+ }
+
+ ///
+ /// Create a window option color with specified value
+ ///
+ ///
+ public WindowOptionColor(int value)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Default color
+ ///
+ public static WindowOptionColor Default { get; } = new WindowOptionColor(unchecked((int)0xFFFFFFFF));
+
+ ///
+ /// No color
+ ///
+ public static WindowOptionColor None { get; } = new WindowOptionColor(unchecked((int)0xFFFFFFFE));
+
+
+ ///
+ /// Convert window option color to color
+ ///
+ ///
+ public static implicit operator Color(WindowOptionColor color) => Color.FromArgb(0xFF, color.R, color.G, color.B);
+
+ ///
+ /// Convert color to window option color
+ ///
+ ///
+ public static implicit operator WindowOptionColor(Color color) => new WindowOptionColor(color.R, color.G, color.B, 0);
+ }
+}
diff --git a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
index 3441d21d5e..22538ebd82 100644
--- a/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
+++ b/dnSpy/dnSpy/Themes/wpf.styles.templates.xaml
@@ -23,7 +23,7 @@
xmlns:disasmviewersettings="clr-namespace:dnSpy.Disassembly.Viewer"
xmlns:bm="clr-namespace:dnSpy.Bookmarks.Settings"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:ws="https://schemas.elecho.dev/wpfsuite">
+ xmlns:ws="clr-namespace:EleCho.WpfSuite">