Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update TitleBar Control (sync with Labs) #158

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;

Check failure on line 18 in CommunityToolkit.App.Shared/Controls/TitleBar/TitleBar.WASDK.cs

View workflow job for this annotation

GitHub Actions / new-experiment

The type or namespace name 'WndProcHelper' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 18 in CommunityToolkit.App.Shared/Controls/TitleBar/TitleBar.WASDK.cs

View workflow job for this annotation

GitHub Actions / project-template

The type or namespace name 'WndProcHelper' could not be found (are you missing a using directive or an assembly reference?)
MenuFlyout MenuFlyout;
ContentPresenter? PART_ContentPresenter;
ContentPresenter? PART_FooterPresenter;

private void SetWASDKTitleBar()
{
Expand All @@ -39,6 +31,16 @@
{
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 @@
};
}

// 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 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 @@
}

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 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
Loading