Skip to content

Commit

Permalink
Merge pull request #508 from ghost1372/Fix-TitleBar3
Browse files Browse the repository at this point in the history
Use WASDK v1.4 new Custom TitleBar API, Simplify Calculating Rects and Fix TitleBar Drag Region for Scale 125%
  • Loading branch information
niels9001 authored Nov 22, 2023
2 parents 3b37fe5 + 3ef7c07 commit 4c4f776
Show file tree
Hide file tree
Showing 5 changed files with 295 additions and 91 deletions.
34 changes: 34 additions & 0 deletions components/TitleBar/src/InfoHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.System.Profile;

namespace CommunityToolkit.WinUI.Controls;
internal static class InfoHelper
{
public static Version AppVersion { get; } = new Version(
Package.Current.Id.Version.Major,
Package.Current.Id.Version.Minor,
Package.Current.Id.Version.Build,
Package.Current.Id.Version.Revision
);

public static Version SystemVersion { get; }

public static SystemDataPaths SystemDataPath { get; } = SystemDataPaths.GetDefault();

public static UserDataPaths UserDataPath { get; } = UserDataPaths.GetDefault();

public static string AppInstalledLocation { get; } = Package.Current.InstalledLocation.Path;

static InfoHelper()
{
string systemVersion = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;
ulong version = ulong.Parse(systemVersion);
SystemVersion = new Version(
(int)((version & 0xFFFF000000000000L) >> 48),
(int)((version & 0x0000FFFF00000000L) >> 32),
(int)((version & 0x00000000FFFF0000L) >> 16),
(int)(version & 0x000000000000FFFFL)
);
}
}
91 changes: 76 additions & 15 deletions components/TitleBar/src/NativeMethods.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#if WINAPPSDK
using System.Runtime.InteropServices;
using System.Runtime.InteropServices;

namespace CommunityToolkit.WinUI.Controls;

internal static class NativeMethods
{
[DllImport("Shcore.dll", SetLastError = true)]
internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
public enum WindowMessage : int
{
WM_NCLBUTTONDOWN = 0x00A1,
WM_NCRBUTTONDOWN = 0x00A4,
WM_SYSCOMMAND = 0x0112,
WM_SYSMENU = 0x0313,
WM_GETMINMAXINFO = 0x0024
}
[Flags]
public enum WindowStyle : uint
{
WS_SYSMENU = 0x80000
}

internal enum Monitor_DPI_Type : int
[Flags]
public enum WindowLongIndexFlags : int
{
MDT_Effective_DPI = 0,
MDT_Angular_DPI = 1,
MDT_Raw_DPI = 2,
MDT_Default = MDT_Effective_DPI
GWL_WNDPROC = -4,
GWL_STYLE = -16
}

[Flags]
public enum SetWindowPosFlags : uint
{
/// <summary>
/// Retains the current position (ignores X and Y parameters).
/// </summary>
SWP_NOMOVE = 0x0002
}

public enum SystemCommand
{
SC_MOUSEMENU = 0xF090,
SC_KEYMENU = 0xF100
}

[DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = false)]
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = false)]
public static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex);

public static int GetWindowLongAuto(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size is 8)
{
return GetWindowLongPtr(hWnd, nIndex);
}
else
{
return GetWindowLong(hWnd, nIndex);
}
}

[DllImport("user32.dll", EntryPoint = "FindWindowExW", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);


[DllImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = false)]
public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)]
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

public static IntPtr SetWindowLongAuto(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size is 8)
{
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
}
else
{
return SetWindowLong(hWnd, nIndex, dwNewLong);
}
}

[DllImport("user32.dll")]
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage Msg, IntPtr wParam, IntPtr lParam);
}
#endif
203 changes: 129 additions & 74 deletions components/TitleBar/src/TitleBar.WASDK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,21 @@

#if WINAPPSDK
using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using static CommunityToolkit.WinUI.Controls.NativeMethods;
using Microsoft.UI.Xaml.Media;

namespace CommunityToolkit.WinUI.Controls;

[TemplatePart(Name = nameof(PART_ButtonsHolderColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_IconColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_TitleColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_LeftDragColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_ContentColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_FooterColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_RightDragColumn), Type = typeof(ColumnDefinition))]
[TemplatePart(Name = nameof(PART_TitleHolder), Type = typeof(StackPanel))]
[TemplatePart(Name = nameof(PART_RootGrid), Type = typeof(Grid))]
[TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))]
[TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))]

public partial class TitleBar : Control
{
ColumnDefinition? PART_ButtonsHolderColumn;
ColumnDefinition? PART_IconColumn;
ColumnDefinition? PART_TitleColumn;
ColumnDefinition? PART_LeftDragColumn;
ColumnDefinition? PART_ContentColumn;
ColumnDefinition? PART_FooterColumn;
ColumnDefinition? PART_RightDragColumn;
StackPanel? PART_TitleHolder;
Grid? PART_RootGrid;
WndProcHelper WndProcHelper;
MenuFlyout MenuFlyout;
ContentPresenter? PART_ContentPresenter;
ContentPresenter? PART_FooterPresenter;

private void SetWASDKTitleBar()
{
Expand All @@ -42,6 +31,16 @@ private void SetWASDKTitleBar()
{
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;

if (this.ContextFlyout != null && this.ContextFlyout is MenuFlyout menuFlyout)
{
this.MenuFlyout = menuFlyout;
WndProcHelper = new WndProcHelper(this.Window);
WndProcHelper.RegisterWndProc(WindowWndProc);
WndProcHelper.RegisterInputNonClientPointerSourceWndProc(InputNonClientPointerSourceWndProc);
}

this.Window.SizeChanged -= Window_SizeChanged;
this.Window.SizeChanged += Window_SizeChanged;
this.Window.Activated -= Window_Activated;
this.Window.Activated += Window_Activated;

Expand All @@ -54,16 +53,8 @@ private void SetWASDKTitleBar()
};
}

// Set the width of padding columns in the UI.
PART_ButtonsHolderColumn = GetTemplateChild(nameof(PART_ButtonsHolderColumn)) as ColumnDefinition;
PART_IconColumn = GetTemplateChild(nameof(PART_IconColumn)) as ColumnDefinition;
PART_TitleColumn = GetTemplateChild(nameof(PART_TitleColumn)) as ColumnDefinition;
PART_LeftDragColumn = GetTemplateChild(nameof(PART_LeftDragColumn)) as ColumnDefinition;
PART_ContentColumn = GetTemplateChild(nameof(PART_ContentColumn)) as ColumnDefinition;
PART_RightDragColumn = GetTemplateChild(nameof(PART_RightDragColumn)) as ColumnDefinition;
PART_FooterColumn = GetTemplateChild(nameof(PART_FooterColumn)) as ColumnDefinition;
PART_TitleHolder = GetTemplateChild(nameof(PART_TitleHolder)) as StackPanel;
PART_RootGrid = GetTemplateChild(nameof(PART_RootGrid)) as Grid;
PART_ContentPresenter = GetTemplateChild(nameof(PART_ContentPresenter)) as ContentPresenter;
PART_FooterPresenter = GetTemplateChild(nameof(PART_FooterPresenter)) as ContentPresenter;

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

private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args)
{
UpdateVisualStateAndDragRegion(args.Size);
}

private void UpdateCaptionButtons(FrameworkElement rootElement)
{
Window.AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
Expand All @@ -112,6 +108,7 @@ private void ResetWASDKTitleBar()
}

Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = false;
this.Window.SizeChanged -= Window_SizeChanged;
this.Window.Activated -= Window_Activated;
SizeChanged -= this.TitleBar_SizeChanged;
Window.AppWindow.TitleBar.ResetToDefault();
Expand All @@ -129,61 +126,119 @@ private void Window_Activated(object sender, WindowActivatedEventArgs args)
}
}

private void SetDragRegionForCustomTitleBar()
public void SetDragRegionForCustomTitleBar()
{
if (AutoConfigureCustomTitleBar && Window != null)
{
ClearDragRegions(NonClientRegionKind.Passthrough);
SetDragRegion(NonClientRegionKind.Passthrough, PART_ContentPresenter, PART_FooterPresenter, PART_ButtonHolder);
}
}

public double GetRasterizationScaleForElement(UIElement element)
{
if (AutoConfigureCustomTitleBar && Window != null && PART_RightPaddingColumn != null && PART_LeftPaddingColumn != null)
if (element.XamlRoot != null)
{
return element.XamlRoot.RasterizationScale;
}
return 0.0;
}

public void SetDragRegion(NonClientRegionKind nonClientRegionKind, params FrameworkElement[] frameworkElements)
{
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
List<Windows.Graphics.RectInt32> rects = new List<Windows.Graphics.RectInt32>();
var scale = GetRasterizationScaleForElement(this);

foreach (var frameworkElement in frameworkElements)
{
if (frameworkElement == null)
{
continue;
}
GeneralTransform transformElement = frameworkElement.TransformToVisual(null);
Windows.Foundation.Rect bounds = transformElement.TransformBounds(new Windows.Foundation.Rect(0, 0, frameworkElement.ActualWidth, frameworkElement.ActualHeight));
var transparentRect = new Windows.Graphics.RectInt32(
_X: (int)Math.Round(bounds.X * scale),
_Y: (int)Math.Round(bounds.Y * scale),
_Width: (int)Math.Round(bounds.Width * scale),
_Height: (int)Math.Round(bounds.Height * scale)
);
rects.Add(transparentRect);
}
if (rects.Count > 0)
{
double scaleAdjustment = GetScaleAdjustment();

PART_RightPaddingColumn.Width = new GridLength(Window.AppWindow.TitleBar.RightInset / scaleAdjustment);
PART_LeftPaddingColumn.Width = new GridLength(Window.AppWindow.TitleBar.LeftInset / scaleAdjustment);

var height = (int)(this.ActualHeight * scaleAdjustment);
Windows.Graphics.RectInt32 rect1 = new(0, 0, 0, height);
Windows.Graphics.RectInt32 rect2 = new(0, 0, 0, height);
Windows.Graphics.RectInt32 rect3 = new(0, 0, 0, height);
Windows.Graphics.RectInt32 rect4 = new(0, 0, 0, height);

rect1.X = 0;
rect1.Width = (int)((PART_RootGrid.Padding.Left
+ PART_LeftPaddingColumn.ActualWidth)
* scaleAdjustment);

rect2.X = rect1.X + rect1.Width + (int)((PART_ButtonsHolderColumn.ActualWidth) * scaleAdjustment);
rect2.Width = (int)((PART_IconColumn.ActualWidth
+ PART_TitleColumn.ActualWidth
+ PART_LeftDragColumn.ActualWidth)
* scaleAdjustment);

rect3.X = rect2.X + rect2.Width + (int)(PART_ContentColumn.ActualWidth * scaleAdjustment);
rect3.Width = (int)(PART_RightDragColumn.ActualWidth * scaleAdjustment);

rect4.X = rect3.X + rect3.Width + (int)((PART_FooterColumn.ActualWidth
+ PART_RightPaddingColumn.ActualWidth
+ PART_RootGrid.Padding.Right)
* scaleAdjustment);
rect4.Width = (int)(PART_RightPaddingColumn.ActualWidth * scaleAdjustment);

Windows.Graphics.RectInt32[] dragRects = new[] { rect1, rect2, rect3, rect4 };

Window.AppWindow.TitleBar.SetDragRectangles(dragRects);
nonClientInputSrc.SetRegionRects(nonClientRegionKind, rects.ToArray());
}
}

private double GetScaleAdjustment()
public void ClearDragRegions(NonClientRegionKind nonClientRegionKind)
{
DisplayArea displayArea = DisplayArea.GetFromWindowId(this.Window.AppWindow.Id, DisplayAreaFallback.Primary);
IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
var noninputsrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
noninputsrc.ClearRegionRects(nonClientRegionKind);
}

// Get DPI.
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
if (result != 0)
private IntPtr InputNonClientPointerSourceWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
{
switch (Msg)
{
throw new Exception("Could not get DPI for monitor.");
case NativeMethods.WindowMessage.WM_NCLBUTTONDOWN:
{
if (MenuFlyout.IsOpen)
{
MenuFlyout.Hide();
}
break;
}
case NativeMethods.WindowMessage.WM_NCRBUTTONDOWN:
{
PointInt32 pt = new PointInt32(lParam.ToInt32() & 0xFFFF, lParam.ToInt32() >> 16);
FlyoutShowOptions options = new FlyoutShowOptions();
options.ShowMode = FlyoutShowMode.Standard;
options.Position = InfoHelper.SystemVersion.Build >= 22000 ?
new Windows.Foundation.Point((pt.X - this.Window.AppWindow.Position.X - 8) / XamlRoot.RasterizationScale, (pt.Y - this.Window.AppWindow.Position.Y) / XamlRoot.RasterizationScale) :
new Windows.Foundation.Point(pt.X - this.Window.AppWindow.Position.X - 8, pt.Y - this.Window.AppWindow.Position.Y);

MenuFlyout.ShowAt(this, options);
return (IntPtr)0;
}
}
return WndProcHelper.CallInputNonClientPointerSourceWindowProc(hWnd, Msg, wParam, lParam);
}

private IntPtr WindowWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
{
switch (Msg)
{
case NativeMethods.WindowMessage.WM_SYSMENU:
{
return (IntPtr)0;
}

case NativeMethods.WindowMessage.WM_SYSCOMMAND:
{
NativeMethods.SystemCommand sysCommand = (NativeMethods.SystemCommand)(wParam.ToInt32() & 0xFFF0);

uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
return scaleFactorPercent / 100.0;
if (sysCommand is NativeMethods.SystemCommand.SC_MOUSEMENU)
{
FlyoutShowOptions options = new FlyoutShowOptions();
options.Position = new Windows.Foundation.Point(0, 15);
options.ShowMode = FlyoutShowMode.Standard;
MenuFlyout.ShowAt(null, options);
return (IntPtr)0;
}
else if (sysCommand is NativeMethods.SystemCommand.SC_KEYMENU)
{
FlyoutShowOptions options = new FlyoutShowOptions();
options.Position = new Windows.Foundation.Point(0, 45);
options.ShowMode = FlyoutShowMode.Standard;
MenuFlyout.ShowAt(null, options);
return (IntPtr)0;
}
break;
}
}
return WndProcHelper.CallWindowProc(hWnd, Msg, wParam, lParam);
}
}
#endif
Loading

0 comments on commit 4c4f776

Please sign in to comment.