maui icon indicating copy to clipboard operation
maui copied to clipboard

[Windows] Add TitleBar Control

Open Foda opened this issue 1 year ago • 4 comments

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: https://github.com/dotnet/maui/issues/13023

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

https://github.com/dotnet/maui/assets/890772/ef0121f9-6ff4-4916-b889-79e9ee4a4f7d

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 avatar Jun 12 '24 22:06 Foda

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.

drasticactions avatar Jun 23 '24 08:06 drasticactions

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 avatar Jun 24 '24 19:06 Foda

@drasticactions here's what it looks like using that API + NavigationPage.TitleView: image

I couldn't quite figure out how to get the NavigationBar to not have that top padding -- someone more familiar w/ macOS layout constraints would be able to figure it out. But other than the top margin, it works as expected: controls in that are can be clicked, etc, without breaking the drag behavior.

Foda avatar Jun 24 '24 23:06 Foda

/rebase

Foda avatar Jun 27 '24 21:06 Foda

/rebase

Foda avatar Jul 10 '24 22:07 Foda

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.

dotMorten avatar Jul 18 '24 20:07 dotMorten

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.

Foda avatar Jul 19 '24 21:07 Foda