diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings
index 1361172fff..7060f4a62a 100644
--- a/Avalonia.sln.DotSettings
+++ b/Avalonia.sln.DotSettings
@@ -3,6 +3,7 @@
ExplicitlyExcluded
ExplicitlyExcluded
ExplicitlyExcluded
+ DO_NOT_SHOW
HINT
<Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />
<Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" />
diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs
index d683092edf..854cae484c 100644
--- a/samples/ControlCatalog.NetCore/Program.cs
+++ b/samples/ControlCatalog.NetCore/Program.cs
@@ -55,7 +55,11 @@ namespace ControlCatalog.NetCore
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure()
.UsePlatformDetect()
- .With(new X11PlatformOptions { EnableMultiTouch = true })
+ .With(new X11PlatformOptions
+ {
+ EnableMultiTouch = true,
+ UseDBusMenu = true
+ })
.With(new Win32PlatformOptions
{
EnableMultitouch = true,
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index 9527ac3b4e..fca155ef70 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/samples/ControlCatalog/MainWindow.xaml
@@ -8,7 +8,34 @@
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index 95c65ed92f..9f62c0da38 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -14,6 +14,7 @@ namespace ControlCatalog
public class MainWindow : Window
{
private WindowNotificationManager _notificationArea;
+ private NativeMenu _recentMenu;
public MainWindow()
{
@@ -29,8 +30,21 @@ namespace ControlCatalog
};
DataContext = new MainWindowViewModel(_notificationArea);
+ _recentMenu = NativeMenu.GetMenu(this).Items[0].Menu.Items[1].Menu;
}
+ public void OnOpenClicked(object sender, EventArgs args)
+ {
+ _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1)));
+ }
+
+ public void OnCloseClicked(object sender, EventArgs args)
+ {
+ Close();
+ }
+
+
+
private void InitializeComponent()
{
// TODO: iOS does not support dynamically loading assemblies
diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs
index ba842d8825..ce60a0f0b9 100644
--- a/src/Avalonia.Controls/Application.cs
+++ b/src/Avalonia.Controls/Application.cs
@@ -210,5 +210,22 @@ namespace Avalonia
{
ResourcesChanged?.Invoke(this, e);
}
+
+ private string _name;
+ ///
+ /// Defines Name property
+ ///
+ public static readonly DirectProperty NameProperty =
+ AvaloniaProperty.RegisterDirect("Name", o => o.Name, (o, v) => o.Name = v);
+
+ ///
+ /// Application name to be used for various platform-specific purposes
+ ///
+ public string Name
+ {
+ get => _name;
+ set => SetAndRaise(NameProperty, ref _name, value);
+ }
+
}
}
diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs
new file mode 100644
index 0000000000..9ce83f3e3c
--- /dev/null
+++ b/src/Avalonia.Controls/NativeMenu.Export.cs
@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+using Avalonia.Controls.Platform;
+using Avalonia.Data;
+
+namespace Avalonia.Controls
+{
+ public partial class NativeMenu
+ {
+ public static readonly AttachedProperty IsNativeMenuExportedProperty =
+ AvaloniaProperty.RegisterAttached("IsNativeMenuExported",
+ defaultBindingMode: BindingMode.OneWayToSource);
+
+ public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty);
+
+ private static readonly AttachedProperty s_nativeMenuInfoProperty =
+ AvaloniaProperty.RegisterAttached("___NativeMenuInfo");
+
+ class NativeMenuInfo
+ {
+ public bool ChangingIsExported { get; set; }
+ public ITopLevelNativeMenuExporter Exporter { get; }
+
+ public NativeMenuInfo(TopLevel target)
+ {
+ Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter;
+ if (Exporter != null)
+ {
+ Exporter.OnIsNativeMenuExportedChanged += delegate
+ {
+ SetIsNativeMenuExported(target, Exporter.IsNativeMenuExported);
+ };
+ }
+ }
+ }
+
+ static NativeMenuInfo GetInfo(TopLevel target)
+ {
+ var rv = target.GetValue(s_nativeMenuInfoProperty);
+ if (rv == null)
+ {
+ target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target));
+ SetIsNativeMenuExported(target, rv.Exporter.IsNativeMenuExported);
+ }
+
+ return rv;
+ }
+
+ static void SetIsNativeMenuExported(TopLevel tl, bool value)
+ {
+ GetInfo(tl).ChangingIsExported = true;
+ tl.SetValue(IsNativeMenuExportedProperty, value);
+ }
+
+ public static readonly AttachedProperty MenuProperty
+ = AvaloniaProperty.RegisterAttached("NativeMenuItems", validate:
+ (o, v) =>
+ {
+ if(!(o is Application || o is TopLevel))
+ throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
+ return v;
+ });
+
+ public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);
+ public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
+
+
+ public static readonly AttachedProperty PrependApplicationMenuProperty
+ = AvaloniaProperty.RegisterAttached("PrependApplicationMenu");
+
+ public static void SetPrependApplicationMenu(TopLevel tl, bool value) =>
+ tl.SetValue(PrependApplicationMenuProperty, value);
+
+ public static bool GetPrependApplicationMenu(TopLevel tl) => tl.GetValue(PrependApplicationMenuProperty);
+
+ static NativeMenu()
+ {
+ // This is needed because of the lack of attached direct properties
+ IsNativeMenuExportedProperty.Changed.Subscribe(args =>
+ {
+ var info = GetInfo((TopLevel)args.Sender);
+ if (!info.ChangingIsExported)
+ throw new InvalidOperationException("IsNativeMenuExported property is read-only");
+ info.ChangingIsExported = false;
+ });
+ MenuProperty.Changed.Subscribe(args =>
+ {
+ if (args.Sender is TopLevel tl)
+ {
+ GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue);
+ }
+ });
+
+ PrependApplicationMenuProperty.Changed.Subscribe(args =>
+ {
+ GetInfo((TopLevel)args.Sender).Exporter?.SetPrependApplicationMenu((bool)args.NewValue);
+ });
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/NativeMenu.cs b/src/Avalonia.Controls/NativeMenu.cs
new file mode 100644
index 0000000000..1e2966ff2b
--- /dev/null
+++ b/src/Avalonia.Controls/NativeMenu.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Collections;
+using Avalonia.Data;
+using Avalonia.LogicalTree;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls
+{
+ public partial class NativeMenu : AvaloniaObject, IEnumerable
+ {
+ private AvaloniaList _items =
+ new AvaloniaList { ResetBehavior = ResetBehavior.Remove };
+ private NativeMenuItem _parent;
+ [Content]
+ public IList Items => _items;
+
+ public NativeMenu()
+ {
+ _items.Validate = Validator;
+ _items.CollectionChanged += ItemsChanged;
+ }
+
+ private void Validator(NativeMenuItem obj)
+ {
+ if (obj.Parent != null)
+ throw new InvalidOperationException("NativeMenuItem already has a parent");
+ }
+
+ private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
+ {
+ if(e.OldItems!=null)
+ foreach (NativeMenuItem i in e.OldItems)
+ i.Parent = null;
+ if(e.NewItems!=null)
+ foreach (NativeMenuItem i in e.NewItems)
+ i.Parent = this;
+ }
+
+ public static readonly DirectProperty ParentProperty =
+ AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v);
+
+ public NativeMenuItem Parent
+ {
+ get => _parent;
+ set => SetAndRaise(ParentProperty, ref _parent, value);
+ }
+
+
+ public void Add(NativeMenuItem item) => _items.Add(item);
+
+ public IEnumerator GetEnumerator() => _items.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs
new file mode 100644
index 0000000000..3f1a80dcfe
--- /dev/null
+++ b/src/Avalonia.Controls/NativeMenuItem.cs
@@ -0,0 +1,171 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Input;
+using Avalonia.Collections;
+using Avalonia.Input;
+using Avalonia.Metadata;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls
+{
+ public class NativeMenuItem : AvaloniaObject
+ {
+ private string _header;
+ private KeyGesture _gesture;
+ private bool _enabled = true;
+ private NativeMenu _menu;
+ private NativeMenu _parent;
+
+ class CanExecuteChangedSubscriber : IWeakSubscriber
+ {
+ private readonly NativeMenuItem _parent;
+
+ public CanExecuteChangedSubscriber(NativeMenuItem parent)
+ {
+ _parent = parent;
+ }
+
+ public void OnEvent(object sender, EventArgs e)
+ {
+ _parent.CanExecuteChanged();
+ }
+ }
+
+ private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
+
+ static NativeMenuItem()
+ {
+ MenuProperty.Changed.Subscribe(args =>
+ {
+ var item = (NativeMenuItem)args.Sender;
+ var value = (NativeMenu)args.NewValue;
+ if (value.Parent != null && value.Parent != item)
+ throw new InvalidOperationException("NativeMenu already has a parent");
+ value.Parent = item;
+ });
+ }
+
+ public NativeMenuItem()
+ {
+ _canExecuteChangedSubscriber = new CanExecuteChangedSubscriber(this);
+ }
+
+ public NativeMenuItem(string header) : this()
+ {
+ Header = header;
+ }
+
+ public static readonly DirectProperty HeaderProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Header), o => o._header, (o, v) => o._header = v);
+
+ public string Header
+ {
+ get => GetValue(HeaderProperty);
+ set => SetValue(HeaderProperty, value);
+ }
+
+ public static readonly DirectProperty GestureProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Gesture), o => o._gesture, (o,v)=> o._gesture = v);
+
+ public KeyGesture Gesture
+ {
+ get => GetValue(GestureProperty);
+ set => SetValue(GestureProperty, value);
+ }
+
+ private ICommand _command;
+
+ public static readonly DirectProperty CommandProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Command),
+ o => o._command, (o, v) =>
+ {
+ if (o._command != null)
+ WeakSubscriptionManager.Unsubscribe(o._command,
+ nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
+ o._command = v;
+ if (o._command != null)
+ WeakSubscriptionManager.Subscribe(o._command,
+ nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber);
+ o.CanExecuteChanged();
+ });
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty