Skip to content

Commit 4c4f776

Browse files
authored
Merge pull request #508 from ghost1372/Fix-TitleBar3
Use WASDK v1.4 new Custom TitleBar API, Simplify Calculating Rects and Fix TitleBar Drag Region for Scale 125%
2 parents 3b37fe5 + 3ef7c07 commit 4c4f776

File tree

5 files changed

+295
-91
lines changed

5 files changed

+295
-91
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Windows.ApplicationModel;
2+
using Windows.Storage;
3+
using Windows.System.Profile;
4+
5+
namespace CommunityToolkit.WinUI.Controls;
6+
internal static class InfoHelper
7+
{
8+
public static Version AppVersion { get; } = new Version(
9+
Package.Current.Id.Version.Major,
10+
Package.Current.Id.Version.Minor,
11+
Package.Current.Id.Version.Build,
12+
Package.Current.Id.Version.Revision
13+
);
14+
15+
public static Version SystemVersion { get; }
16+
17+
public static SystemDataPaths SystemDataPath { get; } = SystemDataPaths.GetDefault();
18+
19+
public static UserDataPaths UserDataPath { get; } = UserDataPaths.GetDefault();
20+
21+
public static string AppInstalledLocation { get; } = Package.Current.InstalledLocation.Path;
22+
23+
static InfoHelper()
24+
{
25+
string systemVersion = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;
26+
ulong version = ulong.Parse(systemVersion);
27+
SystemVersion = new Version(
28+
(int)((version & 0xFFFF000000000000L) >> 48),
29+
(int)((version & 0x0000FFFF00000000L) >> 32),
30+
(int)((version & 0x00000000FFFF0000L) >> 16),
31+
(int)(version & 0x000000000000FFFFL)
32+
);
33+
}
34+
}

components/TitleBar/src/NativeMethods.cs

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,84 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
2-
// The .NET Foundation licenses this file to you under the MIT license.
3-
// See the LICENSE file in the project root for more information.
4-
5-
#if WINAPPSDK
6-
using System.Runtime.InteropServices;
1+
using System.Runtime.InteropServices;
72

83
namespace CommunityToolkit.WinUI.Controls;
9-
104
internal static class NativeMethods
115
{
12-
[DllImport("Shcore.dll", SetLastError = true)]
13-
internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
6+
public enum WindowMessage : int
7+
{
8+
WM_NCLBUTTONDOWN = 0x00A1,
9+
WM_NCRBUTTONDOWN = 0x00A4,
10+
WM_SYSCOMMAND = 0x0112,
11+
WM_SYSMENU = 0x0313,
12+
WM_GETMINMAXINFO = 0x0024
13+
}
14+
[Flags]
15+
public enum WindowStyle : uint
16+
{
17+
WS_SYSMENU = 0x80000
18+
}
1419

15-
internal enum Monitor_DPI_Type : int
20+
[Flags]
21+
public enum WindowLongIndexFlags : int
1622
{
17-
MDT_Effective_DPI = 0,
18-
MDT_Angular_DPI = 1,
19-
MDT_Raw_DPI = 2,
20-
MDT_Default = MDT_Effective_DPI
23+
GWL_WNDPROC = -4,
24+
GWL_STYLE = -16
2125
}
26+
27+
[Flags]
28+
public enum SetWindowPosFlags : uint
29+
{
30+
/// <summary>
31+
/// Retains the current position (ignores X and Y parameters).
32+
/// </summary>
33+
SWP_NOMOVE = 0x0002
34+
}
35+
36+
public enum SystemCommand
37+
{
38+
SC_MOUSEMENU = 0xF090,
39+
SC_KEYMENU = 0xF100
40+
}
41+
42+
[DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = false)]
43+
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
44+
45+
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = false)]
46+
public static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex);
47+
48+
public static int GetWindowLongAuto(IntPtr hWnd, int nIndex)
49+
{
50+
if (IntPtr.Size is 8)
51+
{
52+
return GetWindowLongPtr(hWnd, nIndex);
53+
}
54+
else
55+
{
56+
return GetWindowLong(hWnd, nIndex);
57+
}
58+
}
59+
60+
[DllImport("user32.dll", EntryPoint = "FindWindowExW", SetLastError = true, CharSet = CharSet.Unicode)]
61+
public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);
62+
63+
64+
[DllImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = false)]
65+
public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
66+
67+
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)]
68+
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
69+
70+
public static IntPtr SetWindowLongAuto(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
71+
{
72+
if (IntPtr.Size is 8)
73+
{
74+
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
75+
}
76+
else
77+
{
78+
return SetWindowLong(hWnd, nIndex, dwNewLong);
79+
}
80+
}
81+
82+
[DllImport("user32.dll")]
83+
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage Msg, IntPtr wParam, IntPtr lParam);
2284
}
23-
#endif

components/TitleBar/src/TitleBar.WASDK.cs

Lines changed: 129 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,21 @@
44

55
#if WINAPPSDK
66
using Microsoft.UI;
7+
using Microsoft.UI.Input;
78
using Microsoft.UI.Windowing;
8-
using static CommunityToolkit.WinUI.Controls.NativeMethods;
9+
using Microsoft.UI.Xaml.Media;
910

1011
namespace CommunityToolkit.WinUI.Controls;
1112

12-
[TemplatePart(Name = nameof(PART_ButtonsHolderColumn), Type = typeof(ColumnDefinition))]
13-
[TemplatePart(Name = nameof(PART_IconColumn), Type = typeof(ColumnDefinition))]
14-
[TemplatePart(Name = nameof(PART_TitleColumn), Type = typeof(ColumnDefinition))]
15-
[TemplatePart(Name = nameof(PART_LeftDragColumn), Type = typeof(ColumnDefinition))]
16-
[TemplatePart(Name = nameof(PART_ContentColumn), Type = typeof(ColumnDefinition))]
17-
[TemplatePart(Name = nameof(PART_FooterColumn), Type = typeof(ColumnDefinition))]
18-
[TemplatePart(Name = nameof(PART_RightDragColumn), Type = typeof(ColumnDefinition))]
19-
[TemplatePart(Name = nameof(PART_TitleHolder), Type = typeof(StackPanel))]
20-
[TemplatePart(Name = nameof(PART_RootGrid), Type = typeof(Grid))]
13+
[TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))]
14+
[TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))]
2115

2216
public partial class TitleBar : Control
2317
{
24-
ColumnDefinition? PART_ButtonsHolderColumn;
25-
ColumnDefinition? PART_IconColumn;
26-
ColumnDefinition? PART_TitleColumn;
27-
ColumnDefinition? PART_LeftDragColumn;
28-
ColumnDefinition? PART_ContentColumn;
29-
ColumnDefinition? PART_FooterColumn;
30-
ColumnDefinition? PART_RightDragColumn;
31-
StackPanel? PART_TitleHolder;
32-
Grid? PART_RootGrid;
18+
WndProcHelper WndProcHelper;
19+
MenuFlyout MenuFlyout;
20+
ContentPresenter? PART_ContentPresenter;
21+
ContentPresenter? PART_FooterPresenter;
3322

3423
private void SetWASDKTitleBar()
3524
{
@@ -42,6 +31,16 @@ private void SetWASDKTitleBar()
4231
{
4332
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
4433

34+
if (this.ContextFlyout != null && this.ContextFlyout is MenuFlyout menuFlyout)
35+
{
36+
this.MenuFlyout = menuFlyout;
37+
WndProcHelper = new WndProcHelper(this.Window);
38+
WndProcHelper.RegisterWndProc(WindowWndProc);
39+
WndProcHelper.RegisterInputNonClientPointerSourceWndProc(InputNonClientPointerSourceWndProc);
40+
}
41+
42+
this.Window.SizeChanged -= Window_SizeChanged;
43+
this.Window.SizeChanged += Window_SizeChanged;
4544
this.Window.Activated -= Window_Activated;
4645
this.Window.Activated += Window_Activated;
4746

@@ -54,16 +53,8 @@ private void SetWASDKTitleBar()
5453
};
5554
}
5655

57-
// Set the width of padding columns in the UI.
58-
PART_ButtonsHolderColumn = GetTemplateChild(nameof(PART_ButtonsHolderColumn)) as ColumnDefinition;
59-
PART_IconColumn = GetTemplateChild(nameof(PART_IconColumn)) as ColumnDefinition;
60-
PART_TitleColumn = GetTemplateChild(nameof(PART_TitleColumn)) as ColumnDefinition;
61-
PART_LeftDragColumn = GetTemplateChild(nameof(PART_LeftDragColumn)) as ColumnDefinition;
62-
PART_ContentColumn = GetTemplateChild(nameof(PART_ContentColumn)) as ColumnDefinition;
63-
PART_RightDragColumn = GetTemplateChild(nameof(PART_RightDragColumn)) as ColumnDefinition;
64-
PART_FooterColumn = GetTemplateChild(nameof(PART_FooterColumn)) as ColumnDefinition;
65-
PART_TitleHolder = GetTemplateChild(nameof(PART_TitleHolder)) as StackPanel;
66-
PART_RootGrid = GetTemplateChild(nameof(PART_RootGrid)) as Grid;
56+
PART_ContentPresenter = GetTemplateChild(nameof(PART_ContentPresenter)) as ContentPresenter;
57+
PART_FooterPresenter = GetTemplateChild(nameof(PART_FooterPresenter)) as ContentPresenter;
6758

6859
// Get caption button occlusion information.
6960
int CaptionButtonOcclusionWidthRight = Window.AppWindow.TitleBar.RightInset;
@@ -87,6 +78,11 @@ private void SetWASDKTitleBar()
8778
}
8879
}
8980

81+
private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
82+
{
83+
UpdateVisualStateAndDragRegion(args.Size);
84+
}
85+
9086
private void UpdateCaptionButtons(FrameworkElement rootElement)
9187
{
9288
Window.AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
@@ -112,6 +108,7 @@ private void ResetWASDKTitleBar()
112108
}
113109

114110
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = false;
111+
this.Window.SizeChanged -= Window_SizeChanged;
115112
this.Window.Activated -= Window_Activated;
116113
SizeChanged -= this.TitleBar_SizeChanged;
117114
Window.AppWindow.TitleBar.ResetToDefault();
@@ -129,61 +126,119 @@ private void Window_Activated(object sender, WindowActivatedEventArgs args)
129126
}
130127
}
131128

132-
private void SetDragRegionForCustomTitleBar()
129+
public void SetDragRegionForCustomTitleBar()
130+
{
131+
if (AutoConfigureCustomTitleBar && Window != null)
132+
{
133+
ClearDragRegions(NonClientRegionKind.Passthrough);
134+
SetDragRegion(NonClientRegionKind.Passthrough, PART_ContentPresenter, PART_FooterPresenter, PART_ButtonHolder);
135+
}
136+
}
137+
138+
public double GetRasterizationScaleForElement(UIElement element)
133139
{
134-
if (AutoConfigureCustomTitleBar && Window != null && PART_RightPaddingColumn != null && PART_LeftPaddingColumn != null)
140+
if (element.XamlRoot != null)
141+
{
142+
return element.XamlRoot.RasterizationScale;
143+
}
144+
return 0.0;
145+
}
146+
147+
public void SetDragRegion(NonClientRegionKind nonClientRegionKind, params FrameworkElement[] frameworkElements)
148+
{
149+
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
150+
List<Windows.Graphics.RectInt32> rects = new List<Windows.Graphics.RectInt32>();
151+
var scale = GetRasterizationScaleForElement(this);
152+
153+
foreach (var frameworkElement in frameworkElements)
154+
{
155+
if (frameworkElement == null)
156+
{
157+
continue;
158+
}
159+
GeneralTransform transformElement = frameworkElement.TransformToVisual(null);
160+
Windows.Foundation.Rect bounds = transformElement.TransformBounds(new Windows.Foundation.Rect(0, 0, frameworkElement.ActualWidth, frameworkElement.ActualHeight));
161+
var transparentRect = new Windows.Graphics.RectInt32(
162+
_X: (int)Math.Round(bounds.X * scale),
163+
_Y: (int)Math.Round(bounds.Y * scale),
164+
_Width: (int)Math.Round(bounds.Width * scale),
165+
_Height: (int)Math.Round(bounds.Height * scale)
166+
);
167+
rects.Add(transparentRect);
168+
}
169+
if (rects.Count > 0)
135170
{
136-
double scaleAdjustment = GetScaleAdjustment();
137-
138-
PART_RightPaddingColumn.Width = new GridLength(Window.AppWindow.TitleBar.RightInset / scaleAdjustment);
139-
PART_LeftPaddingColumn.Width = new GridLength(Window.AppWindow.TitleBar.LeftInset / scaleAdjustment);
140-
141-
var height = (int)(this.ActualHeight * scaleAdjustment);
142-
Windows.Graphics.RectInt32 rect1 = new(0, 0, 0, height);
143-
Windows.Graphics.RectInt32 rect2 = new(0, 0, 0, height);
144-
Windows.Graphics.RectInt32 rect3 = new(0, 0, 0, height);
145-
Windows.Graphics.RectInt32 rect4 = new(0, 0, 0, height);
146-
147-
rect1.X = 0;
148-
rect1.Width = (int)((PART_RootGrid.Padding.Left
149-
+ PART_LeftPaddingColumn.ActualWidth)
150-
* scaleAdjustment);
151-
152-
rect2.X = rect1.X + rect1.Width + (int)((PART_ButtonsHolderColumn.ActualWidth) * scaleAdjustment);
153-
rect2.Width = (int)((PART_IconColumn.ActualWidth
154-
+ PART_TitleColumn.ActualWidth
155-
+ PART_LeftDragColumn.ActualWidth)
156-
* scaleAdjustment);
157-
158-
rect3.X = rect2.X + rect2.Width + (int)(PART_ContentColumn.ActualWidth * scaleAdjustment);
159-
rect3.Width = (int)(PART_RightDragColumn.ActualWidth * scaleAdjustment);
160-
161-
rect4.X = rect3.X + rect3.Width + (int)((PART_FooterColumn.ActualWidth
162-
+ PART_RightPaddingColumn.ActualWidth
163-
+ PART_RootGrid.Padding.Right)
164-
* scaleAdjustment);
165-
rect4.Width = (int)(PART_RightPaddingColumn.ActualWidth * scaleAdjustment);
166-
167-
Windows.Graphics.RectInt32[] dragRects = new[] { rect1, rect2, rect3, rect4 };
168-
169-
Window.AppWindow.TitleBar.SetDragRectangles(dragRects);
171+
nonClientInputSrc.SetRegionRects(nonClientRegionKind, rects.ToArray());
170172
}
171173
}
172174

173-
private double GetScaleAdjustment()
175+
public void ClearDragRegions(NonClientRegionKind nonClientRegionKind)
174176
{
175-
DisplayArea displayArea = DisplayArea.GetFromWindowId(this.Window.AppWindow.Id, DisplayAreaFallback.Primary);
176-
IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
177+
var noninputsrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
178+
noninputsrc.ClearRegionRects(nonClientRegionKind);
179+
}
177180

178-
// Get DPI.
179-
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
180-
if (result != 0)
181+
private IntPtr InputNonClientPointerSourceWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
182+
{
183+
switch (Msg)
181184
{
182-
throw new Exception("Could not get DPI for monitor.");
185+
case NativeMethods.WindowMessage.WM_NCLBUTTONDOWN:
186+
{
187+
if (MenuFlyout.IsOpen)
188+
{
189+
MenuFlyout.Hide();
190+
}
191+
break;
192+
}
193+
case NativeMethods.WindowMessage.WM_NCRBUTTONDOWN:
194+
{
195+
PointInt32 pt = new PointInt32(lParam.ToInt32() & 0xFFFF, lParam.ToInt32() >> 16);
196+
FlyoutShowOptions options = new FlyoutShowOptions();
197+
options.ShowMode = FlyoutShowMode.Standard;
198+
options.Position = InfoHelper.SystemVersion.Build >= 22000 ?
199+
new Windows.Foundation.Point((pt.X - this.Window.AppWindow.Position.X - 8) / XamlRoot.RasterizationScale, (pt.Y - this.Window.AppWindow.Position.Y) / XamlRoot.RasterizationScale) :
200+
new Windows.Foundation.Point(pt.X - this.Window.AppWindow.Position.X - 8, pt.Y - this.Window.AppWindow.Position.Y);
201+
202+
MenuFlyout.ShowAt(this, options);
203+
return (IntPtr)0;
204+
}
183205
}
206+
return WndProcHelper.CallInputNonClientPointerSourceWindowProc(hWnd, Msg, wParam, lParam);
207+
}
208+
209+
private IntPtr WindowWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
210+
{
211+
switch (Msg)
212+
{
213+
case NativeMethods.WindowMessage.WM_SYSMENU:
214+
{
215+
return (IntPtr)0;
216+
}
217+
218+
case NativeMethods.WindowMessage.WM_SYSCOMMAND:
219+
{
220+
NativeMethods.SystemCommand sysCommand = (NativeMethods.SystemCommand)(wParam.ToInt32() & 0xFFF0);
184221

185-
uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
186-
return scaleFactorPercent / 100.0;
222+
if (sysCommand is NativeMethods.SystemCommand.SC_MOUSEMENU)
223+
{
224+
FlyoutShowOptions options = new FlyoutShowOptions();
225+
options.Position = new Windows.Foundation.Point(0, 15);
226+
options.ShowMode = FlyoutShowMode.Standard;
227+
MenuFlyout.ShowAt(null, options);
228+
return (IntPtr)0;
229+
}
230+
else if (sysCommand is NativeMethods.SystemCommand.SC_KEYMENU)
231+
{
232+
FlyoutShowOptions options = new FlyoutShowOptions();
233+
options.Position = new Windows.Foundation.Point(0, 45);
234+
options.ShowMode = FlyoutShowMode.Standard;
235+
MenuFlyout.ShowAt(null, options);
236+
return (IntPtr)0;
237+
}
238+
break;
239+
}
240+
}
241+
return WndProcHelper.CallWindowProc(hWnd, Msg, wParam, lParam);
187242
}
188243
}
189244
#endif

0 commit comments

Comments
 (0)