Skip to content

Commit

Permalink
Update TitleBar Control (sync with Labs)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghost1372 authored Nov 30, 2023
1 parent 55e8920 commit b8dcbc4
Show file tree
Hide file tree
Showing 6 changed files with 330 additions and 99 deletions.
34 changes: 34 additions & 0 deletions CommunityToolkit.App.Shared/Controls/TitleBar/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.App.Shared.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)
);
}
}
94 changes: 70 additions & 24 deletions CommunityToolkit.App.Shared/Controls/TitleBar/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,88 @@
#pragma warning disable CA1060

using System.Runtime.InteropServices;
using WinRT.Interop;
using Microsoft.UI;
using Microsoft.UI.Windowing;

namespace CommunityToolkit.App.Shared.Controls;

public partial class TitleBar : Control
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
}

private double GetScaleAdjustment()
[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)
{
IntPtr hWnd = WindowNative.GetWindowHandle(this.Window);
WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary);
IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);

// Get DPI.
int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
if (result != 0)
if (IntPtr.Size is 8)
{
throw new Exception("Could not get DPI for monitor.");
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);

uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
return scaleFactorPercent / 100.0;
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);
}
#pragma warning restore CA1060
#endif
198 changes: 136 additions & 62 deletions CommunityToolkit.App.Shared/Controls/TitleBar/TitleBar.WASDK.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,21 @@

#if WINDOWS_WINAPPSDK
using Microsoft.UI;
using Microsoft.UI.Input;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml.Media;

namespace CommunityToolkit.App.Shared.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_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;
WndProcHelper WndProcHelper;
MenuFlyout MenuFlyout;
ContentPresenter? PART_ContentPresenter;
ContentPresenter? PART_FooterPresenter;

private void SetWASDKTitleBar()
{
Expand All @@ -39,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 @@ -51,15 +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_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 @@ -83,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 @@ -108,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 @@ -125,46 +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 (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)
{
nonClientInputSrc.SetRegionRects(nonClientRegionKind, rects.ToArray());
}
}

public void ClearDragRegions(NonClientRegionKind nonClientRegionKind)
{
var noninputsrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
noninputsrc.ClearRegionRects(nonClientRegionKind);
}

private IntPtr InputNonClientPointerSourceWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
{
switch (Msg)
{
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)
{
if (AutoConfigureCustomTitleBar && Window != null && PART_RightPaddingColumn != null && PART_LeftPaddingColumn != null)
switch (Msg)
{
double scaleAdjustment = GetScaleAdjustment();

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

List<Windows.Graphics.RectInt32> dragRectsList = new();

Windows.Graphics.RectInt32 dragRectL;
dragRectL.X = (int)((PART_LeftPaddingColumn.ActualWidth
+ PART_ButtonsHolderColumn!.ActualWidth)
* scaleAdjustment);
dragRectL.Y = 0;
dragRectL.Height = (int)(this.ActualHeight * scaleAdjustment);
dragRectL.Width = (int)((PART_IconColumn!.ActualWidth
+ PART_TitleColumn!.ActualWidth
+ PART_LeftDragColumn!.ActualWidth)
* scaleAdjustment);
dragRectsList.Add(dragRectL);

Windows.Graphics.RectInt32 dragRectR;
dragRectR.X = (int)((PART_LeftPaddingColumn.ActualWidth
+ PART_IconColumn.ActualWidth
+ PART_ButtonsHolderColumn!.ActualWidth
+ PART_TitleHolder!.ActualWidth
+ PART_LeftDragColumn.ActualWidth
+ PART_ContentColumn!.ActualWidth)
* scaleAdjustment);
dragRectR.Y = 0;
dragRectR.Height = (int)(this.ActualHeight * scaleAdjustment);
dragRectR.Width = (int)(PART_RightDragColumn!.ActualWidth * scaleAdjustment);
dragRectsList.Add(dragRectR);

Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray();

Window.AppWindow.TitleBar.SetDragRectangles(dragRects);
case NativeMethods.WindowMessage.WM_SYSMENU:
{
return (IntPtr)0;
}

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

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 b8dcbc4

Please sign in to comment.