diff --git a/components/TitleBar/samples/TitleBarFullSample.xaml.cs b/components/TitleBar/samples/TitleBarFullSample.xaml.cs index 546d4ec0a..9f950cd4a 100644 --- a/components/TitleBar/samples/TitleBarFullSample.xaml.cs +++ b/components/TitleBar/samples/TitleBarFullSample.xaml.cs @@ -44,7 +44,14 @@ await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => { SystemBackdrop = new MicaBackdrop() }; - newWindow.Content = new ShellPage(newWindow); + + // Create the content for the window to show + // and set the FlowDirection to match the current region. + newWindow.Content = new ShellPage(newWindow) + { + FlowDirection = this.FlowDirection + }; + newWindow.Activate(); #endif } diff --git a/components/TitleBar/src/NativeMethods.cs b/components/TitleBar/src/NativeMethods.cs index d3874648c..d5dff8ccf 100644 --- a/components/TitleBar/src/NativeMethods.cs +++ b/components/TitleBar/src/NativeMethods.cs @@ -19,14 +19,21 @@ public enum WindowMessage : int [Flags] public enum WindowStyle : uint { - WS_SYSMENU = 0x80000 + WS_SYSMENU = 0x80000, + } + + [Flags] + public enum WindowStyleExtended : ulong + { + WS_EX_LAYOUTRTL= 0x00400000L, } [Flags] public enum WindowLongIndexFlags : int { GWL_WNDPROC = -4, - GWL_STYLE = -16 + GWL_STYLE = -16, + GWL_EXSTYLE = -20, } [Flags] @@ -44,43 +51,50 @@ public enum SystemCommand SC_KEYMENU = 0xF100 } + // TODO: Check for typing online. IntPtr, int, or long? + [DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = false)] - public static extern int GetWindowLong(IntPtr hWnd, int nIndex); + public static extern int GetWindowLongW(IntPtr hWnd, int nIndex); + + [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = false)] + public static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = false)] - public static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex); + public static extern int GetWindowLongPtrW(IntPtr hWnd, int nIndex); public static int GetWindowLongAuto(IntPtr hWnd, int nIndex) { if (IntPtr.Size is 8) { - return GetWindowLongPtr(hWnd, nIndex); + return GetWindowLongPtrW(hWnd, nIndex); } else { - return GetWindowLong(hWnd, nIndex); + return GetWindowLongW(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); + public static extern IntPtr SetWindowLongW(IntPtr hWnd, int nIndex, IntPtr dwNewLong); - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)] + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = false)] public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); + [DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)] + public static extern IntPtr SetWindowLongPtrW(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); + return SetWindowLongPtrW(hWnd, nIndex, dwNewLong); } else { - return SetWindowLong(hWnd, nIndex, dwNewLong); + return SetWindowLongW(hWnd, nIndex, dwNewLong); } } diff --git a/components/TitleBar/src/TitleBar.Properties.cs b/components/TitleBar/src/TitleBar.Properties.cs index 17237dacb..168512ea1 100644 --- a/components/TitleBar/src/TitleBar.Properties.cs +++ b/components/TitleBar/src/TitleBar.Properties.cs @@ -58,6 +58,11 @@ public partial class TitleBar : Control public static readonly DependencyProperty AutoConfigureCustomTitleBarProperty = DependencyProperty.Register(nameof(AutoConfigureCustomTitleBar), typeof(bool), typeof(TitleBar), new PropertyMetadata(true, AutoConfigureCustomTitleBarChanged)); #if WINAPPSDK + /// + /// The backing for the property. + /// + public static readonly DependencyProperty AutoChangeWindowLayoutStyleProperty = DependencyProperty.Register(nameof(AutoChangeWindowLayoutStyle), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); + /// /// The backing for the property. /// @@ -165,6 +170,15 @@ public bool AutoConfigureCustomTitleBar } #if WINAPPSDK + /// + /// Gets or sets if the TitleBar should automatically change the Window's LayoutStyle to match the FlowDirection of the TitleBar (WASDK only). + /// + public bool AutoChangeWindowLayoutStyle + { + get => (bool)GetValue(AutoChangeWindowLayoutStyleProperty); + set => SetValue(AutoChangeWindowLayoutStyleProperty, value); + } + /// /// Gets or sets the window the TitleBar should configure (WASDK only). /// diff --git a/components/TitleBar/src/TitleBar.WASDK.cs b/components/TitleBar/src/TitleBar.WASDK.cs index 6e5286658..321872132 100644 --- a/components/TitleBar/src/TitleBar.WASDK.cs +++ b/components/TitleBar/src/TitleBar.WASDK.cs @@ -3,17 +3,17 @@ // See the LICENSE file in the project root for more information. #if WINDOWS_WINAPPSDK && !HAS_UNO -using Windows.Graphics; using Microsoft.UI; using Microsoft.UI.Input; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml.Media; +using System.Runtime.InteropServices; +using Windows.Graphics; namespace CommunityToolkit.WinUI.Controls; [TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))] [TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))] - public partial class TitleBar : Control { WndProcHelper? WndProcHelper; @@ -54,12 +54,26 @@ private void SetWASDKTitleBar() }; } + // Set the caption buttons to match the flow direction of the titlebar + if (AutoChangeWindowLayoutStyle) + { + UpdateCaptionButtonsDirection(this.FlowDirection); + } + 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; int CaptionButtonOcclusionWidthLeft = Window.AppWindow.TitleBar.LeftInset; + + // Swap left/right if in RTL mode + if (this.FlowDirection == FlowDirection.RightToLeft) + { + (CaptionButtonOcclusionWidthRight, CaptionButtonOcclusionWidthLeft) = (CaptionButtonOcclusionWidthLeft, CaptionButtonOcclusionWidthRight); + } + + // Set padding columns to match caption button occlusion. PART_LeftPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthLeft); PART_RightPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthRight); @@ -102,6 +116,27 @@ private void UpdateCaptionButtons(FrameworkElement rootElement) } } + private void UpdateCaptionButtonsDirection(FlowDirection direction) + { + var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this.Window); + + if (hwnd != 0) + { + var exStyle = NativeMethods.GetWindowLongPtr(hwnd, (int)NativeMethods.WindowLongIndexFlags.GWL_EXSTYLE); + + if (direction == FlowDirection.RightToLeft) + { + exStyle |= (nint)NativeMethods.WindowStyleExtended.WS_EX_LAYOUTRTL; + } + else + { + exStyle &= (nint)NativeMethods.WindowStyleExtended.WS_EX_LAYOUTRTL; + } + + NativeMethods.SetWindowLongPtr(hwnd, (int)NativeMethods.WindowLongIndexFlags.GWL_EXSTYLE, exStyle); + } + } + private void ResetWASDKTitleBar() { if (this.Window == null) diff --git a/components/TitleBar/src/WndProcHelper.cs b/components/TitleBar/src/WndProcHelper.cs index 63ed9732e..e40347233 100644 --- a/components/TitleBar/src/WndProcHelper.cs +++ b/components/TitleBar/src/WndProcHelper.cs @@ -44,7 +44,7 @@ public void RegisterInputNonClientPointerSourceWndProc(WNDPROC wndProc) if (inputNonClientPointerSourceHandle != IntPtr.Zero) { - int style = NativeMethods.GetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE); + IntPtr style = NativeMethods.GetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE); NativeMethods.SetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE, (IntPtr)(style & ~(int)NativeMethods.WindowStyle.WS_SYSMENU)); newInputNonClientPointerSourceWndProc = wndProc;