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

Context menu support for Windows and MacCatalyst #9174

Merged
merged 31 commits into from
Aug 23, 2022

Conversation

Eilon
Copy link
Member

@Eilon Eilon commented Aug 3, 2022

Description of Change

Open Questions

  1. Should ContextFlyout be an attached property? On WinUI the ContextFlyout is a property on UIElement which is the base class to everything so that would be the equivalent of us putting it on Element vs View. They also have FlyoutBase which is the left click version of a flyout which is an attached property? Made ContextFlyout an attached property on FlyoutBase
  2. IContextFlyoutContainer vs IContextFlyoutElement Context menu support for Windows and MacCatalyst #9174 (comment) IContextFlyoutElement wins
  3. should we rename ContextFlyout to MenuFlyout and then ContextFlyout is just the container for the attached property? I went with this approach because MenuFlyout is just a type of Flyout and we are most likely going to want to expand on the types that we can specify here. So now View just takes FlyoutBase and MenuFlyout inherits from FlyoutBase. At a later point we'll probably introduce FlyoutView

API CHANGES

MAUI.CORE

// Made this delegate public while working on context menus for webview
public class MauiWebViewUIDelegate
namespace Microsoft.Maui
{
	/// <summary>
	/// Represents a view that can contain a context flyout menu, which is usually represented as a right-click menu.
	/// </summary>
	public interface IContextFlyoutElement
	{
		/// <summary>
		/// Gets the <see cref="ContextFlyout "/> for the view. Menu flyouts, menu flyout subitems, and menu flyout separators can be added to the context flyout.
		/// </summary>
		IFlyout? ContextFlyout { get; }
	}
}
namespace Microsoft.Maui
{
	public interface IFlyout : IElement
	{

	}
}
namespace Microsoft.Maui
{
	public interface IMenuFlyout : IList<IMenuElement>, IFlyout
    { }
}
namespace Microsoft.Maui.Handlers
{
	public interface IMenuFlyoutHandler : IElementHandler
	{
		void Add(IMenuElement view);
		void Remove(IMenuElement view);
		void Clear();
		void Insert(int index, IMenuElement view);

		new PlatformView PlatformView { get; }
		new IMenuFlyout VirtualView { get; }
	}
}

Maui.Controls

namespace Microsoft.Maui.Controls
{
	public abstract class FlyoutBase : Element, IFlyout
	{
                 public static readonly BindableProperty ContextFlyoutProperty = BindableProperty.CreateAttached("ContextFlyout")
	}
}
namespace Microsoft.Maui.Controls
{
    public partial class MenuFlyout : FlyoutBase, IMenuFlyout
    {
    }
}
namespace Microsoft.Maui.Controls
{
     public partial class Element : IView, IPropertyMapperView, IHotReloadableView, IContextFlyoutElement
     {
               IFlyout IContextFlyoutElement.ContextFlyout => FlyoutBase.GetContextFlyout(this);

XAML Sample

<Button Text="Increment by 1 (or right-click me)" Clicked="OnIncrementByOneClicked" FontSize="30" BackgroundColor="DarkGray">
                    <FlyoutBase.ContextFlyout>
                        <MenuFlyout>
                            <MenuFlyoutItem Text="Increment by 10" Clicked="OnIncrementMenuItemClicked" CommandParameter="10"></MenuFlyoutItem>
                            <MenuFlyoutItem Text="Increment by 20" Clicked="OnIncrementMenuItemClicked" CommandParameter="20"></MenuFlyoutItem>
                            <MenuFlyoutSubItem Text="More options">
                                <MenuFlyoutItem Text="Increment by 1,000!" Clicked="OnIncrementMenuItemClicked" CommandParameter="1000"></MenuFlyoutItem>
                                <MenuFlyoutItem Text="Increment by 1,000,000!" Clicked="OnIncrementMenuItemClicked" CommandParameter="1000000"></MenuFlyoutItem>
                            </MenuFlyoutSubItem>
                        </MenuFlyout>
                    </FlyoutBase.ContextFlyout>
                </Button>

Known Issues

Please refer to this comment to see what is and isn't supported.

Issues Fixed

Fixes #7777

@Eilon Eilon added do-not-merge Don't merge this PR legacy-area-desktop Windows / WinUI / Project Reunion & Mac Catalyst / macOS specifics (Menus & other Controls)) labels Aug 3, 2022
@profix898
Copy link

I just started to experiment with the implementation of context menu for MAUI a few hours back. However, I started down a different path by using an attached property. I also considered a behaviour. Obviously, you decided to augment IView directly. Just wanted to point out alternatives.

Also here are links to docs on context menu for ios/macos and Android (which also supports this via 'PopupMenu' class):

https://developer.apple.com/documentation/uikit/uicontextmenuinteraction
https://developer.android.com/guide/topics/ui/menus#context-menu

@Eilon
Copy link
Member Author

Eilon commented Aug 3, 2022

@profix898 thanks for sharing. I added directly to View because it seemed "most natural" to me, and roughly aligns with what WinUI and MacCatalyst do in their respective systems (which this MAUI implementation ultimately wraps). Do you see any pros/cons either way?

@Eilon Eilon force-pushed the eilon/context-menus-everywhere branch from 1e80525 to ac872cc Compare August 5, 2022 22:11
@Eilon Eilon requested a review from PureWeen August 9, 2022 16:17
@Eilon Eilon changed the title WIP: Context menu support for Windows and MacCatalyst Context menu support for Windows and MacCatalyst Aug 9, 2022
@Eilon Eilon force-pushed the eilon/context-menus-everywhere branch 2 times, most recently from 0d5bfc0 to c11dd36 Compare August 9, 2022 23:09
@Eilon Eilon removed the do-not-merge Don't merge this PR label Aug 9, 2022
@Eilon
Copy link
Member Author

Eilon commented Aug 10, 2022

To @PureWeen and any other reviewers:

Per the linked comment above, the mainline scenarios should work: define a context menu on a compatible control and it will show up on MacOS and Windows. The main thing that really should work, but doesn't is: using {Binding ...} doesn't work (just like the MenuBar issue). This is fixed and working now (@PureWeen)

I'd love to get this PR merged ASAP so that it can make its way to customers, while at the same time I can iterate on the missing features to continuously improve it. So, if you have concerns of what's in here, please do let me know, including whether the known limitations are acceptable at this time.

Thanks!

@PureWeen PureWeen force-pushed the eilon/context-menus-everywhere branch from 19e5784 to 6f54023 Compare August 10, 2022 16:53
protected override UIMenuElement CreatePlatformElement()
{

// REVIEW: I don't like this code, but couldn't come up with anything better. It feels right to
Copy link
Member

Choose a reason for hiding this comment

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

I think this was just an oversight and I was unaware that UIAction can just be used in the place of UICommand

#9332

@@ -53,6 +53,9 @@ public void Insert(int index, IMenuElement view)

void Rebuild()
{
// REVIEW: For context flyout support this likely also needs some logic like in MenuFlyoutItemHandler.iOS.cs where
Copy link
Member

Choose a reason for hiding this comment

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

<Label Text="{Binding CounterValue}" FontSize="30" VerticalOptions="Center"></Label>
</HorizontalStackLayout>

<Label Text="Right-click to see beautiful menus" FontSize="30">
Copy link
Member

Choose a reason for hiding this comment

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

@@ -109,6 +109,10 @@ public static IMauiHandlersCollection AddMauiControlsHandlers(this IMauiHandlers
handlersCollection.AddHandler(typeof(Frame), typeof(Handlers.Compatibility.FrameRenderer));
#endif

#if WINDOWS || MACCATALYST
handlersCollection.AddHandler(typeof(ContextFlyout), typeof(ContextFlyoutHandler));
Copy link
Member

@PureWeen PureWeen Aug 15, 2022

Choose a reason for hiding this comment

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

This code appears to work fine on iOS13+. Not sure if there was a specific reason why we aren't letting this code just run on iOS13. I created a tracking issue here that we can address after we're API completely for NET7 since it shouldn't take any API changes to enable the handlers for iOS

Copy link
Contributor

@hartez hartez left a comment

Choose a reason for hiding this comment

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

One minor nitpick.

src/Controls/src/Core/ContextFlyout.cs Outdated Show resolved Hide resolved
@PureWeen PureWeen requested a review from hartez August 15, 2022 21:10
@samhouts samhouts mentioned this pull request Aug 16, 2022
22 tasks
@samhouts samhouts changed the title Context menu support for Windows and MacCatalyst 🔲Context menu support for Windows and MacCatalyst Aug 16, 2022
@samhouts samhouts changed the title 🔲Context menu support for Windows and MacCatalyst Context menu support for Windows and MacCatalyst Aug 16, 2022
@jsuarezruiz jsuarezruiz self-requested a review August 17, 2022 14:39
MenuTestBase<MenuFlyout, IMenuElement, MenuFlyoutItem, ContextFlyoutItemHandlerUpdate>
{
[Fact]
public void BindingContextPropagatesWhenContextFlyoutIsAlreadySetOnParent()
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️

Add context menu (right-click menu) support for Windows and MacCatalyst.

Fixes #7777
@PureWeen PureWeen force-pushed the eilon/context-menus-everywhere branch from 7f8fcba to b872532 Compare August 19, 2022 15:28
- wire up context menu for MKWebView
# Conflicts:
#	src/Controls/src/Core/HandlerImpl/Element/Element.Impl.cs
#	src/Controls/src/Core/PublicAPI/net-android/PublicAPI.Unshipped.txt
#	src/Controls/src/Core/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
#	src/Controls/src/Core/PublicAPI/net-windows/PublicAPI.Unshipped.txt
#	src/Controls/src/Core/PublicAPI/netstandard/PublicAPI.Unshipped.txt
#	src/Core/src/Handlers/View/ViewHandler.cs
#	src/Core/src/PublicAPI/net-android/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/net-ios/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/net-maccatalyst/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/net-tizen/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/net-windows/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/net/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/netstandard/PublicAPI.Unshipped.txt
#	src/Core/src/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
okAction: x => completionHandler(x.TextFields[0].Text),
cancelAction: _ => completionHandler(null)
okAction: x => completionHandler(x.TextFields[0].Text!),
cancelAction: _ => completionHandler(null!)
Copy link
Member

Choose a reason for hiding this comment

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

???

Maybe the delegate should be a nullable string??

Copy link
Member

@PureWeen PureWeen Aug 22, 2022

Choose a reason for hiding this comment

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

AFAIK because it's an override I can't change the delegate. The iOS SDK would have to change it or we'd do it by converting this whole thing to implement the interface ourselves

If I set it to Action<string?> then I get a compile error Nullability of reference types in type doesn't match overidden member

Copy link
Member

Choose a reason for hiding this comment

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

oh dear :( This sounds like a wrong API. Let me open an issue for that.

@PureWeen PureWeen enabled auto-merge (squash) August 22, 2022 20:28
@PureWeen PureWeen merged commit 9e81449 into main Aug 23, 2022
@PureWeen PureWeen deleted the eilon/context-menus-everywhere branch August 23, 2022 12:57
@github-actions github-actions bot locked and limited conversation to collaborators Dec 19, 2023
@Eilon Eilon added t/desktop The issue relates to desktop scenarios (MacOS/MacCatalyst/Windows/WinUI/WinAppSDK) area-controls-contextmenu ContextMenu and removed legacy-area-desktop Windows / WinUI / Project Reunion & Mac Catalyst / macOS specifics (Menus & other Controls)) labels May 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-controls-contextmenu ContextMenu fixed-in-7.0.0-rc.1.6683 platform/macOS 🍏 macOS / Mac Catalyst platform/windows 🪟 t/desktop The issue relates to desktop scenarios (MacOS/MacCatalyst/Windows/WinUI/WinAppSDK)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cross-platform solution for Right click context menu on desktop app
7 participants