From 9054b27d7b18ba3e3547dcd877aa4561ca2f3884 Mon Sep 17 00:00:00 2001 From: Mike Corsaro Date: Wed, 24 Apr 2024 14:46:55 -0700 Subject: [PATCH 01/32] Custom titlebar control support spike --- .../Controls.Sample.Sandbox/MauiProgram.cs | 7 +- .../TitleBarSample.xaml.cs | 12 +++ .../TitlebarSample.xaml | 22 +++++ .../net-windows/PublicAPI.Unshipped.txt | 3 + src/Controls/src/Core/Window/Window.Mapper.cs | 1 + .../src/Core/Window/Window.Windows.cs | 19 ++++ src/Core/src/Core/IWindow.cs | 1 + .../Platform/Windows/NavigationRootManager.cs | 8 ++ .../Windows/Styles/WindowRootViewStyle.xaml | 3 +- .../src/Platform/Windows/WindowExtensions.cs | 15 ++++ .../src/Platform/Windows/WindowRootView.cs | 89 ++++++++++++++++++- .../net-windows/PublicAPI.Unshipped.txt | 1 + 12 files changed, 175 insertions(+), 6 deletions(-) create mode 100644 src/Controls/samples/Controls.Sample.Sandbox/TitleBarSample.xaml.cs create mode 100644 src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml diff --git a/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs b/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs index 3472acaffa8f..8fb1bade91fb 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs +++ b/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs @@ -26,7 +26,12 @@ protected override Window CreateWindow(IActivationState? activationState) if (!useShell) { - return new Window(new NavigationPage(new MainPage())); + var wnd = new Window(new NavigationPage(new MainPage())); + + var titlebar = new TitlebarSample(); + titlebar.HeightRequest = 60; + wnd.TitleBar = titlebar; + return wnd; } else { diff --git a/src/Controls/samples/Controls.Sample.Sandbox/TitleBarSample.xaml.cs b/src/Controls/samples/Controls.Sample.Sandbox/TitleBarSample.xaml.cs new file mode 100644 index 000000000000..32e8000ea147 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.Sandbox/TitleBarSample.xaml.cs @@ -0,0 +1,12 @@ +using Microsoft.Maui.Controls; + +namespace Maui.Controls.Sample +{ + public partial class TitlebarSample : ContentView + { + public TitlebarSample() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml b/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml new file mode 100644 index 000000000000..76ed77955f00 --- /dev/null +++ b/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml @@ -0,0 +1,22 @@ + + + + \ No newline at end of file diff --git a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt index b5c3cd1ee776..21ef5e75c9c2 100644 --- a/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -52,6 +52,7 @@ Microsoft.Maui.Controls.InputView.FontSize.get -> double Microsoft.Maui.Controls.InputView.FontSize.set -> void Microsoft.Maui.Controls.InputView.SelectionLength.get -> int Microsoft.Maui.Controls.InputView.SelectionLength.set -> void +Microsoft.Maui.Controls.Window.TitleBar.set -> void override Microsoft.Maui.Controls.GradientBrush.IsEmpty.get -> bool override Microsoft.Maui.Controls.Label.ArrangeOverride(Microsoft.Maui.Graphics.Rect bounds) -> Microsoft.Maui.Graphics.Size static Microsoft.Maui.Controls.Handlers.ShellItemHandler.MapTitle(Microsoft.Maui.Controls.Handlers.ShellItemHandler! handler, Microsoft.Maui.Controls.ShellItem! item) -> void @@ -135,6 +136,7 @@ override Microsoft.Maui.Controls.SearchBar.IsEnabledCore.get -> bool ~Microsoft.Maui.Controls.InputView.FontFamily.set -> void ~Microsoft.Maui.Controls.WebView.UserAgent.get -> string ~Microsoft.Maui.Controls.WebView.UserAgent.set -> void +~Microsoft.Maui.Controls.Window.TitleBar.get -> Microsoft.Maui.IView ~override Microsoft.Maui.Controls.Handlers.Items.StructuredItemsViewHandler.ConnectHandler(Microsoft.UI.Xaml.Controls.ListViewBase platformView) -> void ~override Microsoft.Maui.Controls.Handlers.Items.StructuredItemsViewHandler.DisconnectHandler(Microsoft.UI.Xaml.Controls.ListViewBase platformView) -> void ~override Microsoft.Maui.Controls.ImageButton.OnPropertyChanged(string propertyName = null) -> void @@ -244,3 +246,4 @@ Microsoft.Maui.Controls.ContentPage.HideSoftInputOnTapped.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.FontSize.set -> void *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.get -> int *REMOVED*Microsoft.Maui.Controls.Entry.SelectionLength.set -> void +~static readonly Microsoft.Maui.Controls.Window.TitleBarProperty -> Microsoft.Maui.Controls.BindableProperty diff --git a/src/Controls/src/Core/Window/Window.Mapper.cs b/src/Controls/src/Core/Window/Window.Mapper.cs index 6b7277d6363e..f999345393fb 100644 --- a/src/Controls/src/Core/Window/Window.Mapper.cs +++ b/src/Controls/src/Core/Window/Window.Mapper.cs @@ -20,6 +20,7 @@ public partial class Window #if WINDOWS WindowHandler.Mapper.PrependToMapping(nameof(ITitledElement.Title), MapTitle); + WindowHandler.Mapper.PrependToMapping(nameof(TitleBar), MapTitleBar);; #endif } } diff --git a/src/Controls/src/Core/Window/Window.Windows.cs b/src/Controls/src/Core/Window/Window.Windows.cs index 1559c199acad..e170ea9063c1 100644 --- a/src/Controls/src/Core/Window/Window.Windows.cs +++ b/src/Controls/src/Core/Window/Window.Windows.cs @@ -24,6 +24,13 @@ static void MapTitle(IWindowHandler handler, Window window) .UpdateTitle(window, window.GetCurrentlyPresentedMauiContext()); } + static void MapTitleBar(IWindowHandler handler, Window window) + { + handler + .PlatformView + .UpdateTitleBar(window, window.GetCurrentlyPresentedMauiContext()); + } + Rect[] IWindow.TitleBarDragRectangles { get @@ -32,5 +39,17 @@ Rect[] IWindow.TitleBarDragRectangles .GetDefaultTitleBarDragRectangles(this.GetCurrentlyPresentedMauiContext()); } } + + public IView TitleBar + { + get => (IView)GetValue(TitleBarProperty); +#pragma warning disable RS0036 // Annotate nullability of public types and members in the declared API + set => SetValue(TitleBarProperty, value); +#pragma warning restore RS0036 // Annotate nullability of public types and members in the declared API + } + + /// Bindable property for . + public static readonly BindableProperty TitleBarProperty = BindableProperty.Create( + nameof(TitleBar), typeof(IView), typeof(Window), default(IView)); } } diff --git a/src/Core/src/Core/IWindow.cs b/src/Core/src/Core/IWindow.cs index 15ce4d4a7458..fb459ee62892 100644 --- a/src/Core/src/Core/IWindow.cs +++ b/src/Core/src/Core/IWindow.cs @@ -130,6 +130,7 @@ public interface IWindow : ITitledElement float RequestDisplayDensity(); #if WINDOWS + IView? TitleBar => null; Rect[]? TitleBarDragRectangles => null; #endif } diff --git a/src/Core/src/Platform/Windows/NavigationRootManager.cs b/src/Core/src/Platform/Windows/NavigationRootManager.cs index 7817562f35c7..52d4957f8fe1 100644 --- a/src/Core/src/Platform/Windows/NavigationRootManager.cs +++ b/src/Core/src/Platform/Windows/NavigationRootManager.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.UI; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -151,6 +152,13 @@ internal string? WindowTitle internal void SetTitle(string? title) => _rootView.WindowTitle = title; + internal void SetTitleBar(FrameworkElement content) + { + _rootView.AppWindowId = _platformWindow.GetAppWindow()?.Id; + _rootView.WindowTitleBarContent = content; + _rootView.WindowTitle = "hello"; + } + void OnWindowActivated(object sender, WindowActivatedEventArgs e) { SolidColorBrush defaultForegroundBrush = (SolidColorBrush)Application.Current.Resources["TextFillColorPrimaryBrush"]; diff --git a/src/Core/src/Platform/Windows/Styles/WindowRootViewStyle.xaml b/src/Core/src/Platform/Windows/Styles/WindowRootViewStyle.xaml index f7c4439c8b7f..8b0b12535b6b 100644 --- a/src/Core/src/Platform/Windows/Styles/WindowRootViewStyle.xaml +++ b/src/Core/src/Platform/Windows/Styles/WindowRootViewStyle.xaml @@ -43,7 +43,7 @@ IsTabStop="False" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" - ContentTemplateSelector="{StaticResource MauiAppTitleBarTemplateSelector}" + Content="{Binding WindowTitleBarContent}" Visibility="{Binding WindowTitleBarContentControlVisibility}" MinHeight="{Binding WindowTitleBarContentControlMinHeight}" x:Name="AppTitleBarContentControl"> @@ -107,4 +107,5 @@ + \ No newline at end of file diff --git a/src/Core/src/Platform/Windows/WindowExtensions.cs b/src/Core/src/Platform/Windows/WindowExtensions.cs index 30f260d93b40..db8f699f58a1 100644 --- a/src/Core/src/Platform/Windows/WindowExtensions.cs +++ b/src/Core/src/Platform/Windows/WindowExtensions.cs @@ -52,6 +52,21 @@ internal static void UpdateTitle(this UI.Xaml.Window platformWindow, IWindow win .SetTitle(window.Title); } + internal static void UpdateTitleBar(this UI.Xaml.Window platformWindow, IWindow window, IMauiContext? mauiContext) + { + if (mauiContext != null) + { + var handler = window.TitleBar?.ToHandler(mauiContext); + if (handler != null && + handler.PlatformView != null) + { + mauiContext? + .GetNavigationRootManager()? + .SetTitleBar(handler.PlatformView); + } + } + } + public static void UpdateX(this UI.Xaml.Window platformWindow, IWindow window) => platformWindow.UpdatePosition(window); diff --git a/src/Core/src/Platform/Windows/WindowRootView.cs b/src/Core/src/Platform/Windows/WindowRootView.cs index 66d7a3dc5ae2..a45fdadf47cd 100644 --- a/src/Core/src/Platform/Windows/WindowRootView.cs +++ b/src/Core/src/Platform/Windows/WindowRootView.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Media; @@ -6,6 +7,11 @@ using Windows.Foundation; using ViewManagement = Windows.UI.ViewManagement; using WThickness = Microsoft.UI.Xaml.Thickness; +using Rect32 = Windows.Graphics.RectInt32; +using FRect = Windows.Foundation.Rect; +using Microsoft.UI.Input; +using Microsoft.UI; +using System.Collections; namespace Microsoft.Maui.Platform { @@ -39,6 +45,7 @@ public WindowRootView() Image? AppFontIcon { get; set; } internal TextBlock? AppTitle { get; private set; } + internal WindowId? AppWindowId { get; set; } public RootNavigationView? NavigationViewControl { get; private set; } @@ -180,12 +187,73 @@ void LoadAppTitleBarContainer() if (sender is not FrameworkElement fe) return; - if (_appTitleBarHeight != fe.ActualHeight) + UpdateTitleBarContentSize(fe); + }; + + UpdateTitleBarContentSize(AppTitleBarContentControl); + } + + void UpdateTitleBarContentSize(FrameworkElement fe) + { + if (_appTitleBarHeight != fe.ActualHeight) + { + UpdateRootNavigationViewMargins(fe.ActualHeight); + this.RefreshThemeResources(); + } + + var rectArray = new List(); + // TODO: the list of pass through children should be cached when the content changes + foreach (var child in GetAllChildren(fe)) + { + // TODO: use attributes to determine if a control should be included in + // the pass through drag region + if (child is not null && child is TextBox) { - UpdateRootNavigationViewMargins(fe.ActualHeight); - this.RefreshThemeResources(); + var transform = child.TransformToVisual(null); + var bounds = transform.TransformBounds( + new FRect(0, 0, child.ActualWidth, child.ActualHeight)); + var rect = GetRect(bounds, child.XamlRoot.RasterizationScale); + + rectArray.Add(rect); } - }; + } + + if (rectArray.Count > 0 && AppWindowId.HasValue) + { + var nonClientInputSrc = + InputNonClientPointerSource.GetForWindowId(AppWindowId.Value); + nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, [.. rectArray]); + } + } + + private static IEnumerable GetAllChildren(FrameworkElement parent) + { + var queue = new Queue(); + queue.Enqueue(parent); + + while (queue.Count > 0) + { + var current = queue.Dequeue(); + yield return current; + + foreach (var child in current.GetChildren()) + { + if (child != null) + { + queue.Enqueue(child); + } + } + } + } + + private static Rect32 GetRect(FRect bounds, double scale) + { + return new Rect32( + _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) + ); } void OnAppTitleBarContentControlLoaded(object sender, RoutedEventArgs e) @@ -407,6 +475,19 @@ void UpdateAppTitleBarTransparency() border.Background = null; } + internal static readonly DependencyProperty WindowTitleBarContentProperty = + DependencyProperty.Register( + nameof(WindowTitleBarContent), + typeof(FrameworkElement), + typeof(WindowRootView), + new PropertyMetadata(null)); + + internal FrameworkElement WindowTitleBarContent + { + get => (FrameworkElement)GetValue(WindowTitleBarContentProperty); + set => SetValue(WindowTitleBarContentProperty, value); + } + internal static readonly DependencyProperty WindowTitleForegroundProperty = DependencyProperty.Register( nameof(WindowTitleForeground), diff --git a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt index 9106ef6dac6b..95ae1a4dcd7a 100644 --- a/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt +++ b/src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt @@ -15,6 +15,7 @@ Microsoft.Maui.ICrossPlatformLayout.CrossPlatformMeasure(double widthConstraint, Microsoft.Maui.ICrossPlatformLayoutBacking Microsoft.Maui.ICrossPlatformLayoutBacking.CrossPlatformLayout.get -> Microsoft.Maui.ICrossPlatformLayout? Microsoft.Maui.ICrossPlatformLayoutBacking.CrossPlatformLayout.set -> void +Microsoft.Maui.IWindow.TitleBar.get -> Microsoft.Maui.IView? Microsoft.Maui.IWindow.TitleBarDragRectangles.get -> Microsoft.Maui.Graphics.Rect[]? Microsoft.Maui.ICommandMapper Microsoft.Maui.ICommandMapper.GetCommand(string! key) -> System.Action? From f0b09d42685c8f388135c6118c80540eff457db3 Mon Sep 17 00:00:00 2001 From: Mike Corsaro Date: Thu, 25 Apr 2024 13:51:19 -0700 Subject: [PATCH 02/32] handle titlebar hit test elements automatically --- .../Controls.Sample.Sandbox/MauiProgram.cs | 19 +++++++------ .../Controls.Sample.Sandbox/SandboxShell.xaml | 10 +++++-- .../TitlebarSample.xaml | 18 ++++++++++-- src/Controls/src/Core/Window/Window.Mapper.cs | 1 - .../src/Core/Window/Window.Windows.cs | 7 ----- .../Handlers/Window/WindowHandler.Windows.cs | 7 +++++ src/Core/src/Handlers/Window/WindowHandler.cs | 3 +- .../Platform/Windows/NavigationRootManager.cs | 7 ++++- .../src/Platform/Windows/WindowExtensions.cs | 28 +++++++++++++++++++ .../src/Platform/Windows/WindowRootView.cs | 24 +++++++--------- 10 files changed, 87 insertions(+), 37 deletions(-) diff --git a/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs b/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs index 8fb1bade91fb..65eb3327195d 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs +++ b/src/Controls/samples/Controls.Sample.Sandbox/MauiProgram.cs @@ -22,21 +22,24 @@ class App : Application protected override Window CreateWindow(IActivationState? activationState) { // To test shell scenarios, change this to true - bool useShell = false; + bool useShell = true; + Window window; if (!useShell) { - var wnd = new Window(new NavigationPage(new MainPage())); - - var titlebar = new TitlebarSample(); - titlebar.HeightRequest = 60; - wnd.TitleBar = titlebar; - return wnd; + window = new Window(new NavigationPage(new MainPage())); } else { - return new Window(new SandboxShell()); + window = new Window(new SandboxShell()); } + + window.TitleBar = new TitlebarSample + { + HeightRequest = 60 + }; + + return window; } } } \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.Sandbox/SandboxShell.xaml b/src/Controls/samples/Controls.Sample.Sandbox/SandboxShell.xaml index 358b0b04ef63..9dc1a3e3b2b7 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/SandboxShell.xaml +++ b/src/Controls/samples/Controls.Sample.Sandbox/SandboxShell.xaml @@ -4,19 +4,23 @@ x:Class="Maui.Controls.Sample.SandboxShell" xmlns:local="clr-namespace:Maui.Controls.Sample" x:Name="shell"> - + + + + - + \ No newline at end of file diff --git a/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml b/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml index 76ed77955f00..f3f1473aae20 100644 --- a/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml +++ b/src/Controls/samples/Controls.Sample.Sandbox/TitlebarSample.xaml @@ -4,12 +4,15 @@ x:Class="Maui.Controls.Sample.TitlebarSample" xmlns:local="clr-namespace:Maui.Controls.Sample"> + ColumnDefinitions="*, *, *" + InputTransparent="True" + CascadeInputTransparent="False">