diff --git a/src/Wpf.Ui/Controls/TitleBar.cs b/src/Wpf.Ui/Controls/TitleBar.cs
index f95153e60..0535b4228 100644
--- a/src/Wpf.Ui/Controls/TitleBar.cs
+++ b/src/Wpf.Ui/Controls/TitleBar.cs
@@ -5,33 +5,39 @@
using System;
using System.ComponentModel;
+using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
+using System.Windows.Interop;
using System.Windows.Media;
+using Wpf.Ui.Common;
using Wpf.Ui.Dpi;
-using Wpf.Ui.TitleBar;
+using Wpf.Ui.Extensions;
+using Wpf.Ui.Interop;
namespace Wpf.Ui.Controls;
///
/// Custom navigation buttons for the window.
///
-[TemplatePart(Name = "PART_MainGrid", Type = typeof(System.Windows.Controls.Grid))]
-[TemplatePart(Name = "PART_MaximizeButton", Type = typeof(Wpf.Ui.Controls.Button))]
-[TemplatePart(Name = "PART_RestoreButton", Type = typeof(Wpf.Ui.Controls.Button))]
+[TemplatePart(Name = ElementMainGrid, Type = typeof(System.Windows.Controls.Grid))]
+[TemplatePart(Name = ElementIcon, Type = typeof(System.Windows.Controls.Image))]
+[TemplatePart(Name = ElementHelpButton, Type = typeof(TitleBarButton))]
+[TemplatePart(Name = ElementMinimizeButton, Type = typeof(TitleBarButton))]
+[TemplatePart(Name = ElementMaximizeButton, Type = typeof(TitleBarButton))]
+[TemplatePart(Name = ElementRestoreButton, Type = typeof(TitleBarButton))]
+[TemplatePart(Name = ElementCloseButton, Type = typeof(TitleBarButton))]
public class TitleBar : System.Windows.Controls.Control, IThemeControl
{
+ private const string ElementIcon = "PART_Icon";
private const string ElementMainGrid = "PART_MainGrid";
-
+ private const string ElementHelpButton = "PART_HelpButton";
+ private const string ElementMinimizeButton = "PART_MinimizeButton";
private const string ElementMaximizeButton = "PART_MaximizeButton";
-
private const string ElementRestoreButton = "PART_RestoreButton";
+ private const string ElementCloseButton = "PART_CloseButton";
- private System.Windows.Window _parent;
-
- internal Interop.WinDef.POINT _doubleClickPoint;
-
- internal SnapLayout _snapLayout;
+ #region Static properties
///
/// Property for .
@@ -74,13 +80,6 @@ public class TitleBar : System.Windows.Controls.Control, IThemeControl
nameof(MinimizeToTray),
typeof(bool), typeof(TitleBar), new PropertyMetadata(false));
- ///
- /// Property for .
- ///
- public static readonly DependencyProperty UseSnapLayoutProperty = DependencyProperty.Register(
- nameof(UseSnapLayout),
- typeof(bool), typeof(TitleBar), new PropertyMetadata(false));
-
///
/// Property for .
///
@@ -136,6 +135,13 @@ public class TitleBar : System.Windows.Controls.Control, IThemeControl
nameof(Icon),
typeof(ImageSource), typeof(TitleBar), new PropertyMetadata(null));
+ ///
+ /// Property for .
+ ///
+ public static readonly DependencyProperty CloseWindowByDoubleClickOnIconProperty = DependencyProperty.Register(
+ nameof(CloseWindowByDoubleClickOnIcon),
+ typeof(bool), typeof(TitleBar), new PropertyMetadata(true));
+
///
/// Property for .
///
@@ -174,6 +180,10 @@ public class TitleBar : System.Windows.Controls.Control, IThemeControl
DependencyProperty.Register(nameof(TemplateButtonCommand),
typeof(Common.IRelayCommand), typeof(TitleBar), new PropertyMetadata(null));
+ #endregion
+
+ #region Properties
+
///
public Appearance.ThemeType Theme
{
@@ -228,15 +238,6 @@ public bool MinimizeToTray
set => SetValue(MinimizeToTrayProperty, value);
}
- ///
- /// Gets or sets information whether the use Windows 11 Snap Layout.
- ///
- public bool UseSnapLayout
- {
- get => (bool)GetValue(UseSnapLayoutProperty);
- set => SetValue(UseSnapLayoutProperty, value);
- }
-
///
/// Gets or sets information whether the current window is maximized.
///
@@ -309,6 +310,15 @@ public ImageSource Icon
set => SetValue(IconProperty, value);
}
+ ///
+ /// Enables or disable closing the window by double clicking on the icon
+ ///
+ public bool CloseWindowByDoubleClickOnIcon
+ {
+ get => (bool)GetValue(CloseWindowByDoubleClickOnIconProperty);
+ set => SetValue(CloseWindowByDoubleClickOnIconProperty, value);
+ }
+
///
/// Tray icon.
///
@@ -357,31 +367,35 @@ public event RoutedEventHandler HelpClicked
///
/// Command triggered after clicking the titlebar button.
///
- public Common.IRelayCommand TemplateButtonCommand => (Common.IRelayCommand)GetValue(TemplateButtonCommandProperty);
+ public IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
///
/// Lets you override the behavior of the Maximize/Restore button with an .
///
- public Action MaximizeActionOverride { get; set; } = null!;
+ public Action? MaximizeActionOverride { get; set; }
///
/// Lets you override the behavior of the Minimize button with an .
///
- public Action MinimizeActionOverride { get; set; } = null!;
+ public Action? MinimizeActionOverride { get; set; }
- ///
- /// Window containing the TitleBar.
- ///
- internal System.Windows.Window ParentWindow => _parent ??= System.Windows.Window.GetWindow(this);
+ #endregion
+
+ private Interop.WinDef.POINT _doubleClickPoint;
+ private System.Windows.Window _currentWindow = null!;
+ private System.Windows.Controls.Grid _mainGrid = null!;
+ private System.Windows.Controls.Image _icon = null!;
+ private readonly TitleBarButton[] _buttons = new TitleBarButton[4];
///
/// Creates a new instance of the class and sets the default event.
///
public TitleBar()
{
- SetValue(TemplateButtonCommandProperty, new Common.RelayCommand(o => OnTemplateButtonClick(o ?? String.Empty)));
+ SetValue(TemplateButtonCommandProperty, new Common.RelayCommand(OnTemplateButtonClick));
Loaded += OnLoaded;
+ Unloaded += OnUnloaded;
}
///
@@ -395,8 +409,26 @@ protected override void OnInitialized(EventArgs e)
protected virtual void OnLoaded(object sender, RoutedEventArgs e)
{
- if (ParentWindow != null)
- ParentWindow.StateChanged += OnParentWindowStateChanged;
+ if (DesignerHelper.IsInDesignMode)
+ return;
+
+ _currentWindow = System.Windows.Window.GetWindow(this) ?? throw new ArgumentNullException("Window is null");
+ _currentWindow.StateChanged += OnParentWindowStateChanged;
+
+ var handle = new WindowInteropHelper(_currentWindow).EnsureHandle();
+ var windowSource = HwndSource.FromHwnd(handle) ?? throw new ArgumentNullException("Window source is null");
+ windowSource.AddHook(HwndSourceHook);
+ }
+
+ private void OnUnloaded(object sender, RoutedEventArgs e)
+ {
+ Loaded -= OnLoaded;
+ Unloaded -= OnUnloaded;
+
+ Appearance.Theme.Changed -= OnThemeChanged;
+
+ _mainGrid.MouseLeftButtonDown -= OnMainGridMouseLeftButtonDown;
+ _mainGrid.MouseMove -= OnMainGridMouseMove;
}
///
@@ -407,18 +439,21 @@ public override void OnApplyTemplate()
{
base.OnApplyTemplate();
- var mainGrid = GetTemplateChild(ElementMainGrid) as System.Windows.Controls.Grid;
- var maximizeButton = GetTemplateChild(ElementMaximizeButton) as Wpf.Ui.Controls.Button;
- var restoreButton = GetTemplateChild(ElementRestoreButton) as Wpf.Ui.Controls.Button;
+ _mainGrid = GetTemplateChild(ElementMainGrid);
+ _mainGrid.MouseLeftButtonDown += OnMainGridMouseLeftButtonDown;
+ _mainGrid.MouseMove += OnMainGridMouseMove;
- if (mainGrid != null)
- {
- mainGrid.MouseLeftButtonDown += OnMainGridMouseLeftButtonDown;
- mainGrid.MouseMove += OnMainGridMouseMove;
- }
+ _icon = GetTemplateChild(ElementIcon);
+
+ var helpButton = GetTemplateChild(ElementHelpButton);
+ var minimizeButton = GetTemplateChild(ElementMinimizeButton);
+ var maximizeButton = GetTemplateChild(ElementMaximizeButton);
+ var closeButton = GetTemplateChild(ElementCloseButton);
- if (ShowMaximize && UseSnapLayout && maximizeButton != null && restoreButton != null)
- InitializeSnapLayout(maximizeButton, restoreButton);
+ _buttons[0] = maximizeButton;
+ _buttons[1] = minimizeButton;
+ _buttons[2] = closeButton;
+ _buttons[3] = helpButton;
}
///
@@ -426,31 +461,24 @@ public override void OnApplyTemplate()
///
protected virtual void OnThemeChanged(Appearance.ThemeType currentTheme, Color systemAccent)
{
-#if DEBUG
- System.Diagnostics.Debug.WriteLine($"INFO | {typeof(TitleBar)} received theme - {currentTheme}",
+ Debug.WriteLine($"INFO | {typeof(TitleBar)} received theme - {currentTheme}",
"Wpf.Ui.TitleBar");
-#endif
- Theme = currentTheme;
- if (_snapLayout != null)
- _snapLayout.Theme = currentTheme;
+ Theme = currentTheme;
}
private void CloseWindow()
{
-#if DEBUG
- System.Diagnostics.Debug.WriteLine($"INFO | {typeof(TitleBar)}.CloseWindow:ForceShutdown - {ForceShutdown}",
+ Debug.WriteLine($"INFO | {typeof(TitleBar)}.CloseWindow:ForceShutdown - {ForceShutdown}",
"Wpf.Ui.TitleBar");
-#endif
if (ForceShutdown)
{
Application.Current.Shutdown();
-
return;
}
- Appearance.Theme.Changed -= OnThemeChanged;
- ParentWindow.Close();
+
+ _currentWindow.Close();
}
private void MinimizeWindow()
@@ -458,14 +486,14 @@ private void MinimizeWindow()
if (MinimizeToTray && Tray.IsRegistered && MinimizeWindowToTray())
return;
- if (MinimizeActionOverride != null)
+ if (MinimizeActionOverride is not null)
{
- MinimizeActionOverride(this, _parent);
+ MinimizeActionOverride(this, _currentWindow);
return;
}
- ParentWindow.WindowState = WindowState.Minimized;
+ _currentWindow.WindowState = WindowState.Minimized;
}
private void MaximizeWindow()
@@ -473,22 +501,22 @@ private void MaximizeWindow()
if (!CanMaximize)
return;
- if (MaximizeActionOverride != null)
+ if (MaximizeActionOverride is not null)
{
- MaximizeActionOverride(this, _parent);
+ MaximizeActionOverride(this, _currentWindow);
return;
}
- if (ParentWindow.WindowState == WindowState.Normal)
+ if (_currentWindow.WindowState == WindowState.Normal)
{
IsMaximized = true;
- ParentWindow.WindowState = WindowState.Maximized;
+ _currentWindow.WindowState = WindowState.Maximized;
}
else
{
IsMaximized = false;
- ParentWindow.WindowState = WindowState.Normal;
+ _currentWindow.WindowState = WindowState.Normal;
}
}
@@ -497,22 +525,22 @@ private void RestoreWindow()
if (!CanMaximize)
return;
- if (MaximizeActionOverride != null)
+ if (MaximizeActionOverride is not null)
{
- MaximizeActionOverride(this, _parent);
+ MaximizeActionOverride(this, _currentWindow);
return;
}
- if (ParentWindow.WindowState == WindowState.Normal)
+ if (_currentWindow.WindowState == WindowState.Normal)
{
IsMaximized = true;
- ParentWindow.WindowState = WindowState.Maximized;
+ _currentWindow.WindowState = WindowState.Maximized;
}
else
{
IsMaximized = false;
- ParentWindow.WindowState = WindowState.Normal;
+ _currentWindow.WindowState = WindowState.Normal;
}
}
@@ -521,44 +549,19 @@ private bool MinimizeWindowToTray()
if (!Tray.IsRegistered)
return false;
- ParentWindow.WindowState = WindowState.Minimized;
- ParentWindow.Hide();
+ _currentWindow.WindowState = WindowState.Minimized;
+ _currentWindow.Hide();
return true;
}
- private void InitializeSnapLayout(Wpf.Ui.Controls.Button maximizeButton, Wpf.Ui.Controls.Button restoreButton)
- {
- if (!SnapLayout.IsSupported())
- return;
-
- _snapLayout = SnapLayout.Register(ParentWindow, maximizeButton, restoreButton);
-
- // Can be taken it from the Template, but honestly - a classic - TODO:
- // ButtonsBackground, but
- _snapLayout.HoverColorLight = new SolidColorBrush(Color.FromArgb(
- (byte)0x1A,
- (byte)0x00,
- (byte)0x00,
- (byte)0x00)
- );
- _snapLayout.HoverColorDark = new SolidColorBrush(Color.FromArgb(
- (byte)0x17,
- (byte)0xFF,
- (byte)0xFF,
- (byte)0xFF)
- );
-
- _snapLayout.Theme = Theme;
- }
-
private void OnMainGridMouseMove(object sender, MouseEventArgs e)
{
- if (e.LeftButton != MouseButtonState.Pressed || ParentWindow == null)
+ if (e.LeftButton != MouseButtonState.Pressed)
return;
// prevent firing from double clicking when the mouse never actually moved
- Interop.User32.GetCursorPos(out var currentMousePos);
+ User32.GetCursorPos(out var currentMousePos);
if (currentMousePos.x == _doubleClickPoint.x && currentMousePos.y == _doubleClickPoint.y)
return;
@@ -577,29 +580,26 @@ private void OnMainGridMouseMove(object sender, MouseEventArgs e)
// how the OS operates.
// - It should be set as a % (e.g. screen X / maximized width),
// then offset from the left to line up more naturally.
- ParentWindow.Left = screenPoint.X - (ParentWindow.RestoreBounds.Width * 0.5);
- ParentWindow.Top = screenPoint.Y;
+ _currentWindow.Left = screenPoint.X - (_currentWindow.RestoreBounds.Width * 0.5);
+ _currentWindow.Top = screenPoint.Y;
// style has to be quickly swapped to avoid restore animation delay
- var style = ParentWindow.WindowStyle;
- ParentWindow.WindowStyle = WindowStyle.None;
- ParentWindow.WindowState = WindowState.Normal;
- ParentWindow.WindowStyle = style;
+ var style = _currentWindow.WindowStyle;
+ _currentWindow.WindowStyle = WindowStyle.None;
+ _currentWindow.WindowState = WindowState.Normal;
+ _currentWindow.WindowStyle = style;
}
// Call drag move only when mouse down, check again
// if()
if (e.LeftButton == MouseButtonState.Pressed)
- ParentWindow.DragMove();
+ _currentWindow.DragMove();
}
private void OnParentWindowStateChanged(object sender, EventArgs e)
{
- if (ParentWindow == null)
- return;
-
- if (IsMaximized != (ParentWindow.WindowState == WindowState.Maximized))
- IsMaximized = ParentWindow.WindowState == WindowState.Maximized;
+ if (IsMaximized != (_currentWindow.WindowState == WindowState.Maximized))
+ IsMaximized = _currentWindow.WindowState == WindowState.Maximized;
}
private void OnMainGridMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -612,33 +612,86 @@ private void OnMainGridMouseLeftButtonDown(object sender, MouseButtonEventArgs e
MaximizeWindow();
}
- private void OnTemplateButtonClick(string parameter)
+ private void OnTemplateButtonClick(TitleBarButtonType buttonType)
{
- switch (parameter)
+ switch (buttonType)
{
- case "maximize":
+ case TitleBarButtonType.Maximize:
RaiseEvent(new RoutedEventArgs(MaximizeClickedEvent, this));
MaximizeWindow();
break;
- case "restore":
+ case TitleBarButtonType.Restore:
RaiseEvent(new RoutedEventArgs(MaximizeClickedEvent, this));
RestoreWindow();
break;
- case "close":
+ case TitleBarButtonType.Close:
RaiseEvent(new RoutedEventArgs(CloseClickedEvent, this));
CloseWindow();
break;
- case "minimize":
+ case TitleBarButtonType.Minimize:
RaiseEvent(new RoutedEventArgs(MinimizeClickedEvent, this));
MinimizeWindow();
break;
- case "help":
+ case TitleBarButtonType.Help:
RaiseEvent(new RoutedEventArgs(HelpClickedEvent, this));
break;
}
}
+
+ private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
+ {
+ var message = (User32.WM)msg;
+
+ if (message is not (User32.WM.NCHITTEST or User32.WM.NCMOUSELEAVE or User32.WM.NCLBUTTONDOWN or User32.WM.NCLBUTTONUP))
+ return IntPtr.Zero;
+
+ foreach (var button in _buttons)
+ {
+ if (!button.ReactToHwndHook(message, lParam, out var returnIntPtr))
+ continue;
+
+ //It happens that the background is not removed from the buttons and you can make all the buttons are in the IsHovered=true
+ //It cleans up
+ foreach (var anotherButton in _buttons)
+ {
+ if (anotherButton == button)
+ continue;
+
+ if (anotherButton.IsHovered && button.IsHovered)
+ {
+ anotherButton.RemoveHover();
+ }
+ }
+
+ handled = true;
+ return returnIntPtr;
+ }
+
+ switch (message)
+ {
+ case User32.WM.NCHITTEST when CloseWindowByDoubleClickOnIcon && _icon.IsMouseOverElement(lParam):
+ handled = true;
+ //Ideally, clicking on the icon should open the system menu, but when the system menu is opened manually, double-clicking on the icon does not close the window
+ return (IntPtr)User32.WM_NCHITTEST.HTSYSMENU;
+ case User32.WM.NCHITTEST when this.IsMouseOverElement(lParam):
+ handled = true;
+ return (IntPtr)User32.WM_NCHITTEST.HTCAPTION;
+ default:
+ return IntPtr.Zero;
+ }
+ }
+
+ private T GetTemplateChild(string name) where T : DependencyObject
+ {
+ var element = base.GetTemplateChild(name);
+
+ if (element is null)
+ throw new ArgumentNullException($"{name} is null");
+
+ return (T)element;
+ }
}
diff --git a/src/Wpf.Ui/Controls/TitleBarButton.cs b/src/Wpf.Ui/Controls/TitleBarButton.cs
new file mode 100644
index 000000000..0b818f5c0
--- /dev/null
+++ b/src/Wpf.Ui/Controls/TitleBarButton.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Automation.Peers;
+using System.Windows.Automation.Provider;
+using System.Windows.Media;
+using Wpf.Ui.Extensions;
+using Wpf.Ui.Interop;
+
+namespace Wpf.Ui.Controls;
+
+internal class TitleBarButton : Wpf.Ui.Controls.Button
+{
+
+ ///
+ /// Property for .
+ ///
+ public static readonly DependencyProperty ButtonTypeProperty = DependencyProperty.Register(nameof(ButtonType),
+ typeof(TitleBarButtonType), typeof(TitleBarButton), new PropertyMetadata(TitleBarButtonType.Unknown, ButtonTypePropertyCallback));
+
+ ///
+ /// Property for .
+ ///
+ public static readonly DependencyProperty ButtonsForegroundProperty = DependencyProperty.Register(
+ nameof(ButtonsForeground),
+ typeof(Brush), typeof(TitleBarButton), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush,
+ FrameworkPropertyMetadataOptions.Inherits));
+
+ ///
+ /// Sets or gets the
+ ///
+ public TitleBarButtonType ButtonType
+ {
+ get => (TitleBarButtonType)GetValue(ButtonTypeProperty);
+ set => SetValue(ButtonTypeProperty, value);
+ }
+
+ ///
+ /// Foreground of the navigation buttons.
+ ///
+ public Brush ButtonsForeground
+ {
+ get => (Brush)GetValue(ButtonsForegroundProperty);
+ set => SetValue(ButtonsForegroundProperty, value);
+ }
+
+ public bool IsHovered { get; private set; }
+
+ private User32.WM_NCHITTEST _returnValue;
+ private Brush _defaultBackgroundBrush = Brushes.Transparent; //Should it be transparent?
+
+ private bool _isClickedDown;
+
+ ///
+ /// Forces button background to change.
+ ///
+ public void Hover()
+ {
+ if (IsHovered)
+ return;
+
+ Background = MouseOverBackground;
+ IsHovered = true;
+ }
+
+ ///
+ /// Forces button background to change.
+ ///
+ public void RemoveHover()
+ {
+ if (!IsHovered)
+ return;
+
+ Background = _defaultBackgroundBrush;
+
+ IsHovered = false;
+ _isClickedDown = false;
+ }
+
+ ///
+ /// Invokes click on the button.
+ ///
+ public void InvokeClick()
+ {
+ if (new ButtonAutomationPeer(this).GetPattern(PatternInterface.Invoke) is IInvokeProvider invokeProvider)
+ invokeProvider.Invoke();
+
+ _isClickedDown = false;
+ }
+
+ 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;
+ }
+ }
+
+ private void UpdateReturnValue(TitleBarButtonType buttonType) =>
+ _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(nameof(buttonType), buttonType, null)
+ };
+
+ private static void ButtonTypePropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var titleBarButton = (TitleBarButton)d;
+ titleBarButton.UpdateReturnValue((TitleBarButtonType)e.NewValue);
+ }
+}
diff --git a/src/Wpf.Ui/TitleBar/TitleBarButton.cs b/src/Wpf.Ui/Controls/TitleBarButtonType.cs
similarity index 93%
rename from src/Wpf.Ui/TitleBar/TitleBarButton.cs
rename to src/Wpf.Ui/Controls/TitleBarButtonType.cs
index 7d02b9bfe..1e2196c3c 100644
--- a/src/Wpf.Ui/TitleBar/TitleBarButton.cs
+++ b/src/Wpf.Ui/Controls/TitleBarButtonType.cs
@@ -3,12 +3,12 @@
// Copyright (C) Leszek Pomianowski and WPF UI Contributors.
// All Rights Reserved.
-namespace Wpf.Ui.TitleBar;
+namespace Wpf.Ui.Controls;
///
/// Type of the Title Bar button.
///
-internal enum TitleBarButton
+public enum TitleBarButtonType
{
///
/// Unknown button.
diff --git a/src/Wpf.Ui/Extensions/UiElementExtensions.cs b/src/Wpf.Ui/Extensions/UiElementExtensions.cs
new file mode 100644
index 000000000..97e5fca0d
--- /dev/null
+++ b/src/Wpf.Ui/Extensions/UiElementExtensions.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Windows;
+
+namespace Wpf.Ui.Extensions;
+
+public static class UiElementExtensions
+{
+ ///
+ /// Do not call it outside of NCHITTEST, NCLBUTTONUP, NCLBUTTONDOWN messages!
+ ///
+ public static bool IsMouseOverElement(this UIElement element, IntPtr lParam)
+ {
+ // This method will be invoked very often and must be as simple as possible.
+
+ if (lParam == IntPtr.Zero)
+ return false;
+
+ var mousePosScreen = new Point(Get_X_LParam(lParam), Get_Y_LParam(lParam));
+ var bounds = new Rect(new Point(), element.RenderSize);
+ var mousePosRelative = element.PointFromScreen(mousePosScreen);
+ return bounds.Contains(mousePosRelative);
+ }
+
+ private static int Get_X_LParam(IntPtr lParam)
+ {
+ return (short)(lParam.ToInt32() & 0xFFFF);
+ }
+
+ private static int Get_Y_LParam(IntPtr lParam)
+ {
+ return (short)(lParam.ToInt32() >> 16);
+ }
+}
diff --git a/src/Wpf.Ui/Styles/Controls/TitleBar.xaml b/src/Wpf.Ui/Styles/Controls/TitleBar.xaml
index e0d1ee4b8..4dd48238b 100644
--- a/src/Wpf.Ui/Styles/Controls/TitleBar.xaml
+++ b/src/Wpf.Ui/Styles/Controls/TitleBar.xaml
@@ -10,60 +10,77 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Wpf.Ui.Controls">
-
-
-