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">