Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Windows] Add TitleBar Control #23019

Open
wants to merge 34 commits into
base: main
Choose a base branch
from
Open

[Windows] Add TitleBar Control #23019

wants to merge 34 commits into from

Conversation

Foda
Copy link
Member

@Foda Foda commented Jun 12, 2024

This PR is a draft and seeking API feedback for an initial release

Description of Change

This PR adds a new TitleBar control and API to set the TitleBar on a Window. It currently only applies to the Windows platform, but it's built entirely as a MAUI control to allow macOS support later.

Please read the full API description here: #13023

Feature demo (PlatformSpecifics page in Maui.Controls.Sample):

titlebar.mp4

API/Usage

The primary API is the Window.SetTitleBar function -- which removes the TitleBar control from the visual tree and inserts it as the native TitleBar (this approach was shamelessly copied from the WinAppSDK). This was used vs an attached property (ex: <Window.TitleBar>) because it was difficult to get a valid Window object when the property was first set.

Example using XAML:

MainWindow.xaml

<Window.TitleBar>
  <TitleBar
    x:Name="TeamsTitleBar"
    HeightRequest="46"
    Title="Hello World">
    <TitleBar.Content>
      <Entry
        x:Name="SearchTitleBar"
        Placeholder="Search"
        VerticalOptions="Center"
        MinimumWidthRequest="300"
        MaximumWidthRequest="450"
        HeightRequest="32"/>
    </TitleBar.Content>
  </TitleBar>
</Window.TitleBar>

Example using code

MainPage.xaml.cs

protected override void OnAppearing()
{
    base.OnAppearing();
    
    Window.TitleBar = new TitleBar()
    {
        Title = "MAUI App",
        Icon = "appicon.png",
        LeadingContent = new AvatarButton()
    };
}

Issues Fixed

Fixes #13023

@Foda Foda added this to the .NET 9 Planning milestone Jun 12, 2024
@Foda Foda requested review from rmarinho and PureWeen June 12, 2024 22:13
Copy link
Contributor

@tj-devel709 tj-devel709 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow this is super cool! Few questions from me!

src/Controls/src/Core/TitleBar/TitleBar.cs Outdated Show resolved Hide resolved
src/Controls/src/Core/TitleBar/TitleBar.cs Outdated Show resolved Hide resolved
src/Controls/src/Core/TitleBar/TitleBar.cs Show resolved Hide resolved
src/Controls/src/Core/Window/Window.cs Show resolved Hide resolved
src/Controls/src/Core/Window/Window.cs Outdated Show resolved Hide resolved
src/Core/src/Core/IWindow.cs Show resolved Hide resolved
Mike Corsaro added 2 commits June 20, 2024 11:10
@drasticactions
Copy link
Contributor

drasticactions commented Jun 23, 2024

This PR adds a new TitleBar control and API to set the TitleBar on a Window. It currently only applies to the Windows platform, but it's built entirely as a MAUI control to allow macOS support later.

How do we plan to add support for this with Catalyst?

If you want to use something like NSToolbarDelegate to build a title bar like this:

Screenshot from 2024-06-23 16-48-49

That uses specific NSToolBar items and a layout system where you set identifiers and set the flexibility of where items should appear. The TitleBar API allows for arbitrary existing controls and layout which AFAIK wouldn't work with that delegate.

So if you want arbitrary controls, you would probably need to do something special with the UIWindow, merge the title bar into the inner content, and handle the managing the layout yourself. Or there could be other ways to create a more "native" looking titlebar on Catalyst that uses arbitrary controls. Making sure we have a plan for that now before deciding on this approach only to see it won't work on MacOS wouldn't be ideal.

@Foda
Copy link
Member Author

Foda commented Jun 24, 2024

This PR adds a new TitleBar control and API to set the TitleBar on a Window. It currently only applies to the Windows platform, but it's built entirely as a MAUI control to allow macOS support later.

How do we plan to add support for this with Catalyst?

If you want to use something like NSToolbarDelegate to build a title bar like this:

Screenshot from 2024-06-23 16-48-49

That uses specific NSToolBar items and a layout system where you set identifiers and set the flexibility of where items should appear. The TitleBar API allows for arbitrary existing controls and layout which AFAIK wouldn't work with that delegate.

So if you want arbitrary controls, you would probably need to do something special with the UIWindow, merge the title bar into the inner content, and handle the managing the layout yourself. Or there could be other ways to create a more "native" looking titlebar on Catalyst that uses arbitrary controls. Making sure we have a plan for that now before deciding on this approach only to see it won't work on MacOS wouldn't be ideal.

Yeah, we did talk briefly about this. One of the reasons why I made it a native MAUI control was so that we could just plop it into the "titlebar area" on both Win and Mac. I know it's possible to draw whatever custom control you want in the TitleBar (see: Edge w/ tabs) but I'm not sure what API does that, or what exactly the behavior is (maybe https://developer.apple.com/documentation/uikit/mac_catalyst/removing_the_title_bar_in_your_mac_app_built_with_mac_catalyst ??).

I'm going to play around today to see how that API works

@Foda Foda marked this pull request as ready for review July 8, 2024 22:05
@Foda Foda requested a review from a team as a code owner July 8, 2024 22:05
@Foda Foda requested a review from jsuarezruiz July 8, 2024 22:05
@Foda
Copy link
Member Author

Foda commented Jul 10, 2024

/rebase

using System.Threading.Tasks;
using Maui.Controls.Sample.ViewModels.Base;

namespace Maui.Controls.Sample.ViewModels
Copy link
Collaborator

@MartyIX MartyIX Jul 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to use file-scope namespaces in new files?

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change does not look like it is needed.

@dotMorten
Copy link
Contributor

Personally I have a couple concerns about this one - mostly around it not considering catalyst yet.

  1. The first one is, that without catalyst support up-front, I think it's going to be hard to get the abstraction right to begin with and smoothly slot in catalyst later. Since it is only Windows that is supported, you can still do this without a maui control by just accessing the native view directly. Personally I'd think a sample showing how to do this makes more sense for now. And that still leaves the question of: but then where is the searchbar going on other platforms?

  2. Perhaps it makes more sense for this to be more of an inherit behavior to AppShell, where you can have a search bar, and if running on Windows it can move up into the Titlebar, but I still get a Searchbar on other platforms too and adapts correctly to the platform. I really don't want to have to write a bunch of platform-specific code where for adding for instance search to my app, with different placements/implementations for each platform. If I'm doing that, then I might as well just use native UIs to do this and get the full power.

this approach was shamelessly copied from the WinAppSDK

There's absolutely no shame in that. We shouldn't need multiple ways of doing the same thing on the various XAML frameworks (* cough * TextBlock/Label * cough *), even if the WinUI API could have been cleaner. But again I still think it makes sense to diverge for the sake of crossplatform when necessary, but without Catalyst in the early designs in this PR, it might be hard to know whether the API should have differed to support them both more naturally. @drasticactions also some valid raised concerns along those lines.

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I initially add the content I can't interact with it

image

But if I add/remove the trailing content then the content becomes interactable

Should we fix this issue?
#17723

Comment on lines +36 to +37
base.OnAppearing();
Window.TitleBar = _customTitleBar;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be removed again when the page closes?
It does feel slightly weird to have the contentpage mess with the app-wide window. That might be more appropriate to use AppShell for instead, since that requires to be the root of the window content and hosts pages inside it.

Mike Corsaro added 2 commits July 19, 2024 10:50
Fix titlebar title spacing w/ default behavior
Fix titlebar content added not allowing hit test
Fix missing public API
@Foda
Copy link
Member Author

Foda commented Jul 19, 2024

Personally I have a couple concerns about this one - mostly around it not considering catalyst yet.

  1. The first one is, that without catalyst support up-front, I think it's going to be hard to get the abstraction right to begin with and smoothly slot in catalyst later. Since it is only Windows that is supported, you can still do this without a maui control by just accessing the native view directly. Personally I'd think a sample showing how to do this makes more sense for now. And that still leaves the question of: but then where is the searchbar going on other platforms?

The nice thing about this control is that because it's fully implemented in MAUI we also have full flexibility over how it appears in the app. I've gotten this to work in a playground app easily using a combo of adding the interface ISafeAreaView to the page, setting the IgnoreSafeArea property, and using the APIs described here.

image

XAML code for macOS title bar
<Grid
    IgnoreSafeArea="True"
    RowDefinitions="60, *">
    <TitleBar
        HorizontalOptions="Fill"
        HeightRequest="64"
        BackgroundColor="#512BD4">
        <TitleBar.Content>
            <HorizontalStackLayout
                Spacing="4"
                HorizontalOptions="Center"
                VerticalOptions="Center"
                IgnoreSafeArea="True">
                <Button
                    BorderWidth="0"
                    Text="&#xF153;"
                    FontFamily="Ionicons"
                    FontSize="14"
                    TextColor="White"
                    VerticalOptions="Center"
                    WidthRequest="40"
                    Padding="0"/>
                <Button
                    BorderWidth="0"
                    Text="&#xF154;"
                    FontFamily="Ionicons"
                    FontSize="14"
                    TextColor="White"
                    VerticalOptions="Center"
                    WidthRequest="40"
                    Padding="0"/>
                <Entry
                    Placeholder="Search"
                    WidthRequest="400"
                    VerticalOptions="Center"
                    HeightRequest="40"/>
            </HorizontalStackLayout>
        </TitleBar.Content>
        <TitleBar.TrailingContent>
            <HorizontalStackLayout
                Spacing="4"
                Margin="0,0,16,0"
                VerticalOptions="Center"
                IgnoreSafeArea="True">
                <Button
                    BorderWidth="0"
                    Text="&#xF1B4;"
                    FontFamily="Ionicons"
                    FontSize="16"
                    TextColor="White"
                    VerticalOptions="Center"
                    WidthRequest="50"
                    Padding="0"/>
                <Button
                    WidthRequest="40"
                    HeightRequest="40"
                    BorderWidth="0"
                    CornerRadius="20"
                    BackgroundColor="Azure"
                    Text="MC"
                    FontSize="10"
                    TextColor="Black"
                    Padding="0"
                    VerticalOptions="Center">
                </Button>
            </HorizontalStackLayout>
        </TitleBar.TrailingContent>
    </TitleBar>

    <Button
        WidthRequest="200"
        Text="Click Me"
        Grid.Row="1"/>
</Grid>

For now, I've added the ISafeAreaView interface to TitleBar since that's the main part needed. The next steps would be to wire up a nice way to automatically handle propagating IgnoreSafeArea to content in the title bar, and inserting it into the layout from the Window control.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Provide a cross platform API for customizing the TitleBar of desktop apps
10 participants