diff --git a/README.md b/README.md
index a498f4b..e850406 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ Watch WinUIEx covered in the On .NET Live show:
- [Splash Screens](https://dotmorten.github.io/WinUIEx/concepts/Splashscreen.html)
- [OAuth Web Authentication](https://dotmorten.github.io/WinUIEx/concepts/WebAuthenticator.html)
- [Custom Window Backdrops](https://dotmorten.github.io/WinUIEx/concepts/CustomBackdrops.html)
+ - TitleBar control
- Code analyzers for Windows App SDK APIs to guide the developer.
diff --git a/docs/index.md b/docs/index.md
index a410309..7e1a035 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -15,6 +15,7 @@ A set of extension methods and classes to fill some gaps in WinUI 3, mostly arou
- [Splash screen](concepts/Splashscreen.md)
- [OAuth Web Authenticator](concepts/WebAuthenticator.md)
- [Custom Backdrops](concepts/CustomBackdrops.md)
+ - TitleBar
And more to come...
diff --git a/src/Dependencies.targets b/src/Dependencies.targets
index f64151b..5d88546 100644
--- a/src/Dependencies.targets
+++ b/src/Dependencies.targets
@@ -1,8 +1,8 @@
- 1.4.230822000
- 10.0.22621.1
+ 1.5.240227000
+ 10.0.22621.756
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index b369503..44a0e10 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -17,8 +17,8 @@
Morten Nielsen - https://xaml.dev
Morten Nielsen - https://xaml.dev
logo.png
- 2.3.4
- 2.3.3
+ 2.4.0
+ 2.3.4
diff --git a/src/WinUIEx/Themes/Generic.xaml b/src/WinUIEx/Themes/Generic.xaml
new file mode 100644
index 0000000..0fb1906
--- /dev/null
+++ b/src/WinUIEx/Themes/Generic.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/WinUIEx/TitleBar/TitleBar.cs b/src/WinUIEx/TitleBar/TitleBar.cs
new file mode 100644
index 0000000..4355153
--- /dev/null
+++ b/src/WinUIEx/TitleBar/TitleBar.cs
@@ -0,0 +1,782 @@
+using System.Collections.Generic;
+using Microsoft.UI.Input;
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Automation;
+using Microsoft.UI.Xaml.Automation.Peers;
+using Microsoft.UI.Xaml.Controls;
+
+namespace WinUIEx;
+
+///
+/// TitleBar control.
+///
+public class TitleBar : Control
+{
+ private double m_compactModeThresholdWidth = 0.0;
+ const string s_leftPaddingColumnName = "LeftPaddingColumn";
+ const string s_rightPaddingColumnName = "RightPaddingColumn";
+ const string s_layoutRootPartName = "PART_LayoutRoot";
+ const string s_backButtonPartName = "PART_BackButton";
+ const string s_paneToggleButtonPartName = "PART_PaneToggleButton";
+ const string s_headerContentPresenterPartName = "PART_HeaderContentPresenter";
+ const string s_contentPresenterGridPartName = "PART_ContentPresenterGrid";
+ const string s_contentPresenterPartName = "PART_ContentPresenter";
+ const string s_footerPresenterPartName = "PART_FooterContentPresenter";
+ const string s_compactVisualStateName = "Compact";
+ const string s_expandedVisualStateName = "Expanded";
+ const string s_compactHeightVisualStateName = "CompactHeight";
+ const string s_expandedHeightVisualStateName = "ExpandedHeight";
+ const string s_defaultSpacingVisualStateName = "DefaultSpacing";
+ const string s_negativeInsetVisualStateName = "NegativeInsetSpacing";
+ const string s_iconVisibleVisualStateName = "IconVisible";
+ const string s_iconCollapsedVisualStateName = "IconCollapsed";
+ const string s_iconDeactivatedVisualStateName = "IconDeactivated";
+ const string s_backButtonVisibleVisualStateName = "BackButtonVisible";
+ const string s_backButtonCollapsedVisualStateName = "BackButtonCollapsed";
+ const string s_backButtonDeactivatedVisualStateName = "BackButtonDeactivated";
+ const string s_paneToggleButtonVisibleVisualStateName = "PaneToggleButtonVisible";
+ const string s_paneToggleButtonCollapsedVisualStateName = "PaneToggleButtonCollapsed";
+ const string s_paneToggleButtonDeactivatedVisualStateName = "PaneToggleButtonDeactivated";
+ const string s_titleTextVisibleVisualStateName = "TitleTextVisible";
+ const string s_titleTextCollapsedVisualStateName = "TitleTextCollapsed";
+ const string s_titleTextDeactivatedVisualStateName = "TitleTextDeactivated";
+ const string s_subtitleTextVisibleVisualStateName = "SubtitleTextVisible";
+ const string s_subtitleTextCollapsedVisualStateName = "SubtitleTextCollapsed";
+ const string s_subtitleTextDeactivatedVisualStateName = "SubtitleTextDeactivated";
+ const string s_headerVisibleVisualStateName = "HeaderVisible";
+ const string s_headerCollapsedVisualStateName = "HeaderCollapsed";
+ const string s_headerDeactivatedVisualStateName = "HeaderDeactivated";
+ const string s_contentVisibleVisualStateName = "ContentVisible";
+ const string s_contentCollapsedVisualStateName = "ContentCollapsed";
+ const string s_contentDeactivatedVisualStateName = "ContentDeactivated";
+ const string s_footerVisibleVisualStateName = "FooterVisible";
+ const string s_footerCollapsedVisualStateName = "FooterCollapsed";
+ const string s_footerDeactivatedVisualStateName = "FooterDeactivated";
+ const string s_titleBarButtonForegroundColorName = "TitleBarButtonForegroundColor";
+ const string s_titleBarButtonBackgroundColorName = "TitleBarButtonBackgroundColor";
+ const string s_titleBarButtonHoverForegroundColorName = "TitleBarButtonHoverForegroundColor";
+ const string s_titleBarButtonHoverBackgroundColorName = "TitleBarButtonHoverBackgroundColor";
+ const string s_titleBarButtonPressedForegroundColorName = "TitleBarButtonPressedForegroundColor";
+ const string s_titleBarButtonPressedBackgroundColorName = "TitleBarButtonPressedBackgroundColor";
+ const string s_titleBarButtonInactiveForegroundColorName = "TitleBarButtonInactiveForegroundColor";
+
+ private ColumnDefinition? m_leftPaddingColumn;
+ private ColumnDefinition? m_rightPaddingColumn;
+ private Button? m_backButton;
+ private Button? m_paneToggleButton;
+ private Grid? m_contentAreaGrid;
+ private FrameworkElement? m_headerArea;
+ private FrameworkElement? m_contentArea;
+ private FrameworkElement? m_footerArea;
+ private InputActivationListener? m_inputActivationListener;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TitleBar()
+ {
+ SetValue(TemplateSettingsProperty, new TitleBarTemplateSettings());
+ this.DefaultStyleKey = typeof(TitleBar);
+ this.SizeChanged += OnSizeChanged;
+ this.LayoutUpdated += OnLayoutUpdated;
+ ActualThemeChanged += (s, e) => UpdateTheme();
+ }
+
+ ///
+ /// Finalizer
+ ///
+ ~TitleBar()
+ {
+ if (m_inputActivationListener != null)
+ m_inputActivationListener.InputActivationChanged -= OnInputActivationChanged;
+ }
+
+ ///
+ protected override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+ m_leftPaddingColumn = GetTemplateChild(s_leftPaddingColumnName) as ColumnDefinition;
+ m_rightPaddingColumn = GetTemplateChild(s_rightPaddingColumnName) as ColumnDefinition;
+ if (XamlRoot?.ContentIslandEnvironment is not null)
+ {
+ var appWindowId = XamlRoot.ContentIslandEnvironment.AppWindowId;
+ if (appWindowId.Value != 0)
+ {
+ m_inputActivationListener = Microsoft.UI.Input.InputActivationListener.GetForWindowId(appWindowId);
+ m_inputActivationListener.InputActivationChanged += OnInputActivationChanged;
+ }
+ }
+ UpdateHeight();
+ UpdatePadding();
+ UpdateIcon();
+ UpdateBackButton();
+ UpdatePaneToggleButton();
+ UpdateTheme();
+ UpdateTitle();
+ UpdateSubtitle();
+ UpdateHeader();
+ UpdateContent();
+ UpdateFooter();
+ UpdateInteractableElementsList();
+ }
+
+ private void OnSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ if(Content is not null)
+ {
+ if(m_contentArea is not null && m_contentAreaGrid is not null)
+ {
+ if(m_compactModeThresholdWidth != 0 && m_contentArea.DesiredSize.Width > m_contentAreaGrid.ActualWidth)
+ {
+ m_compactModeThresholdWidth = e.NewSize.Width;
+ }
+ else if(e.NewSize.Width >= m_compactModeThresholdWidth)
+ {
+ m_compactModeThresholdWidth = 0;
+ VisualStateManager.GoToState(this, s_expandedVisualStateName, false);
+ UpdateTitle();
+ UpdateSubtitle();
+ }
+ }
+ }
+ UpdateDragRegion();
+ }
+
+ private void OnLayoutUpdated(object? sender, object e)
+ {
+ UpdateDragRegion();
+ }
+
+ private void OnInputActivationChanged(InputActivationListener sender, InputActivationListenerActivationChangedEventArgs args)
+ {
+ bool isDeactivate = sender.State == InputActivationState.Deactivated;
+ if (IsBackButtonVisible && IsBackEnabled)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_backButtonDeactivatedVisualStateName : s_backButtonVisibleVisualStateName, false);
+ }
+
+ if (IsPaneToggleButtonVisible)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_paneToggleButtonDeactivatedVisualStateName : s_paneToggleButtonVisibleVisualStateName, false);
+ }
+
+ if (IconSource is not null)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_iconDeactivatedVisualStateName : s_iconVisibleVisualStateName, false);
+ }
+
+ if (!string.IsNullOrEmpty(Title))
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_titleTextDeactivatedVisualStateName : s_titleTextVisibleVisualStateName, false);
+ }
+
+ if (!string.IsNullOrEmpty(Subtitle))
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_subtitleTextDeactivatedVisualStateName : s_subtitleTextVisibleVisualStateName, false);
+ }
+
+ if (Header is not null)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_headerDeactivatedVisualStateName : s_headerVisibleVisualStateName, false);
+ }
+
+ if (Content is not null)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_contentDeactivatedVisualStateName : s_contentVisibleVisualStateName, false);
+ }
+
+ if (Footer is not null)
+ {
+ VisualStateManager.GoToState(this, isDeactivate ? s_footerDeactivatedVisualStateName : s_footerVisibleVisualStateName, false);
+ }
+ }
+
+ private void OnBackButtonClick(object sender, RoutedEventArgs e)
+ {
+ BackRequested?.Invoke(this, e);
+ }
+
+ private void OnPaneToggleButtonClick(object sender, RoutedEventArgs e)
+ {
+ PaneToggleRequested?.Invoke(this, e);
+ }
+
+ private void UpdateIcon()
+ {
+ if ((IconSource is not null))
+ {
+ TemplateSettings.IconElement = MakeIconElementFrom(IconSource);
+ VisualStateManager.GoToState(this, s_iconVisibleVisualStateName, false);
+ }
+ else
+ {
+ TemplateSettings.IconElement = null;
+ VisualStateManager.GoToState(this, s_iconCollapsedVisualStateName, false);
+ }
+ }
+
+ private IconElement? MakeIconElementFrom(IconSource iconSource)
+ {
+ if (iconSource is FontIconSource fontIconSource)
+ {
+ FontIcon fontIcon = new FontIcon();
+
+ fontIcon.Glyph = fontIconSource.Glyph;
+ fontIcon.FontSize = fontIconSource.FontSize;
+ if (fontIconSource.Foreground != null)
+ {
+ fontIcon.Foreground = fontIconSource.Foreground;
+ }
+
+ if (fontIconSource.FontFamily != null)
+ {
+ fontIcon.FontFamily = fontIconSource.FontFamily;
+ }
+
+ fontIcon.FontWeight = fontIconSource.FontWeight;
+ fontIcon.FontStyle = fontIconSource.FontStyle;
+ fontIcon.IsTextScaleFactorEnabled = fontIconSource.IsTextScaleFactorEnabled;
+ fontIcon.MirroredWhenRightToLeft = fontIconSource.MirroredWhenRightToLeft;
+
+ return fontIcon;
+ }
+ else if (iconSource is SymbolIconSource symbolIconSource)
+ {
+ SymbolIcon symbolIcon = new SymbolIcon();
+ symbolIcon.Symbol = symbolIconSource.Symbol;
+ if (symbolIconSource.Foreground != null)
+ {
+ symbolIcon.Foreground = symbolIconSource.Foreground;
+ }
+ return symbolIcon;
+ }
+ // Note: this check must be done before BitmapIconSource
+ // since ImageIconSource uses BitmapIconSource as a composable interface,
+ // so a ImageIconSource will also register as a BitmapIconSource.
+ else if (iconSource is ImageIconSource imageIconSource)
+ {
+ ImageIcon imageIcon = new ImageIcon();
+ if (imageIconSource.ImageSource != null)
+ {
+ imageIcon.Source = imageIconSource.ImageSource;
+ }
+ if (imageIconSource.Foreground != null)
+ {
+ imageIcon.Foreground = imageIconSource.Foreground;
+ }
+ return imageIcon;
+ }
+ else if (iconSource is BitmapIconSource bitmapIconSource)
+ {
+ BitmapIcon bitmapIcon = new BitmapIcon();
+
+ if (bitmapIconSource.UriSource != null)
+ {
+ bitmapIcon.UriSource = bitmapIconSource.UriSource;
+ }
+
+ bitmapIcon.ShowAsMonochrome = bitmapIconSource.ShowAsMonochrome;
+
+ if (bitmapIconSource.Foreground != null)
+ {
+ bitmapIcon.Foreground = bitmapIconSource.Foreground;
+ }
+
+ return bitmapIcon;
+ }
+ // Note: this check must be done before PathIconSource
+ // since AnimatedIconSource uses PathIconSource as a composable interface,
+ // so a AnimatedIconSource will also register as a PathIconSource.
+ else if (iconSource is AnimatedIconSource animatedIconSource)
+ {
+ AnimatedIcon animatedIcon = new AnimatedIcon();
+ if (animatedIconSource.Source != null)
+ {
+ animatedIcon.Source = animatedIconSource.Source;
+ }
+ if (animatedIconSource.FallbackIconSource != null)
+ {
+ animatedIcon.FallbackIconSource = animatedIconSource.FallbackIconSource;
+ }
+ if (animatedIconSource.Foreground != null)
+ {
+ animatedIcon.Foreground = animatedIconSource.Foreground;
+ }
+ return animatedIcon;
+ }
+ else if (iconSource is PathIconSource pathIconSource)
+ {
+ PathIcon pathIcon = new PathIcon();
+
+ if (pathIconSource.Data != null)
+ {
+ pathIcon.Data = pathIconSource.Data;
+ }
+ if (pathIconSource.Foreground != null)
+ {
+ pathIcon.Foreground = pathIconSource.Foreground;
+ }
+ return pathIcon;
+ }
+
+ return null;
+ }
+
+ private void UpdateBackButton()
+ {
+ if (IsBackButtonVisible)
+ {
+ if (m_backButton is null)
+ {
+ LoadBackButton();
+ }
+
+ VisualStateManager.GoToState(this, s_backButtonVisibleVisualStateName, false);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, s_backButtonCollapsedVisualStateName, false);
+ }
+
+ UpdateInteractableElementsList();
+ UpdateHeaderSpacing();
+ }
+
+ private void UpdatePaneToggleButton()
+ {
+ if (IsPaneToggleButtonVisible)
+ {
+ if (m_paneToggleButton is null)
+ {
+ LoadPaneToggleButton();
+ }
+
+ VisualStateManager.GoToState(this, s_paneToggleButtonVisibleVisualStateName, false);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, s_paneToggleButtonCollapsedVisualStateName, false);
+ }
+
+ UpdateInteractableElementsList();
+ UpdateHeaderSpacing();
+ }
+
+ private void UpdateHeight()
+ {
+ VisualStateManager.GoToState(this, (Content is null && Header is null && Footer is null) ? s_compactHeightVisualStateName : s_expandedHeightVisualStateName, false);
+ }
+
+ private void UpdatePadding()
+ {
+ if (XamlRoot?.ContentIslandEnvironment is not null)
+ {
+ var appWindowId = XamlRoot.ContentIslandEnvironment.AppWindowId;
+ var appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(appWindowId);
+
+ if (appWindow.TitleBar is not null)
+ {
+ // TODO 50724421: Bind to appTitleBar Left and Right inset changed event.
+ if (m_leftPaddingColumn is not null)
+ {
+ m_leftPaddingColumn.Width = new GridLength(appWindow.TitleBar.LeftInset);
+ }
+
+ if (m_rightPaddingColumn is not null)
+ {
+ m_rightPaddingColumn.Width = new GridLength(appWindow.TitleBar.RightInset);
+ }
+ }
+ }
+ }
+
+ private object? ResourceLookup(string key)
+ {
+ return this.Resources.ContainsKey(key) ? this.Resources[key] : Application.Current.Resources.ContainsKey(key) ? Application.Current.Resources[key] : null;
+ }
+
+ private void UpdateTheme()
+ {
+ if (XamlRoot?.ContentIslandEnvironment is not null)
+ {
+ var appWindowId = XamlRoot.ContentIslandEnvironment.AppWindowId;
+ var appTitleBar = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(appWindowId)?.TitleBar;
+ // AppWindow TitleBar's caption buttons does not update colors with theme change.
+ // We need to set them here.
+ if (appTitleBar is not null)
+ {
+ // Rest colors.
+ var buttonForegroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonForegroundColorName);
+ appTitleBar.ButtonForegroundColor = buttonForegroundColor;
+
+ var buttonBackgroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonBackgroundColorName);
+ appTitleBar.ButtonBackgroundColor = buttonBackgroundColor;
+ appTitleBar.ButtonInactiveBackgroundColor = buttonBackgroundColor;
+
+ // Hover colors.
+ var buttonHoverForegroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonHoverForegroundColorName);
+ appTitleBar.ButtonHoverForegroundColor = buttonHoverForegroundColor;
+
+ var buttonHoverBackgroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonHoverBackgroundColorName);
+ appTitleBar.ButtonHoverBackgroundColor = buttonHoverBackgroundColor;
+
+ // Pressed colors.
+ var buttonPressedForegroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonPressedForegroundColorName);
+ appTitleBar.ButtonPressedForegroundColor = buttonPressedForegroundColor;
+
+ var buttonPressedBackgroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonPressedBackgroundColorName);
+ appTitleBar.ButtonPressedBackgroundColor= buttonPressedBackgroundColor;
+
+ // Inactive foreground.
+ var buttonInactiveForegroundColor = (Windows.UI.Color?)ResourceLookup(s_titleBarButtonInactiveForegroundColorName);
+ appTitleBar.ButtonInactiveForegroundColor =buttonInactiveForegroundColor;
+ }
+ }
+ }
+
+ private void UpdateTitle()
+ {
+ if (string.IsNullOrEmpty(Title))
+ {
+ VisualStateManager.GoToState(this, s_titleTextCollapsedVisualStateName, false);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, s_titleTextVisibleVisualStateName, false);
+ }
+ }
+
+ private void UpdateSubtitle()
+ {
+ if (string.IsNullOrEmpty(Subtitle))
+ {
+ VisualStateManager.GoToState(this, s_subtitleTextCollapsedVisualStateName, false);
+ }
+ else
+ {
+ VisualStateManager.GoToState(this, s_subtitleTextVisibleVisualStateName, false);
+ }
+ }
+
+ private void UpdateHeader()
+ {
+ if (Header is null)
+ {
+ VisualStateManager.GoToState(this, s_headerCollapsedVisualStateName, false);
+ }
+ else
+ {
+ if (m_headerArea is null)
+ {
+ m_headerArea = GetTemplateChild(s_headerContentPresenterPartName) as FrameworkElement;
+ }
+ VisualStateManager.GoToState(this, s_headerVisibleVisualStateName, false);
+ }
+
+ UpdateHeight();
+ UpdateInteractableElementsList();
+ }
+ private void UpdateContent()
+ {
+ if (Content is null)
+ {
+ VisualStateManager.GoToState(this, s_contentCollapsedVisualStateName, false);
+ }
+ else
+ {
+ if (m_contentArea is null)
+ {
+ m_contentAreaGrid = GetTemplateChild(s_contentPresenterGridPartName) as Grid;
+ m_contentArea = GetTemplateChild(s_contentPresenterPartName) as FrameworkElement;
+ }
+
+ VisualStateManager.GoToState(this, s_contentVisibleVisualStateName, false);
+ }
+
+ UpdateHeight();
+ UpdateInteractableElementsList();
+ }
+ private void UpdateFooter()
+ {
+ if (Footer is null)
+ {
+ VisualStateManager.GoToState(this, s_footerCollapsedVisualStateName, false);
+ }
+ else
+ {
+ if (m_footerArea is null)
+ {
+ m_footerArea = GetTemplateChild(s_footerPresenterPartName) as FrameworkElement;
+ }
+ VisualStateManager.GoToState(this, s_footerVisibleVisualStateName, false);
+ }
+
+ UpdateHeight();
+ UpdateInteractableElementsList();
+ }
+
+ private readonly List m_interactableElementsList = new List();
+
+ private void UpdateDragRegion()
+ {
+ if (XamlRoot?.ContentIslandEnvironment is not null)
+ {
+ var appWindowId = XamlRoot.ContentIslandEnvironment.AppWindowId;
+ var nonClientPointerSource = InputNonClientPointerSource.GetForWindowId(appWindowId);
+
+ if (m_interactableElementsList.Count > 0)
+ {
+ List passthroughRects = new List();
+
+ // Get rects for each interactable element in TitleBar.
+ foreach (var frameworkElement in m_interactableElementsList)
+ {
+ var transformBounds = frameworkElement.TransformToVisual(null);
+ var width = frameworkElement.ActualWidth;
+ var height = frameworkElement.ActualHeight;
+ var bounds = transformBounds.TransformBounds(new Windows.Foundation.Rect(0.0f, 0.0f, width, height));
+
+ if (bounds.X < 0 || bounds.Y < 0)
+ {
+ continue;
+ }
+
+ var scale = XamlRoot.RasterizationScale;
+ var transparentRect = new Windows.Graphics.RectInt32(
+ (int)(bounds.X * scale),
+ (int)(bounds.Y * scale),
+ (int)(bounds.Width * scale),
+ (int)(bounds.Height * scale));
+
+ passthroughRects.Add(transparentRect);
+ }
+
+ // Set list of rects as passthrough regions for the non-client area.
+ nonClientPointerSource.SetRegionRects(NonClientRegionKind.Passthrough, [.. passthroughRects]);
+ }
+ else
+ {
+ // There is no interactable areas. Clear previous passthrough rects.
+ nonClientPointerSource.ClearRegionRects(NonClientRegionKind.Passthrough);
+ }
+ }
+ }
+
+ private void UpdateInteractableElementsList()
+ {
+ m_interactableElementsList.Clear();
+
+ if (IsBackButtonVisible && IsBackEnabled && m_backButton is not null)
+ {
+ m_interactableElementsList.Add(m_backButton);
+ }
+
+ if (IsPaneToggleButtonVisible && m_paneToggleButton is not null)
+ {
+ m_interactableElementsList.Add(m_paneToggleButton);
+ }
+
+ if (Header is not null && m_headerArea is not null)
+ {
+ m_interactableElementsList.Add(m_headerArea);
+ }
+
+ if (Content is not null && m_contentArea is not null)
+ {
+ m_interactableElementsList.Add(m_contentArea);
+ }
+
+
+ if (Footer is not null && m_footerArea is not null)
+ {
+ m_interactableElementsList.Add(m_footerArea);
+ }
+ }
+ private void UpdateHeaderSpacing()
+ {
+ VisualStateManager.GoToState(this, IsBackButtonVisible == IsPaneToggleButtonVisible ? s_defaultSpacingVisualStateName : s_negativeInsetVisualStateName, false);
+ }
+
+ private void LoadBackButton()
+ {
+ m_backButton = GetTemplateChild(s_backButtonPartName) as Button;
+
+ if (m_backButton is not null)
+ {
+ m_backButton.Click += OnBackButtonClick;
+ // Do localization for the back button
+ if (string.IsNullOrEmpty(AutomationProperties.GetName(m_backButton)))
+ {
+ AutomationProperties.SetName(m_backButton, "Back");
+ }
+
+ // Setup the tooltip for the back button
+ var tooltip = new ToolTip();
+ tooltip.Content = "Back";
+ ToolTipService.SetToolTip(m_backButton, tooltip);
+ }
+ }
+
+ private void LoadPaneToggleButton()
+ {
+ m_paneToggleButton = GetTemplateChild(s_paneToggleButtonPartName) as Button;
+
+ if (m_paneToggleButton is not null)
+ {
+ m_paneToggleButton.Click += OnPaneToggleButtonClick;
+
+ // Do localization for paneToggleButton
+ if (string.IsNullOrEmpty(AutomationProperties.GetName(m_paneToggleButton)))
+ {
+ AutomationProperties.SetName(m_paneToggleButton, "Toggle Navigation");
+ }
+
+ // Setup the tooltip for the paneToggleButton
+ var tooltip = new ToolTip();
+ tooltip.Content = AutomationProperties.GetName(m_paneToggleButton);
+ ToolTipService.SetToolTip(m_paneToggleButton, tooltip);
+ }
+ }
+
+ ///
+ protected override AutomationPeer OnCreateAutomationPeer() => new TitleBarAutomationPeer(this);
+
+ ///
+ /// Gets or sets the Icon for the titlebar
+ ///
+ public IconSource IconSource
+ {
+ get { return (IconSource)GetValue(IconSourceProperty); }
+ set { SetValue(IconSourceProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IconSourceProperty =
+ DependencyProperty.Register("IconSource", typeof(IconSource), typeof(TitleBar), new PropertyMetadata(null, (s, e) => ((TitleBar)s).UpdateIcon()));
+
+ ///
+ /// Gets or sets the Header content for the titlebar
+ ///
+ public object? Header
+ {
+ get { return (object)GetValue(HeaderProperty); }
+ set { SetValue(HeaderProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty HeaderProperty =
+ DependencyProperty.Register("Header", typeof(object), typeof(TitleBar), new PropertyMetadata(null, (s, e) => ((TitleBar)s).UpdateHeader()));
+
+ ///
+ /// Gets or sets the Window title
+ ///
+ public string Title
+ {
+ get { return (string)GetValue(TitleProperty); }
+ set { SetValue(TitleProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty TitleProperty =
+ DependencyProperty.Register("Title", typeof(string), typeof(TitleBar), new PropertyMetadata(string.Empty, (s, e) => ((TitleBar)s).UpdateTitle()));
+
+ ///
+ /// Gets or sets the Subtitle for the titlebar
+ ///
+ public string Subtitle
+ {
+ get { return (string)GetValue(SubtitleProperty); }
+ set { SetValue(SubtitleProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty SubtitleProperty =
+ DependencyProperty.Register("Subtitle", typeof(string), typeof(TitleBar), new PropertyMetadata(string.Empty, (s, e) => ((TitleBar)s).UpdateSubtitle()));
+
+ ///
+ /// Gets or sets the content for the titlebar
+ ///
+ public object? Content
+ {
+ get { return (object?)GetValue(ContentProperty); }
+ set { SetValue(ContentProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty ContentProperty =
+ DependencyProperty.Register("Content", typeof(object), typeof(TitleBar), new PropertyMetadata(null, (s, e) => ((TitleBar)s).UpdateContent()));
+
+ ///
+ /// Gets or sets the footer of the titlebar
+ ///
+ public object? Footer
+ {
+ get { return (object?)GetValue(FooterProperty); }
+ set { SetValue(FooterProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty FooterProperty =
+ DependencyProperty.Register("Footer", typeof(object), typeof(TitleBar), new PropertyMetadata(null, (s, e) => ((TitleBar)s).UpdateFooter()));
+
+ ///
+ /// Gets or sets a value indicating whether the back button is visible
+ ///
+ public bool IsBackButtonVisible
+ {
+ get { return (bool)GetValue(IsBackButtonVisibleProperty); }
+ set { SetValue(IsBackButtonVisibleProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsBackButtonVisibleProperty =
+ DependencyProperty.Register("IsBackButtonVisible", typeof(bool), typeof(TitleBar), new PropertyMetadata(false, (s,e) => ((TitleBar)s).UpdateBackButton()));
+
+ ///
+ /// Gets or sets a value indicating whether the back button is enabled
+ ///
+ public bool IsBackEnabled
+ {
+ get { return (bool)GetValue(IsBackEnabledProperty); }
+ set { SetValue(IsBackEnabledProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsBackEnabledProperty =
+ DependencyProperty.Register("IsBackEnabled", typeof(bool), typeof(TitleBar), new PropertyMetadata(true, (s, e) => ((TitleBar)s).UpdateInteractableElementsList()));
+
+ ///
+ /// Gets or sets a value indicating whether the pane toggle button is visible
+ ///
+ public bool IsPaneToggleButtonVisible
+ {
+ get { return (bool)GetValue(IsPaneToggleButtonVisibleProperty); }
+ set { SetValue(IsPaneToggleButtonVisibleProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsPaneToggleButtonVisibleProperty =
+ DependencyProperty.Register("IsPaneToggleButtonVisible", typeof(bool), typeof(TitleBar), new PropertyMetadata(false, (s, e) => ((TitleBar)s).UpdatePaneToggleButton()));
+
+ ///
+ /// Gets the template settings for the titlebar
+ ///
+ public TitleBarTemplateSettings TemplateSettings
+ {
+ get { return (TitleBarTemplateSettings)GetValue(TemplateSettingsProperty); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty TemplateSettingsProperty =
+ DependencyProperty.Register("TemplateSettings", typeof(TitleBarTemplateSettings), typeof(TitleBar), new PropertyMetadata(null));
+
+ ///
+ /// Raised when the back button is clicked
+ ///
+ public event Windows.Foundation.TypedEventHandler? BackRequested;
+
+ ///
+ /// Raised when the Pane toggle button is clicked
+ ///
+ public event Windows.Foundation.TypedEventHandler? PaneToggleRequested;
+}
diff --git a/src/WinUIEx/TitleBar/TitleBar.xaml b/src/WinUIEx/TitleBar/TitleBar.xaml
new file mode 100644
index 0000000..0b8cb78
--- /dev/null
+++ b/src/WinUIEx/TitleBar/TitleBar.xaml
@@ -0,0 +1,285 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/WinUIEx/TitleBar/TitleBarAutomationPeer.cs b/src/WinUIEx/TitleBar/TitleBarAutomationPeer.cs
new file mode 100644
index 0000000..c3721c2
--- /dev/null
+++ b/src/WinUIEx/TitleBar/TitleBarAutomationPeer.cs
@@ -0,0 +1,43 @@
+using Microsoft.UI.Xaml.Automation.Peers;
+
+namespace WinUIEx;
+
+///
+/// Automation peer for the control.
+///
+public class TitleBarAutomationPeer : FrameworkElementAutomationPeer
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// TitleBar owner
+ public TitleBarAutomationPeer(TitleBar owner) : base(owner)
+ {
+ }
+
+ ///
+ protected override AutomationControlType GetAutomationControlTypeCore()
+ {
+ return base.GetAutomationControlTypeCore();
+ }
+
+ ///
+ protected override string GetClassNameCore()
+ {
+ return nameof(TitleBar);
+ }
+
+ ///
+ protected override string GetNameCore()
+ {
+ var name = base.GetNameCore();
+ if (string.IsNullOrEmpty(name))
+ {
+ if (Owner is TitleBar titleBar)
+ {
+ name = titleBar.Name;
+ }
+ }
+ return name;
+ }
+}
diff --git a/src/WinUIEx/TitleBar/TitleBarTemplateSettings.cs b/src/WinUIEx/TitleBar/TitleBarTemplateSettings.cs
new file mode 100644
index 0000000..8138cbf
--- /dev/null
+++ b/src/WinUIEx/TitleBar/TitleBarTemplateSettings.cs
@@ -0,0 +1,30 @@
+using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
+
+namespace WinUIEx;
+
+///
+/// Class used to forward the IconElement property to the template.
+///
+public class TitleBarTemplateSettings : DependencyObject
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public TitleBarTemplateSettings()
+ {
+ }
+
+ ///
+ /// Gets or sets the IconElement property
+ ///
+ public IconElement? IconElement
+ {
+ get { return (IconElement?)GetValue(IconElementProperty); }
+ set { SetValue(IconElementProperty, value); }
+ }
+
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IconElementProperty =
+ DependencyProperty.Register("IconElement", typeof(IconElement), typeof(TitleBarTemplateSettings), new PropertyMetadata(null));
+}
diff --git a/src/WinUIEx/TitleBar/TitleBar_themeresources.xaml b/src/WinUIEx/TitleBar/TitleBar_themeresources.xaml
new file mode 100644
index 0000000..d51dbf1
--- /dev/null
+++ b/src/WinUIEx/TitleBar/TitleBar_themeresources.xaml
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+
+ #FFFFFF
+ #FFFFFF
+ #CFCFCF
+ #717171
+
+
+
+ 32
+ 48
+
+
+
+
+
+
+ #191919
+ #191919
+ #606060
+ #9b9b9b
+
+
+
+ 32
+ 48
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 32
+ 48
+
+
+
+ 0.5
+
+
+
+
+
diff --git a/src/WinUIEx/WinUIEx.csproj b/src/WinUIEx/WinUIEx.csproj
index e779a1d..df9fa6f 100644
--- a/src/WinUIEx/WinUIEx.csproj
+++ b/src/WinUIEx/WinUIEx.csproj
@@ -10,7 +10,7 @@
AnyCPU
enable
README.md
- 10
+ 12
true
true
true
diff --git a/src/WinUIExSample/MainWindow.xaml b/src/WinUIExSample/MainWindow.xaml
index 5e0b19b..f35afcd 100644
--- a/src/WinUIExSample/MainWindow.xaml
+++ b/src/WinUIExSample/MainWindow.xaml
@@ -11,20 +11,24 @@
Width="1024" Height="768"
MinWidth="500" MinHeight="250"
mc:Ignorable="d" >
-
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ IsPaneToggleButtonVisible="False" CompactModeThresholdWidth="0" x:Name="navigationView"
+ OpenPaneLength="250" Grid.Row="1" IsBackButtonVisible="Collapsed" PaneDisplayMode="Auto" SelectionChanged="NavigationView_SelectionChanged">
diff --git a/src/WinUIExSample/MainWindow.xaml.cs b/src/WinUIExSample/MainWindow.xaml.cs
index 2789fff..7c1b290 100644
--- a/src/WinUIExSample/MainWindow.xaml.cs
+++ b/src/WinUIExSample/MainWindow.xaml.cs
@@ -49,6 +49,7 @@ private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.Navigati
if (args.IsSettingsSelected)
{
contentFrame.Navigate(typeof(Pages.Settings));
+ titleBar.Subtitle = "Settings";
}
else
{
@@ -57,6 +58,7 @@ private void NavigationView_SelectionChanged(Microsoft.UI.Xaml.Controls.Navigati
{
string selectedItemTag = ((string)selectedItem.Tag);
sender.Header = selectedItem.Content;
+ titleBar.Subtitle = selectedItem.Content as string;
string pageName = "WinUIExSample.Pages." + selectedItemTag;
Type pageType = Type.GetType(pageName);
contentFrame.Navigate(pageType);
@@ -102,11 +104,11 @@ public void ShowLogWindow()
if (logWindow is null || logWindow.AppWindow is null)
{
logWindow = new LogWindow();
- logWindow.Closed += (s,e) => this.logWindow = null;
+ logWindow.Closed += (s, e) => this.logWindow = null;
}
logWindow.Activate();
}
-
+
public void ToggleWMMessages(bool isOn)
{
@@ -119,7 +121,7 @@ public void ToggleWMMessages(bool isOn)
private void WindowMessageReceived(object sender, WindowMessageEventArgs e)
{
Log(e.Message.ToString());
- if(e.Message.MessageId == 0x0005) //WM_SIZE
+ if (e.Message.MessageId == 0x0005) //WM_SIZE
{
// https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-size
switch (e.Message.WParam)
@@ -135,7 +137,18 @@ private void WindowMessageReceived(object sender, WindowMessageEventArgs e)
private void NavigationView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
- if(contentFrame.CanGoBack)
+ if (contentFrame.CanGoBack)
+ contentFrame.GoBack();
+ }
+
+ private void TitleBar_PaneToggleRequested(TitleBar sender, object args)
+ {
+ navigationView.IsPaneOpen = !navigationView.IsPaneOpen;
+ }
+
+ private void TitleBar_BackRequested(TitleBar sender, object args)
+ {
+ if (contentFrame.CanGoBack)
contentFrame.GoBack();
}
}