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

Global menu bar support #898

Closed
kekekeks opened this issue Feb 17, 2017 · 12 comments
Closed

Global menu bar support #898

kekekeks opened this issue Feb 17, 2017 · 12 comments

Comments

@kekekeks
Copy link
Member

Currently there are at least two global menubars that we need to support: OS X and Ubuntu ones.

Preferably we should synchronize items from our own menubar. To do that we probably want to create a property for that menu bar in Window and alter Window's template.

GTK has various ways of submitting items to the menu bar.

For OS X there is gtk_mac_menu_set_menu_bar that should be followed by gtk_mac_menu_sync calls when toplevel menu item list is changed.

Ubuntu provides GtkMenuProxy module that does some voodoo to detect window's menu, hide it and offload contents to system menubar. That voodoo doesn't always properly work, so it's disabled for some applications (notable example is MonoDevelop).

Starting from GTK3 there is gtk_application_set_app_menu which sets "application" menu, which seems to be shown before any other menu items (at least on Ubuntu, according to docs).

We might want to stick to using gtk_application_set_app_menu, since on OS X gtk_mac_menu_set_menu_bar operates on application level anyway. In this scenario we need to update menubar on window activation.

@kekekeks kekekeks added this to the Beta 1 milestone Feb 17, 2017
@kekekeks kekekeks modified the milestones: Beta 1, Beta 2 Oct 12, 2017
@kekekeks
Copy link
Member Author

Ubuntu Unity is discontinued, so we only need to support OSX now.

@grokys grokys modified the milestones: 0.7.0, 0.8.0 Jul 5, 2018
@kekekeks
Copy link
Member Author

kekekeks commented Oct 1, 2018

The problem here is that on OSX menu bar is global for the application, there is no such thing as window menu. GTK merely emulates "window-owned" menus by appending them to the global menu bar on window activation.

I'm not quite sure if we need to have the same behavior. It might be quite handy since it's the way menus usually work on OSX, so people won't have to add menu items to each window. On the other hand Windows doesn't have the concept of "application" menu.

If we decide to use per-window menus, we need a way to sync menu bar items from our own menu system and a fallback for platforms that don't have global menu.

For the user it would look like:

<Window>
     <MainMenu>
        <MenuItem Header="Top level item">
             <MenuItem Header="Item"/> 
             <Separator/>
        <MenuItem>
     </MainMenu>
</Window>

It would be displayed in Menu control on Windows and synced to the main menu on OSX.

Which brings another problem here: we can't really express items with complex controls inside. Our current way of adding checkboxes to menu items is setting MenuItem's Icon property to something that displays a CheckBox. Obviously that won't work if we are converting our menu items to NSMenuItem. So we might need to add properties like Image and IsChecked.

We could try to create a separate NSView for each NSMenuItem, but that will make said items to look in a non-native way, since we have to do all the drawing manually.

What we could also do is to draw MenuItem.Icon property to RenderTargetBitmap and transfer the resulting icon to NSMenu image, but that will introduce the overhead of actually drawing menus on every sync, even items that aren't currently visible. That might be slow for complex menu hierarchies.

Another approach would be to create a completely separate control hierarchy:

  • GlobalMenuBar
  • GlobalMenuBarItem (HasCheckBox, IsChecked, Icon)
  • GlobalMenuBarSeparator

Those (except for GlobalMenuBar) won't even be controls, they would inherit from AvaloniaObject. GlobalMenuBar will construct a "normal" Menu/MenuItem hierarchy or sync the items to the native windowing backend.

That will solve the mentioned issues, but will limit the user in what they can express with such menus.

@danwalmsley
Copy link
Member

@kekekeks
Copy link
Member Author

kekekeks commented Dec 3, 2018

It seems that Linux global menu is still alive alive and well across non-Unity desktop environments. Here is the definition file for DBus interface:
https://git.launchpad.net/ubuntu/+source/libdbusmenu/tree/libdbusmenu-glib/dbus-menu.xml?h=debian/sid

@kekekeks
Copy link
Member Author

kekekeks commented Dec 3, 2018

Mkay, since this feature is getting a lot of interest lately, here is a proposal:

General export procedure

We introduce SimpleMenuItem class derived from AvaloniaObject with following properties:

  • string Header
  • SimpleMenuItemToggleType ToggleType
  • bool IsToggled
  • Bitmap Image
  • AvaloniaList<ISimpleMenuItem> Children

SimpleMenuItemToggleType enum: None, Checkbox, Radio. It maps directly to toggle-type from dbus-menu. On OSX Checkbox maps to the default onStateImage while Radio maps to a custom image. At first we can only implement None and Checkbox, but this should be an enum to provide backwards compatibility when we add Radio.

We make SimpleMenuItem and Separator implement a "marker" interface ISimpleMenuItem.

SimpleMenuItem could later be used for "system-tray" menus for OSX and various Linux distros.

Then we add MainMenu control for the window that shows a MenuBar with items or/and (APPMENU_SHOW_BOTH=1 environment should force us to show both) exports the items to the system.

IWindowImpl should now have IMenuBarExporterSink MenuBarExporter { get; } optional property. We might also go with T GetFeature<T>() approach for optional features, so exporter would be obtained by PlatformImpl.GetFeature<ISimpleMenuExporterSink>().

Then we would have SimpleMenuExporter class (internal to Avalonia.Controls) that syncs AvaloniaList<ISimpleMenuItem> to ISimpleMenuExporterSink. The proposed API is
public static IDisposable Sync(AvaloniaList<ISimpleMenuItem> items, ISimpleMenuExporterSink sink)

A separate exporter class is needed for implementing exported tray icon menus later.

Application-global menu

Later we can make App class to have ApplicationMenuItems property that gets prepended to each MainMenu control's items.

@danwalmsley @grokys

@kekekeks
Copy link
Member Author

kekekeks commented Dec 13, 2018

https://github.com/KDE/kwayland/blob/master/src/client/protocols/appmenu.xml - KDE's global menu protocol for Wayland. Uses the same DBus menu protocol, this replaces Registar object since wayland doesn't have global window identifiers.

@kekekeks kekekeks mentioned this issue Jan 11, 2019
2 tasks
@grokys grokys modified the milestones: 0.8.0, 0.9 Apr 5, 2019
grokys added a commit that referenced this issue May 1, 2019
For #898, we're going to need data templates that return types that are non-`IControl`s. This PR :

- Relaxes the constraints on the `ITemplate.Build` method to allow building any type
- Adds an `IDataTemplate<TParam, TControl>` which can be used to build any type
- Makes the existing `IDataTemplate` interface be shorthand for `IDataTemplate<object, IControl>`
@danwalmsley
Copy link
Member

@davidhenley
Copy link

Whats the status on this?

@kekekeks
Copy link
Member Author

Implemented ages ago

@davidhenley
Copy link

Can you point me to the documentation? I didn't see it anywhere in the docs.

@kekekeks
Copy link
Member Author

It looks like the docs somehow were lost alongside with the 0.9 release announcement.

You can define native menu items using NativeMenu.Menu attached property like this:

<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="File">
<NativeMenu>
<NativeMenuItem Icon="/Assets/test_icon.ico" Header="Open" Clicked="OnOpenClicked" Gesture="Ctrl+O"/>
<NativeMenuItemSeperator/><!-- Uses incorrect spelling to demonstrate backwards compatibility -->
<NativeMenuItem Icon="/Assets/github_icon.png" Header="Recent">
<NativeMenu/>
</NativeMenuItem>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="{x:Static local:MainWindow.MenuQuitHeader}"
Gesture="{x:Static local:MainWindow.MenuQuitGesture}"
Clicked="OnCloseClicked" />
</NativeMenu>
</NativeMenuItem>
<NativeMenuItem Header="Edit">
<NativeMenu>
<NativeMenuItem Header="Copy"/>
<NativeMenuItem Header="Paste"/>
</NativeMenu>
</NativeMenuItem>
<NativeMenuItem Header="Options">
<NativeMenu>
<NativeMenuItem Header="Check Me (None)"
Command="{Binding ToggleMenuItemCheckedCommand}"
ToggleType="None"
IsChecked="{Binding IsMenuItemChecked}" />
<NativeMenuItem Header="Check Me (CheckBox)"
Command="{Binding ToggleMenuItemCheckedCommand}"
ToggleType="CheckBox"
IsChecked="{Binding IsMenuItemChecked}" />
<NativeMenuItem Header="Check Me (Radio)"
Command="{Binding ToggleMenuItemCheckedCommand}"
ToggleType="Radio"
IsChecked="{Binding IsMenuItemChecked}" />
</NativeMenu>
</NativeMenuItem>
</NativeMenu>
</NativeMenu.Menu>

To avoid copy-pasting the same items to a regular menu bar you can use <NativeMenuBar/> control.

@davidhenley
Copy link

OK thanks, hope the docs get back up soon!

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

No branches or pull requests

4 participants