diff --git a/components/TitleBar/OpenSolution.bat b/components/TitleBar/OpenSolution.bat
new file mode 100644
index 000000000..814a56d4b
--- /dev/null
+++ b/components/TitleBar/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/TitleBar/samples/Assets/icon.png b/components/TitleBar/samples/Assets/icon.png
new file mode 100644
index 000000000..5f574ceca
Binary files /dev/null and b/components/TitleBar/samples/Assets/icon.png differ
diff --git a/components/TitleBar/samples/BlankPage1.xaml b/components/TitleBar/samples/BlankPage1.xaml
new file mode 100644
index 000000000..98044d829
--- /dev/null
+++ b/components/TitleBar/samples/BlankPage1.xaml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/samples/BlankPage1.xaml.cs b/components/TitleBar/samples/BlankPage1.xaml.cs
new file mode 100644
index 000000000..2619ce180
--- /dev/null
+++ b/components/TitleBar/samples/BlankPage1.xaml.cs
@@ -0,0 +1,24 @@
+// 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.
+
+namespace TitleBarExperiment.Samples;
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class BlankPage1 : Page
+{
+#if WINAPPSDK
+ public BlankPage1(Window window)
+ {
+ this.InitializeComponent();
+ appTitleBar.Window = window;
+ }
+#else
+ public BlankPage1()
+ {
+ this.InitializeComponent();
+ Microsoft.UI.Xaml.Controls.BackdropMaterial.SetApplyToRootOrPageBackground(this, true);
+ }
+#endif
+}
diff --git a/components/TitleBar/samples/Dependencies.props b/components/TitleBar/samples/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/TitleBar/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/samples/TitleBar.Samples.csproj b/components/TitleBar/samples/TitleBar.Samples.csproj
new file mode 100644
index 000000000..cc9099b4e
--- /dev/null
+++ b/components/TitleBar/samples/TitleBar.Samples.csproj
@@ -0,0 +1,15 @@
+
+
+ TitleBar
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/samples/TitleBar.md b/components/TitleBar/samples/TitleBar.md
new file mode 100644
index 000000000..656fb9916
--- /dev/null
+++ b/components/TitleBar/samples/TitleBar.md
@@ -0,0 +1,33 @@
+---
+title: TitleBar
+author: niels9001
+description: TODO: Your experiment's description here
+keywords: TitleBar, Control, Layout
+dev_langs:
+ - csharp
+category: Controls
+subcategory: Layout
+discussion-id: 0
+issue-id: 0
+icon: assets/icon.png
+---
+
+
+
+
+
+
+
+
+
+# TitleBar
+
+TODO: Fill in information about this experiment and how to get started here...
+
+## Custom Control
+
+You can inherit from an existing component as well, like `Panel`, this example shows a control without a
+XAML Style that will be more light-weight to consume by an app developer:
+
+> [!Sample TitleBarCustomSample]
+
diff --git a/components/TitleBar/samples/TitleBarCustomSample.xaml b/components/TitleBar/samples/TitleBarCustomSample.xaml
new file mode 100644
index 000000000..e5812ae5f
--- /dev/null
+++ b/components/TitleBar/samples/TitleBarCustomSample.xaml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/samples/TitleBarCustomSample.xaml.cs b/components/TitleBar/samples/TitleBarCustomSample.xaml.cs
new file mode 100644
index 000000000..cad8c10c5
--- /dev/null
+++ b/components/TitleBar/samples/TitleBarCustomSample.xaml.cs
@@ -0,0 +1,64 @@
+// 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.
+
+using CommunityToolkit.WinUI.Controls;
+#if WINDOWS_UWP
+using Windows.ApplicationModel.Core;
+using Windows.UI.Core;
+using Windows.UI.ViewManagement;
+using Windows.UI.WindowManagement;
+using Windows.UI.Xaml.Hosting;
+#endif
+namespace TitleBarExperiment.Samples;
+
+///
+/// An example sample page of a custom control inheriting from Panel.
+///
+[ToolkitSampleTextOption("TitleText", "This is a title", Title = "Input the text")]
+[ToolkitSampleMultiChoiceOption("LayoutOrientation", "Horizontal", "Vertical", Title = "Orientation")]
+
+[ToolkitSample(id: nameof(TitleBarCustomSample), "Custom control", description: $"A sample for showing how to create and use a {nameof(TitleBar)} custom control.")]
+public sealed partial class TitleBarCustomSample : Page
+{
+ public TitleBarCustomSample()
+ {
+ this.InitializeComponent();
+ }
+
+ // TODO: See https://github.com/CommunityToolkit/Labs-Windows/issues/149
+ public static Orientation ConvertStringToOrientation(string orientation) => orientation switch
+ {
+ "Vertical" => Orientation.Vertical,
+ "Horizontal" => Orientation.Horizontal,
+ _ => throw new System.NotImplementedException(),
+ };
+
+ private async void Button_Click(object sender, RoutedEventArgs e)
+ {
+#if WINDOWS_UWP
+ CoreApplicationView newView = CoreApplication.CreateNewView();
+ int newViewId = 0;
+ await newView.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ Frame frame = new Frame();
+ frame.Navigate(typeof(BlankPage1), null);
+ Window.Current.Content = frame;
+ // You have to activate the window in order to show it later.
+ Window.Current.Activate();
+
+ newViewId = ApplicationView.GetForCurrentView().Id;
+ });
+
+ bool viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(newViewId);
+#endif
+#if WINAPPSDK
+ Window newWindow = new Window
+ {
+ SystemBackdrop = new MicaBackdrop()
+ };
+ newWindow.Content = new BlankPage1(newWindow);
+ newWindow.Activate();
+#endif
+ }
+}
diff --git a/components/TitleBar/src/AdditionalAssemblyInfo.cs b/components/TitleBar/src/AdditionalAssemblyInfo.cs
new file mode 100644
index 000000000..8f864b0fd
--- /dev/null
+++ b/components/TitleBar/src/AdditionalAssemblyInfo.cs
@@ -0,0 +1,13 @@
+// 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.
+
+using System.Runtime.CompilerServices;
+
+// These `InternalsVisibleTo` calls are intended to make it easier for
+// for any internal code to be testable in all the different test projects
+// used with the Labs infrastructure.
+[assembly: InternalsVisibleTo("TitleBar.Tests.Uwp")]
+[assembly: InternalsVisibleTo("TitleBar.Tests.WinAppSdk")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")]
diff --git a/components/TitleBar/src/CommunityToolkit.WinUI.Controls.TitleBar.csproj b/components/TitleBar/src/CommunityToolkit.WinUI.Controls.TitleBar.csproj
new file mode 100644
index 000000000..7bf974a32
--- /dev/null
+++ b/components/TitleBar/src/CommunityToolkit.WinUI.Controls.TitleBar.csproj
@@ -0,0 +1,13 @@
+
+
+ TitleBar
+ This package contains TitleBar.
+ 0.0.1
+
+
+ CommunityToolkit.WinUI.Controls.TitleBarRns
+
+
+
+
+
diff --git a/components/TitleBar/src/Dependencies.props b/components/TitleBar/src/Dependencies.props
new file mode 100644
index 000000000..e622e1df4
--- /dev/null
+++ b/components/TitleBar/src/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/src/MultiTarget.props b/components/TitleBar/src/MultiTarget.props
new file mode 100644
index 000000000..b11c19426
--- /dev/null
+++ b/components/TitleBar/src/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;wpf;wasm;linuxgtk;macos;ios;android;
+
+
\ No newline at end of file
diff --git a/components/TitleBar/src/Themes/Generic.xaml b/components/TitleBar/src/Themes/Generic.xaml
new file mode 100644
index 000000000..07014e9bd
--- /dev/null
+++ b/components/TitleBar/src/Themes/Generic.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/components/TitleBar/src/TitleBar.Properties.cs b/components/TitleBar/src/TitleBar.Properties.cs
new file mode 100644
index 000000000..12e8ccf59
--- /dev/null
+++ b/components/TitleBar/src/TitleBar.Properties.cs
@@ -0,0 +1,215 @@
+// 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.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+public partial class TitleBar : Control
+{
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty IconProperty = DependencyProperty.Register(nameof(Icon), typeof(IconElement), typeof(TitleBar), new PropertyMetadata(null, IconChanged));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(TitleBar), new PropertyMetadata(default(string)));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty SubtitleProperty = DependencyProperty.Register(nameof(Subtitle), typeof(string), typeof(TitleBar), new PropertyMetadata(default(string)));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(object), typeof(TitleBar), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(TitleBar), new PropertyMetadata(null));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty IsBackButtonVisibleProperty = DependencyProperty.Register(nameof(IsBackButtonVisible), typeof(bool), typeof(TitleBar), new PropertyMetadata(false, IsBackButtonVisibleChanged));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty IsPaneButtonVisibleProperty = DependencyProperty.Register(nameof(IsPaneButtonVisible), typeof(bool), typeof(TitleBar), new PropertyMetadata(false, IsPaneButtonVisibleChanged));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty DisplayModeProperty = DependencyProperty.Register(nameof(DisplayMode), typeof(DisplayMode), typeof(TitleBar), new PropertyMetadata(DisplayMode.Standard, DisplayModeChanged));
+
+ ///
+ /// The backing for the property.
+ ///
+ public static readonly DependencyProperty CompactStateBreakpointProperty = DependencyProperty.Register(nameof(CompactStateBreakpoint), typeof(int), typeof(TitleBar), new PropertyMetadata(850));
+
+ ///
+ /// The backing for the property.
+ ///
+ 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 WindowProperty = DependencyProperty.Register(nameof(Window), typeof(Window), typeof(TitleBar), new PropertyMetadata(null));
+#endif
+
+ ///
+ /// The event that gets fired when the back button is clicked
+ ///
+ public event EventHandler? BackButtonClick;
+
+ ///
+ /// The event that gets fired when the pane toggle button is clicked
+ ///
+ public event EventHandler? PaneButtonClick;
+
+ ///
+ /// Gets or sets the Icon
+ ///
+ public IconElement Icon
+ {
+ get => (IconElement)GetValue(IconProperty);
+ set => SetValue(IconProperty, value);
+ }
+
+ ///
+ /// Gets or sets the Title
+ ///
+ public string Title
+ {
+ get => (string)GetValue(TitleProperty);
+ set => SetValue(TitleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the Subtitle
+ ///
+ public string Subtitle
+ {
+ get => (string)GetValue(SubtitleProperty);
+ set => SetValue(SubtitleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the content shown at the center of the TitleBar. When setting this, using DisplayMode=Tall is recommended.
+ ///
+ public object Content
+ {
+ get => (object)GetValue(ContentProperty);
+ set => SetValue(ContentProperty, value);
+ }
+
+ ///
+ /// Gets or sets the content shown at the right of the TitleBar, next to the caption buttons. When setting this, using DisplayMode=Tall is recommended.
+ ///
+ public object Footer
+ {
+ get => (object)GetValue(FooterProperty);
+ set => SetValue(FooterProperty, value);
+ }
+
+ ///
+ /// Gets or sets DisplayMode. Compact is default (32px), Tall is recommended when setting the Content or Footer.
+ ///
+ public DisplayMode DisplayMode
+ {
+ get => (DisplayMode)GetValue(DisplayModeProperty);
+ set => SetValue(DisplayModeProperty, value);
+ }
+
+ ///
+ /// Gets or sets the visibility of the back button.
+ ///
+ public bool IsBackButtonVisible
+ {
+ get => (bool)GetValue(IsBackButtonVisibleProperty);
+ set => SetValue(IsBackButtonVisibleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the visibility of the pane toggle button.
+ ///
+ public bool IsPaneButtonVisible
+ {
+ get => (bool)GetValue(IsPaneButtonVisibleProperty);
+ set => SetValue(IsPaneButtonVisibleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the breakpoint of when the compact state is triggered.
+ ///
+ public int CompactStateBreakpoint
+ {
+ get => (int)GetValue(CompactStateBreakpointProperty);
+ set => SetValue(CompactStateBreakpointProperty, value);
+ }
+
+ ///
+ /// Gets or sets if the TitleBar should auto configure ExtendContentIntoTitleBar and CaptionButtion background colors.
+ ///
+ public bool AutoConfigureCustomTitleBar
+ {
+ get => (bool)GetValue(AutoConfigureCustomTitleBarProperty);
+ set => SetValue(AutoConfigureCustomTitleBarProperty, value);
+ }
+
+#if WINAPPSDK
+ ///
+ /// Gets or sets the window the TitleBar should configure (WASDK only).
+ ///
+ public Window Window
+ {
+ get => (Window)GetValue(WindowProperty);
+ set => SetValue(WindowProperty, value);
+ }
+#endif
+
+ private static void IsBackButtonVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((TitleBar)d).Update();
+ }
+
+ private static void IsPaneButtonVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((TitleBar)d).Update();
+ }
+
+ private static void DisplayModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((TitleBar)d).Update();
+ }
+
+ private static void IconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ((TitleBar)d).Update();
+ }
+
+ private static void AutoConfigureCustomTitleBarChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (((TitleBar)d).AutoConfigureCustomTitleBar)
+ {
+ ((TitleBar)d).Configure();
+ }
+ else
+ {
+ ((TitleBar)d).Reset();
+ }
+ }
+}
+
+public enum DisplayMode
+{
+ Standard,
+ Tall
+}
diff --git a/components/TitleBar/src/TitleBar.UWP.cs b/components/TitleBar/src/TitleBar.UWP.cs
new file mode 100644
index 000000000..2464aaaae
--- /dev/null
+++ b/components/TitleBar/src/TitleBar.UWP.cs
@@ -0,0 +1,62 @@
+// 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 WINDOWS_UWP && !HAS_UNO
+using Windows.ApplicationModel.Core;
+using Windows.UI.ViewManagement;
+using Windows.UI;
+
+namespace CommunityToolkit.WinUI.Controls;
+
+[TemplatePart(Name = nameof(PART_DragRegion), Type = typeof(Grid))]
+
+public partial class TitleBar : Control
+{
+ Grid? PART_DragRegion;
+
+ private void SetUWPTitleBar()
+ {
+ if (AutoConfigureCustomTitleBar)
+ {
+ CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
+ CoreApplication.GetCurrentView().TitleBar.LayoutMetricsChanged -= this.TitleBar_LayoutMetricsChanged;
+ CoreApplication.GetCurrentView().TitleBar.LayoutMetricsChanged += this.TitleBar_LayoutMetricsChanged;
+ Window.Current.Activated -= this.Current_Activated;
+ Window.Current.Activated += this.Current_Activated;
+
+ ApplicationView.GetForCurrentView().TitleBar.ButtonBackgroundColor = Colors.Transparent;
+ ApplicationView.GetForCurrentView().TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
+
+ PART_DragRegion = GetTemplateChild(nameof(PART_DragRegion)) as Grid;
+ Window.Current.SetTitleBar(PART_DragRegion);
+ }
+ }
+
+ private void ResetUWPTitleBar()
+ {
+ CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = false;
+ Window.Current.Activated -= this.Current_Activated;
+ CoreApplication.GetCurrentView().TitleBar.LayoutMetricsChanged -= this.TitleBar_LayoutMetricsChanged;
+ Window.Current.SetTitleBar(null);
+ }
+
+ private void Current_Activated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)
+ {
+ if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)
+ {
+ VisualStateManager.GoToState(this, WindowDeactivatedState, true);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, WindowActivatedState, true);
+ }
+ }
+
+ private void TitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
+ {
+ PART_LeftPaddingColumn!.Width = new GridLength(CoreApplication.GetCurrentView().TitleBar.SystemOverlayLeftInset);
+ PART_RightPaddingColumn!.Width = new GridLength(CoreApplication.GetCurrentView().TitleBar.SystemOverlayRightInset);
+ }
+}
+#endif
diff --git a/components/TitleBar/src/TitleBar.WASDK.cs b/components/TitleBar/src/TitleBar.WASDK.cs
new file mode 100644
index 000000000..7cd360ed1
--- /dev/null
+++ b/components/TitleBar/src/TitleBar.WASDK.cs
@@ -0,0 +1,216 @@
+// 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 Microsoft.UI;
+using Microsoft.UI.Windowing;
+using System.Runtime.InteropServices;
+using WinRT.Interop;
+
+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))]
+
+public partial class TitleBar : Control
+{
+ private AppWindow? appWindow;
+ ColumnDefinition? PART_ButtonsHolderColumn;
+ ColumnDefinition? PART_IconColumn;
+ ColumnDefinition? PART_TitleColumn;
+ ColumnDefinition? PART_LeftDragColumn;
+ ColumnDefinition? PART_ContentColumn;
+ ColumnDefinition? PART_FooterColumn;
+ ColumnDefinition? PART_RightDragColumn;
+ StackPanel? PART_TitleHolder;
+
+ private void SetWASDKTitleBar()
+ {
+ if (this.Window == null)
+ {
+ return;
+ // TO DO: Throw exception that window has not been set?
+ }
+ if (AutoConfigureCustomTitleBar)
+ {
+ appWindow = GetAppWindow();
+ appWindow.TitleBar.ExtendsContentIntoTitleBar = true;
+
+ this.Window.Activated -= Window_Activated;
+ this.Window.Activated += Window_Activated;
+
+ if (Window.Content is FrameworkElement rootElement)
+ {
+ UpdateCaptionButtons(rootElement);
+ rootElement.ActualThemeChanged += (s, e) =>
+ {
+ UpdateCaptionButtons(rootElement);
+ };
+ }
+
+ // 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;
+
+ // Get caption button occlusion information.
+ int CaptionButtonOcclusionWidthRight = appWindow.TitleBar.RightInset;
+ int CaptionButtonOcclusionWidthLeft = appWindow.TitleBar.LeftInset;
+ PART_LeftPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthLeft);
+ PART_RightPaddingColumn!.Width = new GridLength(CaptionButtonOcclusionWidthRight);
+
+
+ if (DisplayMode == DisplayMode.Tall)
+ {
+ // Choose a tall title bar to provide more room for interactive elements
+ // like search box or person picture controls.
+ appWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
+ }
+ else
+ {
+ appWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Standard;
+ }
+ // Recalculate the drag region for the custom title bar
+ // if you explicitly defined new draggable areas.
+ SetDragRegionForCustomTitleBar(appWindow);
+ }
+ }
+
+ private void UpdateCaptionButtons(FrameworkElement rootElement)
+ {
+ appWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent;
+ appWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
+ if (rootElement.ActualTheme == ElementTheme.Dark)
+ {
+ appWindow.TitleBar.ButtonForegroundColor = Colors.White;
+ appWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray;
+ }
+ else
+ {
+ appWindow.TitleBar.ButtonForegroundColor = Colors.Black;
+ appWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray;
+ }
+ }
+
+ private void ResetWASDKTitleBar()
+ {
+ if (this.Window == null)
+ {
+ return;
+ // TO DO: Throw exception that window has not been set?
+ }
+
+ appWindow = GetAppWindow();
+ appWindow.TitleBar.ResetToDefault();
+ }
+
+ private void UpdateRegionToSize()
+ {
+ // Update drag region if the size of the title bar changes.
+ SetDragRegionForCustomTitleBar(appWindow!);
+ }
+
+ private void Window_Activated(object sender, WindowActivatedEventArgs args)
+ {
+ if (args.WindowActivationState == WindowActivationState.Deactivated)
+ {
+ VisualStateManager.GoToState(this, WindowDeactivatedState, true);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, WindowActivatedState, true);
+ }
+ }
+
+ private void SetDragRegionForCustomTitleBar(AppWindow appWindow)
+ {
+ if (appWindow != null)
+ {
+ double scaleAdjustment = GetScaleAdjustment();
+
+ PART_RightPaddingColumn!.Width = new GridLength(appWindow.TitleBar.RightInset / scaleAdjustment);
+ PART_LeftPaddingColumn!.Width = new GridLength(appWindow.TitleBar.LeftInset / scaleAdjustment);
+
+ List 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();
+
+ appWindow.TitleBar.SetDragRectangles(dragRects);
+ }
+ }
+
+
+ private AppWindow GetAppWindow()
+ {
+ IntPtr hWnd = WindowNative.GetWindowHandle(this.Window);
+ WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
+ return AppWindow.GetFromWindowId(wndId);
+ }
+
+ [DllImport("Shcore.dll", SetLastError = true)]
+ internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
+
+ internal enum Monitor_DPI_Type : int
+ {
+ MDT_Effective_DPI = 0,
+ MDT_Angular_DPI = 1,
+ MDT_Raw_DPI = 2,
+ MDT_Default = MDT_Effective_DPI
+ }
+
+ private double GetScaleAdjustment()
+ {
+ 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)
+ {
+ throw new Exception("Could not get DPI for monitor.");
+ }
+
+ uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
+ return scaleFactorPercent / 100.0;
+ }
+}
+#endif
diff --git a/components/TitleBar/src/TitleBar.cs b/components/TitleBar/src/TitleBar.cs
new file mode 100644
index 000000000..20fb738f4
--- /dev/null
+++ b/components/TitleBar/src/TitleBar.cs
@@ -0,0 +1,157 @@
+// 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.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+[TemplateVisualState(Name = BackButtonVisibleState, GroupName = BackButtonStates)]
+[TemplateVisualState(Name = BackButtonCollapsedState, GroupName = BackButtonStates)]
+[TemplateVisualState(Name = PaneButtonVisibleState, GroupName = PaneButtonStates)]
+[TemplateVisualState(Name = PaneButtonCollapsedState, GroupName = PaneButtonStates)]
+[TemplateVisualState(Name = WindowActivatedState, GroupName = ActivationStates)]
+[TemplateVisualState(Name = WindowDeactivatedState, GroupName = ActivationStates)]
+[TemplateVisualState(Name = StandardState, GroupName = DisplayModeStates)]
+[TemplateVisualState(Name = TallState, GroupName = DisplayModeStates)]
+[TemplateVisualState(Name = IconVisibleState, GroupName = IconStates)]
+[TemplateVisualState(Name = IconCollapsedState, GroupName = IconStates)]
+[TemplateVisualState(Name = WideState, GroupName = ReflowStates)]
+[TemplateVisualState(Name = NarrowState, GroupName = ReflowStates)]
+[TemplatePart(Name = PartBackButton, Type = typeof(Button))]
+[TemplatePart(Name = PartPaneButton, Type = typeof(Button))]
+[TemplatePart(Name = nameof(PART_LeftPaddingColumn), Type = typeof(ColumnDefinition))]
+[TemplatePart(Name = nameof(PART_RightPaddingColumn), Type = typeof(ColumnDefinition))]
+
+public partial class TitleBar : Control
+{
+ private const string PartBackButton = "PART_BackButton";
+ private const string PartPaneButton = "PART_PaneButton";
+
+ private const string BackButtonVisibleState = "BackButtonVisible";
+ private const string BackButtonCollapsedState = "BackButtonCollapsed";
+ private const string BackButtonStates = "BackButtonStates";
+
+ private const string PaneButtonVisibleState = "PaneButtonVisible";
+ private const string PaneButtonCollapsedState = "PaneButtonCollapsed";
+ private const string PaneButtonStates = "PaneButtonStates";
+
+ private const string WindowActivatedState = "Activated";
+ private const string WindowDeactivatedState = "Deactivated";
+ private const string ActivationStates = "WindowActivationStates";
+
+ private const string IconVisibleState = "IconVisible";
+ private const string IconCollapsedState = "IconCollapsed";
+ private const string IconStates = "IconStates";
+
+ private const string StandardState = "Standard";
+ private const string TallState = "Tall";
+ private const string DisplayModeStates = "DisplayModeStates";
+
+ private const string WideState = "Wide";
+ private const string NarrowState = "Narrow";
+ private const string ReflowStates = "ReflowStates";
+
+ ColumnDefinition? PART_LeftPaddingColumn;
+ ColumnDefinition? PART_RightPaddingColumn;
+
+ public TitleBar()
+ {
+ this.DefaultStyleKey = typeof(TitleBar);
+ }
+
+ protected override void OnApplyTemplate()
+ {
+ PART_LeftPaddingColumn = GetTemplateChild(nameof(PART_LeftPaddingColumn)) as ColumnDefinition;
+ PART_RightPaddingColumn = GetTemplateChild(nameof(PART_RightPaddingColumn)) as ColumnDefinition;
+ Configure();
+ if (GetTemplateChild(PartBackButton) is Button backButton)
+ {
+ backButton.Click -= BackButton_Click;
+ backButton.Click += BackButton_Click;
+ }
+
+ if (GetTemplateChild(PartPaneButton) is Button paneButton)
+ {
+ paneButton.Click -= PaneButton_Click;
+ paneButton.Click += PaneButton_Click;
+ }
+
+
+ SizeChanged -= this.TitleBar_SizeChanged;
+ SizeChanged += this.TitleBar_SizeChanged;
+
+ Update();
+ base.OnApplyTemplate();
+ }
+
+ private void TitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if (e.NewSize.Width <= CompactStateBreakpoint)
+ {
+ if (Content != null || Footer != null)
+ {
+ VisualStateManager.GoToState(this, NarrowState, true);
+ }
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, WideState, true);
+ }
+
+#if WINAPPSDK
+ UpdateRegionToSize();
+#endif
+ }
+
+ private void BackButton_Click(object sender, RoutedEventArgs e)
+ {
+ BackButtonClick?.Invoke(this, new RoutedEventArgs());
+ }
+
+ private void PaneButton_Click(object sender, RoutedEventArgs e)
+ {
+ PaneButtonClick?.Invoke(this, new RoutedEventArgs());
+ }
+
+ private void Update()
+ {
+ if (Icon != null)
+ {
+ VisualStateManager.GoToState(this, IconVisibleState, true);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, IconCollapsedState, true);
+ }
+ VisualStateManager.GoToState(this, IsBackButtonVisible ? BackButtonVisibleState : BackButtonCollapsedState, true);
+ VisualStateManager.GoToState(this, IsPaneButtonVisible ? PaneButtonVisibleState : PaneButtonCollapsedState, true);
+
+ if (DisplayMode == DisplayMode.Tall)
+ {
+ VisualStateManager.GoToState(this, TallState, true);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, StandardState, true);
+ }
+ }
+
+ private void Configure()
+ {
+#if WINDOWS_UWP && !HAS_UNO
+ SetUWPTitleBar();
+#endif
+#if WINAPPSDK
+ SetWASDKTitleBar();
+#endif
+ }
+
+ public void Reset()
+ {
+#if WINDOWS_UWP && !HAS_UNO
+ ResetUWPTitleBar();
+#endif
+#if WINAPPSDK
+ ResetWASDKTitleBar();
+#endif
+ }
+}
diff --git a/components/TitleBar/src/TitleBar.xaml b/components/TitleBar/src/TitleBar.xaml
new file mode 100644
index 000000000..853288be7
--- /dev/null
+++ b/components/TitleBar/src/TitleBar.xaml
@@ -0,0 +1,363 @@
+
+
+ 32
+ 48
+
+
+
+
+
+
+
+
+
+
diff --git a/components/TitleBar/tests/ExampleTitleBarTestClass.cs b/components/TitleBar/tests/ExampleTitleBarTestClass.cs
new file mode 100644
index 000000000..f4a22e8c6
--- /dev/null
+++ b/components/TitleBar/tests/ExampleTitleBarTestClass.cs
@@ -0,0 +1,99 @@
+// 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.
+
+using CommunityToolkit.Tooling.TestGen;
+using CommunityToolkit.Tests;
+using CommunityToolkit.WinUI.Controls;
+
+namespace TitleBarExperiment.Tests;
+
+[TestClass]
+public partial class ExampleTitleBarTestClass : VisualUITestBase
+{
+ // If you don't need access to UI objects directly or async code, use this pattern.
+ [TestMethod]
+ public void SimpleSynchronousExampleTest()
+ {
+ var assembly = typeof(TitleBar).Assembly;
+ var type = assembly.GetType(typeof(TitleBar).FullName ?? string.Empty);
+
+ Assert.IsNotNull(type, "Could not find TitleBar type.");
+ Assert.AreEqual(typeof(TitleBar), type, "Type of TitleBar does not match expected type.");
+ }
+
+ // If you don't need access to UI objects directly, use this pattern.
+ [TestMethod]
+ public async Task SimpleAsyncExampleTest()
+ {
+ await Task.Delay(250);
+
+ Assert.IsTrue(true);
+ }
+
+ // Example that shows how to check for exception throwing.
+ [TestMethod]
+ public void SimpleExceptionCheckTest()
+ {
+ // If you need to check exceptions occur for invalid inputs, etc...
+ // Use Assert.ThrowsException to limit the scope to where you expect the error to occur.
+ // Otherwise, using the ExpectedException attribute could swallow or
+ // catch other issues in setup code.
+ Assert.ThrowsException(() => throw new NotImplementedException());
+ }
+
+ // The UIThreadTestMethod automatically dispatches to the UI for us to work with UI objects.
+ [UIThreadTestMethod]
+ public void SimpleUIAttributeExampleTest()
+ {
+ var component = new TitleBar();
+ Assert.IsNotNull(component);
+ }
+
+ // The UIThreadTestMethod can also easily grab a XAML Page for us by passing its type as a parameter.
+ // This lets us actually test a control as it would behave within an actual application.
+ // The page will already be loaded by the time your test is called.
+ [UIThreadTestMethod]
+ public void SimpleUIExamplePageTest(ExampleTitleBarTestPage page)
+ {
+ // You can use the Toolkit Visual Tree helpers here to find the component by type or name:
+
+ }
+
+ // You can still do async work with a UIThreadTestMethod as well.
+ [UIThreadTestMethod]
+ public async Task SimpleAsyncUIExamplePageTest(ExampleTitleBarTestPage page)
+ {
+ // This helper can be used to wait for a rendering pass to complete.
+ // Note, this is already done by loading a Page with the [UIThreadTestMethod] helper.
+ await CompositionTargetHelper.ExecuteAfterCompositionRenderingAsync(() => { });
+
+
+ }
+
+ //// ----------------------------- ADVANCED TEST SCENARIOS -----------------------------
+
+ // If you need to use DataRow, you can use this pattern with the UI dispatch still.
+ // Otherwise, checkout the UIThreadTestMethod attribute above.
+ // See https://github.com/CommunityToolkit/Labs-Windows/issues/186
+ [TestMethod]
+ public async Task ComplexAsyncUIExampleTest()
+ {
+
+ }
+
+ // If you want to load other content not within a XAML page using the UIThreadTestMethod above.
+ // Then you can do that using the Load/UnloadTestContentAsync methods.
+ [TestMethod]
+ public async Task ComplexAsyncLoadUIExampleTest()
+ {
+
+ }
+
+ // You can still use the UIThreadTestMethod to remove the extra layer for the dispatcher as well:
+ [UIThreadTestMethod]
+ public async Task ComplexAsyncLoadUIExampleWithoutDispatcherTest()
+ {
+
+ }
+}
diff --git a/components/TitleBar/tests/ExampleTitleBarTestPage.xaml b/components/TitleBar/tests/ExampleTitleBarTestPage.xaml
new file mode 100644
index 000000000..0f429f271
--- /dev/null
+++ b/components/TitleBar/tests/ExampleTitleBarTestPage.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/components/TitleBar/tests/ExampleTitleBarTestPage.xaml.cs b/components/TitleBar/tests/ExampleTitleBarTestPage.xaml.cs
new file mode 100644
index 000000000..a3a3ddc3e
--- /dev/null
+++ b/components/TitleBar/tests/ExampleTitleBarTestPage.xaml.cs
@@ -0,0 +1,16 @@
+// 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.
+
+namespace TitleBarExperiment.Tests;
+
+///
+/// An empty page that can be used on its own or navigated to within a Frame.
+///
+public sealed partial class ExampleTitleBarTestPage : Page
+{
+ public ExampleTitleBarTestPage()
+ {
+ this.InitializeComponent();
+ }
+}
diff --git a/components/TitleBar/tests/TitleBar.Tests.projitems b/components/TitleBar/tests/TitleBar.Tests.projitems
new file mode 100644
index 000000000..41ae219e8
--- /dev/null
+++ b/components/TitleBar/tests/TitleBar.Tests.projitems
@@ -0,0 +1,23 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ 3BA0AB2F-24A8-4E53-BEFE-2796E3E82421
+
+
+ TitleBarExperiment.Tests
+
+
+
+
+ ExampleTitleBarTestPage.xaml
+
+
+
+
+ Designer
+ MSBuild:Compile
+
+
+
\ No newline at end of file
diff --git a/components/TitleBar/tests/TitleBar.Tests.shproj b/components/TitleBar/tests/TitleBar.Tests.shproj
new file mode 100644
index 000000000..5c6e8c674
--- /dev/null
+++ b/components/TitleBar/tests/TitleBar.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ 3BA0AB2F-24A8-4E53-BEFE-2796E3E82421
+ 14.0
+
+
+
+
+
+
+
+