using WPFluent.Extensions; using WPFluent.Interop; using System.Windows.Automation.Peers; using System.Windows.Automation.Provider; // ReSharper disable once CheckNamespace namespace WPFluent.Controls; public class TitleBarButton : Button { /// /// Identifies the dependency property. /// public static readonly DependencyProperty ButtonsForegroundProperty = DependencyProperty.Register( nameof(ButtonsForeground), typeof(Brush), typeof(TitleBarButton), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ButtonTypeProperty = DependencyProperty.Register( nameof(ButtonType), typeof(TitleBarButtonType), typeof(TitleBarButton), new PropertyMetadata(TitleBarButtonType.Unknown, OnButtonTypeChanged)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty MouseOverButtonsForegroundProperty = DependencyProperty.Register( nameof(MouseOverButtonsForeground), typeof(Brush), typeof(TitleBarButton), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty RenderButtonsForegroundProperty = DependencyProperty.Register( nameof(RenderButtonsForeground), typeof(Brush), typeof(TitleBarButton), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits)); private readonly Brush _defaultBackgroundBrush = Brushes.Transparent; // REVIEW: Should it be transparent? private bool _isClickedDown; private User32.WM_NCHITTEST _returnValue; public TitleBarButton() { Loaded += TitleBarButton_Loaded; Unloaded += TitleBarButton_Unloaded; } private void OnButtonsForegroundChanged(object sender, EventArgs e) { SetCurrentValue(RenderButtonsForegroundProperty, IsHovered ? MouseOverButtonsForeground : ButtonsForeground); } private static void OnButtonTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if(d is not TitleBarButton titleBarButton) { return; } titleBarButton.OnButtonTypeChanged(e); } private void TitleBarButton_Loaded(object sender, RoutedEventArgs e) { SetCurrentValue(RenderButtonsForegroundProperty, ButtonsForeground); DependencyPropertyDescriptor .FromProperty(ButtonsForegroundProperty, typeof(Brush)) .AddValueChanged(this, OnButtonsForegroundChanged); } private void TitleBarButton_Unloaded(object sender, RoutedEventArgs e) { DependencyPropertyDescriptor .FromProperty(ButtonsForegroundProperty, typeof(Brush)) .RemoveValueChanged(this, OnButtonsForegroundChanged); } protected void OnButtonTypeChanged(DependencyPropertyChangedEventArgs e) { var buttonType = (TitleBarButtonType)e.NewValue; _returnValue = buttonType switch { TitleBarButtonType.Unknown => User32.WM_NCHITTEST.HTNOWHERE, TitleBarButtonType.Help => User32.WM_NCHITTEST.HTHELP, TitleBarButtonType.Minimize => User32.WM_NCHITTEST.HTMINBUTTON, TitleBarButtonType.Close => User32.WM_NCHITTEST.HTCLOSE, TitleBarButtonType.Restore => User32.WM_NCHITTEST.HTMAXBUTTON, TitleBarButtonType.Maximize => User32.WM_NCHITTEST.HTMAXBUTTON, _ => throw new ArgumentOutOfRangeException( "e.NewValue", buttonType, $"Unsupported button type: {buttonType}."), }; } internal bool ReactToHwndHook(User32.WM msg, IntPtr lParam, out IntPtr returnIntPtr) { returnIntPtr = IntPtr.Zero; switch(msg) { case User32.WM.NCHITTEST: if(this.IsMouseOverElement(lParam)) { /*Debug.WriteLine($"Hitting {ButtonType} | return code {_returnValue}");*/ Hover(); returnIntPtr = (IntPtr)_returnValue; return true; } RemoveHover(); return false; case User32.WM.NCMOUSELEAVE: // Mouse leaves the window RemoveHover(); return false; case User32.WM.NCLBUTTONDOWN when this.IsMouseOverElement(lParam): // Left button clicked down _isClickedDown = true; return true; case User32.WM.NCLBUTTONUP when _isClickedDown && this.IsMouseOverElement(lParam): // Left button clicked up InvokeClick(); return true; default: return false; } } /// /// Forces button background to change. /// public void Hover() { if(IsHovered) { return; } SetCurrentValue(BackgroundProperty, MouseOverBackground); if(MouseOverButtonsForeground != null) { SetCurrentValue(RenderButtonsForegroundProperty, MouseOverButtonsForeground); } IsHovered = true; } /// /// Invokes click on the button. /// public void InvokeClick() { if(new ButtonAutomationPeer(this).GetPattern(PatternInterface.Invoke) is IInvokeProvider invokeProvider) { invokeProvider.Invoke(); } _isClickedDown = false; } /// /// Forces button background to change. /// public void RemoveHover() { if(!IsHovered) { return; } SetCurrentValue(BackgroundProperty, _defaultBackgroundBrush); SetCurrentValue(RenderButtonsForegroundProperty, ButtonsForeground); IsHovered = false; _isClickedDown = false; } /// /// Gets or sets the foreground of the navigation buttons. /// public Brush ButtonsForeground { get => (Brush)GetValue(ButtonsForegroundProperty); set => SetValue(ButtonsForegroundProperty, value); } /// /// Gets or sets the type of the button. /// public TitleBarButtonType ButtonType { get => (TitleBarButtonType)GetValue(ButtonTypeProperty); set => SetValue(ButtonTypeProperty, value); } public bool IsHovered { get; private set; } /// /// Gets or sets the foreground of the navigation buttons when moused over. /// public Brush MouseOverButtonsForeground { get => (Brush)GetValue(MouseOverButtonsForegroundProperty); set => SetValue(MouseOverButtonsForegroundProperty, value); } public Brush RenderButtonsForeground { get => (Brush)GetValue(RenderButtonsForegroundProperty); set => SetValue(RenderButtonsForegroundProperty, value); } }