From 2d35fd8530eb131518a78a51f895254358d081d9 Mon Sep 17 00:00:00 2001 From: Niels Laute Date: Wed, 27 Aug 2025 03:27:11 +0200 Subject: [PATCH] [Fix] Adding Toolkit's TitleBar manually (#41383) Temp fix for a VS bug when building the `PowerToys.Settings` project. There seems to be something wrong with the Labs TitleBar package, so following up with the team to get it resolved. Meanwhile, I've moved the Toolkit's source code for TitleBar to Settings as a custom control. Once the package is fixed we can revert this change. --- .github/actions/spell-check/expect.txt | 1 + Directory.Packages.props | 1 - NOTICE.md | 1 - .../Settings.UI/Helpers/NavigatablePage.cs | 37 +- .../Settings.UI/PowerToys.Settings.csproj | 10 +- .../Settings.UI/SettingsXAML/App.xaml | 1 + .../Controls/TitleBar/TitleBar.Properties.cs | 229 +++++++++++ .../Controls/TitleBar/TitleBar.WASDK.cs | 195 +++++++++ .../Controls/TitleBar/TitleBar.cs | 220 +++++++++++ .../Controls/TitleBar/TitleBar.xaml | 371 ++++++++++++++++++ .../SettingsXAML/Views/ShellPage.xaml | 18 +- .../SettingsXAML/Views/ShellPage.xaml.cs | 3 +- 12 files changed, 1039 insertions(+), 48 deletions(-) create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.Properties.cs create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.WASDK.cs create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.cs create mode 100644 src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.xaml diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index ef580c9681..ef03b7528e 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -49,6 +49,7 @@ ALPHATYPE AModifier amr ANDSCANS +animatedvisuals Animnate ANull AOC diff --git a/Directory.Packages.props b/Directory.Packages.props index 4c09a60664..649c927a64 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -23,7 +23,6 @@ - diff --git a/NOTICE.md b/NOTICE.md index 9861a58f4a..bedc11379d 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1499,7 +1499,6 @@ SOFTWARE. - CoenM.ImageSharp.ImageHash - CommunityToolkit.Common - CommunityToolkit.Labs.WinUI.Controls.MarkdownTextBlock -- CommunityToolkit.Labs.WinUI.TitleBar - CommunityToolkit.Mvvm - CommunityToolkit.WinUI.Animations - CommunityToolkit.WinUI.Collections diff --git a/src/settings-ui/Settings.UI/Helpers/NavigatablePage.cs b/src/settings-ui/Settings.UI/Helpers/NavigatablePage.cs index 126b4f2105..b52972c68e 100644 --- a/src/settings-ui/Settings.UI/Helpers/NavigatablePage.cs +++ b/src/settings-ui/Settings.UI/Helpers/NavigatablePage.cs @@ -10,13 +10,14 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Hosting; using Microsoft.UI.Xaml.Media; +using Windows.UI; namespace Microsoft.PowerToys.Settings.UI.Helpers; public abstract partial class NavigatablePage : Page { private const int ExpandWaitDuration = 500; - private const int AnimationDuration = 1000; + private const int AnimationDuration = 2000; private NavigationParams _pendingNavigationParams; @@ -85,15 +86,15 @@ public abstract partial class NavigatablePage : Page // Create a subtle glow effect using drop shadow var dropShadow = compositor.CreateDropShadow(); - dropShadow.Color = Microsoft.UI.Colors.Gray; - dropShadow.BlurRadius = 8f; + dropShadow.Color = (Color)Application.Current.Resources["SystemAccentColorLight2"]; + dropShadow.BlurRadius = 16f; dropShadow.Opacity = 0f; dropShadow.Offset = new Vector3(0, 0, 0); var spriteVisual = compositor.CreateSpriteVisual(); - spriteVisual.Size = new Vector2((float)target.ActualWidth + 16, (float)target.ActualHeight + 16); + spriteVisual.Size = new Vector2((float)target.ActualWidth, (float)target.ActualHeight); spriteVisual.Shadow = dropShadow; - spriteVisual.Offset = new Vector3(-8, -8, 0); + spriteVisual.Offset = new Vector3(0, 0, 0); // Insert the shadow visual behind the target element ElementCompositionPreview.SetElementChildVisual(target, spriteVisual); @@ -106,31 +107,7 @@ public abstract partial class NavigatablePage : Page fadeAnimation.Duration = TimeSpan.FromMilliseconds(AnimationDuration); dropShadow.StartAnimation("Opacity", fadeAnimation); - - if (target is Control ctrl) - { - // TODO: ability to adjust brush color and animation from settings. - var originalBackground = ctrl.Background; - - var highlightBrush = new SolidColorBrush(); - var grayColor = Microsoft.UI.Colors.Gray; - grayColor.A = 50; // Very subtle transparency - highlightBrush.Color = grayColor; - - // Apply the highlight - ctrl.Background = highlightBrush; - - // Wait for animation to complete - await Task.Delay(AnimationDuration); - - // Restore original background - ctrl.Background = originalBackground; - } - else - { - // For non-control elements, just wait for the glow animation - await Task.Delay(AnimationDuration); - } + await Task.Delay(AnimationDuration); // Clean up the shadow visual ElementCompositionPreview.SetElementChildVisual(target, null); diff --git a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj index aaa1c2bdc7..655011e074 100644 --- a/src/settings-ui/Settings.UI/PowerToys.Settings.csproj +++ b/src/settings-ui/Settings.UI/PowerToys.Settings.csproj @@ -25,6 +25,7 @@ + @@ -63,7 +64,6 @@ - @@ -156,7 +156,8 @@ Always - + + MSBuild:Compile @@ -185,9 +186,6 @@ - + \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml index ea2a585b8b..d4a95313d0 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/App.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/App.xaml @@ -11,6 +11,7 @@ + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.Properties.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.Properties.cs new file mode 100644 index 0000000000..db4e5244cf --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.Properties.cs @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Microsoft.PowerToys.Settings.UI.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, ContentChanged)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty FooterProperty = DependencyProperty.Register(nameof(Footer), typeof(object), typeof(TitleBar), new PropertyMetadata(null, FooterChanged)); + + /// + /// 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)); + + /// + /// The backing for the property. + /// + public static readonly DependencyProperty WindowProperty = DependencyProperty.Register(nameof(Window), typeof(Window), typeof(TitleBar), new PropertyMetadata(null)); + + /// + /// The event that gets fired when the back button is clicked + /// +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + public event EventHandler? BackButtonClick; +#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + + /// + /// The event that gets fired when the pane toggle button is clicked + /// +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + public event EventHandler? PaneButtonClick; +#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + + /// + /// 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 a value indicating whether gets or sets the visibility of the back button. + /// + public bool IsBackButtonVisible + { + get => (bool)GetValue(IsBackButtonVisibleProperty); + set => SetValue(IsBackButtonVisibleProperty, value); + } + + /// + /// Gets or sets a value indicating whether 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 a value indicating whether gets or sets if the TitleBar should auto configure ExtendContentIntoTitleBar and CaptionButton background colors. + /// + public bool AutoConfigureCustomTitleBar + { + get => (bool)GetValue(AutoConfigureCustomTitleBarProperty); + set => SetValue(AutoConfigureCustomTitleBarProperty, value); + } + + /// + /// Gets or sets the window the TitleBar should configure. + /// + public Window Window + { + get => (Window)GetValue(WindowProperty); + set => SetValue(WindowProperty, value); + } + + 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 ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ((TitleBar)d).Update(); + } + + private static void FooterChanged(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/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.WASDK.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.WASDK.cs new file mode 100644 index 0000000000..261044d98e --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.WASDK.cs @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.UI; +using Microsoft.UI.Input; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media; +using Windows.Foundation; +using Windows.Graphics; + +namespace Microsoft.PowerToys.Settings.UI.Controls; + +[TemplatePart(Name = nameof(PART_FooterPresenter), Type = typeof(ContentPresenter))] +[TemplatePart(Name = nameof(PART_ContentPresenter), Type = typeof(ContentPresenter))] + +public partial class TitleBar : Control +{ +#pragma warning disable SA1306 // Field names should begin with lower-case letter +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1400 // Access modifier should be declared +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + ContentPresenter? PART_ContentPresenter; + ContentPresenter? PART_FooterPresenter; + #pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. +#pragma warning restore SA1400 // Access modifier should be declared +#pragma warning restore SA1306 // Field names should begin with lower-case letter +#pragma warning restore SA1310 // Field names should not contain underscore + + private void SetWASDKTitleBar() + { + if (this.Window == null) + { + return; + } + + if (AutoConfigureCustomTitleBar) + { + Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true; + + this.Window.SizeChanged -= Window_SizeChanged; + this.Window.SizeChanged += Window_SizeChanged; + this.Window.Activated -= Window_Activated; + this.Window.Activated += Window_Activated; + + if (Window.Content is FrameworkElement rootElement) + { + UpdateCaptionButtons(rootElement); + rootElement.ActualThemeChanged += (s, e) => + { + UpdateCaptionButtons(rootElement); + }; + } + + 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; + 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. + Window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall; + } + else + { + Window.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Standard; + } + + // Recalculate the drag region for the custom title bar + // if you explicitly defined new draggable areas. + SetDragRegionForCustomTitleBar(); + + _isAutoConfigCompleted = true; + } + } + + private void Window_SizeChanged(object sender, WindowSizeChangedEventArgs args) + { + UpdateVisualStateAndDragRegion(args.Size); + } + + private void UpdateCaptionButtons(FrameworkElement rootElement) + { + Window.AppWindow.TitleBar.ButtonBackgroundColor = Colors.Transparent; + Window.AppWindow.TitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; + if (rootElement.ActualTheme == ElementTheme.Dark) + { + Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.White; + Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray; + } + else + { + Window.AppWindow.TitleBar.ButtonForegroundColor = Colors.Black; + Window.AppWindow.TitleBar.ButtonInactiveForegroundColor = Colors.DarkGray; + } + } + + private void ResetWASDKTitleBar() + { + if (this.Window == null) + { + return; + } + + // Only reset if we were the ones who configured + if (_isAutoConfigCompleted) + { + Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = false; + this.Window.SizeChanged -= Window_SizeChanged; + this.Window.Activated -= Window_Activated; + SizeChanged -= this.TitleBar_SizeChanged; + Window.AppWindow.TitleBar.ResetToDefault(); + } + } + + private void Window_Activated(object sender, WindowActivatedEventArgs args) + { + if (args.WindowActivationState == WindowActivationState.Deactivated) + { + VisualStateManager.GoToState(this, WindowDeactivatedState, true); + } + else + { + VisualStateManager.GoToState(this, WindowActivatedState, true); + } + } + + public void SetDragRegionForCustomTitleBar() + { + if (AutoConfigureCustomTitleBar && Window is not null) + { + ClearDragRegions(NonClientRegionKind.Passthrough); +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + var items = new FrameworkElement?[] { PART_ContentPresenter, PART_FooterPresenter, PART_ButtonHolder }; +#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + var validItems = items.Where(x => x is not null).Select(x => x!).ToArray(); // Prune null items + + SetDragRegion(NonClientRegionKind.Passthrough, validItems); + } + } + + public double GetRasterizationScaleForElement(UIElement element) + { + if (element.XamlRoot != null) + { + return element.XamlRoot.RasterizationScale; + } + + return 0.0; + } + + public void SetDragRegion(NonClientRegionKind nonClientRegionKind, params FrameworkElement[] frameworkElements) + { + List rects = new List(); + var scale = GetRasterizationScaleForElement(this); + + foreach (var frameworkElement in frameworkElements) + { + if (frameworkElement == null) + { + continue; + } + + GeneralTransform transformElement = frameworkElement.TransformToVisual(null); + Rect bounds = transformElement.TransformBounds(new Rect(0, 0, frameworkElement.ActualWidth, frameworkElement.ActualHeight)); + var transparentRect = new 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) + { + InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id).SetRegionRects(nonClientRegionKind, rects.ToArray()); + } + } + + public void ClearDragRegions(NonClientRegionKind nonClientRegionKind) + { + InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id).ClearRegionRects(nonClientRegionKind); + } +} diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.cs b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.cs new file mode 100644 index 0000000000..f49bcc3bec --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.cs @@ -0,0 +1,220 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Windows.Foundation; + +namespace Microsoft.PowerToys.Settings.UI.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 = ContentVisibleState, GroupName = ContentStates)] +[TemplateVisualState(Name = ContentCollapsedState, GroupName = ContentStates)] +[TemplateVisualState(Name = FooterVisibleState, GroupName = FooterStates)] +[TemplateVisualState(Name = FooterCollapsedState, GroupName = FooterStates)] +[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))] +[TemplatePart(Name = nameof(PART_ButtonHolder), Type = typeof(StackPanel))] + +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 ContentVisibleState = "ContentVisible"; + private const string ContentCollapsedState = "ContentCollapsed"; + private const string ContentStates = "ContentStates"; + + private const string FooterVisibleState = "FooterVisible"; + private const string FooterCollapsedState = "FooterCollapsed"; + private const string FooterStates = "FooterStates"; + + private const string WideState = "Wide"; + private const string NarrowState = "Narrow"; + private const string ReflowStates = "ReflowStates"; + +#pragma warning disable SA1306 // Field names should begin with lower-case letter +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1400 // Access modifier should be declared +#pragma warning disable CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. + ColumnDefinition? PART_RightPaddingColumn; + ColumnDefinition? PART_LeftPaddingColumn; + StackPanel? PART_ButtonHolder; +#pragma warning restore CS8632 // The annotation for nullable reference types should only be used in code within a '#nullable' annotations context. +#pragma warning restore SA1400 // Access modifier should be declared +#pragma warning restore SA1306 // Field names should begin with lower-case letter +#pragma warning restore SA1310 // Field names should not contain underscore + + // We only want to reset TitleBar configuration in app, if we're the TitleBar instance that's managing that state. + private bool _isAutoConfigCompleted; + + 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; + ConfigureButtonHolder(); + 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) + { + UpdateVisualStateAndDragRegion(e.NewSize); + } + + private void UpdateVisualStateAndDragRegion(Size size) + { + if (size.Width <= CompactStateBreakpoint) + { + if (Content != null || Footer != null) + { + VisualStateManager.GoToState(this, NarrowState, true); + } + } + else + { + VisualStateManager.GoToState(this, WideState, true); + } + + SetDragRegionForCustomTitleBar(); + } + + 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 ConfigureButtonHolder() + { + if (PART_ButtonHolder != null) + { + PART_ButtonHolder.SizeChanged -= PART_ButtonHolder_SizeChanged; + } + + PART_ButtonHolder = GetTemplateChild(nameof(PART_ButtonHolder)) as StackPanel; + + if (PART_ButtonHolder != null) + { + PART_ButtonHolder.SizeChanged += PART_ButtonHolder_SizeChanged; + } + } + + private void PART_ButtonHolder_SizeChanged(object sender, SizeChangedEventArgs e) + { + SetDragRegionForCustomTitleBar(); + } + + private void Configure() + { + SetWASDKTitleBar(); + } + + public void Reset() + { + ResetWASDKTitleBar(); + } + + 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); + } + + if (Content != null) + { + VisualStateManager.GoToState(this, ContentVisibleState, true); + } + else + { + VisualStateManager.GoToState(this, ContentCollapsedState, true); + } + + if (Footer != null) + { + VisualStateManager.GoToState(this, FooterVisibleState, true); + } + else + { + VisualStateManager.GoToState(this, FooterCollapsedState, true); + } + + SetDragRegionForCustomTitleBar(); + } +} diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.xaml new file mode 100644 index 0000000000..09c9344ad7 --- /dev/null +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/TitleBar/TitleBar.xaml @@ -0,0 +1,371 @@ + + + 32 + 48 + 360 + + + + + + + + diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml index 9f112dded2..0bcff4629f 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/ShellPage.xaml @@ -4,6 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:animatedVisuals="using:Microsoft.UI.Xaml.Controls.AnimatedVisuals" xmlns:animations="using:CommunityToolkit.WinUI.Animations" + xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls" xmlns:converters="using:Microsoft.PowerToys.Settings.UI.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:helpers="using:Microsoft.PowerToys.Settings.UI.Helpers" @@ -11,7 +12,6 @@ xmlns:ic="using:Microsoft.Xaml.Interactions.Core" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:models="using:Microsoft.PowerToys.Settings.UI.ViewModels" - xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls" xmlns:tkconverters="using:CommunityToolkit.WinUI.Converters" xmlns:ui="using:CommunityToolkit.WinUI" xmlns:views="using:Microsoft.PowerToys.Settings.UI.Views" @@ -98,17 +98,17 @@ - - + 516 - - + + - - + + - - + + AppTitleBar; + public Controls.TitleBar TitleBar => AppTitleBar; private Dictionary _navViewParentLookup = new Dictionary(); private List _searchSuggestions = [];