[macOS] enhance menu (#4718)
authorGabor Nemeth <37656640+gabor-nemeth@users.noreply.github.com>
Wed, 20 Mar 2019 17:31:00 +0000 (18:31 +0100)
committerSamantha Houts <samhouts@users.noreply.github.com>
Wed, 20 Mar 2019 17:31:00 +0000 (10:31 -0700)
fixes #2618

Xamarin.Forms.ControlGallery.MacOS/AppDelegate.cs
Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs [new file with mode: 0644]
Xamarin.Forms.ControlGallery.MacOS/Xamarin.Forms.ControlGallery.MacOS.csproj
Xamarin.Forms.Platform.MacOS/Extensions/NSMenuExtensions.cs
Xamarin.Forms.Platform.MacOS/FormsApplicationDelegate.cs

index ac4940d..6281a17 100644 (file)
@@ -1,5 +1,6 @@
 using System;
 using System.Globalization;
+using System.Linq;
 using AppKit;
 using CoreGraphics;
 using Foundation;
@@ -18,7 +19,7 @@ namespace Xamarin.Forms.ControlGallery.MacOS
                {
                        ObjCRuntime.Runtime.MarshalManagedException += (sender, args) =>
                        {
-                               Console.WriteLine(args.Exception.ToString());
+                               Console.WriteLine(args.Exception);
                        };
 
                        var style = NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Titled;
@@ -35,12 +36,15 @@ namespace Xamarin.Forms.ControlGallery.MacOS
                        get { return _window; }
                }
 
+
                public override void DidFinishLaunching(NSNotification notification)
                {
                        Forms.Init();
                        FormsMaps.Init();
 
                        var app = new App();
+                       SetupMenu(app);
+
                        // When the native control gallery loads up, it'll let us know so we can add the nested native controls
                        MessagingCenter.Subscribe<NestedNativeControlGalleryPage>(this, NestedNativeControlGalleryPage.ReadyForNativeControlsMessage, AddNativeControls);
                        MessagingCenter.Subscribe<Bugzilla40911>(this, Bugzilla40911.ReadyToSetUp40911Test, SetUp40911Test);
@@ -52,6 +56,75 @@ namespace Xamarin.Forms.ControlGallery.MacOS
                        base.DidFinishLaunching(notification);
                }
 
+               void SetupMenu(Application app)
+               {
+                       NativeMenuItemCreator = (menuItem) =>
+                       {
+                               if (menuItem is SeparatorMenuItem)
+                               {
+                                       return NSMenuItem.SeparatorItem;
+                               }
+
+                               var nsMenuItem = new NSMenuItem { Title = menuItem.Text };
+                               if (menuItem.Text == "Small icons")
+                               {
+                                       nsMenuItem.State = NSCellStateValue.On;
+                                       nsMenuItem.Activated += CheckableNsMenuItem_Activated;
+                               }
+
+                               return nsMenuItem;
+                       };
+
+                       var menu = new Menu();
+
+                       var appMenu = new Menu();
+
+                       appMenu.Items.Add(new MenuItem { Text = "App menu test" });
+                       menu.Add(appMenu);
+
+                       var fileMenu = new Menu { Text = "File" };
+                       fileMenu.Items.Add(new MenuItem { Text = "New" });
+                       fileMenu.Items.Add(new MenuItem { Text = "Open" });
+                       menu.Add(fileMenu);
+
+                       var viewMenu = new Menu { Text = "View" };
+                       var smallIconsMenuItem = new MenuItem { Text = "Small icons" };
+                       smallIconsMenuItem.Clicked += CheckableMenuItem_Clicked;
+                       viewMenu.Items.Add(smallIconsMenuItem);
+                       viewMenu.Items.Add(new MenuItem { Text = "Large icons" });
+                       viewMenu.Items.Add(new SeparatorMenuItem());
+                       viewMenu.Items.Add(new MenuItem { Text = "List", IsEnabled = false });
+                       viewMenu.Items.Add(new MenuItem { Text = "Details" });
+                       menu.Add(viewMenu);
+
+                       Element.SetMenu(app, menu);
+
+                       void CheckableNsMenuItem_Activated(object sender, EventArgs e)
+                       {
+                               // switch state for checkable menu item
+                               var nsMenuItem = sender as NSMenuItem;
+                               nsMenuItem.State = nsMenuItem.State == NSCellStateValue.On ? NSCellStateValue.Off : NSCellStateValue.On;
+                       }
+
+                       void CheckableMenuItem_Clicked(object sender, EventArgs e)
+                       {
+                               var menuItem = sender as MenuItem;
+                               var alert = new NSAlert { MessageText = $"{menuItem.Text} has been clicked!" };
+                               alert.RunModal();
+                       }
+               }
+
+               protected override void SetupMainAppMenu(NSMenu nativeMenu)
+               {
+                       base.SetupMainAppMenu(nativeMenu);
+
+                       // reorder menu items - it can be needed if we add submenus as they are added after simple items
+                       var appMenu = nativeMenu.ItemAt(0);
+                       var testMenuItem = appMenu.Submenu.Items.Last();
+                       appMenu.Submenu.RemoveItem(testMenuItem);
+                       appMenu.Submenu.InsertItem(testMenuItem, 0);
+               }
+
                void AddNativeControls(NestedNativeControlGalleryPage page)
                {
                        if (page.NativeControlsAdded)
diff --git a/Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs b/Xamarin.Forms.ControlGallery.MacOS/SeparatorMenuItem.cs
new file mode 100644 (file)
index 0000000..7e754ef
--- /dev/null
@@ -0,0 +1,10 @@
+
+namespace Xamarin.Forms.ControlGallery.MacOS
+{
+       /// <summary>
+       /// Represents a separator menu item
+       /// </summary>
+       public class SeparatorMenuItem : MenuItem
+       {
+       }
+}
index 4560462..b3d0ff0 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup>
     <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
     <Compile Include="PlatformSpecificCoreGalleryFactory.cs" />
     <Compile Include="RegistrarValidationService.cs" />
     <Compile Include="SampleNativeControl.cs" />
+    <Compile Include="SeparatorMenuItem.cs" />
     <Compile Include="..\Xamarin.Forms.Controls\GalleryPages\OpenGLGalleries\AdvancedOpenGLGallery.cs">
       <Link>GalleryPages\AdvancedOpenGLGallery.cs</Link>
     </Compile>
index a67efba..ece6de7 100644 (file)
@@ -6,34 +6,52 @@ namespace Xamarin.Forms.Platform.macOS.Extensions
 {
        internal static class NSMenuExtensions
        {
-               public static NSMenu ToNSMenu(this Menu menus, NSMenu nsMenu = null)
+               public static NSMenu ToNSMenu(this Menu menus, NSMenu nsMenu = null, Func<MenuItem, NSMenuItem> menuItemCreator = null)
                {
                        if (nsMenu == null)
                                nsMenu = new NSMenu(menus.Text ?? "");
+                       nsMenu.AutoEnablesItems = false;
+
                        foreach (var menu in menus)
                        {
-                               var menuItem = new NSMenuItem(menu.Text ?? "");
-                               var subMenu = new NSMenu(menu.Text ?? "");
-                               menuItem.Submenu = subMenu;
+                               NSMenuItem menuItem = null;
+                               NSMenu subMenu = null;
+                               if (string.IsNullOrEmpty(menu.Text)) // handle menu with empty Text as the application menu
+                               {
+                                       menuItem = nsMenu.Items.FirstOrDefault();
+                                       subMenu = menuItem?.Submenu;
+                               }
+                               if (menuItem == null)
+                               {
+                                       menuItem = new NSMenuItem(menu.Text ?? "");
+                                       menuItem.Submenu = subMenu = new NSMenu(menu.Text ?? "");
+                               }
+
                                foreach (var item in menu.Items)
                                {
-                                       var subMenuItem = item.ToNSMenuItem();
+                                       var subMenuItem = item.ToNSMenuItem(menuItemCreator: menuItemCreator);
                                        GetAccelerators(subMenuItem, item);
                                        subMenu.AddItem(subMenuItem);
                                        item.PropertyChanged += (sender, e) => (sender as MenuItem)?.UpdateNSMenuItem(subMenuItem, new string[] { e.PropertyName });
                                }
-                               nsMenu.AddItem(menuItem);
-                               menu.ToNSMenu(subMenu);
+                               if (!nsMenu.Items.Contains(menuItem))
+                                       nsMenu.AddItem(menuItem);
+                               menu.ToNSMenu(subMenu, menuItemCreator);
                        }
                        return nsMenu;
                }
 
 
-               public static NSMenuItem ToNSMenuItem(this MenuItem menuItem, int i = -1)
+               public static NSMenuItem ToNSMenuItem(this MenuItem menuItem, int i = -1, Func<MenuItem, NSMenuItem> menuItemCreator = null)
                {
-                       var nsMenuItem = new NSMenuItem(menuItem.Text ?? "");
+                       NSMenuItem nsMenuItem = null;
+                       if (menuItemCreator == null)
+                               nsMenuItem = new NSMenuItem(menuItem.Text ?? "");
+                       else
+                               nsMenuItem = menuItemCreator(menuItem);
                        if (i != -1)
                                nsMenuItem.Tag = i;
+
                        nsMenuItem.Enabled = menuItem.IsEnabled;
                        nsMenuItem.Activated += (sender, e) => ((IMenuItemController)menuItem).Activate();
                        if (!string.IsNullOrEmpty(menuItem.Icon))
@@ -42,6 +60,8 @@ namespace Xamarin.Forms.Platform.macOS.Extensions
                        return nsMenuItem;
                }
 
+
+
                public static void UpdateNSMenuItem(this MenuItem item, NSMenuItem menuItem, string[] properties)
                {
                        foreach (var property in properties)
index d85d12a..2e84ea0 100644 (file)
@@ -12,6 +12,7 @@ namespace Xamarin.Forms.Platform.MacOS
                bool _isSuspended;
                static int _storyboardMainMenuCount;
 
+               public Func<MenuItem, NSMenuItem> NativeMenuItemCreator { get; set; }
                public abstract NSWindow MainWindow { get; }
 
                protected override void Dispose(bool disposing)
@@ -116,9 +117,15 @@ namespace Xamarin.Forms.Platform.MacOS
                                Log.Warning("FormsApplicationDelegate", "Please provide a Main.storyboard to handle menus");
                                return;
                        }
-                               
+
                        ClearNSMenu(nsMenu);
-                       Element.GetMenu(_application).ToNSMenu(nsMenu);
+                       SetupMainAppMenu(nsMenu);
+               }
+
+               protected virtual void SetupMainAppMenu(NSMenu nativeMenu)
+               {
+                       var menu = Element.GetMenu(_application);
+                       menu.ToNSMenu(nativeMenu, NativeMenuItemCreator);
                }
 
                static void ClearNSMenu(NSMenu menu)