From ef7e8f2107749bc629675512ac1daa97a0ecc00a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Sep 2019 18:19:19 +0300 Subject: [PATCH 001/118] Make Application to be an AvaloniaObject. With properties and stuff. --- .../Avalonia.Android/AndroidPlatform.cs | 6 +-- src/Avalonia.Controls/AppBuilderBase.cs | 50 +++++++++++-------- src/Avalonia.Controls/Application.cs | 2 +- .../ClassicDesktopStyleApplicationLifetime.cs | 3 +- src/Avalonia.DesktopRuntime/AppBuilder.cs | 11 +--- .../LinuxFramebufferPlatform.cs | 3 +- src/iOS/Avalonia.iOS/AppBuilder.cs | 2 +- 7 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index c91b58311b..c11cadfbac 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -17,7 +17,7 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android"); + builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android"); builder.UseSkia(); return builder; } @@ -41,7 +41,7 @@ namespace Avalonia.Android _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; } - public static void Initialize(Avalonia.Application app) + public static void Initialize(Type appType) { AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -55,7 +55,7 @@ namespace Avalonia.Android .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) .Bind().ToSingleton() - .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); + .Bind().ToConstant(new AssetLoader(appType.Assembly)); SkiaPlatform.Initialize(); ((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 307ddd284c..2f796376ea 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -18,7 +18,9 @@ namespace Avalonia.Controls { private static bool s_setupWasAlreadyCalled; private Action _optionsInitializers; - + private Func _appFactory; + private IApplicationLifetime _lifetime; + /// /// Gets or sets the instance. /// @@ -30,10 +32,15 @@ namespace Avalonia.Controls public Action RuntimePlatformServicesInitializer { get; private set; } /// - /// Gets or sets the instance being initialized. + /// Gets the instance being initialized. /// - public Application Instance { get; protected set; } - + public Application Instance { get; private set; } + + /// + /// Gets the type of the Instance (even if it's not created yet) + /// + public Type ApplicationType { get; private set; } + /// /// Gets or sets a method to call the initialize the windowing subsystem. /// @@ -76,20 +83,11 @@ namespace Avalonia.Controls public static TAppBuilder Configure() where TApp : Application, new() { - return Configure(new TApp()); - } - - /// - /// Begin configuring an . - /// - /// An instance. - public static TAppBuilder Configure(Application app) - { - AvaloniaLocator.CurrentMutable.BindToSelf(app); - return new TAppBuilder() { - Instance = app, + ApplicationType = typeof(TApp), + // Needed for CoreRT compatibility + _appFactory = () => new TApp() }; } @@ -157,6 +155,18 @@ namespace Avalonia.Controls return Self; } + /// + /// Sets up the platform-specific services for the application and initialized it with a particular lifetime, but does not run it. + /// + /// + /// + public TAppBuilder SetupWithLifetime(IApplicationLifetime lifetime) + { + _lifetime = lifetime; + Setup(); + return Self; + } + /// /// Specifies a windowing subsystem to use. /// @@ -254,11 +264,6 @@ namespace Avalonia.Controls /// private void Setup() { - if (Instance == null) - { - throw new InvalidOperationException("No App instance configured."); - } - if (RuntimePlatformServicesInitializer == null) { throw new InvalidOperationException("No runtime platform services configured."); @@ -282,6 +287,9 @@ namespace Avalonia.Controls s_setupWasAlreadyCalled = true; _optionsInitializers?.Invoke(); RuntimePlatformServicesInitializer(); + Instance = _appFactory(); + Instance.ApplicationLifetime = _lifetime; + AvaloniaLocator.CurrentMutable.BindToSelf(Instance); WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); AfterPlatformServicesSetupCallback(Self); diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 382106de65..ba842d8825 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -32,7 +32,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode + public class Application : AvaloniaObject, IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IResourceNode { /// /// The application-global data templates. diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index abca7a64ee..2533191ae4 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -125,8 +125,7 @@ namespace Avalonia where T : AppBuilderBase, new() { var lifetime = new ClassicDesktopStyleApplicationLifetime(builder.Instance) {ShutdownMode = shutdownMode}; - builder.Instance.ApplicationLifetime = lifetime; - builder.SetupWithoutStarting(); + builder.SetupWithLifetime(lifetime); return lifetime.Start(args); } } diff --git a/src/Avalonia.DesktopRuntime/AppBuilder.cs b/src/Avalonia.DesktopRuntime/AppBuilder.cs index dbe3767df6..ff0d84a6e9 100644 --- a/src/Avalonia.DesktopRuntime/AppBuilder.cs +++ b/src/Avalonia.DesktopRuntime/AppBuilder.cs @@ -18,19 +18,10 @@ namespace Avalonia /// public AppBuilder() : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType()?.Assembly)) + builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly)) { } - /// - /// Initializes a new instance of the class. - /// - /// The instance. - public AppBuilder(Application app) : this() - { - Instance = app; - } - bool CheckEnvironment(Type checkerType) { if (checkerType == null) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 8fc555aac2..db37e4af0b 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs @@ -118,8 +118,7 @@ public static class LinuxFramebufferPlatformExtensions where T : AppBuilderBase, new() { var lifetime = LinuxFramebufferPlatform.Initialize(builder, backend); - builder.Instance.ApplicationLifetime = lifetime; - builder.SetupWithoutStarting(); + builder.SetupWithLifetime(lifetime); lifetime.Start(args); builder.Instance.Run(lifetime.Token); return lifetime.ExitCode; diff --git a/src/iOS/Avalonia.iOS/AppBuilder.cs b/src/iOS/Avalonia.iOS/AppBuilder.cs index a68dd6387a..cb8e0a7954 100644 --- a/src/iOS/Avalonia.iOS/AppBuilder.cs +++ b/src/iOS/Avalonia.iOS/AppBuilder.cs @@ -6,7 +6,7 @@ namespace Avalonia public class AppBuilder : AppBuilderBase { public AppBuilder() : base(new StandardRuntimePlatform(), - builder => StandardRuntimePlatformServices.Register(builder.Instance?.GetType().Assembly)) + builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly)) { } From 19962e4c4dad850aec3208a5b2b8d72fba583ffe Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Sep 2019 22:41:12 +0300 Subject: [PATCH 002/118] NativeMenu/NativeMenu item with dbusmenu-based exporter --- Avalonia.sln.DotSettings | 1 + samples/ControlCatalog.NetCore/Program.cs | 6 +- samples/ControlCatalog/MainWindow.xaml | 29 +- samples/ControlCatalog/MainWindow.xaml.cs | 14 + src/Avalonia.Controls/Application.cs | 17 + src/Avalonia.Controls/NativeMenu.Export.cs | 100 +++++ src/Avalonia.Controls/NativeMenu.cs | 61 +++ src/Avalonia.Controls/NativeMenuItem.cs | 171 ++++++++ .../Platform/ITopLevelNativeMenuExporter.cs | 19 + .../Avalonia.FreeDesktop.csproj | 4 + src/Avalonia.FreeDesktop/DBusHelper.cs | 85 ++++ src/Avalonia.FreeDesktop/DBusMenu.cs | 56 +++ src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 364 ++++++++++++++++++ src/Avalonia.X11/X11Platform.cs | 5 +- src/Avalonia.X11/X11Window.cs | 7 +- 15 files changed, 935 insertions(+), 4 deletions(-) create mode 100644 src/Avalonia.Controls/NativeMenu.Export.cs create mode 100644 src/Avalonia.Controls/NativeMenu.cs create mode 100644 src/Avalonia.Controls/NativeMenuItem.cs create mode 100644 src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs create mode 100644 src/Avalonia.FreeDesktop/DBusHelper.cs create mode 100644 src/Avalonia.FreeDesktop/DBusMenu.cs create mode 100644 src/Avalonia.FreeDesktop/DBusMenuExporter.cs 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 CommandParameterProperty = + Button.CommandParameterProperty.AddOwner(); + + public static readonly DirectProperty EnabledProperty = + AvaloniaProperty.RegisterDirect(nameof(Enabled), o => o._enabled, + (o, v) => o._enabled = v, true); + + public bool Enabled + { + get => GetValue(EnabledProperty); + set => SetValue(EnabledProperty, value); + } + + void CanExecuteChanged() + { + Enabled = _command?.CanExecute(null) ?? true; + } + + public ICommand Command + { + get => GetValue(CommandProperty); + set => SetValue(CommandProperty, value); + } + + /// + /// Gets or sets the parameter to pass to the property of a + /// . + /// + public object CommandParameter + { + get { return GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + public static readonly DirectProperty MenuProperty = + AvaloniaProperty.RegisterDirect(nameof(Menu), o => o._menu, + (o, v) => + { + if (v.Parent != null && v.Parent != o) + throw new InvalidOperationException("NativeMenu already has a parent"); + o._menu = v; + }); + + public NativeMenu Menu + { + get => _menu; + set + { + if (value.Parent != null && value.Parent != this) + throw new InvalidOperationException("NativeMenu already has a parent"); + SetAndRaise(MenuProperty, ref _menu, value); + } + } + + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); + + public NativeMenu Parent + { + get => _parent; + set => SetAndRaise(ParentProperty, ref _parent, value); + } + + + public event EventHandler Clicked; + + public void RaiseClick() + { + Clicked?.Invoke(this, new EventArgs()); + + if (Command?.CanExecute(CommandParameter) == true) + { + Command.Execute(CommandParameter); + } + } + } +} diff --git a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs new file mode 100644 index 0000000000..5112424c3c --- /dev/null +++ b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using Avalonia.Platform; + +namespace Avalonia.Controls.Platform +{ + public interface ITopLevelNativeMenuExporter + { + bool IsNativeMenuExported { get; } + event EventHandler OnIsNativeMenuExportedChanged; + void SetNativeMenu(NativeMenu menu); + void SetPrependApplicationMenu(bool prepend); + } + + public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl + { + ITopLevelNativeMenuExporter NativeMenuExporter { get; } + } +} diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index d7e1d8cdb3..d9fd3b78a2 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -8,4 +8,8 @@ + + + + diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs new file mode 100644 index 0000000000..509e312e1b --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading; +using Avalonia.Threading; +using Tmds.DBus; + +namespace Avalonia.FreeDesktop +{ + public class DBusHelper + { + /// + /// This class uses synchronous execution at DBus connection establishment stage + /// then switches to using AvaloniaSynchronizationContext + /// + class DBusSyncContext : SynchronizationContext + { + private SynchronizationContext _ctx; + private object _lock = new object(); + + public override void Post(SendOrPostCallback d, object state) + { + lock (_lock) + { + if (_ctx != null) + _ctx?.Post(d, state); + else + lock (_lock) + d(state); + } + } + + public override void Send(SendOrPostCallback d, object state) + { + lock (_lock) + { + if (_ctx != null) + _ctx?.Send(d, state); + else + + d(state); + } + } + + public void Initialized() + { + lock (_lock) + _ctx = new AvaloniaSynchronizationContext(); + } + } + public static Connection Connection { get; private set; } + + public static Exception TryInitialize(string dbusAddress = null) + { + + Dispatcher.UIThread.VerifyAccess(); + AvaloniaSynchronizationContext.InstallIfNeeded(); + var oldContext = SynchronizationContext.Current; + try + { + + var dbusContext = new DBusSyncContext(); + SynchronizationContext.SetSynchronizationContext(dbusContext); + var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session) + { + AutoConnect = false, + SynchronizationContext = dbusContext + }); + // Connect synchronously + conn.ConnectAsync().Wait(); + + // Initialize a brand new sync-context + dbusContext.Initialized(); + Connection = conn; + } + catch (Exception e) + { + return e; + } + finally + { + SynchronizationContext.SetSynchronizationContext(oldContext); + } + return null; + } + } +} diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs new file mode 100644 index 0000000000..7180345386 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusMenu.cs @@ -0,0 +1,56 @@ + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Tmds.DBus; + +[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] +namespace Avalonia.FreeDesktop.DBusMenu +{ + + [DBusInterface("org.freedesktop.DBus.Properties")] + interface IFreeDesktopDBusProperties : IDBusObject + { + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [DBusInterface("com.canonical.dbusmenu")] + interface IDBusMenu : IFreeDesktopDBusProperties + { + Task<(uint revision, (int, KeyValuePair[], object[]) layout)> GetLayoutAsync(int ParentId, int RecursionDepth, string[] PropertyNames); + Task<(int, KeyValuePair[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames); + Task GetPropertyAsync(int Id, string Name); + Task EventAsync(int Id, string EventId, object Data, uint Timestamp); + Task EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events); + Task AboutToShowAsync(int Id); + Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids); + Task WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action onError = null); + Task WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action onError = null); + Task WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action onError = null); + } + + [Dictionary] + class DBusMenuProperties + { + public uint Version { get; set; } = default (uint); + public string TextDirection { get; set; } = default (string); + public string Status { get; set; } = default (string); + public string[] IconThemePath { get; set; } = default (string[]); + } + + + [DBusInterface("com.canonical.AppMenu.Registrar")] + interface IRegistrar : IDBusObject + { + Task RegisterWindowAsync(uint WindowId, ObjectPath MenuObjectPath); + Task UnregisterWindowAsync(uint WindowId); + Task<(string service, ObjectPath menuObjectPath)> GetMenuForWindowAsync(uint WindowId); + Task<(uint, string, ObjectPath)[]> GetMenusAsync(); + Task WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action onError = null); + Task WatchWindowUnregisteredAsync(Action handler, Action onError = null); + } +} diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs new file mode 100644 index 0000000000..d9b37607f6 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -0,0 +1,364 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Reactive.Disposables; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.FreeDesktop.DBusMenu; +using Avalonia.Input; +using Avalonia.Threading; +using Tmds.DBus; +#pragma warning disable 1998 + +namespace Avalonia.FreeDesktop +{ + public class DBusMenuExporter + { + public static ITopLevelNativeMenuExporter TryCreate(IntPtr xid) + { + if (DBusHelper.Connection == null) + return null; + + return new DBusMenuExporterImpl(DBusHelper.Connection, xid); + } + + class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable + { + private readonly Connection _dbus; + private readonly uint _xid; + private IRegistrar _registar; + private bool _disposed; + private uint _revision = 1; + private NativeMenu _menu; + private Dictionary _idsToItems = new Dictionary(); + private Dictionary _itemsToIds = new Dictionary(); + private bool _resetQueued; + private int _nextId = 1; + public DBusMenuExporterImpl(Connection dbus, IntPtr xid) + { + _dbus = dbus; + _xid = (uint)xid.ToInt32(); + ObjectPath = new ObjectPath("/net/avaloniaui/dbusmenu/" + + Guid.NewGuid().ToString().Replace("-", "")); + SetNativeMenu(new NativeMenu()); + Init(); + } + + async void Init() + { + try + { + await _dbus.RegisterObjectAsync(this); + _registar = DBusHelper.Connection.CreateProxy( + "com.canonical.AppMenu.Registrar", + "/com/canonical/AppMenu/Registrar"); + if (!_disposed) + await _registar.RegisterWindowAsync(_xid, ObjectPath); + } + catch (Exception e) + { + Console.Error.WriteLine(e); + // It's not really important if this code succeeds, + // and it's not important to know if it succeeds + // since even if we register the window it's not guaranteed that + // menu will be actually exported + } + } + + public void Dispose() + { + if (_disposed) + return; + _disposed = true; + _dbus.UnregisterObject(this); + // Fire and forget + _registar?.UnregisterWindowAsync(_xid); + } + + + + public bool IsNativeMenuExported { get; } + public event EventHandler OnIsNativeMenuExportedChanged; + + public void SetNativeMenu(NativeMenu menu) + { + if (menu == null) + menu = new NativeMenu(); + + if (_menu != null) + ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged; + _menu = menu; + ((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged; + + DoLayoutReset(); + } + + /* + This is basic initial implementation, so we don't actually track anything and + just reset the whole layout on *ANY* change + + This is not how it should work and will prevent us from implementing various features, + but that's the fastest way to get things working, so... + */ + void DoLayoutReset() + { + _resetQueued = false; + foreach (var i in _idsToItems.Values) + { + i.PropertyChanged -= OnItemPropertyChanged; + if (i.Menu != null) + ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; + } + _idsToItems.Clear(); + _itemsToIds.Clear(); + LayoutUpdated?.Invoke((_revision++, 0)); + } + + void QueueReset() + { + if(_resetQueued) + return; + _resetQueued = true; + Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); + } + + private (NativeMenuItem item, NativeMenu menu) GetMenu(int id) + { + if (id == 0) + return (null, _menu); + _idsToItems.TryGetValue(id, out var item); + return (item, item?.Menu); + } + + private int GetId(NativeMenuItem item) + { + if (_itemsToIds.TryGetValue(item, out var id)) + return id; + id = _nextId++; + _idsToItems[id] = item; + _itemsToIds[item] = id; + item.PropertyChanged += OnItemPropertyChanged; + if (item.Menu != null) + ((INotifyCollectionChanged)item.Menu.Items).CollectionChanged += OnMenuItemsChanged; + return id; + } + + private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + QueueReset(); + } + + private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + QueueReset(); + } + + public void SetPrependApplicationMenu(bool prepend) + { + // Not implemented yet :( + } + + public ObjectPath ObjectPath { get; } + + + async Task IFreeDesktopDBusProperties.GetAsync(string prop) + { + if (prop == "Version") + return 2; + if (prop == "Status") + return "normal"; + return 0; + } + + async Task IFreeDesktopDBusProperties.GetAllAsync() + { + return new DBusMenuProperties + { + Version = 2, + Status = "normal", + }; + } + + private static string[] AllProperties = new[] + { + "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display" + }; + + object GetProperty((NativeMenuItem item, NativeMenu menu) i, string name) + { + var (item, menu) = i; + if (name == "type") + { + if (item != null && item.Header == null) + return "separator"; + return null; + } + if (name == "label") + return item?.Header ?? ""; + if (name == "enabled") + { + if (item == null) + return null; + if (item.Menu != null && item.Menu.Items.Count == 0) + return false; + if (item.Enabled == false) + return false; + return null; + } + if (name == "shortcut") + { + if (item?.Gesture == null) + return null; + if (item.Gesture.KeyModifiers == 0) + return null; + var lst = new List(); + var mod = item.Gesture; + if ((mod.KeyModifiers & KeyModifiers.Control) != 0) + lst.Add("Control"); + if ((mod.KeyModifiers & KeyModifiers.Alt) != 0) + lst.Add("Alt"); + if ((mod.KeyModifiers & KeyModifiers.Shift) != 0) + lst.Add("Shift"); + if ((mod.KeyModifiers & KeyModifiers.Meta) != 0) + lst.Add("Super"); + lst.Add(item.Gesture.Key.ToString()); + return new[] { lst.ToArray() }; + } + + if (name == "children-display") + return menu != null ? "submenu" : null; + return null; + } + + private List> _reusablePropertyList = new List>(); + KeyValuePair[] GetProperties((NativeMenuItem item, NativeMenu menu) i, string[] names) + { + if (names?.Length > 0 != true) + names = AllProperties; + _reusablePropertyList.Clear(); + foreach (var n in names) + { + var v = GetProperty(i, n); + if (v != null) + _reusablePropertyList.Add(new KeyValuePair(n, v)); + } + + return _reusablePropertyList.ToArray(); + } + + + public Task SetAsync(string prop, object val) => Task.CompletedTask; + + public Task<(uint revision, (int, KeyValuePair[], object[]) layout)> GetLayoutAsync( + int ParentId, int RecursionDepth, string[] PropertyNames) + { + var menu = GetMenu(ParentId); + var rv = (_revision, GetLayout(menu.item, menu.menu, RecursionDepth, PropertyNames)); + return Task.FromResult(rv); + } + + (int, KeyValuePair[], object[]) GetLayout(NativeMenuItem item, NativeMenu menu, int depth, string[] propertyNames) + { + var id = item == null ? 0 : GetId(item); + var props = GetProperties((item, menu), propertyNames); + var children = (depth == 0 || menu == null) ? new object[0] : new object[menu.Items.Count]; + if(menu != null) + for (var c = 0; c < children.Length; c++) + { + var ch = menu.Items[c]; + children[c] = GetLayout(ch, ch.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + } + + return (id, props, children); + } + + public Task<(int, KeyValuePair[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames) + { + var arr = new (int, KeyValuePair[])[Ids.Length]; + for (var c = 0; c < Ids.Length; c++) + { + var id = Ids[c]; + var item = GetMenu(id); + var props = GetProperties(item, PropertyNames); + arr[c] = (id, props); + } + + return Task.FromResult(arr); + } + + public async Task GetPropertyAsync(int Id, string Name) + { + return GetProperty(GetMenu(Id), Name) ?? 0; + } + + + + public void HandleEvent(int id, string eventId, object data, uint timestamp) + { + if (eventId == "clicked") + { + var item = GetMenu(id).item; + if (item?.Enabled == true) + item.RaiseClick(); + } + } + + public Task EventAsync(int Id, string EventId, object Data, uint Timestamp) + { + HandleEvent(Id, EventId, Data, Timestamp); + return Task.CompletedTask; + } + + public Task EventGroupAsync((int id, string eventId, object data, uint timestamp)[] Events) + { + foreach (var e in Events) + HandleEvent(e.id, e.eventId, e.data, e.timestamp); + return Task.FromResult(new int[0]); + } + + public async Task AboutToShowAsync(int Id) + { + return false; + } + + public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) + { + return (new int[0], new int[0]); + } + + #region Events + + private event Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> + ItemsPropertiesUpdated; + private event Action<(uint revision, int parent)> LayoutUpdated; + private event Action<(int id, uint timestamp)> ItemActivationRequested; + private event Action PropertiesChanged; + + async Task IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action onError) + { + ItemsPropertiesUpdated += handler; + return Disposable.Create(() => ItemsPropertiesUpdated -= handler); + } + async Task IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action onError) + { + LayoutUpdated += handler; + return Disposable.Create(() => LayoutUpdated -= handler); + } + + async Task IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action onError) + { + ItemActivationRequested+= handler; + return Disposable.Create(() => ItemActivationRequested -= handler); + } + + async Task IFreeDesktopDBusProperties.WatchPropertiesAsync(Action handler) + { + PropertiesChanged += handler; + return Disposable.Create(() => PropertiesChanged -= handler); + } + + #endregion + } + } +} diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 1d2290236c..d7a7bb97fd 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -38,7 +38,9 @@ namespace Avalonia.X11 throw new Exception("XOpenDisplay failed"); XError.Init(); Info = new X11Info(Display, DeferredDisplay); - + //TODO: log + if (options.UseDBusMenu) + DBusHelper.TryInitialize(); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(this) .Bind().ToConstant(new X11PlatformThreading(this)) @@ -95,6 +97,7 @@ namespace Avalonia public bool UseEGL { get; set; } public bool UseGpu { get; set; } = true; public bool OverlayPopups { get; set; } + public bool UseDBusMenu { get; set; } public List GlxRendererBlacklist { get; set; } = new List { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 975b3d11d7..0fab85d681 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -6,7 +6,9 @@ using System.Linq; using System.Reactive.Disposables; using System.Text; using Avalonia.Controls; +using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.FreeDesktop; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.OpenGL; @@ -19,7 +21,7 @@ using static Avalonia.X11.XLib; // ReSharper disable StringLiteralTypo namespace Avalonia.X11 { - unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client + unsafe class X11Window : IWindowImpl, IPopupImpl, IXI2Client, ITopLevelImplWithNativeMenuExporter { private readonly AvaloniaX11Platform _platform; private readonly IWindowImpl _popupParent; @@ -155,6 +157,8 @@ namespace Avalonia.X11 XFlush(_x11.Display); if(_popup) PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize)); + if (platform.Options.UseDBusMenu) + NativeMenuExporter = DBusMenuExporter.TryCreate(_handle); } class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo @@ -960,5 +964,6 @@ namespace Avalonia.X11 } public IPopupPositioner PopupPositioner { get; } + public ITopLevelNativeMenuExporter NativeMenuExporter { get; } } } From a25b39399e0460dd2199549b0070e354b7c796a4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Sep 2019 22:46:43 +0300 Subject: [PATCH 003/118] Report menu export status change --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index d9b37607f6..18ba4a90d4 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -78,7 +78,7 @@ namespace Avalonia.FreeDesktop - public bool IsNativeMenuExported { get; } + public bool IsNativeMenuExported { get; private set; } public event EventHandler OnIsNativeMenuExportedChanged; public void SetNativeMenu(NativeMenu menu) @@ -255,6 +255,14 @@ namespace Avalonia.FreeDesktop { var menu = GetMenu(ParentId); var rv = (_revision, GetLayout(menu.item, menu.menu, RecursionDepth, PropertyNames)); + if (!IsNativeMenuExported) + { + IsNativeMenuExported = true; + Dispatcher.UIThread.Post(() => + { + OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty); + }); + } return Task.FromResult(rv); } From 2e643d65a99997103f04400a60959dcb1c113b91 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Sep 2019 23:06:03 +0300 Subject: [PATCH 004/118] Fixed threading --- src/Avalonia.Controls/AppBuilderBase.cs | 6 +++--- src/Avalonia.FreeDesktop/DBusHelper.cs | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/AppBuilderBase.cs b/src/Avalonia.Controls/AppBuilderBase.cs index 2f796376ea..d9be9171ed 100644 --- a/src/Avalonia.Controls/AppBuilderBase.cs +++ b/src/Avalonia.Controls/AppBuilderBase.cs @@ -287,12 +287,12 @@ namespace Avalonia.Controls s_setupWasAlreadyCalled = true; _optionsInitializers?.Invoke(); RuntimePlatformServicesInitializer(); - Instance = _appFactory(); - Instance.ApplicationLifetime = _lifetime; - AvaloniaLocator.CurrentMutable.BindToSelf(Instance); WindowingSubsystemInitializer(); RenderingSubsystemInitializer(); AfterPlatformServicesSetupCallback(Self); + Instance = _appFactory(); + Instance.ApplicationLifetime = _lifetime; + AvaloniaLocator.CurrentMutable.BindToSelf(Instance); Instance.RegisterServices(); Instance.Initialize(); AfterSetupCallback(Self); diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index 509e312e1b..b445f86613 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -50,9 +50,6 @@ namespace Avalonia.FreeDesktop public static Exception TryInitialize(string dbusAddress = null) { - - Dispatcher.UIThread.VerifyAccess(); - AvaloniaSynchronizationContext.InstallIfNeeded(); var oldContext = SynchronizationContext.Current; try { From 641c07a5c0b0efdbbf248d67837d88bbd60b5b31 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 11 Sep 2019 23:34:54 +0300 Subject: [PATCH 005/118] [DBusMenu] Send proper _revision value to LayoutUpdated --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 18ba4a90d4..1f1a244fb7 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -112,7 +112,8 @@ namespace Avalonia.FreeDesktop } _idsToItems.Clear(); _itemsToIds.Clear(); - LayoutUpdated?.Invoke((_revision++, 0)); + _revision++; + LayoutUpdated?.Invoke((_revision, 0)); } void QueueReset() From 8520eb32bc164b6ad648da7daeebd6c213597df0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 22 Sep 2019 11:07:43 +0100 Subject: [PATCH 006/118] add scafolding for native osx menu --- .../AvaloniaNativeMenuExporter.cs | 25 +++++++++++++++++++ src/Avalonia.Native/WindowImpl.cs | 8 +++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 src/Avalonia.Native/AvaloniaNativeMenuExporter.cs diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs new file mode 100644 index 0000000000..76598237e2 --- /dev/null +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls; +using Avalonia.Controls.Platform; + +namespace Avalonia.Native +{ + class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter + { + public bool IsNativeMenuExported => throw new NotImplementedException(); + + public event EventHandler OnIsNativeMenuExportedChanged; + + public void SetNativeMenu(NativeMenu menu) + { + throw new NotImplementedException(); + } + + public void SetPrependApplicationMenu(bool prepend) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 490d5688a8..76ee52f3ab 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -3,13 +3,14 @@ using System; using Avalonia.Controls; +using Avalonia.Controls.Platform; using Avalonia.Native.Interop; using Avalonia.Platform; using Avalonia.Platform.Interop; namespace Avalonia.Native { - public class WindowImpl : WindowBaseImpl, IWindowImpl + public class WindowImpl : WindowBaseImpl, IWindowImpl, ITopLevelImplWithNativeMenuExporter { private readonly IAvaloniaNativeFactory _factory; private readonly AvaloniaNativePlatformOptions _opts; @@ -22,6 +23,8 @@ namespace Avalonia.Native { Init(_native = factory.CreateWindow(e), factory.CreateScreens()); } + + NativeMenuExporter = new AvaloniaNativeMenuExporter(); } class WindowEvents : WindowBaseEvents, IAvnWindowEvents @@ -104,6 +107,9 @@ namespace Avalonia.Native } public Func Closing { get; set; } + + public ITopLevelNativeMenuExporter NativeMenuExporter { get; } + public void Move(PixelPoint point) => Position = point; public override IPopupImpl CreatePopup() => From 72c950588afedd3827858f58ed6bd74754cea97a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 22 Sep 2019 11:49:23 +0100 Subject: [PATCH 007/118] add some more menu implementation. --- .../AvaloniaNativeMenuExporter.cs | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 76598237e2..59a92a0ba8 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -1,25 +1,86 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.Threading; namespace Avalonia.Native { class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter { + private NativeMenu _menu; + private bool _resetQueued; + private Dictionary _idsToItems = new Dictionary(); + private Dictionary _itemsToIds = new Dictionary(); + private uint _revision = 1; + public bool IsNativeMenuExported => throw new NotImplementedException(); public event EventHandler OnIsNativeMenuExportedChanged; + private event Action<(uint revision, int parent)> LayoutUpdated; + public void SetNativeMenu(NativeMenu menu) { - throw new NotImplementedException(); + if (menu == null) + menu = new NativeMenu(); + + if (_menu != null) + ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged; + _menu = menu; + ((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged; + + DoLayoutReset(); } public void SetPrependApplicationMenu(bool prepend) { throw new NotImplementedException(); } + + private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) + { + QueueReset(); + } + + private void OnMenuItemsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + QueueReset(); + } + + /* + This is basic initial implementation, so we don't actually track anything and + just reset the whole layout on *ANY* change + + This is not how it should work and will prevent us from implementing various features, + but that's the fastest way to get things working, so... + */ + void DoLayoutReset() + { + _resetQueued = false; + foreach (var i in _idsToItems.Values) + { + i.PropertyChanged -= OnItemPropertyChanged; + if (i.Menu != null) + ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; + } + + _idsToItems.Clear(); + _itemsToIds.Clear(); + + _revision++; + + LayoutUpdated?.Invoke((_revision, 0)); + } + + private void QueueReset() + { + if (_resetQueued) + return; + _resetQueued = true; + Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); + } } } From 160deba7725fbb4b5fb37600064633d1d90bd17b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 15:27:47 +0100 Subject: [PATCH 008/118] fix null check when there is no exporter. --- src/Avalonia.Controls/NativeMenu.Export.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 9ce83f3e3c..40ea8bd2d4 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -40,7 +40,7 @@ namespace Avalonia.Controls if (rv == null) { target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target)); - SetIsNativeMenuExported(target, rv.Exporter.IsNativeMenuExported); + SetIsNativeMenuExported(target, rv.Exporter?.IsNativeMenuExported ?? false); } return rv; From 22e14443ab2f3492183438262b2e5482b79151dd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 15:37:19 +0100 Subject: [PATCH 009/118] add native apis for setting working with osx menus --- native/Avalonia.Native/inc/IGetNative.h | 10 + native/Avalonia.Native/inc/avalonia-native.h | 27 +++ .../project.pbxproj | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + native/Avalonia.Native/src/OSX/common.h | 9 + native/Avalonia.Native/src/OSX/main.mm | 127 ++++++++++- native/Avalonia.Native/src/OSX/menu.h | 79 +++++++ native/Avalonia.Native/src/OSX/menu.mm | 211 ++++++++++++++++++ .../src/OSX/platformthreading.mm | 6 - 9 files changed, 474 insertions(+), 10 deletions(-) create mode 100644 native/Avalonia.Native/inc/IGetNative.h create mode 100644 native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 native/Avalonia.Native/src/OSX/menu.h create mode 100644 native/Avalonia.Native/src/OSX/menu.mm diff --git a/native/Avalonia.Native/inc/IGetNative.h b/native/Avalonia.Native/inc/IGetNative.h new file mode 100644 index 0000000000..85ae030d74 --- /dev/null +++ b/native/Avalonia.Native/inc/IGetNative.h @@ -0,0 +1,10 @@ +#ifndef igetnative_h +#define igetnative_h + +class IGetNative +{ +public: + virtual void* GetNative() = 0; +}; + +#endif diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index e54f3fa6a7..16d4fa4107 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -22,6 +22,8 @@ struct IAvnGlContext; struct IAvnGlDisplay; struct IAvnGlSurfaceRenderTarget; struct IAvnGlSurfaceRenderingSession; +struct IAvnAppMenu; +struct IAvnAppMenuItem; struct AvnSize { @@ -173,6 +175,9 @@ public: virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; + virtual HRESULT ObtainAppMenu (IAvnAppMenu** ppv) = 0; + virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0; + virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0; }; AVNCOM(IAvnString, 17) : IUnknown @@ -258,6 +263,7 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents AVNCOM(IAvnMacOptions, 07) : IUnknown { virtual HRESULT SetShowInDock(int show) = 0; + virtual HRESULT SetApplicationTitle (void* utf8string) = 0; }; AVNCOM(IAvnActionCallback, 08) : IUnknown @@ -367,4 +373,25 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown virtual HRESULT GetScaling(double* ret) = 0; }; +AVNCOM(IAvnAppMenu, 17) : IUnknown +{ + virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0; + virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0; + virtual HRESULT SetTitle (void* utf8String) = 0; + virtual HRESULT Clear () = 0; +}; + +AVNCOM(IAvnPredicateCallback, 18) : IUnknown +{ + virtual bool Evaluate() = 0; +}; + +AVNCOM(IAvnAppMenuItem, 19) : IUnknown +{ + virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0; + virtual HRESULT SetTitle (void* utf8String) = 0; + virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0; + virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0; +}; + extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative(); diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index 1870ef7ab3..84c3a84b91 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; }; 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; }; + 520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; }; 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; @@ -32,6 +33,8 @@ 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = ""; }; 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = ""; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; + 520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = ""; }; + 5296D43022F30EBC005B125D /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = ../../../../../../../../System/Volumes/Data/Users/danwalmsley/repos/Avalonia/native/Avalonia.Native/src/OSX/menu.h; sourceTree = ""; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; }; @@ -85,6 +88,8 @@ AB661C1F2148286E00291242 /* window.mm */, 37C09D8A21581EF2006A6758 /* window.h */, AB00E4F62147CA920032A60A /* main.mm */, + 520624B222973F4100C4DCEF /* menu.mm */, + 5296D43022F30EBC005B125D /* menu.h */, 37A517B22159597E00FBA241 /* Screens.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, AB7A61F02147C815003C5833 /* Products */, @@ -150,6 +155,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = AB7A61E62147C814003C5833; @@ -173,6 +179,7 @@ 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */, AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, + 520624B322973F4100C4DCEF /* menu.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */, 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 45ec40c361..c066ebb498 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -19,6 +19,10 @@ extern IAvnClipboard* CreateClipboard(); extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnGlFeature* GetGlFeature(); extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view); +extern IAvnAppMenu* GetAppMenu(); +extern IAvnAppMenu* CreateAppMenu(); +extern IAvnAppMenuItem* CreateAppMenuItem(); + extern void InitializeAvnApp(); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; extern NSPoint ToNSPoint (AvnPoint p); @@ -40,4 +44,9 @@ template inline T* objc_cast(id from) { return nil; } +@interface ActionCallback : NSObject +- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback; +- (void) action; +@end + #endif diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 70bd1e67f6..159f01d1d7 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -5,10 +5,115 @@ #define COM_GUIDS_MATERIALIZE #include "common.h" +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +void SetProcessName(CFStringRef process_name) { + if (!process_name || CFStringGetLength(process_name) == 0) { + //NOTREACHED() << "SetProcessName given bad name."; + return; + } + + if (![NSThread isMainThread]) { + //NOTREACHED() << "Should only set process name from main thread."; + return; + } + + // Warning: here be dragons! This is SPI reverse-engineered from WebKit's + // plugin host, and could break at any time (although realistically it's only + // likely to break in a new major release). + // When 10.7 is available, check that this still works, and update this + // comment for 10.8. + + // Private CFType used in these LaunchServices calls. + typedef CFTypeRef PrivateLSASN; + typedef PrivateLSASN (*LSGetCurrentApplicationASNType)(); + typedef OSStatus (*LSSetApplicationInformationItemType)(int, PrivateLSASN, + CFStringRef, + CFStringRef, + CFDictionaryRef*); + + static LSGetCurrentApplicationASNType ls_get_current_application_asn_func = + NULL; + static LSSetApplicationInformationItemType + ls_set_application_information_item_func = NULL; + static CFStringRef ls_display_name_key = NULL; + + static bool did_symbol_lookup = false; + if (!did_symbol_lookup) { + did_symbol_lookup = true; + CFBundleRef launch_services_bundle = + CFBundleGetBundleWithIdentifier(CFSTR("com.apple.LaunchServices")); + if (!launch_services_bundle) { + //LOG(ERROR) << "Failed to look up LaunchServices bundle"; + return; + } + + ls_get_current_application_asn_func = + reinterpret_cast( + CFBundleGetFunctionPointerForName( + launch_services_bundle, CFSTR("_LSGetCurrentApplicationASN"))); + if (!ls_get_current_application_asn_func){} + //LOG(ERROR) << "Could not find _LSGetCurrentApplicationASN"; + + ls_set_application_information_item_func = + reinterpret_cast( + CFBundleGetFunctionPointerForName( + launch_services_bundle, + CFSTR("_LSSetApplicationInformationItem"))); + if (!ls_set_application_information_item_func){} + //LOG(ERROR) << "Could not find _LSSetApplicationInformationItem"; + + CFStringRef* key_pointer = reinterpret_cast( + CFBundleGetDataPointerForName(launch_services_bundle, + CFSTR("_kLSDisplayNameKey"))); + ls_display_name_key = key_pointer ? *key_pointer : NULL; + if (!ls_display_name_key){} + //LOG(ERROR) << "Could not find _kLSDisplayNameKey"; + + // Internally, this call relies on the Mach ports that are started up by the + // Carbon Process Manager. In debug builds this usually happens due to how + // the logging layers are started up; but in release, it isn't started in as + // much of a defined order. So if the symbols had to be loaded, go ahead + // and force a call to make sure the manager has been initialized and hence + // the ports are opened. + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + } + if (!ls_get_current_application_asn_func || + !ls_set_application_information_item_func || + !ls_display_name_key) { + return; + } + + PrivateLSASN asn = ls_get_current_application_asn_func(); + // Constant used by WebKit; what exactly it means is unknown. + const int magic_session_constant = -2; + OSErr err = + ls_set_application_information_item_func(magic_session_constant, asn, + ls_display_name_key, + process_name, + NULL /* optional out param */); + //LOG_IF(ERROR, err) << "Call to set process name failed, err " << err; +} + class MacOptions : public ComSingleObject { public: FORWARD_IUNKNOWN() + + virtual HRESULT SetApplicationTitle(void* utf8String) override + { + auto appTitle = [NSString stringWithUTF8String:(const char*)utf8String]; + + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + CFStringRef titleRef = (__bridge CFStringRef)appTitle; + SetProcessName(titleRef); + + return S_OK; + } + virtual HRESULT SetShowInDock(int show) override { AvnDesiredActivationPolicy = show @@ -17,8 +122,6 @@ public: } }; - - /// See "Using POSIX Threads in a Cocoa Application" section here: /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024 @interface ThreadingInitializer : NSObject @@ -43,8 +146,6 @@ public: close(_fds[0]); close(_fds[1]); } - - @end @@ -123,6 +224,24 @@ public: *ppv = rv; return S_OK; } + + virtual HRESULT ObtainAppMenu(IAvnAppMenu** ppv) override + { + *ppv = ::GetAppMenu(); + return S_OK; + } + + virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override + { + *ppv = ::CreateAppMenu(); + return S_OK; + } + + virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override + { + *ppv = ::CreateAppMenuItem(); + return S_OK; + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h new file mode 100644 index 0000000000..e3c1fa9768 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/menu.h @@ -0,0 +1,79 @@ +// +// menu.h +// Avalonia.Native.OSX +// +// Created by Dan Walmsley on 01/08/2019. +// Copyright © 2019 Avalonia. All rights reserved. +// + +#ifndef menu_h +#define menu_h + +#include "common.h" + +class AvnAppMenuItem; +class AvnAppMenu; + +@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain + +@end + +@interface AvnMenuItem : NSMenuItem +- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem; +- (void)didSelectItem:(id)sender; +@end + +class AvnAppMenuItem : public ComSingleObject +{ +private: + AvnMenuItem* _native; // here we hold a pointer to an AvnMenuItem + IAvnActionCallback* _callback; + IAvnPredicateCallback* _predicate; + +public: + FORWARD_IUNKNOWN() + + AvnAppMenuItem(); + + AvnMenuItem* GetNative(); + + virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override; + + virtual HRESULT SetTitle (void* utf8String) override; + + virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override; + + virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override; + + bool EvaluateItemEnabled(); + + void RaiseOnClicked(); +}; + + +class AvnAppMenu : public ComSingleObject +{ +private: + AvnMenu* _native; + +public: + FORWARD_IUNKNOWN() + + AvnAppMenu(); + + AvnAppMenu(AvnMenu* native); + + AvnMenu* GetNative(); + + virtual HRESULT AddItem (IAvnAppMenuItem* item) override; + + virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override; + + virtual HRESULT SetTitle (void* utf8String) override; + + virtual HRESULT Clear () override; +}; + + +#endif + diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm new file mode 100644 index 0000000000..f861774b29 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -0,0 +1,211 @@ + +#include "common.h" +#include "IGetNative.h" +#include "menu.h" + +@implementation AvnMenu +@end + +@implementation AvnMenuItem +{ + AvnAppMenuItem* _item; +} + +- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem +{ + if(self != nil) + { + _item = menuItem; + self = [super initWithTitle:@"" + action:@selector(didSelectItem:) + keyEquivalent:@""]; + + [self setEnabled:YES]; + + [self setTarget:self]; + } + + return self; +} + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + if([self submenu] != nil) + { + return YES; + } + + return _item->EvaluateItemEnabled(); +} + +- (void)didSelectItem:(nullable id)sender +{ + _item->RaiseOnClicked(); +} +@end + +AvnAppMenuItem::AvnAppMenuItem() +{ + _native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this]; + _callback = nullptr; +} + +AvnMenuItem* AvnAppMenuItem::GetNative() +{ + return _native; +} + +HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu) +{ + auto nsMenu = dynamic_cast(menu)->GetNative(); + + [_native setSubmenu: nsMenu]; + + return S_OK; +} + +HRESULT AvnAppMenuItem::SetTitle (void* utf8String) +{ + [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + + return S_OK; +} + +HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers) +{ + NSEventModifierFlags flags = 0; + + if (modifiers & Control) + flags |= NSEventModifierFlagControl; + if (modifiers & Shift) + flags |= NSEventModifierFlagShift; + if (modifiers & Alt) + flags |= NSEventModifierFlagOption; + if (modifiers & Windows) + flags |= NSEventModifierFlagCommand; + + [_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]]; + [_native setKeyEquivalentModifierMask:flags]; + + return S_OK; +} + +HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) +{ + _predicate = predicate; + _callback = callback; + return S_OK; +} + +bool AvnAppMenuItem::EvaluateItemEnabled() +{ + if(_predicate != nullptr) + { + auto result = _predicate->Evaluate (); + + return result; + } + + return false; +} + +void AvnAppMenuItem::RaiseOnClicked() +{ + if(_callback != nullptr) + { + _callback->Run(); + } +} + +AvnAppMenu::AvnAppMenu() +{ + _native = [AvnMenu new]; +} + +AvnAppMenu::AvnAppMenu(AvnMenu* native) +{ + _native = native; +} + +AvnMenu* AvnAppMenu::GetNative() +{ + return _native; +} + +HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item) +{ + auto avnMenuItem = dynamic_cast(item); + + if(avnMenuItem != nullptr) + { + + [_native addItem: avnMenuItem->GetNative()]; + } + + return S_OK; +} + +HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item) +{ + auto avnMenuItem = dynamic_cast(item); + + if(avnMenuItem != nullptr) + { + [_native removeItem:avnMenuItem->GetNative()]; + } + + return S_OK; +} + +HRESULT AvnAppMenu::SetTitle (void* utf8String) +{ + [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + + return S_OK; +} + +HRESULT AvnAppMenu::Clear() +{ + [_native removeAllItems]; + return S_OK; +} + +static IAvnAppMenu* s_AppMenu = nullptr; + +extern IAvnAppMenu* GetAppMenu() +{ + @autoreleasepool + { + if(s_AppMenu == nullptr) + { + id menubar = [NSMenu new]; + [menubar setTitle:@"Test"]; + [NSApp setMainMenu:menubar]; + + id appMenuItem = [AvnMenuItem new]; + [[NSApp mainMenu] addItem:appMenuItem]; + + [appMenuItem setSubmenu:[AvnMenu new]]; + + s_AppMenu = new AvnAppMenu([[NSApplication sharedApplication] mainMenu]); + } + + return s_AppMenu; + } +} + +extern IAvnAppMenu* CreateAppMenu() +{ + @autoreleasepool + { + return new AvnAppMenu(); + } +} + +extern IAvnAppMenuItem* CreateAppMenuItem() +{ + @autoreleasepool + { + return new AvnAppMenuItem(); + } +} diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm index 297097584a..e7abedae51 100644 --- a/native/Avalonia.Native/src/OSX/platformthreading.mm +++ b/native/Avalonia.Native/src/OSX/platformthreading.mm @@ -10,12 +10,6 @@ class PlatformThreadingInterface; -(Signaler*) init; @end - -@interface ActionCallback : NSObject -- (ActionCallback*) initWithCallback: (IAvnActionCallback*) callback; -- (void) action; -@end - @implementation ActionCallback { ComPtr _callback; From fde1e095ab6c2031d001d93e89a0724843445351 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 15:48:32 +0100 Subject: [PATCH 010/118] add previous osx menu implementation. --- .../AvaloniaNativeMenuExporter.cs | 151 ++++++++++++++++++ src/Avalonia.Native/WindowImpl.cs | 2 +- 2 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 59a92a0ba8..647b867184 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -4,18 +4,56 @@ using System.Collections.Specialized; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.Native.Interop; +using Avalonia.Platform.Interop; using Avalonia.Threading; namespace Avalonia.Native { + public class MenuActionCallback : CallbackBase, IAvnActionCallback + { + private Action _action; + + public MenuActionCallback(Action action) + { + _action = action; + } + + void IAvnActionCallback.Run() + { + _action?.Invoke(); + } + } + + public class PredicateCallback : CallbackBase, IAvnPredicateCallback + { + private Func _predicate; + + public PredicateCallback(Func predicate) + { + _predicate = predicate; + } + + bool IAvnPredicateCallback.Evaluate() + { + return _predicate(); + } + } + class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter { + private IAvaloniaNativeFactory _factory; private NativeMenu _menu; private bool _resetQueued; private Dictionary _idsToItems = new Dictionary(); private Dictionary _itemsToIds = new Dictionary(); private uint _revision = 1; + public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) + { + _factory = factory; + } + public bool IsNativeMenuExported => throw new NotImplementedException(); public event EventHandler OnIsNativeMenuExportedChanged; @@ -73,6 +111,8 @@ namespace Avalonia.Native _revision++; LayoutUpdated?.Invoke((_revision, 0)); + + SetMenu(_menu.Items); } private void QueueReset() @@ -82,5 +122,116 @@ namespace Avalonia.Native _resetQueued = true; Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); } + + private IAvnAppMenu CreateSubmenu(ICollection children) + { + var menu = _factory.CreateMenu(); + + SetChildren(menu, children); + + return menu; + } + + private void SetChildren(IAvnAppMenu menu, ICollection children) + { + foreach (var item in children) + { + var menuItem = _factory.CreateMenuItem(); + + using (var buffer = new Utf8Buffer(item.Header)) + { + menuItem.Title = buffer.DangerousGetHandle(); + } + + if (item.Gesture != null) + { + using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + { + menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + } + } + + menuItem.SetAction(new PredicateCallback(() => + { + if (item.Command != null) + { + return item.Command.CanExecute(null); + } + + return false; + }), new MenuActionCallback(() => { item.RaiseClick(); })); + menu.AddItem(menuItem); + + if (item.Menu?.Items?.Count > 0) + { + var submenu = _factory.CreateMenu(); + + using (var buffer = new Utf8Buffer(item.Header)) + { + submenu.Title = buffer.DangerousGetHandle(); + } + + menuItem.SetSubMenu(submenu); + + AddItemsToMenu(submenu, item.Menu?.Items); + } + } + } + + private void AddItemsToMenu(IAvnAppMenu menu, ICollection items, bool isMainMenu = false) + { + foreach (var item in items) + { + var menuItem = _factory.CreateMenuItem(); + + menuItem.SetAction(new PredicateCallback(() => + { + if (item.Command != null) + { + return item.Command.CanExecute(null); + } + + return false; + }), new MenuActionCallback(() => { item.RaiseClick(); })); + + if (item.Menu?.Items.Count > 0 || isMainMenu) + { + var subMenu = CreateSubmenu(item.Menu?.Items); + + menuItem.SetSubMenu(subMenu); + + using (var buffer = new Utf8Buffer(item.Header)) + { + subMenu.Title = buffer.DangerousGetHandle(); + } + } + else + { + using (var buffer = new Utf8Buffer(item.Header)) + { + menuItem.Title = buffer.DangerousGetHandle(); + } + + if (item.Gesture != null) + { + using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + { + menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + } + } + } + + menu.AddItem(menuItem); + } + } + + private void SetMenu(ICollection menuItems) + { + var appMenu = _factory.ObtainAppMenu(); + + appMenu.Clear(); + + AddItemsToMenu(appMenu, menuItems); + } } } diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 76ee52f3ab..02c20b04ee 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native Init(_native = factory.CreateWindow(e), factory.CreateScreens()); } - NativeMenuExporter = new AvaloniaNativeMenuExporter(); + NativeMenuExporter = new AvaloniaNativeMenuExporter(factory); } class WindowEvents : WindowBaseEvents, IAvnWindowEvents From 238855c5d1a50b133d019751216360913848b881 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 17:06:41 +0100 Subject: [PATCH 011/118] allow application name to be set on osx. --- samples/ControlCatalog/App.xaml.cs | 2 ++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 27 ++++++++++++++----- .../AvaloniaNativePlatformExtensions.cs | 12 +++++++-- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 07c42c60c4..3b758b0ba8 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -9,6 +9,8 @@ namespace ControlCatalog public override void Initialize() { AvaloniaXamlLoader.Load(this); + + Name = "Avalonia"; } public override void OnFrameworkInitializationCompleted() diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 6d48ab3829..3b26b6a60a 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -9,6 +9,7 @@ using Avalonia.Native.Interop; using Avalonia.OpenGL; using Avalonia.Platform; using Avalonia.Rendering; +using Avalonia.Platform.Interop; namespace Avalonia.Native { @@ -27,15 +28,17 @@ namespace Avalonia.Native public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO - public static void Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) + public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) { - new AvaloniaNativePlatform(new IAvaloniaNativeFactory(factory)) - .DoInitialize(options); + var result = new AvaloniaNativePlatform(new IAvaloniaNativeFactory(factory)); + result.DoInitialize(options); + + return result; } delegate IntPtr CreateAvaloniaNativeDelegate(); - public static void Initialize(AvaloniaNativePlatformOptions options) + public static AvaloniaNativePlatform Initialize(AvaloniaNativePlatformOptions options) { if (options.AvaloniaNativeLibraryPath != null) { @@ -48,10 +51,21 @@ namespace Avalonia.Native var d = Marshal.GetDelegateForFunctionPointer(proc); - Initialize(d(), options); + return Initialize(d(), options); } else - Initialize(CreateAvaloniaNative(), options); + return Initialize(CreateAvaloniaNative(), options); + } + + public void SetupApplicationName() + { + if(!string.IsNullOrWhiteSpace(Application.Current.Name)) + { + using (var buffer = new Utf8Buffer(Application.Current.Name)) + { + _factory.MacOptions.SetApplicationTitle(buffer.DangerousGetHandle()); + } + } } private AvaloniaNativePlatform(IAvaloniaNativeFactory factory) @@ -66,6 +80,7 @@ namespace Avalonia.Native if (_factory.MacOptions != null) { var macOpts = AvaloniaLocator.Current.GetService(); + _factory.MacOptions.ShowInDock = macOpts?.ShowInDock != false ? 1 : 0; } diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 02810ed155..df7b00ddd8 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -13,9 +13,17 @@ namespace Avalonia where T : AppBuilderBase, new() { builder.UseWindowingSubsystem(() => - AvaloniaNativePlatform.Initialize( + { + var platform = AvaloniaNativePlatform.Initialize( AvaloniaLocator.Current.GetService() ?? - new AvaloniaNativePlatformOptions())); + new AvaloniaNativePlatformOptions()); + + builder.AfterSetup (x=> + { + platform.SetupApplicationName(); + }); + }); + return builder; } } From aaedaebe94a113be8e452f59e08c1a17fb06cf28 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 17:15:17 +0100 Subject: [PATCH 012/118] working enabled when using click handlers. --- src/Avalonia.Controls/NativeMenu.cs | 1 - src/Avalonia.Controls/NativeMenuItem.cs | 2 ++ src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 13 ++++++++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenu.cs b/src/Avalonia.Controls/NativeMenu.cs index 1e2966ff2b..e30029d21e 100644 --- a/src/Avalonia.Controls/NativeMenu.cs +++ b/src/Avalonia.Controls/NativeMenu.cs @@ -48,7 +48,6 @@ namespace Avalonia.Controls set => SetAndRaise(ParentProperty, ref _parent, value); } - public void Add(NativeMenuItem item) => _items.Add(item); public IEnumerator GetEnumerator() => _items.GetEnumerator(); diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 3f1a80dcfe..e26176676a 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -110,6 +110,8 @@ namespace Avalonia.Controls Enabled = _command?.CanExecute(null) ?? true; } + public bool HasClickHandlers => Clicked != null; + public ICommand Command { get => GetValue(CommandProperty); diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 647b867184..1fade4b1a1 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -48,13 +48,14 @@ namespace Avalonia.Native private Dictionary _idsToItems = new Dictionary(); private Dictionary _itemsToIds = new Dictionary(); private uint _revision = 1; + private bool _exported = false; public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) { _factory = factory; } - public bool IsNativeMenuExported => throw new NotImplementedException(); + public bool IsNativeMenuExported => _exported; public event EventHandler OnIsNativeMenuExportedChanged; @@ -113,6 +114,8 @@ namespace Avalonia.Native LayoutUpdated?.Invoke((_revision, 0)); SetMenu(_menu.Items); + + _exported = true; } private void QueueReset() @@ -153,9 +156,9 @@ namespace Avalonia.Native menuItem.SetAction(new PredicateCallback(() => { - if (item.Command != null) + if (item.Command != null || item.HasClickHandlers) { - return item.Command.CanExecute(null); + return item.Enabled; } return false; @@ -186,9 +189,9 @@ namespace Avalonia.Native menuItem.SetAction(new PredicateCallback(() => { - if (item.Command != null) + if (item.Command != null || item.HasClickHandlers) { - return item.Command.CanExecute(null); + return item.Enabled; } return false; From e9d3b8ec5a484457c2eca8efdcc8869209699c45 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 17:17:50 +0100 Subject: [PATCH 013/118] support CMD in keygestures. --- src/Avalonia.Input/KeyGesture.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index 5eaee4833c..7bf0fe6b70 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -141,6 +141,11 @@ namespace Avalonia.Input return KeyModifiers.Control; } + if (modifier.Equals("cmd".AsSpan(), StringComparison.OrdinalIgnoreCase)) + { + return KeyModifiers.Meta; + } + return (KeyModifiers)Enum.Parse(typeof(KeyModifiers), modifier.ToString(), true); } @@ -159,4 +164,4 @@ namespace Avalonia.Input } } } -} +} \ No newline at end of file From 5889c7feb812b320c052f0411474e9e4901f58fd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 17:19:04 +0100 Subject: [PATCH 014/118] use gesture for osx. --- samples/ControlCatalog/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index fca155ef70..882c7955d0 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -20,7 +20,7 @@ - + From ffcfeaa956d7fdd27e4a126c0f4334f1b4059a87 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 20:37:21 +0100 Subject: [PATCH 015/118] work towards menu per window. --- native/Avalonia.Native/inc/avalonia-native.h | 3 +- .../project.pbxproj | 2 -- native/Avalonia.Native/src/OSX/common.h | 1 - native/Avalonia.Native/src/OSX/main.mm | 6 ---- native/Avalonia.Native/src/OSX/menu.mm | 12 ++++---- native/Avalonia.Native/src/OSX/window.h | 1 + native/Avalonia.Native/src/OSX/window.mm | 30 +++++++++++++++++++ .../AvaloniaNativeMenuExporter.cs | 6 ++-- 8 files changed, 44 insertions(+), 17 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 16d4fa4107..4cec243d0b 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -175,7 +175,6 @@ public: virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; - virtual HRESULT ObtainAppMenu (IAvnAppMenu** ppv) = 0; virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0; virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0; }; @@ -208,6 +207,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown virtual HRESULT SetCursor(IAvnCursor* cursor) = 0; virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0; virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0; + virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0; + virtual HRESULT GetMainMenu(IAvnAppMenu** ret) = 0; virtual bool TryLock() = 0; virtual void Unlock() = 0; }; diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index 84c3a84b91..a0c138b241 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = ""; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; 520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = ""; }; - 5296D43022F30EBC005B125D /* menu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = menu.h; path = ../../../../../../../../System/Volumes/Data/Users/danwalmsley/repos/Avalonia/native/Avalonia.Native/src/OSX/menu.h; sourceTree = ""; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; }; @@ -89,7 +88,6 @@ 37C09D8A21581EF2006A6758 /* window.h */, AB00E4F62147CA920032A60A /* main.mm */, 520624B222973F4100C4DCEF /* menu.mm */, - 5296D43022F30EBC005B125D /* menu.h */, 37A517B22159597E00FBA241 /* Screens.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, AB7A61F02147C815003C5833 /* Products */, diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index c066ebb498..ccd0a5a9b9 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -19,7 +19,6 @@ extern IAvnClipboard* CreateClipboard(); extern IAvnCursorFactory* CreateCursorFactory(); extern IAvnGlFeature* GetGlFeature(); extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view); -extern IAvnAppMenu* GetAppMenu(); extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenuItem* CreateAppMenuItem(); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 159f01d1d7..bdea26b761 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -225,12 +225,6 @@ public: return S_OK; } - virtual HRESULT ObtainAppMenu(IAvnAppMenu** ppv) override - { - *ppv = ::GetAppMenu(); - return S_OK; - } - virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override { *ppv = ::CreateAppMenu(); diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index f861774b29..89ae31cc35 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -170,12 +170,13 @@ HRESULT AvnAppMenu::Clear() return S_OK; } -static IAvnAppMenu* s_AppMenu = nullptr; +//static IAvnAppMenu* s_AppMenu = nullptr; -extern IAvnAppMenu* GetAppMenu() +/*extern IAvnAppMenu* GetAppMenu() { @autoreleasepool { + //todo get rid of this method. if(s_AppMenu == nullptr) { id menubar = [NSMenu new]; @@ -183,7 +184,7 @@ extern IAvnAppMenu* GetAppMenu() [NSApp setMainMenu:menubar]; id appMenuItem = [AvnMenuItem new]; - [[NSApp mainMenu] addItem:appMenuItem]; + [menubar addItem:appMenuItem]; [appMenuItem setSubmenu:[AvnMenu new]]; @@ -192,13 +193,14 @@ extern IAvnAppMenu* GetAppMenu() return s_AppMenu; } -} +}*/ extern IAvnAppMenu* CreateAppMenu() { @autoreleasepool { - return new AvnAppMenu(); + id menuBar = [NSMenu new]; + return new AvnAppMenu(menuBar); } } diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index e2221217f3..1e9cdcfbf8 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -20,6 +20,7 @@ class WindowBaseImpl; -(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) restoreParentWindow; -(bool) shouldTryToHandleEvents; +-(void) setMenu:(NSMenu *)menu; @end struct INSWindowHolder diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 3347d58004..205b1421d3 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -63,9 +63,11 @@ public: SoftwareDrawingOperation CurrentSwDrawingOperation; AvnPoint lastPositionSet; NSString* _lastTitle; + IAvnAppMenu* _mainMenu; WindowBaseImpl(IAvnWindowBaseEvents* events) { + _mainMenu = nullptr; BaseEvents = events; View = [[AvnView alloc] initWithParent:this]; @@ -209,6 +211,27 @@ public: } } + virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override + { + _mainMenu = menu; + + + + return S_OK; + } + + virtual HRESULT GetMainMenu(IAvnAppMenu** ret) override + { + if(ret == nullptr) + { + return E_POINTER; + } + + *ret = _mainMenu; + + return S_OK; + } + virtual bool TryLock() override { @autoreleasepool @@ -1042,6 +1065,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent ComPtr _parent; bool _canBecomeKeyAndMain; bool _closed; + NSMenu* _menu; } - (void)dealloc @@ -1065,6 +1089,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } +-(void) setMenu:(NSMenu *)menu +{ + _menu = menu; + [NSApp setMenu:menu]; +} + -(void) setCanBecomeKeyAndMain { _canBecomeKeyAndMain = true; diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 1fade4b1a1..5e473d9aa8 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -114,7 +114,8 @@ namespace Avalonia.Native LayoutUpdated?.Invoke((_revision, 0)); SetMenu(_menu.Items); - + + _exported = true; } @@ -228,8 +229,9 @@ namespace Avalonia.Native } } - private void SetMenu(ICollection menuItems) + private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { + var appMenu = _factory.ObtainAppMenu(); appMenu.Clear(); From 448b549034f60bbe895dbf164ad79f7c0a012dd1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 21:01:51 +0100 Subject: [PATCH 016/118] set menu on per window basis. --- native/Avalonia.Native/inc/avalonia-native.h | 2 +- .../AvaloniaNativeMenuExporter.cs | 17 ++++++++++++----- src/Avalonia.Native/WindowImpl.cs | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 4cec243d0b..cbd90e1dcf 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -208,7 +208,7 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0; virtual HRESULT GetSoftwareFramebuffer(AvnFramebuffer*ret) = 0; virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0; - virtual HRESULT GetMainMenu(IAvnAppMenu** ret) = 0; + virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0; virtual bool TryLock() = 0; virtual void Unlock() = 0; }; diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 5e473d9aa8..1ec5db149c 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -49,10 +49,12 @@ namespace Avalonia.Native private Dictionary _itemsToIds = new Dictionary(); private uint _revision = 1; private bool _exported = false; + private IAvnWindow _nativeWindow; - public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) + public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) { _factory = factory; + _nativeWindow = nativeWindow; } public bool IsNativeMenuExported => _exported; @@ -113,8 +115,7 @@ namespace Avalonia.Native LayoutUpdated?.Invoke((_revision, 0)); - SetMenu(_menu.Items); - + SetMenu(_nativeWindow, _menu.Items); _exported = true; } @@ -231,8 +232,14 @@ namespace Avalonia.Native private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { - - var appMenu = _factory.ObtainAppMenu(); + var appMenu = avnWindow.ObtainMainMenu(); + + if(appMenu is null) + { + appMenu = _factory.CreateMenu(); + + avnWindow.SetMainMenu(appMenu); + } appMenu.Clear(); diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 02c20b04ee..a7828bedaf 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Native Init(_native = factory.CreateWindow(e), factory.CreateScreens()); } - NativeMenuExporter = new AvaloniaNativeMenuExporter(factory); + NativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory); } class WindowEvents : WindowBaseEvents, IAvnWindowEvents From 18def34bf74a2bade370a65e785f849e3d1d408c Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 21:24:22 +0100 Subject: [PATCH 017/118] implement per window menus on osx side. --- .../OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj | 4 ++++ native/Avalonia.Native/src/OSX/menu.h | 2 +- native/Avalonia.Native/src/OSX/window.h | 2 +- native/Avalonia.Native/src/OSX/window.mm | 9 +++++++-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index a0c138b241..c0a49382a7 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; }; + 37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; }; 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; }; @@ -25,6 +26,7 @@ /* Begin PBXFileReference section */ 1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = ""; }; + 37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; sourceTree = ""; }; 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; }; 37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; }; 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; }; @@ -87,6 +89,7 @@ AB661C1F2148286E00291242 /* window.mm */, 37C09D8A21581EF2006A6758 /* window.h */, AB00E4F62147CA920032A60A /* main.mm */, + 37155CE3233C00EB0034DCE9 /* menu.h */, 520624B222973F4100C4DCEF /* menu.mm */, 37A517B22159597E00FBA241 /* Screens.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, @@ -110,6 +113,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 37155CE4233C00EB0034DCE9 /* menu.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h index e3c1fa9768..56cf0f6fe7 100644 --- a/native/Avalonia.Native/src/OSX/menu.h +++ b/native/Avalonia.Native/src/OSX/menu.h @@ -15,7 +15,7 @@ class AvnAppMenuItem; class AvnAppMenu; @interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain - +- (void)setMenu:(NSMenu*) menu; @end @interface AvnMenuItem : NSMenuItem diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 1e9cdcfbf8..557e19e7a8 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -20,7 +20,7 @@ class WindowBaseImpl; -(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) restoreParentWindow; -(bool) shouldTryToHandleEvents; --(void) setMenu:(NSMenu *)menu; +-(void) applyMenu:(NSMenu *)menu; @end struct INSWindowHolder diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 205b1421d3..39c9ceec13 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -5,6 +5,7 @@ #include "window.h" #include "KeyTransform.h" #include "cursor.h" +#include "menu.h" #include class SoftwareDrawingOperation @@ -215,12 +216,16 @@ public: { _mainMenu = menu; + auto nativeMenu = dynamic_cast(menu); + auto nsmenu = nativeMenu->GetNative(); + + [Window applyMenu:nsmenu]; return S_OK; } - virtual HRESULT GetMainMenu(IAvnAppMenu** ret) override + virtual HRESULT ObtainMainMenu(IAvnAppMenu** ret) override { if(ret == nullptr) { @@ -1089,7 +1094,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } } --(void) setMenu:(NSMenu *)menu +-(void) applyMenu:(NSMenu *)menu { _menu = menu; [NSApp setMenu:menu]; From f5020af86032daaad025ea4696c3d5463cc83f17 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 21:38:15 +0100 Subject: [PATCH 018/118] change window menus when windows are changed. --- native/Avalonia.Native/src/OSX/window.mm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 39c9ceec13..21e190695e 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1192,6 +1192,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if([self activateAppropriateChild: true]) { + if(_menu == nullptr) + { + [NSApp setMenu: [NSMenu new]]; + } + else + { + [NSApp setMenu:_menu]; + } + _parent->BaseEvents->Activated(); [super becomeKeyWindow]; } From ec6f1e824f46a8bad4e364441285d70b56ce8380 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 21:41:21 +0100 Subject: [PATCH 019/118] tidy avnwindow menu code. --- native/Avalonia.Native/src/OSX/menu.mm | 25 ------------------------ native/Avalonia.Native/src/OSX/window.mm | 13 +++++++----- 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 89ae31cc35..909a504dc7 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -170,31 +170,6 @@ HRESULT AvnAppMenu::Clear() return S_OK; } -//static IAvnAppMenu* s_AppMenu = nullptr; - -/*extern IAvnAppMenu* GetAppMenu() -{ - @autoreleasepool - { - //todo get rid of this method. - if(s_AppMenu == nullptr) - { - id menubar = [NSMenu new]; - [menubar setTitle:@"Test"]; - [NSApp setMainMenu:menubar]; - - id appMenuItem = [AvnMenuItem new]; - [menubar addItem:appMenuItem]; - - [appMenuItem setSubmenu:[AvnMenu new]]; - - s_AppMenu = new AvnAppMenu([[NSApplication sharedApplication] mainMenu]); - } - - return s_AppMenu; - } -}*/ - extern IAvnAppMenu* CreateAppMenu() { @autoreleasepool diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 21e190695e..baa8be8e05 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1096,6 +1096,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent -(void) applyMenu:(NSMenu *)menu { + if(menu == nullptr) + { + menu = [NSMenu new]; + } + _menu = menu; [NSApp setMenu:menu]; } @@ -1194,13 +1199,11 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if(_menu == nullptr) { - [NSApp setMenu: [NSMenu new]]; - } - else - { - [NSApp setMenu:_menu]; + _menu = [NSMenu new]; } + [NSApp setMenu:_menu]; + _parent->BaseEvents->Activated(); [super becomeKeyWindow]; } From 034e0a1c2bfcec2c5411477756e095edddca0e4b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 22:02:50 +0100 Subject: [PATCH 020/118] fix osx menu cant be selected when running under xcode. --- native/Avalonia.Native/src/OSX/window.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index baa8be8e05..e7a2b976e5 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -96,6 +96,7 @@ public: UpdateStyle(); [Window makeKeyAndOrderFront:Window]; + [NSApp activateIgnoringOtherApps:YES]; [Window setTitle:_lastTitle]; [Window setTitleVisibility:NSWindowTitleVisible]; @@ -125,6 +126,7 @@ public: if(Window != nullptr) { [Window makeKeyWindow]; + [NSApp activateIgnoringOtherApps:YES]; } } From 72a9a21299a9bb714d443a862637dfb694e20cfd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 22:15:16 +0100 Subject: [PATCH 021/118] osx native menu items respond to model changes. --- .../AvaloniaNativeMenuExporter.cs | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 1ec5db149c..6c7e5f63e3 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -44,12 +44,11 @@ namespace Avalonia.Native { private IAvaloniaNativeFactory _factory; private NativeMenu _menu; - private bool _resetQueued; - private Dictionary _idsToItems = new Dictionary(); - private Dictionary _itemsToIds = new Dictionary(); + private bool _resetQueued; private uint _revision = 1; private bool _exported = false; private IAvnWindow _nativeWindow; + private List _menuItems = new List(); public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) { @@ -101,19 +100,18 @@ namespace Avalonia.Native void DoLayoutReset() { _resetQueued = false; - foreach (var i in _idsToItems.Values) + foreach (var i in _menuItems) { i.PropertyChanged -= OnItemPropertyChanged; if (i.Menu != null) ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; } - _idsToItems.Clear(); - _itemsToIds.Clear(); + _menuItems.Clear(); _revision++; - LayoutUpdated?.Invoke((_revision, 0)); + LayoutUpdated?.Invoke((_revision, 0)); SetMenu(_nativeWindow, _menu.Items); @@ -137,10 +135,20 @@ namespace Avalonia.Native return menu; } + private void AddMenuItem(NativeMenuItem item) + { + if(item.Menu?.Items != null) + { + ((INotifyCollectionChanged)item.Menu.Items).CollectionChanged += OnMenuItemsChanged; + } + } + private void SetChildren(IAvnAppMenu menu, ICollection children) { foreach (var item in children) { + AddMenuItem(item); + var menuItem = _factory.CreateMenuItem(); using (var buffer = new Utf8Buffer(item.Header)) @@ -189,6 +197,8 @@ namespace Avalonia.Native { var menuItem = _factory.CreateMenuItem(); + AddMenuItem(item); + menuItem.SetAction(new PredicateCallback(() => { if (item.Command != null || item.HasClickHandlers) From 1d6d18503812ba592330c3f22ae416654a5d7faf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Sep 2019 22:21:53 +0100 Subject: [PATCH 022/118] remove unused code. --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 6c7e5f63e3..16f2cc32b7 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -44,8 +44,7 @@ namespace Avalonia.Native { private IAvaloniaNativeFactory _factory; private NativeMenu _menu; - private bool _resetQueued; - private uint _revision = 1; + private bool _resetQueued; private bool _exported = false; private IAvnWindow _nativeWindow; private List _menuItems = new List(); @@ -60,8 +59,6 @@ namespace Avalonia.Native public event EventHandler OnIsNativeMenuExportedChanged; - private event Action<(uint revision, int parent)> LayoutUpdated; - public void SetNativeMenu(NativeMenu menu) { if (menu == null) @@ -107,11 +104,7 @@ namespace Avalonia.Native ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; } - _menuItems.Clear(); - - _revision++; - - LayoutUpdated?.Invoke((_revision, 0)); + _menuItems.Clear(); SetMenu(_nativeWindow, _menu.Items); From 92e0e2bccc14d3d855f897970876050e76c36a98 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 20:30:11 +0100 Subject: [PATCH 023/118] attempt at application menu pre-pending. --- samples/ControlCatalog/App.xaml | 32 +++++++++++++++++-- samples/ControlCatalog/MainWindow.xaml | 2 +- .../AvaloniaNativeMenuExporter.cs | 15 ++++++++- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 2f6d25c089..8facde71c1 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -2,9 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.App"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 882c7955d0..1b01fa56a8 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow"> + x:Class="ControlCatalog.MainWindow" NativeMenu.PrependApplicationMenu="True"> diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 16f2cc32b7..bc626add91 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.Linq; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; @@ -47,6 +48,7 @@ namespace Avalonia.Native private bool _resetQueued; private bool _exported = false; private IAvnWindow _nativeWindow; + private bool _prependAppMenu; private List _menuItems = new List(); public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) @@ -74,7 +76,7 @@ namespace Avalonia.Native public void SetPrependApplicationMenu(bool prepend) { - throw new NotImplementedException(); + _prependAppMenu = prepend; } private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) @@ -235,6 +237,17 @@ namespace Avalonia.Native private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { + if (_prependAppMenu) + { + var menu = NativeMenu.GetMenu(Application.Current); + + var items = menuItems.ToList(); + + items.InsertRange(0, menu.Items); + + menuItems = items; + } + var appMenu = avnWindow.ObtainMainMenu(); if(appMenu is null) From 13f78139843dae704e0ab2e4a49cc26a2527cdc4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 20:32:22 +0100 Subject: [PATCH 024/118] add support for , in key gesture. --- src/Avalonia.Input/KeyGesture.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index 7bf0fe6b70..490c31bef9 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -14,7 +14,7 @@ namespace Avalonia.Input { private static readonly Dictionary s_keySynonyms = new Dictionary { - { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod } + { "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }, { ",", Key.OemComma } }; [Obsolete("Use constructor taking KeyModifiers")] @@ -164,4 +164,4 @@ namespace Avalonia.Input } } } -} \ No newline at end of file +} From aa37fa40d52315d658136e236aa0b9123accfa59 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 20:36:47 +0100 Subject: [PATCH 025/118] osx always exports app menu if it exists. --- .../AvaloniaNativeMenuExporter.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index bc626add91..dc713df56c 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -45,11 +45,10 @@ namespace Avalonia.Native { private IAvaloniaNativeFactory _factory; private NativeMenu _menu; - private bool _resetQueued; + private bool _resetQueued; private bool _exported = false; private IAvnWindow _nativeWindow; - private bool _prependAppMenu; - private List _menuItems = new List(); + private List _menuItems = new List(); public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) { @@ -76,7 +75,7 @@ namespace Avalonia.Native public void SetPrependApplicationMenu(bool prepend) { - _prependAppMenu = prepend; + // OSX always exports the app menu. } private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) @@ -106,10 +105,10 @@ namespace Avalonia.Native ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; } - _menuItems.Clear(); + _menuItems.Clear(); SetMenu(_nativeWindow, _menu.Items); - + _exported = true; } @@ -132,7 +131,7 @@ namespace Avalonia.Native private void AddMenuItem(NativeMenuItem item) { - if(item.Menu?.Items != null) + if (item.Menu?.Items != null) { ((INotifyCollectionChanged)item.Menu.Items).CollectionChanged += OnMenuItemsChanged; } @@ -237,10 +236,10 @@ namespace Avalonia.Native private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { - if (_prependAppMenu) - { - var menu = NativeMenu.GetMenu(Application.Current); + var menu = NativeMenu.GetMenu(Application.Current); + if (menu != null) + { var items = menuItems.ToList(); items.InsertRange(0, menu.Items); @@ -250,7 +249,7 @@ namespace Avalonia.Native var appMenu = avnWindow.ObtainMainMenu(); - if(appMenu is null) + if (appMenu is null) { appMenu = _factory.CreateMenu(); From c17058c9aa86b4e00db59973eee423dff4e71ac2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 20:54:33 +0100 Subject: [PATCH 026/118] implement app menu osx. --- native/Avalonia.Native/src/OSX/menu.mm | 5 ++++- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 9 ++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 909a504dc7..ca03c50e59 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -159,7 +159,10 @@ HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item) HRESULT AvnAppMenu::SetTitle (void* utf8String) { - [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + if (utf8String != nullptr) + { + [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + } return S_OK; } diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index dc713df56c..1ae4a783d7 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -54,6 +54,8 @@ namespace Avalonia.Native { _factory = factory; _nativeWindow = nativeWindow; + + DoLayoutReset(); } public bool IsNativeMenuExported => _exported; @@ -107,7 +109,7 @@ namespace Avalonia.Native _menuItems.Clear(); - SetMenu(_nativeWindow, _menu.Items); + SetMenu(_nativeWindow, _menu?.Items); _exported = true; } @@ -236,6 +238,11 @@ namespace Avalonia.Native private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { + if (menuItems is null) + { + menuItems = new List(); + } + var menu = NativeMenu.GetMenu(Application.Current); if (menu != null) From 0a1269636f82be376220952e25c8ac92c0e069db Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 21:32:44 +0100 Subject: [PATCH 027/118] add a menu to decorated window. --- samples/ControlCatalog/DecoratedWindow.xaml | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml index cb6016b324..8e4c97b7f0 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml +++ b/samples/ControlCatalog/DecoratedWindow.xaml @@ -3,6 +3,31 @@ x:Class="ControlCatalog.DecoratedWindow" Title="Avalonia Control Gallery" xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False" Name="Window"> + + + + + + + + + + + + + + + + + + + + + + + + + From 50fa18b6e1b58990d7bd04430da67a9b769e82d6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 21:45:46 +0100 Subject: [PATCH 028/118] fix wierd menu in menu issue on dialogs. --- native/Avalonia.Native/src/OSX/window.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index e7a2b976e5..78ba930c9c 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1250,6 +1250,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { if(_parent) _parent->BaseEvents->Deactivated(); + + [NSApp setMenu:nullptr]; [super resignKeyWindow]; } From 4bd7c3960980fc978c078d0dbe9102cd0f16b941 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 23:46:04 +0100 Subject: [PATCH 029/118] working reparenting of app menu. --- native/Avalonia.Native/inc/avalonia-native.h | 2 + native/Avalonia.Native/src/OSX/common.h | 3 ++ native/Avalonia.Native/src/OSX/main.mm | 18 +++++++ native/Avalonia.Native/src/OSX/menu.mm | 31 +++++++++++ native/Avalonia.Native/src/OSX/window.mm | 51 ++++++++++++++++++- .../AvaloniaNativeMenuExporter.cs | 44 ++++++++++++---- src/Avalonia.Native/AvaloniaNativePlatform.cs | 7 ++- .../AvaloniaNativePlatformExtensions.cs | 1 + 8 files changed, 145 insertions(+), 12 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index cbd90e1dcf..7d9b89852e 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -175,6 +175,8 @@ public: virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0; virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0; virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; + virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0; + virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0; virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0; virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0; }; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index ccd0a5a9b9..91a1ba51c3 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -21,6 +21,9 @@ extern IAvnGlFeature* GetGlFeature(); extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view); extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenuItem* CreateAppMenuItem(); +extern void SetAppMenu (IAvnAppMenu* appMenu); +extern IAvnAppMenu* GetAppMenu (); +extern NSMenuItem* GetAppMenuItem (); extern void InitializeAvnApp(); extern NSApplicationActivationPolicy AvnDesiredActivationPolicy; diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index bdea26b761..ade077f00b 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -236,6 +236,24 @@ public: *ppv = ::CreateAppMenuItem(); return S_OK; } + + virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override + { + ::SetAppMenu(appMenu); + return S_OK; + } + + virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override + { + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = ::GetAppMenu(); + + return S_OK; + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index ca03c50e59..9aec33d3db 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -189,3 +189,34 @@ extern IAvnAppMenuItem* CreateAppMenuItem() return new AvnAppMenuItem(); } } + +static IAvnAppMenu* s_appMenu = nullptr; +static NSMenuItem* s_appMenuItem = nullptr; + +extern void SetAppMenu (IAvnAppMenu* appMenu) +{ + s_appMenu = appMenu; + + if(s_appMenu != nullptr) + { + auto nativeMenu = dynamic_cast(s_appMenu); + + s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; + } + else + { + s_appMenuItem = nullptr; + } +} + +extern IAvnAppMenu* GetAppMenu () +{ + return s_appMenu; +} + +extern NSMenuItem* GetAppMenuItem () +{ + return s_appMenuItem; +} + + diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 78ba930c9c..dbb437243a 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -1073,6 +1073,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent bool _canBecomeKeyAndMain; bool _closed; NSMenu* _menu; + bool _isAppMenuApplied; } - (void)dealloc @@ -1104,7 +1105,22 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } _menu = menu; - [NSApp setMenu:menu]; + + if ([self isKeyWindow]) + { + auto appMenu = ::GetAppMenuItem(); + + if(appMenu != nullptr) + { + [[appMenu menu] removeItem:appMenu]; + + [_menu insertItem:appMenu atIndex:0]; + + _isAppMenuApplied = true; + } + + [NSApp setMenu:menu]; + } } -(void) setCanBecomeKeyAndMain @@ -1204,6 +1220,17 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent _menu = [NSMenu new]; } + auto appMenu = ::GetAppMenuItem(); + + if(appMenu != nullptr) + { + [[appMenu menu] removeItem:appMenu]; + + [_menu insertItem:appMenu atIndex:0]; + + _isAppMenuApplied = true; + } + [NSApp setMenu:_menu]; _parent->BaseEvents->Activated(); @@ -1251,7 +1278,27 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent if(_parent) _parent->BaseEvents->Deactivated(); - [NSApp setMenu:nullptr]; + auto appMenuItem = ::GetAppMenuItem(); + + if(appMenuItem != nullptr) + { + auto appMenu = ::GetAppMenu(); + + auto nativeAppMenu = dynamic_cast(appMenu); + + [[appMenuItem menu] removeItem:appMenuItem]; + + [nativeAppMenu->GetNative() addItem:appMenuItem]; + + [NSApp setMenu:nativeAppMenu->GetNative()]; + } + else + { + [NSApp setMenu:nullptr]; + } + + // remove window menu items from appmenu? + [super resignKeyWindow]; } diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 1ae4a783d7..40159077d5 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -58,6 +58,14 @@ namespace Avalonia.Native DoLayoutReset(); } + public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) + { + _factory = factory; + + _menu = NativeMenu.GetMenu(Application.Current); + DoLayoutReset(); + } + public bool IsNativeMenuExported => _exported; public event EventHandler OnIsNativeMenuExportedChanged; @@ -109,7 +117,14 @@ namespace Avalonia.Native _menuItems.Clear(); - SetMenu(_nativeWindow, _menu?.Items); + if(_nativeWindow is null) + { + SetMenu(_menu?.Items); + } + else + { + SetMenu(_nativeWindow, _menu?.Items); + } _exported = true; } @@ -236,7 +251,7 @@ namespace Avalonia.Native } } - private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) + private void SetMenu(ICollection menuItems) { if (menuItems is null) { @@ -247,11 +262,24 @@ namespace Avalonia.Native if (menu != null) { - var items = menuItems.ToList(); + var appMenu = _factory.ObtainAppMenu (); - items.InsertRange(0, menu.Items); + if (appMenu is null) + { + appMenu = _factory.CreateMenu(); + } - menuItems = items; + AddItemsToMenu(appMenu, menuItems); + + _factory.SetAppMenu(appMenu); + } + } + + private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) + { + if (menuItems is null) + { + menuItems = new List(); } var appMenu = avnWindow.ObtainMainMenu(); @@ -259,13 +287,11 @@ namespace Avalonia.Native if (appMenu is null) { appMenu = _factory.CreateMenu(); - - avnWindow.SetMainMenu(appMenu); } - appMenu.Clear(); - AddItemsToMenu(appMenu, menuItems); + + avnWindow.SetMainMenu(appMenu); } } } diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 3b26b6a60a..ddb71b61bb 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -57,7 +57,12 @@ namespace Avalonia.Native return Initialize(CreateAvaloniaNative(), options); } - public void SetupApplicationName() + public void SetupApplicationMenuExporter () + { + var exporter = new AvaloniaNativeMenuExporter(_factory); + } + + public void SetupApplicationName () { if(!string.IsNullOrWhiteSpace(Application.Current.Name)) { diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index df7b00ddd8..a22777d5eb 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -20,6 +20,7 @@ namespace Avalonia builder.AfterSetup (x=> { + platform.SetupApplicationMenuExporter(); platform.SetupApplicationName(); }); }); From 796221d7f35221a843f2430fcb3b47b16d14505f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 26 Sep 2019 23:56:58 +0100 Subject: [PATCH 030/118] remove IGetNative interface --- native/Avalonia.Native/inc/IGetNative.h | 10 ---------- native/Avalonia.Native/src/OSX/menu.mm | 1 - 2 files changed, 11 deletions(-) delete mode 100644 native/Avalonia.Native/inc/IGetNative.h diff --git a/native/Avalonia.Native/inc/IGetNative.h b/native/Avalonia.Native/inc/IGetNative.h deleted file mode 100644 index 85ae030d74..0000000000 --- a/native/Avalonia.Native/inc/IGetNative.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef igetnative_h -#define igetnative_h - -class IGetNative -{ -public: - virtual void* GetNative() = 0; -}; - -#endif diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 9aec33d3db..cbcef277cc 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -1,6 +1,5 @@ #include "common.h" -#include "IGetNative.h" #include "menu.h" @implementation AvnMenu From 5736a7965de9f4323a5d3012d45bbf63be330cca Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 10:34:18 +0100 Subject: [PATCH 031/118] add support for seperators --- samples/ControlCatalog/App.xaml | 4 + src/Avalonia.Controls/NativeMenu.cs | 14 +- src/Avalonia.Controls/NativeMenuItem.cs | 55 +------ src/Avalonia.Controls/NativeMenuItemBase.cs | 56 +++++++ .../NativeMenuItemSeperator.cs | 7 + .../AvaloniaNativeMenuExporter.cs | 141 +++++++++--------- 6 files changed, 149 insertions(+), 128 deletions(-) create mode 100644 src/Avalonia.Controls/NativeMenuItemBase.cs create mode 100644 src/Avalonia.Controls/NativeMenuItemSeperator.cs diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 8facde71c1..a82996773f 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -24,7 +24,9 @@ + + @@ -34,9 +36,11 @@ + + diff --git a/src/Avalonia.Controls/NativeMenu.cs b/src/Avalonia.Controls/NativeMenu.cs index e30029d21e..8d3dcd9f8f 100644 --- a/src/Avalonia.Controls/NativeMenu.cs +++ b/src/Avalonia.Controls/NativeMenu.cs @@ -9,13 +9,13 @@ using Avalonia.Metadata; namespace Avalonia.Controls { - public partial class NativeMenu : AvaloniaObject, IEnumerable + public partial class NativeMenu : AvaloniaObject, IEnumerable { - private AvaloniaList _items = - new AvaloniaList { ResetBehavior = ResetBehavior.Remove }; + private AvaloniaList _items = + new AvaloniaList { ResetBehavior = ResetBehavior.Remove }; private NativeMenuItem _parent; [Content] - public IList Items => _items; + public IList Items => _items; public NativeMenu() { @@ -23,7 +23,7 @@ namespace Avalonia.Controls _items.CollectionChanged += ItemsChanged; } - private void Validator(NativeMenuItem obj) + private void Validator(NativeMenuItemBase obj) { if (obj.Parent != null) throw new InvalidOperationException("NativeMenuItem already has a parent"); @@ -48,9 +48,9 @@ namespace Avalonia.Controls set => SetAndRaise(ParentProperty, ref _parent, value); } - public void Add(NativeMenuItem item) => _items.Add(item); + public void Add(NativeMenuItemBase item) => _items.Add(item); - public IEnumerator GetEnumerator() => _items.GetEnumerator(); + public IEnumerator GetEnumerator() => _items.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() { diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index e26176676a..25e0c9e48e 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -1,20 +1,16 @@ 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 + public class NativeMenuItem : NativeMenuItemBase { private string _header; private KeyGesture _gesture; private bool _enabled = true; - private NativeMenu _menu; - private NativeMenu _parent; + class CanExecuteChangedSubscriber : IWeakSubscriber { @@ -33,18 +29,7 @@ namespace Avalonia.Controls 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); @@ -54,7 +39,7 @@ namespace Avalonia.Controls { Header = header; } - + public static readonly DirectProperty HeaderProperty = AvaloniaProperty.RegisterDirect(nameof(Header), o => o._header, (o, v) => o._header = v); @@ -65,7 +50,7 @@ namespace Avalonia.Controls } public static readonly DirectProperty GestureProperty = - AvaloniaProperty.RegisterDirect(nameof(Gesture), o => o._gesture, (o,v)=> o._gesture = v); + AvaloniaProperty.RegisterDirect(nameof(Gesture), o => o._gesture, (o, v) => o._gesture = v); public KeyGesture Gesture { @@ -128,36 +113,6 @@ namespace Avalonia.Controls set { SetValue(CommandParameterProperty, value); } } - public static readonly DirectProperty MenuProperty = - AvaloniaProperty.RegisterDirect(nameof(Menu), o => o._menu, - (o, v) => - { - if (v.Parent != null && v.Parent != o) - throw new InvalidOperationException("NativeMenu already has a parent"); - o._menu = v; - }); - - public NativeMenu Menu - { - get => _menu; - set - { - if (value.Parent != null && value.Parent != this) - throw new InvalidOperationException("NativeMenu already has a parent"); - SetAndRaise(MenuProperty, ref _menu, value); - } - } - - public static readonly DirectProperty ParentProperty = - AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); - - public NativeMenu Parent - { - get => _parent; - set => SetAndRaise(ParentProperty, ref _parent, value); - } - - public event EventHandler Clicked; public void RaiseClick() diff --git a/src/Avalonia.Controls/NativeMenuItemBase.cs b/src/Avalonia.Controls/NativeMenuItemBase.cs new file mode 100644 index 0000000000..e17ed86054 --- /dev/null +++ b/src/Avalonia.Controls/NativeMenuItemBase.cs @@ -0,0 +1,56 @@ +using System; + +namespace Avalonia.Controls +{ + public class NativeMenuItemBase : AvaloniaObject + { + private NativeMenu _menu; + private NativeMenu _parent; + + static NativeMenuItemBase() + { + 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; + }); + } + + internal NativeMenuItemBase() + { + + } + + public static readonly DirectProperty MenuProperty = + AvaloniaProperty.RegisterDirect(nameof(Menu), o => o._menu, + (o, v) => + { + if (v.Parent != null && v.Parent != o) + throw new InvalidOperationException("NativeMenu already has a parent"); + o._menu = v; + }); + + public NativeMenu Menu + { + get => _menu; + set + { + if (value.Parent != null && value.Parent != this) + throw new InvalidOperationException("NativeMenu already has a parent"); + SetAndRaise(MenuProperty, ref _menu, value); + } + } + + public static readonly DirectProperty ParentProperty = + AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); + + public NativeMenu Parent + { + get => _parent; + set => SetAndRaise(ParentProperty, ref _parent, value); + } + } +} diff --git a/src/Avalonia.Controls/NativeMenuItemSeperator.cs b/src/Avalonia.Controls/NativeMenuItemSeperator.cs new file mode 100644 index 0000000000..85d62023d4 --- /dev/null +++ b/src/Avalonia.Controls/NativeMenuItemSeperator.cs @@ -0,0 +1,7 @@ +namespace Avalonia.Controls +{ + public class NativeMenuItemSeperator : NativeMenuItemBase + { + + } +} diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 40159077d5..31ccfab18c 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -98,13 +98,6 @@ namespace Avalonia.Native QueueReset(); } - /* - This is basic initial implementation, so we don't actually track anything and - just reset the whole layout on *ANY* change - - This is not how it should work and will prevent us from implementing various features, - but that's the fastest way to get things working, so... - */ void DoLayoutReset() { _resetQueued = false; @@ -137,7 +130,7 @@ namespace Avalonia.Native Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); } - private IAvnAppMenu CreateSubmenu(ICollection children) + private IAvnAppMenu CreateSubmenu(ICollection children) { var menu = _factory.CreateMenu(); @@ -154,108 +147,114 @@ namespace Avalonia.Native } } - private void SetChildren(IAvnAppMenu menu, ICollection children) + private void SetChildren(IAvnAppMenu menu, ICollection children) { - foreach (var item in children) + foreach (var i in children) { - AddMenuItem(item); - - var menuItem = _factory.CreateMenuItem(); - - using (var buffer = new Utf8Buffer(item.Header)) + if (i is NativeMenuItem item) { - menuItem.Title = buffer.DangerousGetHandle(); - } + AddMenuItem(item); - if (item.Gesture != null) - { - using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + var menuItem = _factory.CreateMenuItem(); + + using (var buffer = new Utf8Buffer(item.Header)) { - menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + menuItem.Title = buffer.DangerousGetHandle(); } - } - menuItem.SetAction(new PredicateCallback(() => - { - if (item.Command != null || item.HasClickHandlers) + if (item.Gesture != null) { - return item.Enabled; + using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + { + menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + } } - return false; - }), new MenuActionCallback(() => { item.RaiseClick(); })); - menu.AddItem(menuItem); + menuItem.SetAction(new PredicateCallback(() => + { + if (item.Command != null || item.HasClickHandlers) + { + return item.Enabled; + } - if (item.Menu?.Items?.Count > 0) - { - var submenu = _factory.CreateMenu(); + return false; + }), new MenuActionCallback(() => { item.RaiseClick(); })); + menu.AddItem(menuItem); - using (var buffer = new Utf8Buffer(item.Header)) + if (item.Menu?.Items?.Count > 0) { - submenu.Title = buffer.DangerousGetHandle(); - } + var submenu = _factory.CreateMenu(); + + using (var buffer = new Utf8Buffer(item.Header)) + { + submenu.Title = buffer.DangerousGetHandle(); + } - menuItem.SetSubMenu(submenu); + menuItem.SetSubMenu(submenu); - AddItemsToMenu(submenu, item.Menu?.Items); + AddItemsToMenu(submenu, item.Menu?.Items); + } } } } - private void AddItemsToMenu(IAvnAppMenu menu, ICollection items, bool isMainMenu = false) + private void AddItemsToMenu(IAvnAppMenu menu, ICollection items, bool isMainMenu = false) { - foreach (var item in items) + foreach (var i in items) { - var menuItem = _factory.CreateMenuItem(); + if (i is NativeMenuItem item) + { + var menuItem = _factory.CreateMenuItem(); - AddMenuItem(item); + AddMenuItem(item); - menuItem.SetAction(new PredicateCallback(() => - { - if (item.Command != null || item.HasClickHandlers) + menuItem.SetAction(new PredicateCallback(() => { - return item.Enabled; - } + if (item.Command != null || item.HasClickHandlers) + { + return item.Enabled; + } - return false; - }), new MenuActionCallback(() => { item.RaiseClick(); })); + return false; + }), new MenuActionCallback(() => { item.RaiseClick(); })); - if (item.Menu?.Items.Count > 0 || isMainMenu) - { - var subMenu = CreateSubmenu(item.Menu?.Items); + if (item.Menu?.Items.Count > 0 || isMainMenu) + { + var subMenu = CreateSubmenu(item.Menu?.Items); - menuItem.SetSubMenu(subMenu); + menuItem.SetSubMenu(subMenu); - using (var buffer = new Utf8Buffer(item.Header)) - { - subMenu.Title = buffer.DangerousGetHandle(); + using (var buffer = new Utf8Buffer(item.Header)) + { + subMenu.Title = buffer.DangerousGetHandle(); + } } - } - else - { - using (var buffer = new Utf8Buffer(item.Header)) + else { - menuItem.Title = buffer.DangerousGetHandle(); - } + using (var buffer = new Utf8Buffer(item.Header)) + { + menuItem.Title = buffer.DangerousGetHandle(); + } - if (item.Gesture != null) - { - using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + if (item.Gesture != null) { - menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + { + menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); + } } } - } - menu.AddItem(menuItem); + menu.AddItem(menuItem); + } } } - private void SetMenu(ICollection menuItems) + private void SetMenu(ICollection menuItems) { if (menuItems is null) { - menuItems = new List(); + menuItems = new List(); } var menu = NativeMenu.GetMenu(Application.Current); @@ -275,11 +274,11 @@ namespace Avalonia.Native } } - private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) + private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) { if (menuItems is null) { - menuItems = new List(); + menuItems = new List(); } var appMenu = avnWindow.ObtainMainMenu(); From 1a3506cbcbbcbedac4fb2729ba1e0ed47e8f7ee8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 10:49:41 +0100 Subject: [PATCH 032/118] implement seperator support on native side. --- native/Avalonia.Native/inc/avalonia-native.h | 1 + native/Avalonia.Native/src/OSX/common.h | 1 + native/Avalonia.Native/src/OSX/main.mm | 6 +++++ native/Avalonia.Native/src/OSX/menu.h | 7 ++--- native/Avalonia.Native/src/OSX/menu.mm | 27 ++++++++++++++++---- 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 7d9b89852e..f1c7664c3e 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -179,6 +179,7 @@ public: virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0; virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0; virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0; + virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0; }; AVNCOM(IAvnString, 17) : IUnknown diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 91a1ba51c3..c91f562989 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -21,6 +21,7 @@ extern IAvnGlFeature* GetGlFeature(); extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view); extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenuItem* CreateAppMenuItem(); +extern IAvnAppMenuItem* CreateAppMenuItemSeperator(); extern void SetAppMenu (IAvnAppMenu* appMenu); extern IAvnAppMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index ade077f00b..1a46e495ba 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -237,6 +237,12 @@ public: return S_OK; } + virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) override + { + *ppv = ::CreateAppMenuItemSeperator(); + return S_OK; + } + virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override { ::SetAppMenu(appMenu); diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h index 56cf0f6fe7..befbe6a7e0 100644 --- a/native/Avalonia.Native/src/OSX/menu.h +++ b/native/Avalonia.Native/src/OSX/menu.h @@ -26,16 +26,17 @@ class AvnAppMenu; class AvnAppMenuItem : public ComSingleObject { private: - AvnMenuItem* _native; // here we hold a pointer to an AvnMenuItem + NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem IAvnActionCallback* _callback; IAvnPredicateCallback* _predicate; + bool _isSeperator; public: FORWARD_IUNKNOWN() - AvnAppMenuItem(); + AvnAppMenuItem(bool isSeperator); - AvnMenuItem* GetNative(); + NSMenuItem* GetNative(); virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override; diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index cbcef277cc..397d587ec3 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -43,13 +43,23 @@ } @end -AvnAppMenuItem::AvnAppMenuItem() +AvnAppMenuItem::AvnAppMenuItem(bool isSeperator) { - _native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this]; + _isSeperator = isSeperator; + + if(isSeperator) + { + _native = [NSMenuItem separatorItem]; + } + else + { + _native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this]; + } + _callback = nullptr; } -AvnMenuItem* AvnAppMenuItem::GetNative() +NSMenuItem* AvnAppMenuItem::GetNative() { return _native; } @@ -137,7 +147,6 @@ HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item) if(avnMenuItem != nullptr) { - [_native addItem: avnMenuItem->GetNative()]; } @@ -185,7 +194,15 @@ extern IAvnAppMenuItem* CreateAppMenuItem() { @autoreleasepool { - return new AvnAppMenuItem(); + return new AvnAppMenuItem(false); + } +} + +extern IAvnAppMenuItem* CreateAppMenuItemSeperator() +{ + @autoreleasepool + { + return new AvnAppMenuItem(true); } } From 0dabaee113053f420f4743acde52f5277f4616f6 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 10:56:47 +0100 Subject: [PATCH 033/118] make dbus menu exporter compile. --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 99 +++++++++++--------- 1 file changed, 54 insertions(+), 45 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 1f1a244fb7..1a9a7bc231 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -31,8 +31,8 @@ namespace Avalonia.FreeDesktop private bool _disposed; private uint _revision = 1; private NativeMenu _menu; - private Dictionary _idsToItems = new Dictionary(); - private Dictionary _itemsToIds = new Dictionary(); + private Dictionary _idsToItems = new Dictionary(); + private Dictionary _itemsToIds = new Dictionary(); private bool _resetQueued; private int _nextId = 1; public DBusMenuExporterImpl(Connection dbus, IntPtr xid) @@ -124,7 +124,7 @@ namespace Avalonia.FreeDesktop Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background); } - private (NativeMenuItem item, NativeMenu menu) GetMenu(int id) + private (NativeMenuItemBase item, NativeMenu menu) GetMenu(int id) { if (id == 0) return (null, _menu); @@ -132,7 +132,7 @@ namespace Avalonia.FreeDesktop return (item, item?.Menu); } - private int GetId(NativeMenuItem item) + private int GetId(NativeMenuItemBase item) { if (_itemsToIds.TryGetValue(item, out var id)) return id; @@ -186,54 +186,59 @@ namespace Avalonia.FreeDesktop "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display" }; - object GetProperty((NativeMenuItem item, NativeMenu menu) i, string name) + object GetProperty((NativeMenuItemBase item, NativeMenu menu) i, string name) { - var (item, menu) = i; - if (name == "type") - { - if (item != null && item.Header == null) - return "separator"; - return null; - } - if (name == "label") - return item?.Header ?? ""; - if (name == "enabled") - { - if (item == null) - return null; - if (item.Menu != null && item.Menu.Items.Count == 0) - return false; - if (item.Enabled == false) - return false; - return null; - } - if (name == "shortcut") + var (it, menu) = i; + + if (it is NativeMenuItem item) { - if (item?.Gesture == null) + if (name == "type") + { + if (item != null && item.Header == null) + return "separator"; return null; - if (item.Gesture.KeyModifiers == 0) + } + if (name == "label") + return item?.Header ?? ""; + if (name == "enabled") + { + if (item == null) + return null; + if (item.Menu != null && item.Menu.Items.Count == 0) + return false; + if (item.Enabled == false) + return false; return null; - var lst = new List(); - var mod = item.Gesture; - if ((mod.KeyModifiers & KeyModifiers.Control) != 0) - lst.Add("Control"); - if ((mod.KeyModifiers & KeyModifiers.Alt) != 0) - lst.Add("Alt"); - if ((mod.KeyModifiers & KeyModifiers.Shift) != 0) - lst.Add("Shift"); - if ((mod.KeyModifiers & KeyModifiers.Meta) != 0) - lst.Add("Super"); - lst.Add(item.Gesture.Key.ToString()); - return new[] { lst.ToArray() }; + } + if (name == "shortcut") + { + if (item?.Gesture == null) + return null; + if (item.Gesture.KeyModifiers == 0) + return null; + var lst = new List(); + var mod = item.Gesture; + if ((mod.KeyModifiers & KeyModifiers.Control) != 0) + lst.Add("Control"); + if ((mod.KeyModifiers & KeyModifiers.Alt) != 0) + lst.Add("Alt"); + if ((mod.KeyModifiers & KeyModifiers.Shift) != 0) + lst.Add("Shift"); + if ((mod.KeyModifiers & KeyModifiers.Meta) != 0) + lst.Add("Super"); + lst.Add(item.Gesture.Key.ToString()); + return new[] { lst.ToArray() }; + } + + if (name == "children-display") + return menu != null ? "submenu" : null; } - if (name == "children-display") - return menu != null ? "submenu" : null; return null; } private List> _reusablePropertyList = new List>(); - KeyValuePair[] GetProperties((NativeMenuItem item, NativeMenu menu) i, string[] names) + KeyValuePair[] GetProperties((NativeMenuItemBase item, NativeMenu menu) i, string[] names) { if (names?.Length > 0 != true) names = AllProperties; @@ -267,7 +272,7 @@ namespace Avalonia.FreeDesktop return Task.FromResult(rv); } - (int, KeyValuePair[], object[]) GetLayout(NativeMenuItem item, NativeMenu menu, int depth, string[] propertyNames) + (int, KeyValuePair[], object[]) GetLayout(NativeMenuItemBase item, NativeMenu menu, int depth, string[] propertyNames) { var id = item == null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); @@ -308,8 +313,12 @@ namespace Avalonia.FreeDesktop if (eventId == "clicked") { var item = GetMenu(id).item; - if (item?.Enabled == true) - item.RaiseClick(); + + if (item is NativeMenuItem menuItem) + { + if (menuItem?.Enabled == true) + menuItem.RaiseClick(); + } } } From 2baa06f344176b7a38b7a50eb4956854624ad208 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 11:00:34 +0100 Subject: [PATCH 034/118] osx add native seperators to menu --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 31ccfab18c..93e0dba73b 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -195,6 +195,10 @@ namespace Avalonia.Native AddItemsToMenu(submenu, item.Menu?.Items); } } + else if (i is NativeMenuItemSeperator seperator) + { + menu.AddItem(_factory.CreateMenuItemSeperator()); + } } } @@ -247,6 +251,10 @@ namespace Avalonia.Native menu.AddItem(menuItem); } + else if(i is NativeMenuItemSeperator seperator) + { + menu.AddItem(_factory.CreateMenuItemSeperator()); + } } } From 2ad9dc56e8b0fd68ebd1f60c00ddf696c572f4b4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 11:05:43 +0100 Subject: [PATCH 035/118] fix native menu cast error. --- src/Avalonia.Controls/NativeMenu.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenu.cs b/src/Avalonia.Controls/NativeMenu.cs index 8d3dcd9f8f..58cff581e1 100644 --- a/src/Avalonia.Controls/NativeMenu.cs +++ b/src/Avalonia.Controls/NativeMenu.cs @@ -32,10 +32,10 @@ namespace Avalonia.Controls private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e) { if(e.OldItems!=null) - foreach (NativeMenuItem i in e.OldItems) + foreach (NativeMenuItemBase i in e.OldItems) i.Parent = null; if(e.NewItems!=null) - foreach (NativeMenuItem i in e.NewItems) + foreach (NativeMenuItemBase i in e.NewItems) i.Parent = this; } From d2848809a2d100956761851e456796665601960b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:14:18 +0100 Subject: [PATCH 036/118] Implement auto generate standard osx app menu items. --- native/Avalonia.Native/src/OSX/common.h | 2 +- native/Avalonia.Native/src/OSX/main.mm | 14 +++++-- native/Avalonia.Native/src/OSX/menu.mm | 49 ++++++++++++++++++++++++- samples/ControlCatalog/App.xaml | 15 -------- 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index c91f562989..10534dea26 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -22,7 +22,7 @@ extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* extern IAvnAppMenu* CreateAppMenu(); extern IAvnAppMenuItem* CreateAppMenuItem(); extern IAvnAppMenuItem* CreateAppMenuItemSeperator(); -extern void SetAppMenu (IAvnAppMenu* appMenu); +extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu); extern IAvnAppMenu* GetAppMenu (); extern NSMenuItem* GetAppMenuItem (); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 1a46e495ba..9418782fd1 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -5,10 +5,16 @@ #define COM_GUIDS_MATERIALIZE #include "common.h" +static NSString* s_appTitle = @"Avalonia"; + // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -void SetProcessName(CFStringRef process_name) { +void SetProcessName(NSString* appTitle) { + s_appTitle = appTitle; + + CFStringRef process_name = (__bridge CFStringRef)appTitle; + if (!process_name || CFStringGetLength(process_name) == 0) { //NOTREACHED() << "SetProcessName given bad name."; return; @@ -108,8 +114,8 @@ public: [[NSProcessInfo processInfo] setProcessName:appTitle]; - CFStringRef titleRef = (__bridge CFStringRef)appTitle; - SetProcessName(titleRef); + + SetProcessName(appTitle); return S_OK; } @@ -245,7 +251,7 @@ public: virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override { - ::SetAppMenu(appMenu); + ::SetAppMenu(s_appTitle, appMenu); return S_OK; } diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 397d587ec3..84dc80806a 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -209,15 +209,60 @@ extern IAvnAppMenuItem* CreateAppMenuItemSeperator() static IAvnAppMenu* s_appMenu = nullptr; static NSMenuItem* s_appMenuItem = nullptr; -extern void SetAppMenu (IAvnAppMenu* appMenu) +extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) { - s_appMenu = appMenu; + s_appMenu = menu; if(s_appMenu != nullptr) { auto nativeMenu = dynamic_cast(s_appMenu); s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; + + auto appMenu = [s_appMenuItem submenu]; + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Services item and menu + auto servicesItem = [[NSMenuItem alloc] init]; + servicesItem.title = @"Services"; + NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; + servicesItem.submenu = servicesMenu; + [NSApplication sharedApplication].servicesMenu = servicesMenu; + [appMenu addItem:servicesItem]; + + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Hide Application + auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"]; + //hideItem.target = self; + [appMenu addItem:hideItem]; + + // Hide Others + auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" + action:@selector(hideOtherApplications:) + keyEquivalent:@"h"]; + //hideAllOthersItem.target = self; + hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; + [appMenu addItem:hideAllOthersItem]; + + // Show All + auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All" + action:@selector(unhideAllApplications:) + keyEquivalent:@""]; + //showAllItem.target = self; + [appMenu addItem:showAllItem]; + + [appMenu addItem:[NSMenuItem separatorItem]]; + + // Quit Application + auto quitItem = [[NSMenuItem alloc] init]; + quitItem.title = [@"Quit " stringByAppendingString:appName]; + quitItem.keyEquivalent = @"q"; + // This will remain true until synced with a QCocoaMenuItem. + // This way, we will always have a functional Quit menu item + // even if no QAction is added. + quitItem.action = @selector(terminate:); + [appMenu addItem:quitItem]; } else { diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index a82996773f..726a3c915e 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -27,21 +27,6 @@ - - - - - - - - - - - - - - - From 08fa46288134c7822af9f96b376cd1305d8995f5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:15:08 +0100 Subject: [PATCH 037/118] [OSX platform] set app title before setting exporter. --- src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index a22777d5eb..091056142f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -20,8 +20,8 @@ namespace Avalonia builder.AfterSetup (x=> { - platform.SetupApplicationMenuExporter(); platform.SetupApplicationName(); + platform.SetupApplicationMenuExporter(); }); }); From 8463fb00933e37d787c15932f0196e611d780a10 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:15:49 +0100 Subject: [PATCH 038/118] submenu only on menuitems not seprators --- src/Avalonia.Controls/NativeMenuItem.cs | 34 +++++++++++++++++++++ src/Avalonia.Controls/NativeMenuItemBase.cs | 33 -------------------- 2 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs index 25e0c9e48e..c1144d45b2 100644 --- a/src/Avalonia.Controls/NativeMenuItem.cs +++ b/src/Avalonia.Controls/NativeMenuItem.cs @@ -11,6 +11,20 @@ namespace Avalonia.Controls private KeyGesture _gesture; private bool _enabled = true; + private NativeMenu _menu; + + 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; + }); + } + class CanExecuteChangedSubscriber : IWeakSubscriber { @@ -40,6 +54,26 @@ namespace Avalonia.Controls Header = header; } + public static readonly DirectProperty MenuProperty = + AvaloniaProperty.RegisterDirect(nameof(Menu), o => o._menu, + (o, v) => + { + if (v.Parent != null && v.Parent != o) + throw new InvalidOperationException("NativeMenu already has a parent"); + o._menu = v; + }); + + public NativeMenu Menu + { + get => _menu; + set + { + if (value.Parent != null && value.Parent != this) + throw new InvalidOperationException("NativeMenu already has a parent"); + SetAndRaise(MenuProperty, ref _menu, value); + } + } + public static readonly DirectProperty HeaderProperty = AvaloniaProperty.RegisterDirect(nameof(Header), o => o._header, (o, v) => o._header = v); diff --git a/src/Avalonia.Controls/NativeMenuItemBase.cs b/src/Avalonia.Controls/NativeMenuItemBase.cs index e17ed86054..47eb86cdc3 100644 --- a/src/Avalonia.Controls/NativeMenuItemBase.cs +++ b/src/Avalonia.Controls/NativeMenuItemBase.cs @@ -4,46 +4,13 @@ namespace Avalonia.Controls { public class NativeMenuItemBase : AvaloniaObject { - private NativeMenu _menu; private NativeMenu _parent; - static NativeMenuItemBase() - { - 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; - }); - } - internal NativeMenuItemBase() { } - public static readonly DirectProperty MenuProperty = - AvaloniaProperty.RegisterDirect(nameof(Menu), o => o._menu, - (o, v) => - { - if (v.Parent != null && v.Parent != o) - throw new InvalidOperationException("NativeMenu already has a parent"); - o._menu = v; - }); - - public NativeMenu Menu - { - get => _menu; - set - { - if (value.Parent != null && value.Parent != this) - throw new InvalidOperationException("NativeMenu already has a parent"); - SetAndRaise(MenuProperty, ref _menu, value); - } - } - public static readonly DirectProperty ParentProperty = AvaloniaProperty.RegisterDirect("Parent", o => o.Parent, (o, v) => o.Parent = v); From 1e00b3fe3f22e5f7a42fab157fbfe67ac6e393af Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:25:39 +0100 Subject: [PATCH 039/118] fix build --- samples/ControlCatalog/MainWindow.xaml.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 9f62c0da38..819ab0655a 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -30,7 +30,7 @@ namespace ControlCatalog }; DataContext = new MainWindowViewModel(_notificationArea); - _recentMenu = NativeMenu.GetMenu(this).Items[0].Menu.Items[1].Menu; + _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; } public void OnOpenClicked(object sender, EventArgs args) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 1a9a7bc231..0ab949ee69 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -107,8 +107,8 @@ namespace Avalonia.FreeDesktop foreach (var i in _idsToItems.Values) { i.PropertyChanged -= OnItemPropertyChanged; - if (i.Menu != null) - ((INotifyCollectionChanged)i.Menu.Items).CollectionChanged -= OnMenuItemsChanged; + if (i is NativeMenuItem nmi) + ((INotifyCollectionChanged)nmi.Menu.Items).CollectionChanged -= OnMenuItemsChanged; } _idsToItems.Clear(); _itemsToIds.Clear(); @@ -129,7 +129,7 @@ namespace Avalonia.FreeDesktop if (id == 0) return (null, _menu); _idsToItems.TryGetValue(id, out var item); - return (item, item?.Menu); + return (item, (item as NativeMenuItem)?.Menu); } private int GetId(NativeMenuItemBase item) @@ -140,8 +140,8 @@ namespace Avalonia.FreeDesktop _idsToItems[id] = item; _itemsToIds[item] = id; item.PropertyChanged += OnItemPropertyChanged; - if (item.Menu != null) - ((INotifyCollectionChanged)item.Menu.Items).CollectionChanged += OnMenuItemsChanged; + if (item is NativeMenuItem nmi) + ((INotifyCollectionChanged)nmi.Menu.Items).CollectionChanged += OnMenuItemsChanged; return id; } @@ -281,7 +281,8 @@ namespace Avalonia.FreeDesktop for (var c = 0; c < children.Length; c++) { var ch = menu.Items[c]; - children[c] = GetLayout(ch, ch.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + + children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); } return (id, props, children); From a6e5073c6bb26b9c309c87f106275a67c9b68546 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:36:06 +0100 Subject: [PATCH 040/118] attempt to test changes on application menu --- samples/ControlCatalog/App.xaml | 21 ++++++++++++++++----- samples/ControlCatalog/App.xaml.cs | 11 +++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 726a3c915e..30bfd556c5 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -20,13 +20,24 @@ - + - - - - + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 3b758b0ba8..1eebc7e3b5 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -1,4 +1,6 @@ +using System; using Avalonia; +using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; @@ -6,11 +8,20 @@ namespace ControlCatalog { public class App : Application { + private NativeMenu _recentMenu; + public override void Initialize() { AvaloniaXamlLoader.Load(this); Name = "Avalonia"; + + _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; + } + + public void OnOpenClicked(object sender, EventArgs args) + { + _recentMenu.Items.Insert(0, new NativeMenuItem("Item " + (_recentMenu.Items.Count + 1))); } public override void OnFrameworkInitializationCompleted() From 23a056f2e3a7e3299be57b824590c3cb06705612 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:47:09 +0100 Subject: [PATCH 041/118] fix clearing items when updating --- samples/ControlCatalog/App.xaml | 2 +- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 30bfd556c5..324498f94d 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -29,7 +29,7 @@ - + diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 93e0dba73b..33420f49f5 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -276,6 +276,7 @@ namespace Avalonia.Native appMenu = _factory.CreateMenu(); } + appMenu.Clear(); AddItemsToMenu(appMenu, menuItems); _factory.SetAppMenu(appMenu); @@ -296,6 +297,7 @@ namespace Avalonia.Native appMenu = _factory.CreateMenu(); } + appMenu.Clear(); AddItemsToMenu(appMenu, menuItems); avnWindow.SetMainMenu(appMenu); From 512cc4108080bcdee315bc4a24da4e1ed4a32fa5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 12:53:44 +0100 Subject: [PATCH 042/118] fix app menu test --- samples/ControlCatalog/App.xaml | 22 ++++------------------ samples/ControlCatalog/App.xaml.cs | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 324498f94d..9fbf5768b1 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -20,27 +20,13 @@ - + + - - - - - - - - - - - - - - - - - + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 1eebc7e3b5..4fc63ea054 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -16,7 +16,7 @@ namespace ControlCatalog Name = "Avalonia"; - _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; + _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu; } public void OnOpenClicked(object sender, EventArgs args) From d0dda3e7c6fbb33402320b6d9638f1d39053f752 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 13:10:10 +0100 Subject: [PATCH 043/118] correctly handle changes on app menu --- native/Avalonia.Native/src/OSX/menu.mm | 10 ++++++++++ samples/ControlCatalog/App.xaml | 14 ++++++++++---- samples/ControlCatalog/App.xaml.cs | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 84dc80806a..10047453c2 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -217,8 +217,18 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) { auto nativeMenu = dynamic_cast(s_appMenu); + auto currentMenu = [s_appMenuItem menu]; + + [currentMenu removeItem:s_appMenuItem]; + s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; + [[s_appMenuItem menu] removeItem:s_appMenuItem]; + + [currentMenu insertItem:s_appMenuItem atIndex:0]; + + //[NSApp setMenu:nativeMenu->GetNative()]; + auto appMenu = [s_appMenuItem submenu]; [appMenu addItem:[NSMenuItem separatorItem]]; diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 9fbf5768b1..914f5d5b9b 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -20,13 +20,19 @@ - - + - + + + + + + + + + - diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 4fc63ea054..1eebc7e3b5 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -16,7 +16,7 @@ namespace ControlCatalog Name = "Avalonia"; - _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu; + _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; } public void OnOpenClicked(object sender, EventArgs args) From a465b3ab212f956ec05728b4f034f8a368193fe4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 13:21:24 +0100 Subject: [PATCH 044/118] make app menu declaration sensible. --- samples/ControlCatalog/App.xaml | 24 +++++++------------ samples/ControlCatalog/App.xaml.cs | 2 +- .../AvaloniaNativeMenuExporter.cs | 11 ++++++++- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 914f5d5b9b..335c460b40 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -19,20 +19,14 @@ - - - - - - - - - - - - - - - + + + + + + + + + diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 1eebc7e3b5..4fc63ea054 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -16,7 +16,7 @@ namespace ControlCatalog Name = "Avalonia"; - _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; + _recentMenu = (NativeMenu.GetMenu(this).Items[1] as NativeMenuItem).Menu; } public void OnOpenClicked(object sender, EventArgs args) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 33420f49f5..cee30a6876 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -276,8 +276,17 @@ namespace Avalonia.Native appMenu = _factory.CreateMenu(); } + var menuItem = new NativeMenuItem(); + + menuItem.Menu = new NativeMenu(); + + foreach(var item in menuItems) + { + menuItem.Menu.Add(item); + } + appMenu.Clear(); - AddItemsToMenu(appMenu, menuItems); + AddItemsToMenu(appMenu, new List { menuItem }); _factory.SetAppMenu(appMenu); } From 2182b91023acab8625b2d66fd6ed36b6995d83c9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 13:23:18 +0100 Subject: [PATCH 045/118] tidy appmenu code --- .../AvaloniaNativeMenuExporter.cs | 32 ++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index cee30a6876..966430a241 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -112,6 +112,7 @@ namespace Avalonia.Native if(_nativeWindow is null) { + _menu = NativeMenu.GetMenu(Application.Current); SetMenu(_menu?.Items); } else @@ -265,31 +266,26 @@ namespace Avalonia.Native menuItems = new List(); } - var menu = NativeMenu.GetMenu(Application.Current); + var appMenu = _factory.ObtainAppMenu(); - if (menu != null) + if (appMenu is null) { - var appMenu = _factory.ObtainAppMenu (); - - if (appMenu is null) - { - appMenu = _factory.CreateMenu(); - } + appMenu = _factory.CreateMenu(); + } - var menuItem = new NativeMenuItem(); + var menuItem = new NativeMenuItem(); - menuItem.Menu = new NativeMenu(); + menuItem.Menu = new NativeMenu(); - foreach(var item in menuItems) - { - menuItem.Menu.Add(item); - } + foreach (var item in menuItems) + { + menuItem.Menu.Add(item); + } - appMenu.Clear(); - AddItemsToMenu(appMenu, new List { menuItem }); + appMenu.Clear(); + AddItemsToMenu(appMenu, new List { menuItem }); - _factory.SetAppMenu(appMenu); - } + _factory.SetAppMenu(appMenu); } private void SetMenu(IAvnWindow avnWindow, ICollection menuItems) From 7d1505e42e6f7c7705db1e830eb7c9bd28b22b33 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 13:37:12 +0100 Subject: [PATCH 046/118] tidy osx menu code --- native/Avalonia.Native/src/OSX/menu.mm | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 10047453c2..416aa12ab1 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -227,8 +227,6 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) [currentMenu insertItem:s_appMenuItem atIndex:0]; - //[NSApp setMenu:nativeMenu->GetNative()]; - auto appMenu = [s_appMenuItem submenu]; [appMenu addItem:[NSMenuItem separatorItem]]; @@ -244,14 +242,14 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) // Hide Application auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"]; - //hideItem.target = self; + [appMenu addItem:hideItem]; // Hide Others auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - //hideAllOthersItem.target = self; + hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; [appMenu addItem:hideAllOthersItem]; @@ -259,7 +257,7 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - //showAllItem.target = self; + [appMenu addItem:showAllItem]; [appMenu addItem:[NSMenuItem separatorItem]]; @@ -268,9 +266,6 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) auto quitItem = [[NSMenuItem alloc] init]; quitItem.title = [@"Quit " stringByAppendingString:appName]; quitItem.keyEquivalent = @"q"; - // This will remain true until synced with a QCocoaMenuItem. - // This way, we will always have a functional Quit menu item - // even if no QAction is added. quitItem.action = @selector(terminate:); [appMenu addItem:quitItem]; } From b7e9a50a42b92a7ab6541ee7d2f8d33bb74e5ba2 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 14:12:16 +0100 Subject: [PATCH 047/118] OSX fix logic for setting app menu. --- .../AvaloniaNativeMenuExporter.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 966430a241..d7635ebe78 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -113,7 +113,11 @@ namespace Avalonia.Native if(_nativeWindow is null) { _menu = NativeMenu.GetMenu(Application.Current); - SetMenu(_menu?.Items); + + if(_menu != null) + { + SetMenu(_menu); + } } else { @@ -259,13 +263,8 @@ namespace Avalonia.Native } } - private void SetMenu(ICollection menuItems) + private void SetMenu(NativeMenu menu) { - if (menuItems is null) - { - menuItems = new List(); - } - var appMenu = _factory.ObtainAppMenu(); if (appMenu is null) @@ -273,15 +272,15 @@ namespace Avalonia.Native appMenu = _factory.CreateMenu(); } - var menuItem = new NativeMenuItem(); + var menuItem = menu.Parent; - menuItem.Menu = new NativeMenu(); - - foreach (var item in menuItems) + if(menu.Parent is null) { - menuItem.Menu.Add(item); + menuItem = new NativeMenuItem(); } + menuItem.Menu = menu; + appMenu.Clear(); AddItemsToMenu(appMenu, new List { menuItem }); From c29603f132e11c825407c6cb645a8b596e7a82dd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 14:12:29 +0100 Subject: [PATCH 048/118] [osx] fix responding to updates on app menu --- native/Avalonia.Native/src/OSX/menu.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 416aa12ab1..cdd6dd6c78 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -219,10 +219,18 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) auto currentMenu = [s_appMenuItem menu]; - [currentMenu removeItem:s_appMenuItem]; + if (currentMenu != nullptr) + { + [currentMenu removeItem:s_appMenuItem]; + } s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; + if (currentMenu == nullptr) + { + currentMenu = [s_appMenuItem menu]; + } + [[s_appMenuItem menu] removeItem:s_appMenuItem]; [currentMenu insertItem:s_appMenuItem atIndex:0]; From e738cae5c9c6886c9b05bb3f50fa8267befb6c20 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 15:33:15 +0100 Subject: [PATCH 049/118] fix build issue --- samples/ControlCatalog.Android/MainActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 157609088f..40d001a195 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -20,7 +20,7 @@ namespace ControlCatalog.Android { if (Avalonia.Application.Current == null) { - AppBuilder.Configure(new App()) + AppBuilder.Configure() .UseAndroid() .SetupWithoutStarting(); Content = new MainView(); From 981bc5cff1eb633c5d066c4471991d4f9b12f910 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 18:07:16 +0100 Subject: [PATCH 050/118] trigger ci --- samples/ControlCatalog.Android/MainActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 40d001a195..cb2bfcd943 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -20,7 +20,7 @@ namespace ControlCatalog.Android { if (Avalonia.Application.Current == null) { - AppBuilder.Configure() + AppBuilder.Configure() .UseAndroid() .SetupWithoutStarting(); Content = new MainView(); From e3b4ba5a61dfb1265a703e4d05b2f7ae563f0aa0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 18:57:09 +0100 Subject: [PATCH 051/118] trigger ci --- samples/ControlCatalog.Android/MainActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index cb2bfcd943..40d001a195 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -20,7 +20,7 @@ namespace ControlCatalog.Android { if (Avalonia.Application.Current == null) { - AppBuilder.Configure() + AppBuilder.Configure() .UseAndroid() .SetupWithoutStarting(); Content = new MainView(); From 4df785b9d5bc0b89ae6ab7b964665d88cac3bd54 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 19:24:31 +0100 Subject: [PATCH 052/118] target monoandroid90 --- src/Android/Avalonia.Android/Avalonia.Android.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 0089ea3b8d..c170e8449c 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -1,6 +1,6 @@  - monoandroid80 + monoandroid90 true From 0ca7fa3786de5c1c29de7e7d862616e2c5e1b254 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 19:33:12 +0100 Subject: [PATCH 053/118] fix control catalog android --- samples/ControlCatalog.Android/ControlCatalog.Android.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index bc76a39f08..5b82e2caee 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v8.0 + v9.0 Properties\AndroidManifest.xml From 8d585947a3d9f8e9928ea2459bb3c6164883e26b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 19:34:54 +0100 Subject: [PATCH 054/118] update android test project --- .../Avalonia.AndroidTestApplication.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index 1b2b205d45..2f95a6e4bd 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -16,7 +16,7 @@ Resources\Resource.Designer.cs Off False - v8.0 + v9.0 Properties\AndroidManifest.xml From 02af14f8843c2cee867c541fb01ffe422d33ccb9 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Sep 2019 19:55:21 +0100 Subject: [PATCH 055/118] update sdk extras? --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 6d12c28846..1e599211d4 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "msbuild-sdks": { "Microsoft.Build.Traversal": "1.0.43", - "MSBuild.Sdk.Extras": "1.6.65", + "MSBuild.Sdk.Extras": "2.0.46", "AggregatePackage.NuGet.Sdk" : "0.1.12" } } From d3dab89adc5352a7fa0cff338f84ea43159f972f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 28 Sep 2019 10:22:57 +0100 Subject: [PATCH 056/118] diable android builds --- dirs.proj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dirs.proj b/dirs.proj index e56320e73f..4b3b1183f0 100644 --- a/dirs.proj +++ b/dirs.proj @@ -8,7 +8,8 @@ - + + From 3c0ab70697a68483c3828bb0b62d9205850a5899 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sun, 29 Sep 2019 18:41:20 +0100 Subject: [PATCH 057/118] minor fixes to cope with empty application menu. --- native/Avalonia.Native/src/OSX/menu.mm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index cdd6dd6c78..d9dfe36444 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -75,7 +75,10 @@ HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu) HRESULT AvnAppMenuItem::SetTitle (void* utf8String) { - [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + if (utf8String != nullptr) + { + [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; + } return S_OK; } @@ -235,7 +238,13 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) [currentMenu insertItem:s_appMenuItem atIndex:0]; + if([s_appMenuItem submenu] == nullptr) + { + [s_appMenuItem setSubmenu:[NSMenu new]]; + } + auto appMenu = [s_appMenuItem submenu]; + [appMenu addItem:[NSMenuItem separatorItem]]; // Services item and menu From 01a404836ee80d7dc43113f97da25139391c50d9 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 1 Oct 2019 22:57:44 +0200 Subject: [PATCH 058/118] Reduce allocations caused by logging. --- src/Avalonia.Base/AvaloniaObject.cs | 68 ++++++++---- src/Avalonia.Base/Logging/ILogSink.cs | 71 ++++++++++++ src/Avalonia.Base/Logging/Logger.cs | 90 +++++++++++++++ .../Primitives/TemplatedControl.cs | 2 +- src/Avalonia.Layout/LayoutManager.cs | 32 ++++-- src/Avalonia.Layout/Layoutable.cs | 8 +- src/Avalonia.Logging.Serilog/SerilogLogger.cs | 104 +++++++++++++++++- src/Avalonia.Visuals/Visual.cs | 4 +- tests/Avalonia.UnitTests/TestLogSink.cs | 32 +++++- 9 files changed, 365 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8cc512d132..22a85663c7 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -326,8 +326,6 @@ namespace Avalonia VerifyAccess(); - var description = GetDescription(source); - if (property.IsDirect) { if (property.IsReadOnly) @@ -335,12 +333,18 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - Logger.Verbose( - LogArea.Property, - this, - "Bound {Property} to {Binding} with priority LocalValue", - property, - description); + if (Logger.IsEnabled(LogEventLevel.Verbose)) + { + var description = GetDescription(source); + + Logger.Log( + LogEventLevel.Verbose, + LogArea.Property, + this, + "Bound {Property} to {Binding} with priority LocalValue", + property, + description); + } if (_directBindings == null) { @@ -351,13 +355,19 @@ namespace Avalonia } else { - Logger.Verbose( - LogArea.Property, - this, - "Bound {Property} to {Binding} with priority {Priority}", - property, - description, - priority); + if (Logger.IsEnabled(LogEventLevel.Verbose)) + { + var description = GetDescription(source); + + Logger.Log( + LogEventLevel.Verbose, + LogArea.Property, + this, + "Bound {Property} to {Binding} with priority {Priority}", + property, + description, + priority); + } return Values.AddBinding(property, source, priority); } @@ -406,14 +416,18 @@ namespace Avalonia { RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); - Logger.Verbose( - LogArea.Property, - this, - "{Property} changed from {$Old} to {$Value} with priority {Priority}", - property, - oldValue, - newValue, - (BindingPriority)priority); + if (Logger.IsEnabled(LogEventLevel.Verbose)) + { + Logger.Log( + LogEventLevel.Verbose, + LogArea.Property, + this, + "{Property} changed from {$Old} to {$Value} with priority {Priority}", + property, + oldValue, + newValue, + (BindingPriority)priority); + } } } @@ -812,7 +826,13 @@ namespace Avalonia /// The priority. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) { - Logger.Verbose( + if (!Logger.IsEnabled(LogEventLevel.Verbose)) + { + return; + } + + Logger.Log( + LogEventLevel.Verbose, LogArea.Property, this, "Set {Property} to {$Value} with priority {Priority}", diff --git a/src/Avalonia.Base/Logging/ILogSink.cs b/src/Avalonia.Base/Logging/ILogSink.cs index 0ed4eede8f..8b5751b0af 100644 --- a/src/Avalonia.Base/Logging/ILogSink.cs +++ b/src/Avalonia.Base/Logging/ILogSink.cs @@ -8,6 +8,77 @@ namespace Avalonia.Logging /// public interface ILogSink { + /// + /// Checks if given log level is enabled. + /// + /// The log event level. + /// if given log level is enabled. + bool IsEnabled(LogEventLevel level); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1); + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2); + /// /// Logs a new event. /// diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs index b1132ff4a9..c46edd4b4b 100644 --- a/src/Avalonia.Base/Logging/Logger.cs +++ b/src/Avalonia.Base/Logging/Logger.cs @@ -15,6 +15,96 @@ namespace Avalonia.Logging /// public static ILogSink Sink { get; set; } + /// + /// Checks if given log level is enabled. + /// + /// The log event level. + /// if given log level is enabled. + public static bool IsEnabled(LogEventLevel level) + { + return Sink?.IsEnabled(level) == true; + } + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate) + { + Sink?.Log(level, area, source, messageTemplate); + } + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0) + { + Sink?.Log(level, area, source, messageTemplate, propertyValue0); + } + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + Sink?.Log(level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + /// + /// Logs an event. + /// + /// The log event level. + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + Sink?.Log(level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + /// /// Logs an event. /// diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 47c3240374..daf79b3558 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -255,7 +255,7 @@ namespace Avalonia.Controls.Primitives if (template != null) { - Logger.Verbose(LogArea.Control, this, "Creating control template"); + Logger.Log(LogEventLevel.Verbose, LogArea.Control, this, "Creating control template"); var (child, nameScope) = template.Build(this); ApplyTemplatedParent(child); diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index 45efccc1fa..ef5464c4ae 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Diagnostics; using Avalonia.Logging; using Avalonia.Threading; @@ -69,15 +70,22 @@ namespace Avalonia.Layout { _running = true; - Logger.Information( - LogArea.Layout, - this, - "Started layout pass. To measure: {Measure} To arrange: {Arrange}", - _toMeasure.Count, - _toArrange.Count); + Stopwatch stopwatch = null; - var stopwatch = new System.Diagnostics.Stopwatch(); - stopwatch.Start(); + bool captureTiming = Logger.IsEnabled(LogEventLevel.Information); + + if (captureTiming) + { + Logger.Log(LogEventLevel.Information, + LogArea.Layout, + this, + "Started layout pass. To measure: {Measure} To arrange: {Arrange}", + _toMeasure.Count, + _toArrange.Count); + + stopwatch = new Stopwatch(); + stopwatch.Start(); + } _toMeasure.BeginLoop(MaxPasses); _toArrange.BeginLoop(MaxPasses); @@ -103,8 +111,12 @@ namespace Avalonia.Layout _toMeasure.EndLoop(); _toArrange.EndLoop(); - stopwatch.Stop(); - Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); + if (captureTiming) + { + stopwatch.Stop(); + + Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); + } } _queued = false; diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index bd248d6d44..9b5f66b19d 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -329,7 +329,7 @@ namespace Avalonia.Layout DesiredSize = desiredSize; _previousMeasure = availableSize; - Logger.Verbose(LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize); + Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize); if (DesiredSize != previousDesiredSize) { @@ -356,7 +356,7 @@ namespace Avalonia.Layout if (!IsArrangeValid || _previousArrange != rect) { - Logger.Verbose(LogArea.Layout, this, "Arrange to {Rect} ", rect); + Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Arrange to {Rect} ", rect); IsArrangeValid = true; ArrangeCore(rect); @@ -381,7 +381,7 @@ namespace Avalonia.Layout { if (IsMeasureValid) { - Logger.Verbose(LogArea.Layout, this, "Invalidated measure"); + Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Invalidated measure"); IsMeasureValid = false; IsArrangeValid = false; @@ -402,7 +402,7 @@ namespace Avalonia.Layout { if (IsArrangeValid) { - Logger.Verbose(LogArea.Layout, this, "Invalidated arrange"); + Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Invalidated arrange"); IsArrangeValid = false; (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this); diff --git a/src/Avalonia.Logging.Serilog/SerilogLogger.cs b/src/Avalonia.Logging.Serilog/SerilogLogger.cs index 0534fe3012..895ee268d2 100644 --- a/src/Avalonia.Logging.Serilog/SerilogLogger.cs +++ b/src/Avalonia.Logging.Serilog/SerilogLogger.cs @@ -34,6 +34,76 @@ namespace Avalonia.Logging.Serilog Logger.Sink = new SerilogLogger(output); } + public bool IsEnabled(LogEventLevel level) + { + return _output.IsEnabled((SerilogLogEventLevel)level); + } + + public void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate); + } + } + + public void Log( + LogEventLevel level, + string area, object source, + string messageTemplate, + T0 propertyValue0) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0); + } + } + + public void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1); + } + } + + public void Log( + LogEventLevel level, + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + Contract.Requires(area != null); + Contract.Requires(messageTemplate != null); + + using (PushLogContextProperties(area, source)) + { + _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + } + /// public void Log( AvaloniaLogEventLevel level, @@ -45,12 +115,40 @@ namespace Avalonia.Logging.Serilog Contract.Requires(area != null); Contract.Requires(messageTemplate != null); - using (LogContext.PushProperty("Area", area)) - using (LogContext.PushProperty("SourceType", source?.GetType())) - using (LogContext.PushProperty("SourceHash", source?.GetHashCode())) + using (PushLogContextProperties(area, source)) { _output.Write((SerilogLogEventLevel)level, messageTemplate, propertyValues); } } + + private static LogContextDisposable PushLogContextProperties(string area, object source) + { + return new LogContextDisposable( + LogContext.PushProperty("Area", area), + LogContext.PushProperty("SourceType", source?.GetType()), + LogContext.PushProperty("SourceHash", source?.GetHashCode()) + ); + } + + private readonly struct LogContextDisposable : IDisposable + { + private readonly IDisposable _areaDisposable; + private readonly IDisposable _sourceTypeDisposable; + private readonly IDisposable _sourceHashDisposable; + + public LogContextDisposable(IDisposable areaDisposable, IDisposable sourceTypeDisposable, IDisposable sourceHashDisposable) + { + _areaDisposable = areaDisposable; + _sourceTypeDisposable = sourceTypeDisposable; + _sourceHashDisposable = sourceHashDisposable; + } + + public void Dispose() + { + _areaDisposable.Dispose(); + _sourceTypeDisposable.Dispose(); + _sourceHashDisposable.Dispose(); + } + } } } diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 1f2d67b69e..8d32371c6d 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -359,7 +359,7 @@ namespace Avalonia /// The event args. protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { - Logger.Verbose(LogArea.Visual, this, "Attached to visual tree"); + Logger.Log(LogEventLevel.Verbose, LogArea.Visual, this, "Attached to visual tree"); _visualRoot = e.Root; @@ -388,7 +388,7 @@ namespace Avalonia /// The event args. protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { - Logger.Verbose(LogArea.Visual, this, "Detached from visual tree"); + Logger.Log(LogEventLevel.Verbose, LogArea.Visual, this, "Detached from visual tree"); _visualRoot = null; diff --git a/tests/Avalonia.UnitTests/TestLogSink.cs b/tests/Avalonia.UnitTests/TestLogSink.cs index 8e4dd7164f..a2b188c273 100644 --- a/tests/Avalonia.UnitTests/TestLogSink.cs +++ b/tests/Avalonia.UnitTests/TestLogSink.cs @@ -16,7 +16,7 @@ namespace Avalonia.UnitTests public class TestLogSink : ILogSink { - private LogCallback _callback; + private readonly LogCallback _callback; public TestLogSink(LogCallback callback) { @@ -30,7 +30,35 @@ namespace Avalonia.UnitTests return Disposable.Create(() => Logger.Sink = null); } - public void Log(LogEventLevel level, string area, object source, string messageTemplate, params object[] propertyValues) + public bool IsEnabled(LogEventLevel level) + { + return true; + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate) + { + _callback(level, area, source, messageTemplate); + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, T0 propertyValue0) + { + _callback(level, area, source, messageTemplate, propertyValue0); + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, + T0 propertyValue0, T1 propertyValue1) + { + _callback(level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, + T0 propertyValue0, T1 propertyValue1, T2 propertyValue2) + { + _callback(level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + + public void Log(LogEventLevel level, string area, object source, string messageTemplate, + params object[] propertyValues) { _callback(level, area, source, messageTemplate, propertyValues); } From 068b73750b0f7bb5adc67c5470e30589eb36b79f Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Fri, 4 Oct 2019 00:01:44 +0200 Subject: [PATCH 059/118] Cleanup conditional logging API. --- src/Avalonia.Base/AvaloniaObject.cs | 49 +++----- src/Avalonia.Base/Logging/Logger.cs | 30 +++++ .../Logging/ParametrizedLogger.cs | 116 ++++++++++++++++++ 3 files changed, 166 insertions(+), 29 deletions(-) create mode 100644 src/Avalonia.Base/Logging/ParametrizedLogger.cs diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 22a85663c7..ddcf16e11a 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -333,12 +333,11 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - if (Logger.IsEnabled(LogEventLevel.Verbose)) + if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) { var description = GetDescription(source); - Logger.Log( - LogEventLevel.Verbose, + logger.Log( LogArea.Property, this, "Bound {Property} to {Binding} with priority LocalValue", @@ -355,12 +354,11 @@ namespace Avalonia } else { - if (Logger.IsEnabled(LogEventLevel.Verbose)) + if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) { var description = GetDescription(source); - Logger.Log( - LogEventLevel.Verbose, + logger.Log( LogArea.Property, this, "Bound {Property} to {Binding} with priority {Priority}", @@ -416,18 +414,14 @@ namespace Avalonia { RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); - if (Logger.IsEnabled(LogEventLevel.Verbose)) - { - Logger.Log( - LogEventLevel.Verbose, - LogArea.Property, - this, - "{Property} changed from {$Old} to {$Value} with priority {Priority}", - property, - oldValue, - newValue, - (BindingPriority)priority); - } + Logger.TryGetLogger(LogEventLevel.Verbose)?.Log( + LogArea.Property, + this, + "{Property} changed from {$Old} to {$Value} with priority {Priority}", + property, + oldValue, + newValue, + (BindingPriority)priority); } } @@ -826,19 +820,16 @@ namespace Avalonia /// The priority. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) { - if (!Logger.IsEnabled(LogEventLevel.Verbose)) + if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) { - return; + logger.Log( + LogArea.Property, + this, + "Set {Property} to {$Value} with priority {Priority}", + property, + value, + priority); } - - Logger.Log( - LogEventLevel.Verbose, - LogArea.Property, - this, - "Set {Property} to {$Value} with priority {Priority}", - property, - value, - priority); } private class DirectBindingSubscription : IObserver, IDisposable diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs index c46edd4b4b..ef4c29b9f3 100644 --- a/src/Avalonia.Base/Logging/Logger.cs +++ b/src/Avalonia.Base/Logging/Logger.cs @@ -25,6 +25,36 @@ namespace Avalonia.Logging return Sink?.IsEnabled(level) == true; } + /// + /// Returns parametrized logging sink if given log level is enabled. + /// + /// The log event level. + /// Log sink or if log level is not enabled. + public static ParametrizedLogger? TryGetLogger(LogEventLevel level) + { + if (!IsEnabled(level)) + { + return null; + } + + return new ParametrizedLogger(Sink, level); + } + + /// + /// Returns parametrized logging sink if given log level is enabled. + /// + /// The log event level. + /// Log sink that is valid only if method returns . + /// if logger was obtained successfully. + public static bool TryGetLogger(LogEventLevel level, out ParametrizedLogger outLogger) + { + ParametrizedLogger? logger = TryGetLogger(level); + + outLogger = logger.GetValueOrDefault(); + + return logger.HasValue; + } + /// /// Logs an event. /// diff --git a/src/Avalonia.Base/Logging/ParametrizedLogger.cs b/src/Avalonia.Base/Logging/ParametrizedLogger.cs new file mode 100644 index 0000000000..1a0678c79a --- /dev/null +++ b/src/Avalonia.Base/Logging/ParametrizedLogger.cs @@ -0,0 +1,116 @@ +// Copyright (c) The Avalonia Project. All rights reserved. +// Licensed under the MIT license. See licence.md file in the project root for full license information. + +using System.Runtime.CompilerServices; + +namespace Avalonia.Logging +{ + /// + /// Logger sink parametrized for given logging level. + /// + public readonly struct ParametrizedLogger + { + private readonly ILogSink _sink; + private readonly LogEventLevel _level; + + public ParametrizedLogger(ILogSink sink, LogEventLevel level) + { + _sink = sink; + _level = level; + } + + /// + /// Checks if this logger can be used. + /// + public bool IsValid => _sink != null; + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate) + { + _sink.Log(_level, area, source, messageTemplate); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// The message property values. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + params object[] propertyValues) + { + _sink.Log(_level, area, source, messageTemplate, propertyValues); + } + } +} From 1f693404526da965fad8f6d30aef138125b9e9f6 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 6 Oct 2019 00:26:10 +0200 Subject: [PATCH 060/118] Cleanup. --- src/Avalonia.Base/AvaloniaObject.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index ddcf16e11a..8d9a98d087 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -820,16 +820,13 @@ namespace Avalonia /// The priority. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) { - if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) - { - logger.Log( - LogArea.Property, - this, - "Set {Property} to {$Value} with priority {Priority}", - property, - value, - priority); - } + Logger.TryGetLogger(LogEventLevel.Verbose)?.Log( + LogArea.Property, + this, + "Set {Property} to {$Value} with priority {Priority}", + property, + value, + priority); } private class DirectBindingSubscription : IObserver, IDisposable From 2116439c727d8e9001975e3cdf4691e10b9b39d4 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 6 Oct 2019 15:53:39 +0200 Subject: [PATCH 061/118] Optimize TypeNameAndClassSelector. --- .../Styling/TypeNameAndClassSelector.cs | 90 +++++++++++-------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs index 362ac86e50..f1fd2f6c7f 100644 --- a/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs @@ -18,8 +18,9 @@ namespace Avalonia.Styling internal class TypeNameAndClassSelector : Selector { private readonly Selector _previous; + private readonly Lazy> _classes = new Lazy>(() => new List()); private Type _targetType; - private Lazy> _classes = new Lazy>(() => new List()); + private string _selectorString; public static TypeNameAndClassSelector OfType(Selector previous, Type targetType) @@ -27,6 +28,7 @@ namespace Avalonia.Styling var result = new TypeNameAndClassSelector(previous); result._targetType = targetType; result.IsConcreteType = true; + return result; } @@ -35,6 +37,7 @@ namespace Avalonia.Styling var result = new TypeNameAndClassSelector(previous); result._targetType = targetType; result.IsConcreteType = false; + return result; } @@ -42,6 +45,7 @@ namespace Avalonia.Styling { var result = new TypeNameAndClassSelector(previous); result.Name = name; + return result; } @@ -49,6 +53,7 @@ namespace Avalonia.Styling { var result = new TypeNameAndClassSelector(previous); result.Classes.Add(className); + return result; } @@ -126,9 +131,11 @@ namespace Avalonia.Styling if (subscribe) { var observable = new ClassObserver(control.Classes, _classes.Value); + return new SelectorMatch(observable); } - else if (!Matches(control.Classes)) + + if (!AreClassesMatching(control.Classes, Classes)) { return SelectorMatch.NeverThisInstance; } @@ -139,21 +146,6 @@ namespace Avalonia.Styling protected override Selector MovePrevious() => _previous; - private bool Matches(IEnumerable classes) - { - int remaining = Classes.Count; - - foreach (var c in classes) - { - if (Classes.Contains(c)) - { - --remaining; - } - } - - return remaining == 0; - } - private string BuildSelectorString() { var builder = new StringBuilder(); @@ -199,11 +191,41 @@ namespace Avalonia.Styling return builder.ToString(); } - private class ClassObserver : LightweightObservableBase + private static bool AreClassesMatching(IReadOnlyList classes, IList toMatch) { - readonly IList _match; - IAvaloniaReadOnlyList _classes; - bool _value; + int remainingMatches = toMatch.Count; + int classesCount = classes.Count; + + // Early bail out - we can't match if control does not have enough classes. + if (classesCount < remainingMatches) + { + return false; + } + + for (var i = 0; i < classesCount; i++) + { + var c = classes[i]; + + if (toMatch.Contains(c)) + { + --remainingMatches; + + // Already matched so we can skip checking other classes. + if (remainingMatches == 0) + { + break; + } + } + } + + return remainingMatches == 0; + } + + private sealed class ClassObserver : LightweightObservableBase + { + private readonly IList _match; + private readonly IAvaloniaReadOnlyList _classes; + private bool _hasMatch; public ClassObserver(IAvaloniaReadOnlyList classes, IList match) { @@ -215,42 +237,32 @@ namespace Avalonia.Styling protected override void Initialize() { - _value = GetResult(); + _hasMatch = IsMatching(); _classes.CollectionChanged += ClassesChanged; } protected override void Subscribed(IObserver observer, bool first) { - observer.OnNext(_value); + observer.OnNext(_hasMatch); } private void ClassesChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Move) { - var value = GetResult(); + var hasMatch = IsMatching(); - if (value != _value) + if (hasMatch != _hasMatch) { - PublishNext(GetResult()); - _value = value; + PublishNext(hasMatch); + _hasMatch = hasMatch; } } } - private bool GetResult() + private bool IsMatching() { - int remaining = _match.Count; - - foreach (var c in _classes) - { - if (_match.Contains(c)) - { - --remaining; - } - } - - return remaining == 0; + return AreClassesMatching(_classes, _match); } } } From a95273902b2f975787f2b7d3d48516d305a31989 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 6 Oct 2019 20:45:17 +0200 Subject: [PATCH 062/118] Queue caret update with correct priority. When trying to scroll the caret into view, if we need to wait for a layout first make sure that we queue the update with a lower priority then `Layout`. `Render` is the next lowest priority so use this. Fixes #3070 --- src/Avalonia.Controls/Presenters/TextPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index debbb81264..5931fec350 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -244,7 +244,7 @@ namespace Avalonia.Controls.Presenters var rect = FormattedText.HitTestTextPosition(caretIndex); this.BringIntoView(rect); }, - DispatcherPriority.Normal); + DispatcherPriority.Render); } } } From 5814bd7163193c614dbffd8364b04bece257afa3 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 6 Oct 2019 22:16:07 +0200 Subject: [PATCH 063/118] Remove direct logging functions. --- src/Avalonia.Base/AvaloniaObject.cs | 11 +- .../Data/Core/BindingExpression.cs | 2 +- src/Avalonia.Base/Logging/Logger.cs | 209 +----------------- .../Logging/ParametrizedLogger.cs | 66 +++++- src/Avalonia.Base/PriorityValue.cs | 2 +- src/Avalonia.Controls/DropDown.cs | 4 +- .../Primitives/SelectingItemsControl.cs | 2 +- .../Primitives/TemplatedControl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 2 +- src/Avalonia.Layout/LayoutManager.cs | 7 +- src/Avalonia.Layout/Layoutable.cs | 8 +- src/Avalonia.OpenGL/EglGlPlatformFeature.cs | 2 +- src/Avalonia.Styling/StyledElement.cs | 4 +- .../Animation/Animators/TransformAnimator.cs | 4 +- .../Rendering/DeferredRenderer.cs | 3 +- .../Rendering/ImmediateRenderer.cs | 3 +- src/Avalonia.Visuals/Rendering/RenderLoop.cs | 4 +- src/Avalonia.Visuals/Visual.cs | 7 +- src/Avalonia.X11/Glx/GlxPlatformFeature.cs | 2 +- .../AvaloniaPropertyTypeConverter.cs | 2 +- .../Markup/Data/DelayedBinding.cs | 2 +- .../Media/StreamGeometryContextImpl.cs | 2 +- 22 files changed, 103 insertions(+), 247 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 8d9a98d087..1d49401d99 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -333,7 +333,7 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) + if (Logger.TryGet(LogEventLevel.Verbose, out var logger)) { var description = GetDescription(source); @@ -354,7 +354,7 @@ namespace Avalonia } else { - if (Logger.TryGetLogger(LogEventLevel.Verbose, out var logger)) + if (Logger.TryGet(LogEventLevel.Verbose, out var logger)) { var description = GetDescription(source); @@ -414,7 +414,7 @@ namespace Avalonia { RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority); - Logger.TryGetLogger(LogEventLevel.Verbose)?.Log( + Logger.TryGet(LogEventLevel.Verbose)?.Log( LogArea.Property, this, "{Property} changed from {$Old} to {$Value} with priority {Priority}", @@ -466,8 +466,7 @@ namespace Avalonia /// The binding error. protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e) { - Logger.Log( - LogEventLevel.Warning, + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Binding, this, "Error in binding to {Target}.{Property}: {Message}", @@ -820,7 +819,7 @@ namespace Avalonia /// The priority. private void LogPropertySet(AvaloniaProperty property, object value, BindingPriority priority) { - Logger.TryGetLogger(LogEventLevel.Verbose)?.Log( + Logger.TryGet(LogEventLevel.Verbose)?.Log( LogArea.Property, this, "Set {Property} to {$Value} with priority {Priority}", diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 7f8396cdfa..986e2cf012 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -165,7 +165,7 @@ namespace Avalonia.Data.Core } else { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Binding, this, "Could not convert FallbackValue {FallbackValue} to {Type}", diff --git a/src/Avalonia.Base/Logging/Logger.cs b/src/Avalonia.Base/Logging/Logger.cs index ef4c29b9f3..c895c70094 100644 --- a/src/Avalonia.Base/Logging/Logger.cs +++ b/src/Avalonia.Base/Logging/Logger.cs @@ -1,8 +1,6 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using System.Runtime.CompilerServices; - namespace Avalonia.Logging { /// @@ -30,7 +28,7 @@ namespace Avalonia.Logging /// /// The log event level. /// Log sink or if log level is not enabled. - public static ParametrizedLogger? TryGetLogger(LogEventLevel level) + public static ParametrizedLogger? TryGet(LogEventLevel level) { if (!IsEnabled(level)) { @@ -46,214 +44,13 @@ namespace Avalonia.Logging /// The log event level. /// Log sink that is valid only if method returns . /// if logger was obtained successfully. - public static bool TryGetLogger(LogEventLevel level, out ParametrizedLogger outLogger) + public static bool TryGet(LogEventLevel level, out ParametrizedLogger outLogger) { - ParametrizedLogger? logger = TryGetLogger(level); + ParametrizedLogger? logger = TryGet(level); outLogger = logger.GetValueOrDefault(); return logger.HasValue; } - - /// - /// Logs an event. - /// - /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate) - { - Sink?.Log(level, area, source, messageTemplate); - } - - /// - /// Logs an event. - /// - /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// Message property value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate, - T0 propertyValue0) - { - Sink?.Log(level, area, source, messageTemplate, propertyValue0); - } - - /// - /// Logs an event. - /// - /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// Message property value. - /// Message property value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate, - T0 propertyValue0, - T1 propertyValue1) - { - Sink?.Log(level, area, source, messageTemplate, propertyValue0, propertyValue1); - } - - /// - /// Logs an event. - /// - /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// Message property value. - /// Message property value. - /// Message property value. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate, - T0 propertyValue0, - T1 propertyValue1, - T2 propertyValue2) - { - Sink?.Log(level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); - } - - /// - /// Logs an event. - /// - /// The log event level. - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Log( - LogEventLevel level, - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Sink?.Log(level, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Verbose( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Verbose, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Debug( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Debug, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Information( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Information, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Warning( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Warning, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Error( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Error, area, source, messageTemplate, propertyValues); - } - - /// - /// Logs an event with the level. - /// - /// The area that the event originates. - /// The object from which the event originates. - /// The message template. - /// The message property values. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Fatal( - string area, - object source, - string messageTemplate, - params object[] propertyValues) - { - Log(LogEventLevel.Fatal, area, source, messageTemplate, propertyValues); - } } } diff --git a/src/Avalonia.Base/Logging/ParametrizedLogger.cs b/src/Avalonia.Base/Logging/ParametrizedLogger.cs index 1a0678c79a..1550cc1b40 100644 --- a/src/Avalonia.Base/Logging/ParametrizedLogger.cs +++ b/src/Avalonia.Base/Logging/ParametrizedLogger.cs @@ -102,15 +102,73 @@ namespace Avalonia.Logging /// The area that the event originates. /// The object from which the event originates. /// The message template. - /// The message property values. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Log( + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( string area, object source, string messageTemplate, - params object[] propertyValues) + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3, + T4 propertyValue4) + { + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4); + } + + /// + /// Logs an event. + /// + /// The area that the event originates. + /// The object from which the event originates. + /// The message template. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + /// Message property value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Log( + string area, + object source, + string messageTemplate, + T0 propertyValue0, + T1 propertyValue1, + T2 propertyValue2, + T3 propertyValue3, + T4 propertyValue4, + T5 propertyValue5) { - _sink.Log(_level, area, source, messageTemplate, propertyValues); + _sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5); } } } diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs index 2871271062..61184ef7b1 100644 --- a/src/Avalonia.Base/PriorityValue.cs +++ b/src/Avalonia.Base/PriorityValue.cs @@ -301,7 +301,7 @@ namespace Avalonia } else { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Binding, Owner, "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs index b67d9ef89a..9da803d16d 100644 --- a/src/Avalonia.Controls/DropDown.cs +++ b/src/Avalonia.Controls/DropDown.cs @@ -9,7 +9,7 @@ namespace Avalonia.Controls { public DropDown() { - Logger.Warning(LogArea.Control, this, "DropDown is deprecated: Use ComboBox"); + Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDown is deprecated: Use ComboBox"); } Type IStyleable.StyleKey => typeof(ComboBox); @@ -20,7 +20,7 @@ namespace Avalonia.Controls { public DropDownItem() { - Logger.Warning(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem"); + Logger.TryGet(LogEventLevel.Warning)?.Log(LogArea.Control, this, "DropDownItem is deprecated: Use ComboBoxItem"); } Type IStyleable.StyleKey => typeof(ComboBoxItem); diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 6869ea0822..c6172c0f36 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -1062,7 +1062,7 @@ namespace Avalonia.Controls.Primitives } catch (Exception ex) { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Property, this, "Error thrown updating SelectedItems: {Error}", diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index daf79b3558..7d0f306db8 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -255,7 +255,7 @@ namespace Avalonia.Controls.Primitives if (template != null) { - Logger.Log(LogEventLevel.Verbose, LogArea.Control, this, "Creating control template"); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Control, this, "Creating control template"); var (child, nameScope) = template.Build(this); ApplyTemplatedParent(child); diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index e0acab1133..c54ebd5360 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -330,7 +330,7 @@ namespace Avalonia.Controls if (result == null) { - Logger.Warning( + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Control, this, "Could not create {Service} : maybe Application.RegisterServices() wasn't called?", diff --git a/src/Avalonia.Layout/LayoutManager.cs b/src/Avalonia.Layout/LayoutManager.cs index ef5464c4ae..855f123748 100644 --- a/src/Avalonia.Layout/LayoutManager.cs +++ b/src/Avalonia.Layout/LayoutManager.cs @@ -72,11 +72,12 @@ namespace Avalonia.Layout Stopwatch stopwatch = null; - bool captureTiming = Logger.IsEnabled(LogEventLevel.Information); + const LogEventLevel timingLogLevel = LogEventLevel.Information; + bool captureTiming = Logger.IsEnabled(timingLogLevel); if (captureTiming) { - Logger.Log(LogEventLevel.Information, + Logger.TryGet(timingLogLevel)?.Log( LogArea.Layout, this, "Started layout pass. To measure: {Measure} To arrange: {Arrange}", @@ -115,7 +116,7 @@ namespace Avalonia.Layout { stopwatch.Stop(); - Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); + Logger.TryGet(timingLogLevel)?.Log(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed); } } diff --git a/src/Avalonia.Layout/Layoutable.cs b/src/Avalonia.Layout/Layoutable.cs index 9b5f66b19d..b0757a823d 100644 --- a/src/Avalonia.Layout/Layoutable.cs +++ b/src/Avalonia.Layout/Layoutable.cs @@ -329,7 +329,7 @@ namespace Avalonia.Layout DesiredSize = desiredSize; _previousMeasure = availableSize; - Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Measure requested {DesiredSize}", DesiredSize); if (DesiredSize != previousDesiredSize) { @@ -356,7 +356,7 @@ namespace Avalonia.Layout if (!IsArrangeValid || _previousArrange != rect) { - Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Arrange to {Rect} ", rect); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Arrange to {Rect} ", rect); IsArrangeValid = true; ArrangeCore(rect); @@ -381,7 +381,7 @@ namespace Avalonia.Layout { if (IsMeasureValid) { - Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Invalidated measure"); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated measure"); IsMeasureValid = false; IsArrangeValid = false; @@ -402,7 +402,7 @@ namespace Avalonia.Layout { if (IsArrangeValid) { - Logger.Log(LogEventLevel.Verbose, LogArea.Layout, this, "Invalidated arrange"); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Layout, this, "Invalidated arrange"); IsArrangeValid = false; (VisualRoot as ILayoutRoot)?.LayoutManager?.InvalidateArrange(this); diff --git a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs index 86411b89da..5f5064fba5 100644 --- a/src/Avalonia.OpenGL/EglGlPlatformFeature.cs +++ b/src/Avalonia.OpenGL/EglGlPlatformFeature.cs @@ -31,7 +31,7 @@ namespace Avalonia.OpenGL } catch(Exception e) { - Logger.Error("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e); + Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", null, "Unable to initialize EGL-based rendering: {0}", e); return null; } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 38c29289b6..1465b9eb85 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -743,11 +743,11 @@ namespace Avalonia #if DEBUG if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0) { - Logger.Warning( + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Control, this, "{Type} detached from logical tree but still has class listeners", - this.GetType()); + GetType()); } #endif } diff --git a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs index e7be272f13..1f1590bdcd 100644 --- a/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs +++ b/src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs @@ -65,14 +65,14 @@ namespace Avalonia.Animation.Animators } } - Logger.Warning( + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Animations, control, $"Cannot find the appropriate transform: \"{Property.OwnerType}\" in {control}."); } else { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Animations, control, $"Cannot apply animation: Target property owner {Property.OwnerType} is not a Transform object."); diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs index efcc555159..d9a68b236a 100644 --- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Avalonia.Logging; using Avalonia.Media; using Avalonia.Media.Immutable; using Avalonia.Platform; @@ -269,7 +270,7 @@ namespace Avalonia.Rendering } catch (RenderTargetCorruptedException ex) { - Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex); RenderTarget?.Dispose(); RenderTarget = null; } diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs index b2d242d4af..68d56eeedd 100644 --- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs +++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Logging; using Avalonia.Media; using Avalonia.Platform; using Avalonia.VisualTree; @@ -80,7 +81,7 @@ namespace Avalonia.Rendering } catch (RenderTargetCorruptedException ex) { - Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex); + Logger.TryGet(LogEventLevel.Information)?.Log("Renderer", this, "Render target was corrupted. Exception: {0}", ex); _renderTarget.Dispose(); _renderTarget = null; } diff --git a/src/Avalonia.Visuals/Rendering/RenderLoop.cs b/src/Avalonia.Visuals/Rendering/RenderLoop.cs index 140688f8bc..c2594658b9 100644 --- a/src/Avalonia.Visuals/Rendering/RenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/RenderLoop.cs @@ -120,7 +120,7 @@ namespace Avalonia.Rendering } catch (Exception ex) { - Logger.Error(LogArea.Visual, this, "Exception in render update: {Error}", ex); + Logger.TryGet(LogEventLevel.Error)?.Log(LogArea.Visual, this, "Exception in render update: {Error}", ex); } } } @@ -136,7 +136,7 @@ namespace Avalonia.Rendering } catch (Exception ex) { - Logger.Error(LogArea.Visual, this, "Exception in render loop: {Error}", ex); + Logger.TryGet(LogEventLevel.Error)?.Log(LogArea.Visual, this, "Exception in render loop: {Error}", ex); } finally { diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs index 8d32371c6d..f4306d3929 100644 --- a/src/Avalonia.Visuals/Visual.cs +++ b/src/Avalonia.Visuals/Visual.cs @@ -359,7 +359,7 @@ namespace Avalonia /// The event args. protected virtual void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e) { - Logger.Log(LogEventLevel.Verbose, LogArea.Visual, this, "Attached to visual tree"); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Visual, this, "Attached to visual tree"); _visualRoot = e.Root; @@ -388,7 +388,7 @@ namespace Avalonia /// The event args. protected virtual void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e) { - Logger.Log(LogEventLevel.Verbose, LogArea.Visual, this, "Detached from visual tree"); + Logger.TryGet(LogEventLevel.Verbose)?.Log(LogArea.Visual, this, "Detached from visual tree"); _visualRoot = null; @@ -453,8 +453,7 @@ namespace Avalonia return; } - Logger.Log( - LogEventLevel.Warning, + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Binding, this, "Error in binding to {Target}.{Property}: {Message}", diff --git a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs index d15b5fe4b8..3dc2e8e41f 100644 --- a/src/Avalonia.X11/Glx/GlxPlatformFeature.cs +++ b/src/Avalonia.X11/Glx/GlxPlatformFeature.cs @@ -36,7 +36,7 @@ namespace Avalonia.X11.Glx } catch(Exception e) { - Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e); + Logger.TryGet(LogEventLevel.Error)?.Log("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e); return null; } } diff --git a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs index b42bd53619..6000b71f9d 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Converters/AvaloniaPropertyTypeConverter.cs @@ -42,7 +42,7 @@ namespace Avalonia.Markup.Xaml.Converters !property.IsAttached && !registry.IsRegistered(targetType, property)) { - Logger.Warning( + Logger.TryGet(LogEventLevel.Warning)?.Log( LogArea.Property, this, "Property '{Owner}.{Name}' is not registered on '{Type}'.", diff --git a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs index e03427c161..f7d228609d 100644 --- a/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs +++ b/src/Markup/Avalonia.Markup/Markup/Data/DelayedBinding.cs @@ -150,7 +150,7 @@ namespace Avalonia.Markup.Data } catch (Exception e) { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Property, control, "Error setting {Property} on {Target}: {Exception}", diff --git a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs index 10b89d79b8..fd2f14fc58 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/StreamGeometryContextImpl.cs @@ -85,7 +85,7 @@ namespace Avalonia.Direct2D1.Media } catch (Exception ex) { - Logger.Error( + Logger.TryGet(LogEventLevel.Error)?.Log( LogArea.Visual, this, "GeometrySink.Close exception: {Exception}", From 55e9037383df98654870186db13031ab50f27bca Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Oct 2019 10:39:50 +0200 Subject: [PATCH 064/118] Added failing test for #2980. --- .../TreeViewTests.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs index 6c9f0f4f57..a91b7a0701 100644 --- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs @@ -14,6 +14,7 @@ using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Styling; using Avalonia.UnitTests; using Xunit; @@ -892,6 +893,46 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(2, GetItem(target, 0, 1, 0).Level); } + [Fact] + public void Auto_Expanding_In_Style_Should_Not_Break_Range_Selection() + { + /// Issue #2980. + using (UnitTestApplication.Start(TestServices.RealStyler)) + { + var target = new DerivedTreeView + { + Template = CreateTreeViewTemplate(), + SelectionMode = SelectionMode.Multiple, + Items = new List + { + new Node { Value = "Root1", }, + new Node { Value = "Root2", }, + }, + }; + + var visualRoot = new TestRoot + { + Styles = + { + new Style(x => x.OfType()) + { + Setters = + { + new Setter(TreeViewItem.IsExpandedProperty, true), + }, + }, + }, + Child = target, + }; + + CreateNodeDataTemplate(target); + ApplyTemplates(target); + + _mouse.Click(GetItem(target, 0)); + _mouse.Click(GetItem(target, 1), modifiers: InputModifiers.Shift); + } + } + private void ApplyTemplates(TreeView tree) { tree.ApplyTemplate(); From 75fdd0d3432a033c0b8c93e8d583754c58a6a2e3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 7 Oct 2019 10:41:44 +0200 Subject: [PATCH 065/118] Check for empty items in GetContainerInDirection. Don't try to navigate into `TreeViewItem`s with no children: this causes a out-of-range exception. Fixes #2980 --- src/Avalonia.Controls/TreeView.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index cc47365214..8907137ecb 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -1,3 +1,4 @@ + // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. @@ -470,7 +471,7 @@ namespace Avalonia.Controls if (index > 0) { var previous = (TreeViewItem)parentGenerator.ContainerFromIndex(index - 1); - result = previous.IsExpanded ? + result = previous.IsExpanded && previous.ItemCount > 0 ? (TreeViewItem)previous.ItemContainerGenerator.ContainerFromIndex(previous.ItemCount - 1) : previous; } @@ -482,7 +483,7 @@ namespace Avalonia.Controls break; case NavigationDirection.Down: - if (from.IsExpanded && intoChildren) + if (from.IsExpanded && intoChildren && from.ItemCount > 0) { result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0); } From b7efe2037c5ac3cb9976e95d36b2285ec95d0306 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Oct 2019 00:35:20 +0200 Subject: [PATCH 066/118] Only set SizeToContent for non-set width/height. Always setting `SizeToContext = WidthAndHeight` was causing `Grid` to default to `Auto` sizing for `Star` rows/columns at design-time. Fixes #2862 --- src/Avalonia.DesignerSupport/DesignWindowLoader.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs index a7d4b96974..f3bb0edce5 100644 --- a/src/Avalonia.DesignerSupport/DesignWindowLoader.cs +++ b/src/Avalonia.DesignerSupport/DesignWindowLoader.cs @@ -69,7 +69,17 @@ namespace Avalonia.DesignerSupport } if (!window.IsSet(Window.SizeToContentProperty)) - window.SizeToContent = SizeToContent.WidthAndHeight; + { + if (double.IsNaN(window.Width)) + { + window.SizeToContent |= SizeToContent.Width; + } + + if (double.IsNaN(window.Height)) + { + window.SizeToContent |= SizeToContent.Height; + } + } } window.Show(); Design.ApplyDesignModeProperties(window, control); From d2dbc12ee8540aeb80d6e914617bfe99c14d2ce1 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Oct 2019 00:49:23 +0200 Subject: [PATCH 067/118] Final review fixes. --- src/Avalonia.Base/AvaloniaObject.cs | 36 +++++++++++------------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1d49401d99..2450f1a3a1 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -333,17 +333,12 @@ namespace Avalonia throw new ArgumentException($"The property {property.Name} is readonly."); } - if (Logger.TryGet(LogEventLevel.Verbose, out var logger)) - { - var description = GetDescription(source); - - logger.Log( - LogArea.Property, - this, - "Bound {Property} to {Binding} with priority LocalValue", - property, - description); - } + Logger.TryGet(LogEventLevel.Verbose)?.Log( + LogArea.Property, + this, + "Bound {Property} to {Binding} with priority LocalValue", + property, + GetDescription(source)); if (_directBindings == null) { @@ -354,18 +349,13 @@ namespace Avalonia } else { - if (Logger.TryGet(LogEventLevel.Verbose, out var logger)) - { - var description = GetDescription(source); - - logger.Log( - LogArea.Property, - this, - "Bound {Property} to {Binding} with priority {Priority}", - property, - description, - priority); - } + Logger.TryGet(LogEventLevel.Verbose)?.Log( + LogArea.Property, + this, + "Bound {Property} to {Binding} with priority {Priority}", + property, + GetDescription(source), + priority); return Values.AddBinding(property, source, priority); } From cb663f98b18f638ec63abaa5226cc79a6c085718 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Sun, 6 Oct 2019 19:03:05 +0200 Subject: [PATCH 068/118] Add missing IEquatable interfaces to structs. Standardize parsing error messages. Fix two instances of boxing in hash code. --- src/Avalonia.Visuals/CornerRadius.cs | 68 +++++++++++++++---- src/Avalonia.Visuals/Matrix.cs | 6 +- src/Avalonia.Visuals/Media/PixelPoint.cs | 20 ++++-- src/Avalonia.Visuals/Media/PixelRect.cs | 18 +++-- src/Avalonia.Visuals/Media/PixelSize.cs | 18 +++-- src/Avalonia.Visuals/Point.cs | 27 ++++++-- src/Avalonia.Visuals/Rect.cs | 38 ++++++++--- src/Avalonia.Visuals/RelativePoint.cs | 7 +- src/Avalonia.Visuals/RelativeRect.cs | 9 +-- src/Avalonia.Visuals/Size.cs | 26 +++++-- src/Avalonia.Visuals/Thickness.cs | 34 +++++++--- src/Avalonia.Visuals/Vector.cs | 2 +- .../VisualTree/TransformedBounds.cs | 3 +- 13 files changed, 200 insertions(+), 76 deletions(-) diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index 09163a2dac..5445c5a200 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -8,7 +8,10 @@ using Avalonia.Utilities; namespace Avalonia { - public struct CornerRadius + /// + /// Represents the radii of a rectangle's corners. + /// + public readonly struct CornerRadius : IEquatable { static CornerRadius() { @@ -33,20 +36,60 @@ namespace Avalonia BottomLeft = bottomLeft; } + /// + /// Radius of the top left corner. + /// public double TopLeft { get; } + + /// + /// Radius of the top right corner. + /// public double TopRight { get; } + + /// + /// Radius of the bottom right corner. + /// public double BottomRight { get; } + + /// + /// Radius of the bottom left corner. + /// public double BottomLeft { get; } + + /// + /// Gets a value indicating whether all corner radii are set to 0. + /// public bool IsEmpty => TopLeft.Equals(0) && IsUniform; + + /// + /// Gets a value indicating whether all corner radii are equal. + /// public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight); + /// + /// Returns a boolean indicating whether the corner radius is equal to the other given corner radius. + /// + /// The other corner radius to test equality against. + /// True if this corner radius is equal to other; False otherwise. + public bool Equals(CornerRadius other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return TopLeft == other.TopLeft && + + TopRight == other.TopRight && + BottomRight == other.BottomRight && + BottomLeft == other.BottomLeft; + // ReSharper restore CompareOfFloatsByEqualityOperator + } + public override bool Equals(object obj) { - if (obj is CornerRadius) + if (!(obj is CornerRadius)) { - return this == (CornerRadius)obj; + return false; } - return false; + + return Equals((CornerRadius)obj); } public override int GetHashCode() @@ -61,7 +104,9 @@ namespace Avalonia public static CornerRadius Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) + const string exceptionMessage = "Invalid CornerRadius."; + + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage)) { if (tokenizer.TryReadDouble(out var a)) { @@ -78,21 +123,18 @@ namespace Avalonia return new CornerRadius(a); } - throw new FormatException("Invalid CornerRadius."); + throw new FormatException(exceptionMessage); } } - public static bool operator ==(CornerRadius cr1, CornerRadius cr2) + public static bool operator ==(CornerRadius left, CornerRadius right) { - return cr1.TopLeft.Equals(cr2.TopLeft) - && cr1.TopRight.Equals(cr2.TopRight) - && cr1.BottomRight.Equals(cr2.BottomRight) - && cr1.BottomLeft.Equals(cr2.BottomLeft); + return left.Equals(right); } - public static bool operator !=(CornerRadius cr1, CornerRadius cr2) + public static bool operator !=(CornerRadius left, CornerRadius right) { - return !(cr1 == cr2); + return !(left == right); } } } diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index d083a2aaf8..6f9839b6a1 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// A 2x3 matrix. /// - public readonly struct Matrix + public readonly struct Matrix : IEquatable { private readonly double _m11; private readonly double _m12; @@ -235,12 +235,14 @@ namespace Avalonia /// True if this matrix is equal to other; False otherwise. public bool Equals(Matrix other) { + // ReSharper disable CompareOfFloatsByEqualityOperator return _m11 == other.M11 && _m12 == other.M12 && _m21 == other.M21 && _m22 == other.M22 && _m31 == other.M31 && _m32 == other.M32; + // ReSharper restore CompareOfFloatsByEqualityOperator } /// @@ -316,7 +318,7 @@ namespace Avalonia /// The . public static Matrix Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Matrix.")) { return new Matrix( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs index d62c2a2e55..5a329d0238 100644 --- a/src/Avalonia.Visuals/Media/PixelPoint.cs +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a point in device pixels. /// - public readonly struct PixelPoint + public readonly struct PixelPoint : IEquatable { /// /// A point representing 0,0. @@ -46,7 +46,7 @@ namespace Avalonia /// True if the points are equal; otherwise false. public static bool operator ==(PixelPoint left, PixelPoint right) { - return left.X == right.X && left.Y == right.Y; + return left.Equals(right); } /// @@ -120,7 +120,7 @@ namespace Avalonia /// The . public static PixelPoint Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint.")) { return new PixelPoint( tokenizer.ReadInt32(), @@ -128,6 +128,18 @@ namespace Avalonia } } + /// + /// Returns a boolean indicating whether the point is equal to the other given point. + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + public bool Equals(PixelPoint other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return X == other.X && Y == other.Y; + // ReSharper restore CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a point and an object. /// @@ -139,7 +151,7 @@ namespace Avalonia { if (obj is PixelPoint other) { - return this == other; + return Equals(other); } return false; diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs index 0e2094da07..b830f4b4b4 100644 --- a/src/Avalonia.Visuals/Media/PixelRect.cs +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a rectangle in device pixels. /// - public readonly struct PixelRect + public readonly struct PixelRect : IEquatable { /// /// An empty rectangle. @@ -148,7 +148,7 @@ namespace Avalonia /// True if the rects are equal; otherwise false. public static bool operator ==(PixelRect left, PixelRect right) { - return left.Position == right.Position && left.Size == right.Size; + return left.Equals(right); } /// @@ -196,6 +196,16 @@ namespace Avalonia rect.Height); } + /// + /// Returns a boolean indicating whether the rect is equal to the other given rect. + /// + /// The other rect to test equality against. + /// True if this rect is equal to other; False otherwise. + public bool Equals(PixelRect other) + { + return Position == other.Position && Size == other.Size; + } + /// /// Returns a boolean indicating whether the given object is equal to this rectangle. /// @@ -205,7 +215,7 @@ namespace Avalonia { if (obj is PixelRect other) { - return this == other; + return Equals(other); } return false; @@ -432,7 +442,7 @@ namespace Avalonia /// The parsed . public static PixelRect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect.")) { return new PixelRect( tokenizer.ReadInt32(), diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index b903b804f9..e2d6b46225 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -10,7 +10,7 @@ namespace Avalonia /// /// Represents a size in device pixels. /// - public readonly struct PixelSize + public readonly struct PixelSize : IEquatable { /// /// A size representing zero @@ -51,7 +51,7 @@ namespace Avalonia /// True if the sizes are equal; otherwise false. public static bool operator ==(PixelSize left, PixelSize right) { - return left.Width == right.Width && left.Height == right.Height; + return left.Equals(right); } /// @@ -72,7 +72,7 @@ namespace Avalonia /// The . public static PixelSize Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize.")) { return new PixelSize( tokenizer.ReadInt32(), @@ -80,6 +80,16 @@ namespace Avalonia } } + /// + /// Returns a boolean indicating whether the size is equal to the other given size. + /// + /// The other size to test equality against. + /// True if this size is equal to other; False otherwise. + public bool Equals(PixelSize other) + { + return Width == other.Width && Height == other.Height; + } + /// /// Checks for equality between a size and an object. /// @@ -91,7 +101,7 @@ namespace Avalonia { if (obj is PixelSize other) { - return this == other; + return Equals(other); } return false; diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 0d3e354615..0d4dcb7c73 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -1,6 +1,7 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using System.Globalization; using Avalonia.Animation.Animators; using Avalonia.Utilities; @@ -10,7 +11,7 @@ namespace Avalonia /// /// Defines a point. /// - public readonly struct Point + public readonly struct Point : IEquatable { static Point() { @@ -75,7 +76,7 @@ namespace Avalonia /// True if the points are equal; otherwise false. public static bool operator ==(Point left, Point right) { - return left.X == right.X && left.Y == right.Y; + return left.Equals(right); } /// @@ -177,7 +178,7 @@ namespace Avalonia /// The . public static Point Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Point.")) { return new Point( tokenizer.ReadDouble(), @@ -186,6 +187,19 @@ namespace Avalonia } } + /// + /// Returns a boolean indicating whether the point is equal to the other given point. + /// + /// The other point to test equality against. + /// True if this point is equal to other; False otherwise. + public bool Equals(Point other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _x == other._x && + _y == other._y; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a point and an object. /// @@ -195,13 +209,12 @@ namespace Avalonia /// public override bool Equals(object obj) { - if (obj is Point) + if (!(obj is Point)) { - var other = (Point)obj; - return X == other.X && Y == other.Y; + return false; } - return false; + return Equals((Point)obj); } /// diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 8f08f7f51f..94fd2afb3d 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a rectangle. /// - public readonly struct Rect + public readonly struct Rect : IEquatable { static Rect() { @@ -164,7 +164,9 @@ namespace Avalonia /// /// Gets a value that indicates whether the rectangle is empty. /// + // ReSharper disable CompareOfFloatsByEqualityOperator public bool IsEmpty => _width == 0 && _height == 0; + // ReSharper restore CompareOfFloatsByEqualityOperator /// /// Checks for equality between two s. @@ -174,7 +176,7 @@ namespace Avalonia /// True if the rects are equal; otherwise false. public static bool operator ==(Rect left, Rect right) { - return left.Position == right.Position && left.Size == right.Size; + return left.Equals(right); } /// @@ -297,6 +299,21 @@ namespace Avalonia Size.Deflate(thickness)); } + /// + /// Returns a boolean indicating whether the rect is equal to the other given rect. + /// + /// The other rect to test equality against. + /// True if this rect is equal to other; False otherwise. + public bool Equals(Rect other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _x == other._x && + _y == other._y && + _width == other._width && + _height == other._height; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Returns a boolean indicating whether the given object is equal to this rectangle. /// @@ -304,13 +321,12 @@ namespace Avalonia /// True if the object is equal to this rectangle; false otherwise. public override bool Equals(object obj) { - if (obj is Rect) + if (!(obj is Rect)) { - var other = (Rect)obj; - return Position == other.Position && Size == other.Size; + return false; } - return false; + return Equals((Rect)obj); } /// @@ -422,10 +438,10 @@ namespace Avalonia } else { - var x1 = Math.Min(this.X, rect.X); - var x2 = Math.Max(this.Right, rect.Right); - var y1 = Math.Min(this.Y, rect.Y); - var y2 = Math.Max(this.Bottom, rect.Bottom); + var x1 = Math.Min(X, rect.X); + var x2 = Math.Max(Right, rect.Right); + var y1 = Math.Min(Y, rect.Y); + var y2 = Math.Max(Bottom, rect.Bottom); return new Rect(new Point(x1, y1), new Point(x2, y2)); } @@ -493,7 +509,7 @@ namespace Avalonia /// The parsed . public static Rect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Rect.")) { return new Rect( tokenizer.ReadDouble(), diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index d38bb1d496..c822486767 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -130,10 +130,7 @@ namespace Avalonia { unchecked { - int hash = 17; - hash = (hash * 23) + Unit.GetHashCode(); - hash = (hash * 23) + Point.GetHashCode(); - return hash; + return (_point.GetHashCode() * 397) ^ (int)_unit; } } @@ -156,7 +153,7 @@ namespace Avalonia /// The parsed . public static RelativePoint Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid RelativePoint.")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index 927ec3ef75..c69dab3c42 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -139,10 +139,7 @@ namespace Avalonia { unchecked { - int hash = 17; - hash = (hash * 23) + Unit.GetHashCode(); - hash = (hash * 23) + Rect.GetHashCode(); - return hash; + return ((int)Unit * 397) ^ Rect.GetHashCode(); } } @@ -161,7 +158,7 @@ namespace Avalonia Rect.Width * size.Width, Rect.Height * size.Height); } - + /// /// Parses a string. /// @@ -169,7 +166,7 @@ namespace Avalonia /// The parsed . public static RelativeRect Parse(string s) { - using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect")) + using (var tokenizer = new StringTokenizer(s, exceptionMessage: "Invalid RelativeRect.")) { var x = tokenizer.ReadString(); var y = tokenizer.ReadString(); diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 782c5ea67b..9d524d6fa7 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a size. /// - public readonly struct Size + public readonly struct Size : IEquatable { static Size() { @@ -72,7 +72,7 @@ namespace Avalonia /// True if the sizes are equal; otherwise false. public static bool operator ==(Size left, Size right) { - return left._width == right._width && left._height == right._height; + return left.Equals(right); } /// @@ -158,7 +158,7 @@ namespace Avalonia /// The . public static Size Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size")) + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size.")) { return new Size( tokenizer.ReadDouble(), @@ -191,6 +191,19 @@ namespace Avalonia Math.Max(0, _height - thickness.Top - thickness.Bottom)); } + /// + /// Returns a boolean indicating whether the size is equal to the other given size. + /// + /// The other size to test equality against. + /// True if this size is equal to other; False otherwise. + public bool Equals(Size other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _width == other._width && + _height == other._height; + // ReSharper enable CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a size and an object. /// @@ -200,13 +213,12 @@ namespace Avalonia /// public override bool Equals(object obj) { - if (obj is Size) + if (!(obj is Size)) { - var other = (Size)obj; - return Width == other.Width && Height == other.Height; + return false; } - return false; + return Equals((Size)obj); } /// diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index 830ee4666e..d0fc63e254 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -3,7 +3,6 @@ using System; using System.Globalization; -using Avalonia.Animation; using Avalonia.Animation.Animators; using Avalonia.Utilities; @@ -12,7 +11,7 @@ namespace Avalonia /// /// Describes the thickness of a frame around a rectangle. /// - public readonly struct Thickness + public readonly struct Thickness : IEquatable { static Thickness() { @@ -204,7 +203,9 @@ namespace Avalonia /// The . public static Thickness Parse(string s) { - using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Thickness")) + const string exceptionMessage = "Invalid Thickness."; + + using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage)) { if (tokenizer.TryReadDouble(out var a)) { @@ -221,10 +222,25 @@ namespace Avalonia return new Thickness(a); } - throw new FormatException("Invalid Thickness."); + throw new FormatException(exceptionMessage); } } + /// + /// Returns a boolean indicating whether the thickness is equal to the other given point. + /// + /// The other thickness to test equality against. + /// True if this thickness is equal to other; False otherwise. + public bool Equals(Thickness other) + { + // ReSharper disable CompareOfFloatsByEqualityOperator + return _left == other._left && + _top == other._top && + _right == other._right && + _bottom == other._bottom; + // ReSharper restore CompareOfFloatsByEqualityOperator + } + /// /// Checks for equality between a thickness and an object. /// @@ -234,16 +250,12 @@ namespace Avalonia /// public override bool Equals(object obj) { - if (obj is Thickness) + if (!(obj is Thickness)) { - Thickness other = (Thickness)obj; - return Left == other.Left && - Top == other.Top && - Right == other.Right && - Bottom == other.Bottom; + return false; } - return false; + return Equals((Thickness)obj); } /// diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index 11bda8b00e..bd2dfdc828 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -11,7 +11,7 @@ namespace Avalonia /// /// Defines a vector. /// - public readonly struct Vector + public readonly struct Vector : IEquatable { static Vector() { diff --git a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs index 39b328adc2..5fb22680dc 100644 --- a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs @@ -1,13 +1,14 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; namespace Avalonia.VisualTree { /// /// Holds information about the bounds of a control, together with a transform and a clip. /// - public readonly struct TransformedBounds + public readonly struct TransformedBounds : IEquatable { /// /// Initializes a new instance of the struct. From 58af05644088e9590dc03d40f27abf6aa207d4fd Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Tue, 8 Oct 2019 01:01:41 +0200 Subject: [PATCH 069/118] Streamline object.Equals implementation. --- src/Avalonia.Visuals/CornerRadius.cs | 15 ++++++--------- src/Avalonia.Visuals/Matrix.cs | 10 +--------- src/Avalonia.Visuals/Media/PixelPoint.cs | 10 +--------- src/Avalonia.Visuals/Media/PixelRect.cs | 10 +--------- src/Avalonia.Visuals/Media/PixelSize.cs | 10 +--------- src/Avalonia.Visuals/Point.cs | 10 +--------- src/Avalonia.Visuals/Rect.cs | 10 +--------- src/Avalonia.Visuals/RelativePoint.cs | 5 +---- src/Avalonia.Visuals/RelativeRect.cs | 5 +---- src/Avalonia.Visuals/Size.cs | 10 +--------- src/Avalonia.Visuals/Thickness.cs | 10 +--------- src/Avalonia.Visuals/Vector.cs | 9 +-------- .../VisualTree/TransformedBounds.cs | 10 +--------- 13 files changed, 18 insertions(+), 106 deletions(-) diff --git a/src/Avalonia.Visuals/CornerRadius.cs b/src/Avalonia.Visuals/CornerRadius.cs index 5445c5a200..ecb9e75d82 100644 --- a/src/Avalonia.Visuals/CornerRadius.cs +++ b/src/Avalonia.Visuals/CornerRadius.cs @@ -82,15 +82,12 @@ namespace Avalonia // ReSharper restore CompareOfFloatsByEqualityOperator } - public override bool Equals(object obj) - { - if (!(obj is CornerRadius)) - { - return false; - } - - return Equals((CornerRadius)obj); - } + /// + /// Returns a boolean indicating whether the given Object is equal to this corner radius instance. + /// + /// The Object to compare against. + /// True if the Object is equal to this corner radius; False otherwise. + public override bool Equals(object obj) => obj is CornerRadius other && Equals(other); public override int GetHashCode() { diff --git a/src/Avalonia.Visuals/Matrix.cs b/src/Avalonia.Visuals/Matrix.cs index 6f9839b6a1..92b7dae904 100644 --- a/src/Avalonia.Visuals/Matrix.cs +++ b/src/Avalonia.Visuals/Matrix.cs @@ -250,15 +250,7 @@ namespace Avalonia /// /// The Object to compare against. /// True if the Object is equal to this matrix; False otherwise. - public override bool Equals(object obj) - { - if (!(obj is Matrix)) - { - return false; - } - - return Equals((Matrix)obj); - } + public override bool Equals(object obj) => obj is Matrix other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/Media/PixelPoint.cs b/src/Avalonia.Visuals/Media/PixelPoint.cs index 5a329d0238..5384a40be9 100644 --- a/src/Avalonia.Visuals/Media/PixelPoint.cs +++ b/src/Avalonia.Visuals/Media/PixelPoint.cs @@ -147,15 +147,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - public override bool Equals(object obj) - { - if (obj is PixelPoint other) - { - return Equals(other); - } - - return false; - } + public override bool Equals(object obj) => obj is PixelPoint other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Media/PixelRect.cs b/src/Avalonia.Visuals/Media/PixelRect.cs index b830f4b4b4..a024e1af11 100644 --- a/src/Avalonia.Visuals/Media/PixelRect.cs +++ b/src/Avalonia.Visuals/Media/PixelRect.cs @@ -211,15 +211,7 @@ namespace Avalonia /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - public override bool Equals(object obj) - { - if (obj is PixelRect other) - { - return Equals(other); - } - - return false; - } + public override bool Equals(object obj) => obj is PixelRect other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/Media/PixelSize.cs b/src/Avalonia.Visuals/Media/PixelSize.cs index e2d6b46225..a49aa82c0d 100644 --- a/src/Avalonia.Visuals/Media/PixelSize.cs +++ b/src/Avalonia.Visuals/Media/PixelSize.cs @@ -97,15 +97,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) - { - if (obj is PixelSize other) - { - return Equals(other); - } - - return false; - } + public override bool Equals(object obj) => obj is PixelSize other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Point.cs b/src/Avalonia.Visuals/Point.cs index 0d4dcb7c73..d92f8b0fc4 100644 --- a/src/Avalonia.Visuals/Point.cs +++ b/src/Avalonia.Visuals/Point.cs @@ -207,15 +207,7 @@ namespace Avalonia /// /// True if is a point that equals the current point. /// - public override bool Equals(object obj) - { - if (!(obj is Point)) - { - return false; - } - - return Equals((Point)obj); - } + public override bool Equals(object obj) => obj is Point other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs index 94fd2afb3d..4dfd641525 100644 --- a/src/Avalonia.Visuals/Rect.cs +++ b/src/Avalonia.Visuals/Rect.cs @@ -319,15 +319,7 @@ namespace Avalonia /// /// The object to compare against. /// True if the object is equal to this rectangle; false otherwise. - public override bool Equals(object obj) - { - if (!(obj is Rect)) - { - return false; - } - - return Equals((Rect)obj); - } + public override bool Equals(object obj) => obj is Rect other && Equals(other); /// /// Returns the hash code for this instance. diff --git a/src/Avalonia.Visuals/RelativePoint.cs b/src/Avalonia.Visuals/RelativePoint.cs index c822486767..2e8fb16bc1 100644 --- a/src/Avalonia.Visuals/RelativePoint.cs +++ b/src/Avalonia.Visuals/RelativePoint.cs @@ -107,10 +107,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - public override bool Equals(object obj) - { - return (obj is RelativePoint) && Equals((RelativePoint)obj); - } + public override bool Equals(object obj) => obj is RelativePoint other && Equals(other); /// /// Checks if the equals another point. diff --git a/src/Avalonia.Visuals/RelativeRect.cs b/src/Avalonia.Visuals/RelativeRect.cs index c69dab3c42..d2e4b2dc26 100644 --- a/src/Avalonia.Visuals/RelativeRect.cs +++ b/src/Avalonia.Visuals/RelativeRect.cs @@ -116,10 +116,7 @@ namespace Avalonia /// /// The other object. /// True if the objects are equal, otherwise false. - public override bool Equals(object obj) - { - return (obj is RelativeRect) && Equals((RelativeRect)obj); - } + public override bool Equals(object obj) => obj is RelativeRect other && Equals(other); /// /// Checks if the equals another rectangle. diff --git a/src/Avalonia.Visuals/Size.cs b/src/Avalonia.Visuals/Size.cs index 9d524d6fa7..aba2ed8d62 100644 --- a/src/Avalonia.Visuals/Size.cs +++ b/src/Avalonia.Visuals/Size.cs @@ -211,15 +211,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) - { - if (!(obj is Size)) - { - return false; - } - - return Equals((Size)obj); - } + public override bool Equals(object obj) => obj is Size other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Thickness.cs b/src/Avalonia.Visuals/Thickness.cs index d0fc63e254..44ff66069f 100644 --- a/src/Avalonia.Visuals/Thickness.cs +++ b/src/Avalonia.Visuals/Thickness.cs @@ -248,15 +248,7 @@ namespace Avalonia /// /// True if is a size that equals the current size. /// - public override bool Equals(object obj) - { - if (!(obj is Thickness)) - { - return false; - } - - return Equals((Thickness)obj); - } + public override bool Equals(object obj) => obj is Thickness other && Equals(other); /// /// Returns a hash code for a . diff --git a/src/Avalonia.Visuals/Vector.cs b/src/Avalonia.Visuals/Vector.cs index bd2dfdc828..576d2daaaa 100644 --- a/src/Avalonia.Visuals/Vector.cs +++ b/src/Avalonia.Visuals/Vector.cs @@ -138,7 +138,6 @@ namespace Avalonia /// /// The other vector. /// True if vectors are nearly equal. - [Pure] public bool NearlyEquals(Vector other) { const float tolerance = float.Epsilon; @@ -146,13 +145,7 @@ namespace Avalonia return Math.Abs(_x - other._x) < tolerance && Math.Abs(_y - other._y) < tolerance; } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - - return obj is Vector vector && Equals(vector); - } + public override bool Equals(object obj) => obj is Vector other && Equals(other); public override int GetHashCode() { diff --git a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs index 5fb22680dc..b2121aa8da 100644 --- a/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs +++ b/src/Avalonia.Visuals/VisualTree/TransformedBounds.cs @@ -57,15 +57,7 @@ namespace Avalonia.VisualTree return Bounds == other.Bounds && Clip == other.Clip && Transform == other.Transform; } - public override bool Equals(object obj) - { - if (obj is null) - { - return false; - } - - return obj is TransformedBounds other && Equals(other); - } + public override bool Equals(object obj) => obj is TransformedBounds other && Equals(other); public override int GetHashCode() { From d600ffcb7e3f6565770e1716c104067382c680c6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 8 Oct 2019 14:33:06 +0200 Subject: [PATCH 070/118] Added Window.OnClosed and sealed HandleClosed. --- src/Avalonia.Controls/TopLevel.cs | 8 +++++++- src/Avalonia.Controls/Window.cs | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index c54ebd5360..293809bf51 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -271,7 +271,7 @@ namespace Avalonia.Controls { (this as IInputRoot).MouseDevice?.TopLevelClosed(this); PlatformImpl = null; - Closed?.Invoke(this, EventArgs.Empty); + OnClosed(EventArgs.Empty); Renderer?.Dispose(); Renderer = null; } @@ -317,6 +317,12 @@ namespace Avalonia.Controls /// The event args. protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e); + /// + /// Raises the event. + /// + /// The event args. + protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e); + /// /// Tries to get a service from an , logging a /// warning if not found. diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ef43746665..ec2fca0db5 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -557,7 +557,7 @@ namespace Avalonia.Controls return result; } - protected override void HandleClosed() + protected sealed override void HandleClosed() { RaiseEvent(new RoutedEventArgs(WindowClosedEvent)); @@ -565,7 +565,7 @@ namespace Avalonia.Controls } /// - protected override void HandleResized(Size clientSize) + protected sealed override void HandleResized(Size clientSize) { if (!AutoSizing) { From 918af64ea4ad430b146451d7e34ad54c5bfd54f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Tue, 8 Oct 2019 21:10:48 +0100 Subject: [PATCH 071/118] Added missing AffectsMeasure to UniformGrid. --- src/Avalonia.Controls/Primitives/UniformGrid.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/UniformGrid.cs b/src/Avalonia.Controls/Primitives/UniformGrid.cs index f3580eee10..09554412db 100644 --- a/src/Avalonia.Controls/Primitives/UniformGrid.cs +++ b/src/Avalonia.Controls/Primitives/UniformGrid.cs @@ -28,6 +28,11 @@ namespace Avalonia.Controls.Primitives private int _rows; private int _columns; + static UniformGrid() + { + AffectsMeasure(RowsProperty, ColumnsProperty, FirstColumnProperty); + } + /// /// Specifies the row count. If set to 0, row count will be calculated automatically. /// From a2c6bc1b7cb052d2cd2b746d32c290e6c2e6232d Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Wed, 9 Oct 2019 00:06:39 +0200 Subject: [PATCH 072/118] Remove usage of AddClassHandler that returns new lambda per invocation. --- src/Avalonia.Base/AvaloniaObjectExtensions.cs | 9 ++-- src/Avalonia.Controls.DataGrid/DataGrid.cs | 46 +++++++++---------- .../DataGridCell.cs | 4 +- .../DataGridColumnHeader.cs | 2 +- src/Avalonia.Controls.DataGrid/DataGridRow.cs | 8 ++-- .../DataGridRowGroupHeader.cs | 4 +- src/Avalonia.Controls/AutoCompleteBox.cs | 18 ++++---- src/Avalonia.Controls/Calendar/Calendar.cs | 23 +++++----- src/Avalonia.Controls/Calendar/DatePicker.cs | 16 +++---- src/Avalonia.Controls/ComboBox.cs | 4 +- src/Avalonia.Controls/ContentControl.cs | 2 +- src/Avalonia.Controls/DataValidationErrors.cs | 2 +- src/Avalonia.Controls/Decorator.cs | 2 +- src/Avalonia.Controls/Expander.cs | 2 +- src/Avalonia.Controls/ItemsControl.cs | 4 +- .../LayoutTransformControl.cs | 8 ++-- src/Avalonia.Controls/MenuBase.cs | 2 +- src/Avalonia.Controls/MenuItem.cs | 12 ++--- .../Presenters/CarouselPresenter.cs | 4 +- .../Presenters/ContentPresenter.cs | 6 +-- .../Presenters/ItemsPresenter.cs | 2 +- .../Presenters/ItemsPresenterBase.cs | 2 +- .../Presenters/ScrollContentPresenter.cs | 2 +- .../Primitives/HeaderedContentControl.cs | 2 +- .../Primitives/HeaderedItemsControl.cs | 2 +- .../HeaderedSelectingItemsControl.cs | 2 +- src/Avalonia.Controls/Primitives/Popup.cs | 4 +- src/Avalonia.Controls/Primitives/ScrollBar.cs | 4 +- .../Primitives/SelectingItemsControl.cs | 2 +- .../Primitives/TemplatedControl.cs | 2 +- src/Avalonia.Controls/Primitives/Thumb.cs | 6 +-- src/Avalonia.Controls/Primitives/Track.cs | 6 +-- src/Avalonia.Controls/ProgressBar.cs | 5 +- src/Avalonia.Controls/ScrollViewer.cs | 4 +- src/Avalonia.Controls/Slider.cs | 6 +-- src/Avalonia.Controls/TabItem.cs | 4 +- src/Avalonia.Controls/TreeViewItem.cs | 2 +- src/Avalonia.Controls/WindowBase.cs | 2 +- src/Avalonia.Input/InputElement.cs | 24 +++++----- src/Avalonia.Interactivity/RoutedEvent.cs | 30 ++++++++---- src/Avalonia.Styling/StyledElement.cs | 2 +- src/Avalonia.Visuals/Media/Geometry.cs | 2 +- .../InteractiveTests.cs | 2 +- 43 files changed, 156 insertions(+), 141 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs index 393482cccf..ad1cefd4ea 100644 --- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs +++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs @@ -192,9 +192,9 @@ namespace Avalonia { return observable.Subscribe(e => { - if (e.Sender is TTarget) + if (e.Sender is TTarget target) { - action((TTarget)e.Sender, e); + action(target, e); } }); } @@ -207,6 +207,7 @@ namespace Avalonia /// The property changed observable. /// Given a TTarget, returns the handler. /// A disposable that can be used to terminate the subscription. + [Obsolete("Use overload taking Action.")] public static IDisposable AddClassHandler( this IObservable observable, Func> handler) @@ -238,9 +239,7 @@ namespace Avalonia Func> handler) where TTarget : class { - var target = e.Sender as TTarget; - - if (target != null) + if (e.Sender is TTarget target) { handler(target)(e); } diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index 490a724eda..a6aaed1e80 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -723,29 +723,29 @@ namespace Avalonia.Controls PseudoClass(IsValidProperty, x => !x, ":invalid"); - ItemsProperty.Changed.AddClassHandler(x => x.OnItemsPropertyChanged); - CanUserResizeColumnsProperty.Changed.AddClassHandler(x => x.OnCanUserResizeColumnsChanged); - ColumnWidthProperty.Changed.AddClassHandler(x => x.OnColumnWidthChanged); - RowBackgroundProperty.Changed.AddClassHandler(x => x.OnRowBackgroundChanged); - AlternatingRowBackgroundProperty.Changed.AddClassHandler(x => x.OnRowBackgroundChanged); - FrozenColumnCountProperty.Changed.AddClassHandler(x => x.OnFrozenColumnCountChanged); - GridLinesVisibilityProperty.Changed.AddClassHandler(x => x.OnGridLinesVisibilityChanged); - HeadersVisibilityProperty.Changed.AddClassHandler(x => x.OnHeadersVisibilityChanged); - HorizontalGridLinesBrushProperty.Changed.AddClassHandler(x => x.OnHorizontalGridLinesBrushChanged); - IsReadOnlyProperty.Changed.AddClassHandler(x => x.OnIsReadOnlyChanged); - MaxColumnWidthProperty.Changed.AddClassHandler(x => x.OnMaxColumnWidthChanged); - MinColumnWidthProperty.Changed.AddClassHandler(x => x.OnMinColumnWidthChanged); - RowHeightProperty.Changed.AddClassHandler(x => x.OnRowHeightChanged); - RowHeaderWidthProperty.Changed.AddClassHandler(x => x.OnRowHeaderWidthChanged); - SelectionModeProperty.Changed.AddClassHandler(x => x.OnSelectionModeChanged); - VerticalGridLinesBrushProperty.Changed.AddClassHandler(x => x.OnVerticalGridLinesBrushChanged); - SelectedIndexProperty.Changed.AddClassHandler(x => x.OnSelectedIndexChanged); - SelectedItemProperty.Changed.AddClassHandler(x => x.OnSelectedItemChanged); - IsEnabledProperty.Changed.AddClassHandler(x => x.DataGrid_IsEnabledChanged); - AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler(x => x.OnAreRowGroupHeadersFrozenChanged); - RowDetailsTemplateProperty.Changed.AddClassHandler(x => x.OnRowDetailsTemplateChanged); - RowDetailsVisibilityModeProperty.Changed.AddClassHandler(x => x.OnRowDetailsVisibilityModeChanged); - AutoGenerateColumnsProperty.Changed.AddClassHandler(x => x.OnAutoGenerateColumnsChanged); + ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); + CanUserResizeColumnsProperty.Changed.AddClassHandler((x,e) => x.OnCanUserResizeColumnsChanged(e)); + ColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnColumnWidthChanged(e)); + RowBackgroundProperty.Changed.AddClassHandler((x,e) => x.OnRowBackgroundChanged(e)); + AlternatingRowBackgroundProperty.Changed.AddClassHandler((x,e) => x.OnRowBackgroundChanged(e)); + FrozenColumnCountProperty.Changed.AddClassHandler((x,e) => x.OnFrozenColumnCountChanged(e)); + GridLinesVisibilityProperty.Changed.AddClassHandler((x,e) => x.OnGridLinesVisibilityChanged(e)); + HeadersVisibilityProperty.Changed.AddClassHandler((x,e) => x.OnHeadersVisibilityChanged(e)); + HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x,e) => x.OnHorizontalGridLinesBrushChanged(e)); + IsReadOnlyProperty.Changed.AddClassHandler((x,e) => x.OnIsReadOnlyChanged(e)); + MaxColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnMaxColumnWidthChanged(e)); + MinColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnMinColumnWidthChanged(e)); + RowHeightProperty.Changed.AddClassHandler((x,e) => x.OnRowHeightChanged(e)); + RowHeaderWidthProperty.Changed.AddClassHandler((x,e) => x.OnRowHeaderWidthChanged(e)); + SelectionModeProperty.Changed.AddClassHandler((x,e) => x.OnSelectionModeChanged(e)); + VerticalGridLinesBrushProperty.Changed.AddClassHandler((x,e) => x.OnVerticalGridLinesBrushChanged(e)); + SelectedIndexProperty.Changed.AddClassHandler((x,e) => x.OnSelectedIndexChanged(e)); + SelectedItemProperty.Changed.AddClassHandler((x,e) => x.OnSelectedItemChanged(e)); + IsEnabledProperty.Changed.AddClassHandler((x,e) => x.DataGrid_IsEnabledChanged(e)); + AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x,e) => x.OnAreRowGroupHeadersFrozenChanged(e)); + RowDetailsTemplateProperty.Changed.AddClassHandler((x,e) => x.OnRowDetailsTemplateChanged(e)); + RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x,e) => x.OnRowDetailsVisibilityModeChanged(e)); + AutoGenerateColumnsProperty.Changed.AddClassHandler((x,e) => x.OnAutoGenerateColumnsChanged(e)); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs index a21583b38e..e56c534f50 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls static DataGridCell() { PointerPressedEvent.AddClassHandler( - x => x.DataGridCell_PointerPressed, handledEventsToo: true); + (x,e) => x.DataGridCell_PointerPressed(e), handledEventsToo: true); } public DataGridCell() { } @@ -219,4 +219,4 @@ namespace Avalonia.Controls } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 6cb0807e29..4c77c8b5ae 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -67,7 +67,7 @@ namespace Avalonia.Controls static DataGridColumnHeader() { - AreSeparatorsVisibleProperty.Changed.AddClassHandler(x => x.OnAreSeparatorsVisibleChanged); + AreSeparatorsVisibleProperty.Changed.AddClassHandler((x,e) => x.OnAreSeparatorsVisibleChanged(e)); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs index 04a1575486..c9924660be 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs @@ -116,10 +116,10 @@ namespace Avalonia.Controls static DataGridRow() { - HeaderProperty.Changed.AddClassHandler(x => x.OnHeaderChanged); - DetailsTemplateProperty.Changed.AddClassHandler(x => x.OnDetailsTemplateChanged); - AreDetailsVisibleProperty.Changed.AddClassHandler(x => x.OnAreDetailsVisibleChanged); - PointerPressedEvent.AddClassHandler(x => x.DataGridRow_PointerPressed, handledEventsToo: true); + HeaderProperty.Changed.AddClassHandler((x, e) => x.OnHeaderChanged(e)); + DetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnDetailsTemplateChanged(e)); + AreDetailsVisibleProperty.Changed.AddClassHandler((x, e) => x.OnAreDetailsVisibleChanged(e)); + PointerPressedEvent.AddClassHandler((x, e) => x.DataGridRow_PointerPressed(e), handledEventsToo: true); } /// diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs index 716997f62c..7dafef9d8b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs @@ -109,7 +109,7 @@ namespace Avalonia.Controls static DataGridRowGroupHeader() { - SublevelIndentProperty.Changed.AddClassHandler(x => x.OnSublevelIndentChanged); + SublevelIndentProperty.Changed.AddClassHandler((x,e) => x.OnSublevelIndentChanged(e)); } /// @@ -446,4 +446,4 @@ namespace Avalonia.Controls } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs index 1e2fc9f9d0..ce4358648b 100644 --- a/src/Avalonia.Controls/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox.cs @@ -805,15 +805,15 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(true); - MinimumPopulateDelayProperty.Changed.AddClassHandler(x => x.OnMinimumPopulateDelayChanged); - IsDropDownOpenProperty.Changed.AddClassHandler(x => x.OnIsDropDownOpenChanged); - SelectedItemProperty.Changed.AddClassHandler(x => x.OnSelectedItemPropertyChanged); - TextProperty.Changed.AddClassHandler(x => x.OnTextPropertyChanged); - SearchTextProperty.Changed.AddClassHandler(x => x.OnSearchTextPropertyChanged); - FilterModeProperty.Changed.AddClassHandler(x => x.OnFilterModePropertyChanged); - ItemFilterProperty.Changed.AddClassHandler(x => x.OnItemFilterPropertyChanged); - ItemsProperty.Changed.AddClassHandler(x => x.OnItemsPropertyChanged); - IsEnabledProperty.Changed.AddClassHandler(x => x.OnControlIsEnabledChanged); + MinimumPopulateDelayProperty.Changed.AddClassHandler((x,e) => x.OnMinimumPopulateDelayChanged(e)); + IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e)); + SelectedItemProperty.Changed.AddClassHandler((x,e) => x.OnSelectedItemPropertyChanged(e)); + TextProperty.Changed.AddClassHandler((x,e) => x.OnTextPropertyChanged(e)); + SearchTextProperty.Changed.AddClassHandler((x,e) => x.OnSearchTextPropertyChanged(e)); + FilterModeProperty.Changed.AddClassHandler((x,e) => x.OnFilterModePropertyChanged(e)); + ItemFilterProperty.Changed.AddClassHandler((x,e) => x.OnItemFilterPropertyChanged(e)); + ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); + IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnControlIsEnabledChanged(e)); } /// diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 56805e1d6a..89b375996b 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -2057,18 +2057,17 @@ namespace Avalonia.Controls static Calendar() { - IsEnabledProperty.Changed.AddClassHandler(x => x.OnIsEnabledChanged); - FirstDayOfWeekProperty.Changed.AddClassHandler(x => x.OnFirstDayOfWeekChanged); - IsTodayHighlightedProperty.Changed.AddClassHandler(x => x.OnIsTodayHighlightedChanged); - DisplayModeProperty.Changed.AddClassHandler(x => x.OnDisplayModePropertyChanged); - SelectionModeProperty.Changed.AddClassHandler(x => x.OnSelectionModeChanged); - SelectedDateProperty.Changed.AddClassHandler(x => x.OnSelectedDateChanged); - DisplayDateProperty.Changed.AddClassHandler(x => x.OnDisplayDateChanged); - DisplayDateStartProperty.Changed.AddClassHandler(x => x.OnDisplayDateStartChanged); - DisplayDateEndProperty.Changed.AddClassHandler(x => x.OnDisplayDateEndChanged); - KeyDownEvent.AddClassHandler(x => x.Calendar_KeyDown); - KeyUpEvent.AddClassHandler(x => x.Calendar_KeyUp); - + IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnIsEnabledChanged(e)); + FirstDayOfWeekProperty.Changed.AddClassHandler((x,e) => x.OnFirstDayOfWeekChanged(e)); + IsTodayHighlightedProperty.Changed.AddClassHandler((x,e) => x.OnIsTodayHighlightedChanged(e)); + DisplayModeProperty.Changed.AddClassHandler((x,e) => x.OnDisplayModePropertyChanged(e)); + SelectionModeProperty.Changed.AddClassHandler((x,e) => x.OnSelectionModeChanged(e)); + SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e)); + DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e)); + DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e)); + DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e)); + KeyDownEvent.AddClassHandler((x,e) => x.Calendar_KeyDown(e)); + KeyUpEvent.AddClassHandler((x,e) => x.Calendar_KeyUp(e)); } /// diff --git a/src/Avalonia.Controls/Calendar/DatePicker.cs b/src/Avalonia.Controls/Calendar/DatePicker.cs index 55797ae1e3..841b73cd92 100644 --- a/src/Avalonia.Controls/Calendar/DatePicker.cs +++ b/src/Avalonia.Controls/Calendar/DatePicker.cs @@ -393,14 +393,14 @@ namespace Avalonia.Controls { FocusableProperty.OverrideDefaultValue(true); - DisplayDateProperty.Changed.AddClassHandler(x => x.OnDisplayDateChanged); - DisplayDateStartProperty.Changed.AddClassHandler(x => x.OnDisplayDateStartChanged); - DisplayDateEndProperty.Changed.AddClassHandler(x => x.OnDisplayDateEndChanged); - IsDropDownOpenProperty.Changed.AddClassHandler(x => x.OnIsDropDownOpenChanged); - SelectedDateProperty.Changed.AddClassHandler(x => x.OnSelectedDateChanged); - SelectedDateFormatProperty.Changed.AddClassHandler(x => x.OnSelectedDateFormatChanged); - CustomDateFormatStringProperty.Changed.AddClassHandler(x => x.OnCustomDateFormatStringChanged); - TextProperty.Changed.AddClassHandler(x => x.OnTextChanged); + DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e)); + DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e)); + DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e)); + IsDropDownOpenProperty.Changed.AddClassHandler((x,e) => x.OnIsDropDownOpenChanged(e)); + SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e)); + SelectedDateFormatProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateFormatChanged(e)); + CustomDateFormatStringProperty.Changed.AddClassHandler((x,e) => x.OnCustomDateFormatStringChanged(e)); + TextProperty.Changed.AddClassHandler((x,e) => x.OnTextChanged(e)); } /// /// Initializes a new instance of the diff --git a/src/Avalonia.Controls/ComboBox.cs b/src/Avalonia.Controls/ComboBox.cs index a70d26624c..c2cf20b32d 100644 --- a/src/Avalonia.Controls/ComboBox.cs +++ b/src/Avalonia.Controls/ComboBox.cs @@ -65,8 +65,8 @@ namespace Avalonia.Controls { ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); - SelectedItemProperty.Changed.AddClassHandler(x => x.SelectedItemChanged); - KeyDownEvent.AddClassHandler(x => x.OnKeyDown, Interactivity.RoutingStrategies.Tunnel); + SelectedItemProperty.Changed.AddClassHandler((x,e) => x.SelectedItemChanged(e)); + KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e), Interactivity.RoutingStrategies.Tunnel); } /// diff --git a/src/Avalonia.Controls/ContentControl.cs b/src/Avalonia.Controls/ContentControl.cs index 02d7890404..bb3cc4585b 100644 --- a/src/Avalonia.Controls/ContentControl.cs +++ b/src/Avalonia.Controls/ContentControl.cs @@ -43,7 +43,7 @@ namespace Avalonia.Controls static ContentControl() { - ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); + ContentProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e)); } /// diff --git a/src/Avalonia.Controls/DataValidationErrors.cs b/src/Avalonia.Controls/DataValidationErrors.cs index 50b387e636..2588b7cc11 100644 --- a/src/Avalonia.Controls/DataValidationErrors.cs +++ b/src/Avalonia.Controls/DataValidationErrors.cs @@ -56,7 +56,7 @@ namespace Avalonia.Controls { ErrorsProperty.Changed.Subscribe(ErrorsChanged); HasErrorsProperty.Changed.Subscribe(HasErrorsChanged); - TemplatedParentProperty.Changed.AddClassHandler(x => x.OnTemplatedParentChange); + TemplatedParentProperty.Changed.AddClassHandler((x, e) => x.OnTemplatedParentChange(e)); } private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e) diff --git a/src/Avalonia.Controls/Decorator.cs b/src/Avalonia.Controls/Decorator.cs index 15651b918e..6f16870b9f 100644 --- a/src/Avalonia.Controls/Decorator.cs +++ b/src/Avalonia.Controls/Decorator.cs @@ -29,7 +29,7 @@ namespace Avalonia.Controls static Decorator() { AffectsMeasure(ChildProperty, PaddingProperty); - ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); + ChildProperty.Changed.AddClassHandler((x, e) => x.ChildChanged(e)); } /// diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 1fa9798784..b2a442b6cc 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -37,7 +37,7 @@ namespace Avalonia.Controls PseudoClass(IsExpandedProperty, ":expanded"); - IsExpandedProperty.Changed.AddClassHandler(x => x.OnIsExpandedChanged); + IsExpandedProperty.Changed.AddClassHandler((x, e) => x.OnIsExpandedChanged(e)); } public IPageTransition ContentTransition diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 0fe7291835..558f496ede 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -64,8 +64,8 @@ namespace Avalonia.Controls /// static ItemsControl() { - ItemsProperty.Changed.AddClassHandler(x => x.ItemsChanged); - ItemTemplateProperty.Changed.AddClassHandler(x => x.ItemTemplateChanged); + ItemsProperty.Changed.AddClassHandler((x, e) => x.ItemsChanged(e)); + ItemTemplateProperty.Changed.AddClassHandler((x, e) => x.ItemTemplateChanged(e)); } /// diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index 1430c39c76..db67a24159 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -28,11 +28,13 @@ namespace Avalonia.Controls ClipToBoundsProperty.OverrideDefaultValue(true); LayoutTransformProperty.Changed - .AddClassHandler(x => x.OnLayoutTransformChanged); + .AddClassHandler((x, e) => x.OnLayoutTransformChanged(e)); ChildProperty.Changed - .AddClassHandler(x => x.OnChildChanged); - UseRenderTransformProperty.Changed.AddClassHandler(x => x.OnUseRenderTransformPropertyChanged); + .AddClassHandler((x, e) => x.OnChildChanged(e)); + + UseRenderTransformProperty.Changed + .AddClassHandler((x, e) => x.OnUseRenderTransformPropertyChanged(e)); } /// diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index 8eed58bb4d..be677b5479 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -64,7 +64,7 @@ namespace Avalonia.Controls /// static MenuBase() { - MenuItem.SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); + MenuItem.SubmenuOpenedEvent.AddClassHandler((x, e) => x.OnSubmenuOpened(e)); } /// diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 33a708b6a5..8c82fed58d 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -102,13 +102,13 @@ namespace Avalonia.Controls SelectableMixin.Attach(IsSelectedProperty); CommandProperty.Changed.Subscribe(CommandChanged); FocusableProperty.OverrideDefaultValue(true); - HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); - IconProperty.Changed.AddClassHandler(x => x.IconChanged); - IsSelectedProperty.Changed.AddClassHandler(x => x.IsSelectedChanged); + HeaderProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); + IconProperty.Changed.AddClassHandler((x, e) => x.IconChanged(e)); + IsSelectedProperty.Changed.AddClassHandler((x, e) => x.IsSelectedChanged(e)); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); - ClickEvent.AddClassHandler(x => x.OnClick); - SubmenuOpenedEvent.AddClassHandler(x => x.OnSubmenuOpened); - IsSubMenuOpenProperty.Changed.AddClassHandler(x => x.SubMenuOpenChanged); + ClickEvent.AddClassHandler((x, e) => x.OnClick(e)); + SubmenuOpenedEvent.AddClassHandler((x, e) => x.OnSubmenuOpened(e)); + IsSubMenuOpenProperty.Changed.AddClassHandler((x, e) => x.SubMenuOpenChanged(e)); } public MenuItem() diff --git a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs index dedab3e43e..89de24a81a 100644 --- a/src/Avalonia.Controls/Presenters/CarouselPresenter.cs +++ b/src/Avalonia.Controls/Presenters/CarouselPresenter.cs @@ -46,8 +46,8 @@ namespace Avalonia.Controls.Presenters /// static CarouselPresenter() { - IsVirtualizedProperty.Changed.AddClassHandler(x => x.IsVirtualizedChanged); - SelectedIndexProperty.Changed.AddClassHandler(x => x.SelectedIndexChanged); + IsVirtualizedProperty.Changed.AddClassHandler((x, e) => x.IsVirtualizedChanged(e)); + SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.SelectedIndexChanged(e)); } /// diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs index a5374e7c5a..5e1a844720 100644 --- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs @@ -94,9 +94,9 @@ namespace Avalonia.Controls.Presenters { AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty); AffectsMeasure(BorderThicknessProperty, PaddingProperty); - ContentProperty.Changed.AddClassHandler(x => x.ContentChanged); - ContentTemplateProperty.Changed.AddClassHandler(x => x.ContentChanged); - TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); + ContentProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e)); + ContentTemplateProperty.Changed.AddClassHandler((x, e) => x.ContentChanged(e)); + TemplatedParentProperty.Changed.AddClassHandler((x, e) => x.TemplatedParentChanged(e)); } /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 500c7aa187..7a5451821e 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Presenters KeyboardNavigationMode.Once); VirtualizationModeProperty.Changed - .AddClassHandler(x => x.VirtualizationModeChanged); + .AddClassHandler((x,e) => x.VirtualizationModeChanged(e)); } /// diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs index ea56a0c6fc..0f0cdc37cf 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs @@ -45,7 +45,7 @@ namespace Avalonia.Controls.Presenters /// static ItemsPresenterBase() { - TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); + TemplatedParentProperty.Changed.AddClassHandler((x,e) => x.TemplatedParentChanged(e)); } /// diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index ec6a228421..6fffc3741a 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls.Presenters static ScrollContentPresenter() { ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); - ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); + ChildProperty.Changed.AddClassHandler((x,e) => x.ChildChanged(e)); AffectsArrange(OffsetProperty); } diff --git a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs index 3cf50a7b80..d431420a8f 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedContentControl.cs @@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedContentControl() { - ContentProperty.Changed.AddClassHandler(x => x.HeaderChanged); + ContentProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); } /// diff --git a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs index e0eb0b005f..f4af694f28 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedItemsControl() { - HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); + HeaderProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); } /// diff --git a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs index 533b643ea6..5e053ed9b4 100644 --- a/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs @@ -24,7 +24,7 @@ namespace Avalonia.Controls.Primitives /// static HeaderedSelectingItemsControl() { - HeaderProperty.Changed.AddClassHandler(x => x.HeaderChanged); + HeaderProperty.Changed.AddClassHandler((x, e) => x.HeaderChanged(e)); } /// diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b3f86e8a76..77febf9384 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -93,8 +93,8 @@ namespace Avalonia.Controls.Primitives static Popup() { IsHitTestVisibleProperty.OverrideDefaultValue(false); - ChildProperty.Changed.AddClassHandler(x => x.ChildChanged); - IsOpenProperty.Changed.AddClassHandler(x => x.IsOpenChanged); + ChildProperty.Changed.AddClassHandler((x, e) => x.ChildChanged(e)); + IsOpenProperty.Changed.AddClassHandler((x, e) => x.IsOpenChanged(e)); } public Popup() diff --git a/src/Avalonia.Controls/Primitives/ScrollBar.cs b/src/Avalonia.Controls/Primitives/ScrollBar.cs index c6119e89dc..9251ca273f 100644 --- a/src/Avalonia.Controls/Primitives/ScrollBar.cs +++ b/src/Avalonia.Controls/Primitives/ScrollBar.cs @@ -58,8 +58,8 @@ namespace Avalonia.Controls.Primitives PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical"); PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); - Thumb.DragDeltaEvent.AddClassHandler(o => o.OnThumbDragDelta, RoutingStrategies.Bubble); - Thumb.DragCompletedEvent.AddClassHandler(o => o.OnThumbDragComplete, RoutingStrategies.Bubble); + Thumb.DragDeltaEvent.AddClassHandler((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble); + Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragComplete(e), RoutingStrategies.Bubble); } /// diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 6869ea0822..b752b3f7a8 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -119,7 +119,7 @@ namespace Avalonia.Controls.Primitives /// static SelectingItemsControl() { - IsSelectedChangedEvent.AddClassHandler(x => x.ContainerSelectionChanged); + IsSelectedChangedEvent.AddClassHandler((x,e) => x.ContainerSelectionChanged(e)); } /// diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 47c3240374..ba4c6830d1 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -99,7 +99,7 @@ namespace Avalonia.Controls.Primitives static TemplatedControl() { ClipToBoundsProperty.OverrideDefaultValue(true); - TemplateProperty.Changed.AddClassHandler(x => x.OnTemplateChanged); + TemplateProperty.Changed.AddClassHandler((x, e) => x.OnTemplateChanged(e)); } /// diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index b01ddd5dba..7e9680dc9f 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -22,9 +22,9 @@ namespace Avalonia.Controls.Primitives static Thumb() { - DragStartedEvent.AddClassHandler(x => x.OnDragStarted, RoutingStrategies.Bubble); - DragDeltaEvent.AddClassHandler(x => x.OnDragDelta, RoutingStrategies.Bubble); - DragCompletedEvent.AddClassHandler(x => x.OnDragCompleted, RoutingStrategies.Bubble); + DragStartedEvent.AddClassHandler((x,e) => x.OnDragStarted(e), RoutingStrategies.Bubble); + DragDeltaEvent.AddClassHandler((x, e) => x.OnDragDelta(e), RoutingStrategies.Bubble); + DragCompletedEvent.AddClassHandler((x, e) => x.OnDragCompleted(e), RoutingStrategies.Bubble); } public event EventHandler DragStarted diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index a569808b35..292c65aa06 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -48,9 +48,9 @@ namespace Avalonia.Controls.Primitives { PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical"); PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); - ThumbProperty.Changed.AddClassHandler(x => x.ThumbChanged); - IncreaseButtonProperty.Changed.AddClassHandler(x => x.ButtonChanged); - DecreaseButtonProperty.Changed.AddClassHandler(x => x.ButtonChanged); + ThumbProperty.Changed.AddClassHandler((x,e) => x.ThumbChanged(e)); + IncreaseButtonProperty.Changed.AddClassHandler((x, e) => x.ButtonChanged(e)); + DecreaseButtonProperty.Changed.AddClassHandler((x, e) => x.ButtonChanged(e)); AffectsArrange(MinimumProperty, MaximumProperty, ValueProperty, OrientationProperty); } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 29e3a17f74..8963ceeddf 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. +using System; using Avalonia.Controls.Primitives; using Avalonia.Layout; @@ -38,8 +39,8 @@ namespace Avalonia.Controls PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); PseudoClass(IsIndeterminateProperty, ":indeterminate"); - ValueProperty.Changed.AddClassHandler(x => x.UpdateIndicatorWhenPropChanged); - IsIndeterminateProperty.Changed.AddClassHandler(x => x.UpdateIndicatorWhenPropChanged); + ValueProperty.Changed.AddClassHandler((x,e) => x.UpdateIndicatorWhenPropChanged(e)); + IsIndeterminateProperty.Changed.AddClassHandler((x,e) => x.UpdateIndicatorWhenPropChanged(e)); } public bool IsIndeterminate diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs index c9b5cbb75b..cdf5010920 100644 --- a/src/Avalonia.Controls/ScrollViewer.cs +++ b/src/Avalonia.Controls/ScrollViewer.cs @@ -163,8 +163,8 @@ namespace Avalonia.Controls { AffectsValidation(ExtentProperty, OffsetProperty); AffectsValidation(ViewportProperty, OffsetProperty); - HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler(x => x.ScrollBarVisibilityChanged); - VerticalScrollBarVisibilityProperty.Changed.AddClassHandler(x => x.ScrollBarVisibilityChanged); + HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); + VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e)); } /// diff --git a/src/Avalonia.Controls/Slider.cs b/src/Avalonia.Controls/Slider.cs index 9eaa246434..f71be8d836 100644 --- a/src/Avalonia.Controls/Slider.cs +++ b/src/Avalonia.Controls/Slider.cs @@ -45,9 +45,9 @@ namespace Avalonia.Controls OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal); PseudoClass(OrientationProperty, o => o == Orientation.Vertical, ":vertical"); PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); - Thumb.DragStartedEvent.AddClassHandler(x => x.OnThumbDragStarted, RoutingStrategies.Bubble); - Thumb.DragDeltaEvent.AddClassHandler(x => x.OnThumbDragDelta, RoutingStrategies.Bubble); - Thumb.DragCompletedEvent.AddClassHandler(x => x.OnThumbDragCompleted, RoutingStrategies.Bubble); + Thumb.DragStartedEvent.AddClassHandler((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble); + Thumb.DragDeltaEvent.AddClassHandler((x, e) => x.OnThumbDragDelta(e), RoutingStrategies.Bubble); + Thumb.DragCompletedEvent.AddClassHandler((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble); } /// diff --git a/src/Avalonia.Controls/TabItem.cs b/src/Avalonia.Controls/TabItem.cs index 47a2348d59..fca1e022aa 100644 --- a/src/Avalonia.Controls/TabItem.cs +++ b/src/Avalonia.Controls/TabItem.cs @@ -30,8 +30,8 @@ namespace Avalonia.Controls { SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(typeof(TabItem), true); - IsSelectedProperty.Changed.AddClassHandler(x => x.UpdateSelectedContent); - DataContextProperty.Changed.AddClassHandler(x => x.UpdateHeader); + IsSelectedProperty.Changed.AddClassHandler((x, e) => x.UpdateSelectedContent(e)); + DataContextProperty.Changed.AddClassHandler((x, e) => x.UpdateHeader(e)); } /// diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index c7fd96b68a..07d5497c14 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -54,7 +54,7 @@ namespace Avalonia.Controls SelectableMixin.Attach(IsSelectedProperty); FocusableProperty.OverrideDefaultValue(true); ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); - RequestBringIntoViewEvent.AddClassHandler(x => x.OnRequestBringIntoView); + RequestBringIntoViewEvent.AddClassHandler((x, e) => x.OnRequestBringIntoView(e)); } /// diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index a47c55f87c..196110edf7 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -47,7 +47,7 @@ namespace Avalonia.Controls static WindowBase() { IsVisibleProperty.OverrideDefaultValue(false); - IsVisibleProperty.Changed.AddClassHandler(x => x.IsVisibleChanged); + IsVisibleProperty.Changed.AddClassHandler((x,e) => x.IsVisibleChanged(e)); TopmostProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue)); diff --git a/src/Avalonia.Input/InputElement.cs b/src/Avalonia.Input/InputElement.cs index 47e85416cf..535b930f8b 100644 --- a/src/Avalonia.Input/InputElement.cs +++ b/src/Avalonia.Input/InputElement.cs @@ -169,18 +169,18 @@ namespace Avalonia.Input { IsEnabledProperty.Changed.Subscribe(IsEnabledChanged); - GotFocusEvent.AddClassHandler(x => x.OnGotFocus); - LostFocusEvent.AddClassHandler(x => x.OnLostFocus); - KeyDownEvent.AddClassHandler(x => x.OnKeyDown); - KeyUpEvent.AddClassHandler(x => x.OnKeyUp); - TextInputEvent.AddClassHandler(x => x.OnTextInput); - PointerEnterEvent.AddClassHandler(x => x.OnPointerEnterCore); - PointerLeaveEvent.AddClassHandler(x => x.OnPointerLeaveCore); - PointerMovedEvent.AddClassHandler(x => x.OnPointerMoved); - PointerPressedEvent.AddClassHandler(x => x.OnPointerPressed); - PointerReleasedEvent.AddClassHandler(x => x.OnPointerReleased); - PointerCaptureLostEvent.AddClassHandler(x => x.OnPointerCaptureLost); - PointerWheelChangedEvent.AddClassHandler(x => x.OnPointerWheelChanged); + GotFocusEvent.AddClassHandler((x, e) => x.OnGotFocus(e)); + LostFocusEvent.AddClassHandler((x, e) => x.OnLostFocus(e)); + KeyDownEvent.AddClassHandler((x, e) => x.OnKeyDown(e)); + KeyUpEvent.AddClassHandler((x, e) => x.OnKeyUp(e)); + TextInputEvent.AddClassHandler((x, e) => x.OnTextInput(e)); + PointerEnterEvent.AddClassHandler((x, e) => x.OnPointerEnterCore(e)); + PointerLeaveEvent.AddClassHandler((x, e) => x.OnPointerLeaveCore(e)); + PointerMovedEvent.AddClassHandler((x, e) => x.OnPointerMoved(e)); + PointerPressedEvent.AddClassHandler((x, e) => x.OnPointerPressed(e)); + PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); + PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); + PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); PseudoClass(IsEffectivelyEnabledProperty, x => !x, ":disabled"); PseudoClass(IsFocusedProperty, ":focus"); diff --git a/src/Avalonia.Interactivity/RoutedEvent.cs b/src/Avalonia.Interactivity/RoutedEvent.cs index cfbaddb327..bc5dec9a90 100644 --- a/src/Avalonia.Interactivity/RoutedEvent.cs +++ b/src/Avalonia.Interactivity/RoutedEvent.cs @@ -113,24 +113,38 @@ namespace Avalonia.Interactivity Contract.Requires(ownerType != null); } + [Obsolete("Use overload taking Action.")] public IDisposable AddClassHandler( Func> handler, RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, bool handledEventsToo = false) - where TTarget : class, IInteractive + where TTarget : class, IInteractive { - EventHandler adapter = (sender, e) => + void Adapter(object sender, RoutedEventArgs e) { - var target = sender as TTarget; - var args = e as TEventArgs; - - if (target != null && args != null) + if (sender is TTarget target && e is TEventArgs args) { handler(target)(args); } - }; + } + + return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo); + } + + public IDisposable AddClassHandler( + Action handler, + RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble, + bool handledEventsToo = false) where TTarget : class, IInteractive + { + void Adapter(object sender, RoutedEventArgs e) + { + if (sender is TTarget target && e is TEventArgs args) + { + handler(target, args); + } + } - return AddClassHandler(typeof(TTarget), adapter, routes, handledEventsToo); + return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo); } } } diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs index 38c29289b6..910846ae63 100644 --- a/src/Avalonia.Styling/StyledElement.cs +++ b/src/Avalonia.Styling/StyledElement.cs @@ -73,7 +73,7 @@ namespace Avalonia /// static StyledElement() { - DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore); + DataContextProperty.Changed.AddClassHandler((x,e) => x.OnDataContextChangedCore(e)); } /// diff --git a/src/Avalonia.Visuals/Media/Geometry.cs b/src/Avalonia.Visuals/Media/Geometry.cs index 748d2526af..f9bcea85af 100644 --- a/src/Avalonia.Visuals/Media/Geometry.cs +++ b/src/Avalonia.Visuals/Media/Geometry.cs @@ -22,7 +22,7 @@ namespace Avalonia.Media static Geometry() { - TransformProperty.Changed.AddClassHandler(x => x.TransformChanged); + TransformProperty.Changed.AddClassHandler((x,e) => x.TransformChanged(e)); } /// diff --git a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs index 58ee63cea4..414e67bb94 100644 --- a/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs +++ b/tests/Avalonia.Interactivity.UnitTests/InteractiveTests.cs @@ -331,7 +331,7 @@ namespace Avalonia.Interactivity.UnitTests var target = CreateTree(ev, null, 0); - ev.AddClassHandler(x => x.ClassHandler, RoutingStrategies.Bubble); + ev.AddClassHandler((x, e) => x.ClassHandler(e), RoutingStrategies.Bubble); var args = new RoutedEventArgs(ev, target); target.RaiseEvent(args); From 5041f0a6df4349d1f5e501fcd188252e4f7476c4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 9 Oct 2019 11:57:46 +0300 Subject: [PATCH 073/118] Fixed DBusMenuExporter --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 27 +++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 0ab949ee69..911dc82e96 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -33,6 +33,7 @@ namespace Avalonia.FreeDesktop private NativeMenu _menu; private Dictionary _idsToItems = new Dictionary(); private Dictionary _itemsToIds = new Dictionary(); + private readonly HashSet _menus = new HashSet(); private bool _resetQueued; private int _nextId = 1; public DBusMenuExporterImpl(Connection dbus, IntPtr xid) @@ -104,12 +105,11 @@ namespace Avalonia.FreeDesktop void DoLayoutReset() { _resetQueued = false; - foreach (var i in _idsToItems.Values) - { + foreach (var i in _idsToItems.Values) i.PropertyChanged -= OnItemPropertyChanged; - if (i is NativeMenuItem nmi) - ((INotifyCollectionChanged)nmi.Menu.Items).CollectionChanged -= OnMenuItemsChanged; - } + foreach(var menu in _menus) + ((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged; + _menus.Clear(); _idsToItems.Clear(); _itemsToIds.Clear(); _revision++; @@ -132,6 +132,12 @@ namespace Avalonia.FreeDesktop return (item, (item as NativeMenuItem)?.Menu); } + private void EnsureSubscribed(NativeMenu menu) + { + if(menu!=null && _menus.Add(menu)) + ((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged; + } + private int GetId(NativeMenuItemBase item) { if (_itemsToIds.TryGetValue(item, out var id)) @@ -141,7 +147,7 @@ namespace Avalonia.FreeDesktop _itemsToIds[item] = id; item.PropertyChanged += OnItemPropertyChanged; if (item is NativeMenuItem nmi) - ((INotifyCollectionChanged)nmi.Menu.Items).CollectionChanged += OnMenuItemsChanged; + EnsureSubscribed(nmi.Menu); return id; } @@ -190,12 +196,15 @@ namespace Avalonia.FreeDesktop { var (it, menu) = i; - if (it is NativeMenuItem item) + if (it is NativeMenuItemSeperator) + { + if (name == "type") + return "separator"; + } + else if (it is NativeMenuItem item) { if (name == "type") { - if (item != null && item.Header == null) - return "separator"; return null; } if (name == "label") From 465273c883125861d0dcb692e3d12d8f8d70d34e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 9 Oct 2019 11:58:08 +0300 Subject: [PATCH 074/118] Implemented managed menu bar presenter for native menus --- samples/ControlCatalog/MainWindow.xaml | 2 ++ samples/ControlCatalog/MainWindow.xaml.cs | 2 +- samples/ControlCatalog/Pages/MenuPage.xaml | 5 ++- src/Avalonia.Controls/NativeMenu.Export.cs | 5 ++- src/Avalonia.Controls/NativeMenu.cs | 2 +- src/Avalonia.Controls/NativeMenuBar.cs | 36 +++++++++++++++++++ .../NativeMenuItemSeperator.cs | 7 ++-- src/Avalonia.Themes.Default/DefaultTheme.xaml | 1 + .../InverseBooleanValueConverter.cs | 20 +++++++++++ .../NativeMenuBar.xaml | 25 +++++++++++++ 10 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 src/Avalonia.Controls/NativeMenuBar.cs create mode 100644 src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs create mode 100644 src/Avalonia.Themes.Default/NativeMenuBar.xaml diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index 1b01fa56a8..a8fc6bb07d 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -15,11 +15,13 @@ + + diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs index 819ab0655a..7b0ee897c4 100644 --- a/samples/ControlCatalog/MainWindow.xaml.cs +++ b/samples/ControlCatalog/MainWindow.xaml.cs @@ -30,7 +30,7 @@ namespace ControlCatalog }; DataContext = new MainWindowViewModel(_notificationArea); - _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[1] as NativeMenuItem).Menu; + _recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu; } public void OnOpenClicked(object sender, EventArgs args) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index e1a5cf2c5a..868f0df6ad 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -3,8 +3,11 @@ x:Class="ControlCatalog.Pages.MenuPage"> Menu + Exported menu fallback + (Should be only visible on platforms without desktop-global menu bar) + A window menu - + IsNativeMenuExportedProperty = - AvaloniaProperty.RegisterAttached("IsNativeMenuExported", - defaultBindingMode: BindingMode.OneWayToSource); + AvaloniaProperty.RegisterAttached("IsNativeMenuExported"); public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); @@ -53,7 +52,7 @@ namespace Avalonia.Controls } public static readonly AttachedProperty MenuProperty - = AvaloniaProperty.RegisterAttached("NativeMenuItems", validate: + = AvaloniaProperty.RegisterAttached("Menu", validate: (o, v) => { if(!(o is Application || o is TopLevel)) diff --git a/src/Avalonia.Controls/NativeMenu.cs b/src/Avalonia.Controls/NativeMenu.cs index 58cff581e1..54aa2b5e3d 100644 --- a/src/Avalonia.Controls/NativeMenu.cs +++ b/src/Avalonia.Controls/NativeMenu.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls { public partial class NativeMenu : AvaloniaObject, IEnumerable { - private AvaloniaList _items = + private readonly AvaloniaList _items = new AvaloniaList { ResetBehavior = ResetBehavior.Remove }; private NativeMenuItem _parent; [Content] diff --git a/src/Avalonia.Controls/NativeMenuBar.cs b/src/Avalonia.Controls/NativeMenuBar.cs new file mode 100644 index 0000000000..9b96ab9c8c --- /dev/null +++ b/src/Avalonia.Controls/NativeMenuBar.cs @@ -0,0 +1,36 @@ +using System; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Styling; + +namespace Avalonia.Controls +{ + public class NativeMenuBar : TemplatedControl + { + public static readonly AttachedProperty EnableMenuItemClickForwardingProperty = + AvaloniaProperty.RegisterAttached( + "EnableMenuItemClickForwarding"); + + static NativeMenuBar() + { + EnableMenuItemClickForwardingProperty.Changed.Subscribe(args => + { + var item = (MenuItem)args.Sender; + if (args.NewValue.Equals(true)) + item.Click += OnMenuItemClick; + else + item.Click -= OnMenuItemClick; + }); + } + + public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) + { + menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); + } + + private static void OnMenuItemClick(object sender, RoutedEventArgs e) + { + (((MenuItem)sender).DataContext as NativeMenuItem)?.RaiseClick(); + } + } +} diff --git a/src/Avalonia.Controls/NativeMenuItemSeperator.cs b/src/Avalonia.Controls/NativeMenuItemSeperator.cs index 85d62023d4..e743483dab 100644 --- a/src/Avalonia.Controls/NativeMenuItemSeperator.cs +++ b/src/Avalonia.Controls/NativeMenuItemSeperator.cs @@ -1,7 +1,10 @@ -namespace Avalonia.Controls +using System; + +namespace Avalonia.Controls { public class NativeMenuItemSeperator : NativeMenuItemBase { - + [Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)] + public string Header => "-"; } } diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 114979fba2..67279fca99 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -51,4 +51,5 @@ + diff --git a/src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs b/src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs new file mode 100644 index 0000000000..7befc81b8e --- /dev/null +++ b/src/Avalonia.Themes.Default/InverseBooleanValueConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace Avalonia.Themes.Default +{ + class InverseBooleanValueConverter : IValueConverter + { + public bool Default { get; set; } + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is bool b ? !b : Default; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is bool b ? !b : !Default; + } + } +} diff --git a/src/Avalonia.Themes.Default/NativeMenuBar.xaml b/src/Avalonia.Themes.Default/NativeMenuBar.xaml new file mode 100644 index 0000000000..2832bab226 --- /dev/null +++ b/src/Avalonia.Themes.Default/NativeMenuBar.xaml @@ -0,0 +1,25 @@ + + + + + + From dbcc58fa074e028a2158907f071d14a5d90e3291 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 9 Oct 2019 12:00:15 +0300 Subject: [PATCH 075/118] Remove NativeMenu.PrependApplicationMenu for now --- src/Avalonia.Controls/NativeMenu.Export.cs | 14 -------------- .../Platform/ITopLevelNativeMenuExporter.cs | 1 - src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 5 ----- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 5 ----- 4 files changed, 25 deletions(-) diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index e3e134d316..5d3a4526cc 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -62,15 +62,6 @@ namespace Avalonia.Controls 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() { @@ -89,11 +80,6 @@ namespace Avalonia.Controls 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/Platform/ITopLevelNativeMenuExporter.cs b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs index 5112424c3c..3ac5f28956 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs @@ -9,7 +9,6 @@ namespace Avalonia.Controls.Platform bool IsNativeMenuExported { get; } event EventHandler OnIsNativeMenuExportedChanged; void SetNativeMenu(NativeMenu menu); - void SetPrependApplicationMenu(bool prepend); } public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 911dc82e96..90239b5a49 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -161,11 +161,6 @@ namespace Avalonia.FreeDesktop QueueReset(); } - public void SetPrependApplicationMenu(bool prepend) - { - // Not implemented yet :( - } - public ObjectPath ObjectPath { get; } diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index d7635ebe78..1a22b95409 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -83,11 +83,6 @@ namespace Avalonia.Native DoLayoutReset(); } - public void SetPrependApplicationMenu(bool prepend) - { - // OSX always exports the app menu. - } - private void OnItemPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) { QueueReset(); From f02bc61bfee4871c45b8868d4f511aba53bfadf1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 9 Oct 2019 12:21:23 +0300 Subject: [PATCH 076/118] I've said remove NativeMenu.PrependApplicationMenu for now --- samples/ControlCatalog/MainWindow.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml index a8fc6bb07d..6088f2ec57 100644 --- a/samples/ControlCatalog/MainWindow.xaml +++ b/samples/ControlCatalog/MainWindow.xaml @@ -7,7 +7,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:ControlCatalog.ViewModels" xmlns:v="clr-namespace:ControlCatalog.Views" - x:Class="ControlCatalog.MainWindow" NativeMenu.PrependApplicationMenu="True"> + x:Class="ControlCatalog.MainWindow"> From 10618f9beb62f5dbb1209ac086393922e2d39a5c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Oct 2019 21:42:16 +0200 Subject: [PATCH 077/118] Seal PopupRoot as suggested by @kekekeks. --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index b7f0c8f47d..74a6d288f4 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -17,7 +17,7 @@ namespace Avalonia.Controls.Primitives /// /// The root window of a . /// - public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost + public sealed class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost, IPopupHost { private readonly TopLevel _parent; private PopupPositionerParameters _positionerParameters; From e1bfdf03246a8fe2df2fb443eb6226e17ef1940d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Oct 2019 23:01:30 +0200 Subject: [PATCH 078/118] Added failing test for #3094. --- .../Primitives/SelectingItemsControlTests.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index d5237e2aca..17f0e609a5 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -1000,6 +1000,26 @@ namespace Avalonia.Controls.UnitTests.Primitives Assert.Equal(new[] { "Bar" }, selectedItems); } + [Fact] + public void MoveSelection_Wrap_Does_Not_Hang_With_No_Focusable_Controls() + { + // Issue #3094. + var target = new TestSelector + { + Template = Template(), + Items = new[] + { + new ListBoxItem { Focusable = false }, + new ListBoxItem { Focusable = false }, + }, + SelectedIndex = 0, + }; + + target.Measure(new Size(100, 100)); + target.Arrange(new Rect(0, 0, 100, 100)); + target.MoveSelection(NavigationDirection.Next, true); + } + private FuncControlTemplate Template() { return new FuncControlTemplate((control, scope) => @@ -1044,5 +1064,13 @@ namespace Avalonia.Controls.UnitTests.Primitives public List Items { get; set; } = new List() { "a", "b", "c", "d", "e" }; public string Selected { get; set; } = "b"; } + + private class TestSelector : SelectingItemsControl + { + public new bool MoveSelection(NavigationDirection direction, bool wrap) + { + return base.MoveSelection(direction, wrap); + } + } } } From 60c9015a7fd42a5cc13d156fd70c0c3f5444ceda Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 10 Oct 2019 23:03:12 +0200 Subject: [PATCH 079/118] Prevent hang in ItemsControl with no focusable controls. --- src/Avalonia.Controls/ItemsControl.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 0fe7291835..6b81237e6f 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -489,18 +489,20 @@ namespace Avalonia.Controls bool wrap) { IInputElement result; + var c = from; do { - result = container.GetControl(direction, from, wrap); + result = container.GetControl(direction, c, wrap); + from ??= result; if (result?.Focusable == true) { return result; } - from = result; - } while (from != null); + c = result; + } while (c != null && c != from); return null; } From 400e64dc15ffd9c140b48918853660ccc2c6c470 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 11 Oct 2019 00:05:11 +0200 Subject: [PATCH 080/118] Fix formatting. --- src/Avalonia.Controls.DataGrid/DataGrid.cs | 50 +++++++++---------- .../Presenters/ItemsPresenter.cs | 2 +- .../Presenters/ScrollContentPresenter.cs | 6 +-- .../Primitives/SelectingItemsControl.cs | 6 +-- src/Avalonia.Controls/ProgressBar.cs | 4 +- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs index a6aaed1e80..b65fd2a8b7 100644 --- a/src/Avalonia.Controls.DataGrid/DataGrid.cs +++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs @@ -723,29 +723,29 @@ namespace Avalonia.Controls PseudoClass(IsValidProperty, x => !x, ":invalid"); - ItemsProperty.Changed.AddClassHandler((x,e) => x.OnItemsPropertyChanged(e)); - CanUserResizeColumnsProperty.Changed.AddClassHandler((x,e) => x.OnCanUserResizeColumnsChanged(e)); - ColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnColumnWidthChanged(e)); - RowBackgroundProperty.Changed.AddClassHandler((x,e) => x.OnRowBackgroundChanged(e)); - AlternatingRowBackgroundProperty.Changed.AddClassHandler((x,e) => x.OnRowBackgroundChanged(e)); - FrozenColumnCountProperty.Changed.AddClassHandler((x,e) => x.OnFrozenColumnCountChanged(e)); - GridLinesVisibilityProperty.Changed.AddClassHandler((x,e) => x.OnGridLinesVisibilityChanged(e)); - HeadersVisibilityProperty.Changed.AddClassHandler((x,e) => x.OnHeadersVisibilityChanged(e)); - HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x,e) => x.OnHorizontalGridLinesBrushChanged(e)); - IsReadOnlyProperty.Changed.AddClassHandler((x,e) => x.OnIsReadOnlyChanged(e)); - MaxColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnMaxColumnWidthChanged(e)); - MinColumnWidthProperty.Changed.AddClassHandler((x,e) => x.OnMinColumnWidthChanged(e)); - RowHeightProperty.Changed.AddClassHandler((x,e) => x.OnRowHeightChanged(e)); - RowHeaderWidthProperty.Changed.AddClassHandler((x,e) => x.OnRowHeaderWidthChanged(e)); - SelectionModeProperty.Changed.AddClassHandler((x,e) => x.OnSelectionModeChanged(e)); - VerticalGridLinesBrushProperty.Changed.AddClassHandler((x,e) => x.OnVerticalGridLinesBrushChanged(e)); - SelectedIndexProperty.Changed.AddClassHandler((x,e) => x.OnSelectedIndexChanged(e)); - SelectedItemProperty.Changed.AddClassHandler((x,e) => x.OnSelectedItemChanged(e)); - IsEnabledProperty.Changed.AddClassHandler((x,e) => x.DataGrid_IsEnabledChanged(e)); - AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x,e) => x.OnAreRowGroupHeadersFrozenChanged(e)); - RowDetailsTemplateProperty.Changed.AddClassHandler((x,e) => x.OnRowDetailsTemplateChanged(e)); - RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x,e) => x.OnRowDetailsVisibilityModeChanged(e)); - AutoGenerateColumnsProperty.Changed.AddClassHandler((x,e) => x.OnAutoGenerateColumnsChanged(e)); + ItemsProperty.Changed.AddClassHandler((x, e) => x.OnItemsPropertyChanged(e)); + CanUserResizeColumnsProperty.Changed.AddClassHandler((x, e) => x.OnCanUserResizeColumnsChanged(e)); + ColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnColumnWidthChanged(e)); + RowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e)); + AlternatingRowBackgroundProperty.Changed.AddClassHandler((x, e) => x.OnRowBackgroundChanged(e)); + FrozenColumnCountProperty.Changed.AddClassHandler((x, e) => x.OnFrozenColumnCountChanged(e)); + GridLinesVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnGridLinesVisibilityChanged(e)); + HeadersVisibilityProperty.Changed.AddClassHandler((x, e) => x.OnHeadersVisibilityChanged(e)); + HorizontalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnHorizontalGridLinesBrushChanged(e)); + IsReadOnlyProperty.Changed.AddClassHandler((x, e) => x.OnIsReadOnlyChanged(e)); + MaxColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMaxColumnWidthChanged(e)); + MinColumnWidthProperty.Changed.AddClassHandler((x, e) => x.OnMinColumnWidthChanged(e)); + RowHeightProperty.Changed.AddClassHandler((x, e) => x.OnRowHeightChanged(e)); + RowHeaderWidthProperty.Changed.AddClassHandler((x, e) => x.OnRowHeaderWidthChanged(e)); + SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e)); + VerticalGridLinesBrushProperty.Changed.AddClassHandler((x, e) => x.OnVerticalGridLinesBrushChanged(e)); + SelectedIndexProperty.Changed.AddClassHandler((x, e) => x.OnSelectedIndexChanged(e)); + SelectedItemProperty.Changed.AddClassHandler((x, e) => x.OnSelectedItemChanged(e)); + IsEnabledProperty.Changed.AddClassHandler((x, e) => x.DataGrid_IsEnabledChanged(e)); + AreRowGroupHeadersFrozenProperty.Changed.AddClassHandler((x, e) => x.OnAreRowGroupHeadersFrozenChanged(e)); + RowDetailsTemplateProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsTemplateChanged(e)); + RowDetailsVisibilityModeProperty.Changed.AddClassHandler((x, e) => x.OnRowDetailsVisibilityModeChanged(e)); + AutoGenerateColumnsProperty.Changed.AddClassHandler((x, e) => x.OnAutoGenerateColumnsChanged(e)); } /// @@ -3533,7 +3533,7 @@ namespace Avalonia.Controls if (AreColumnHeadersVisible && _vScrollBar != null && _vScrollBar.IsVisible) { - _topRightCornerHeader.IsVisible = true; ; + _topRightCornerHeader.IsVisible = true; } else { @@ -5594,7 +5594,7 @@ namespace Avalonia.Controls { // This will trigger a call to this method via Cells_SizeChanged for // which no processing is needed. - _vScrollBar.IsVisible = true; ; + _vScrollBar.IsVisible = true; if (_vScrollBar.DesiredSize.Width == 0) { // We need to know the width for the rest of layout to work correctly so measure it now diff --git a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs index 7a5451821e..ab40fbd53b 100644 --- a/src/Avalonia.Controls/Presenters/ItemsPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ItemsPresenter.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.Presenters KeyboardNavigationMode.Once); VirtualizationModeProperty.Changed - .AddClassHandler((x,e) => x.VirtualizationModeChanged(e)); + .AddClassHandler((x, e) => x.VirtualizationModeChanged(e)); } /// diff --git a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs index 6fffc3741a..48d0aff551 100644 --- a/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs +++ b/src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs @@ -73,7 +73,7 @@ namespace Avalonia.Controls.Presenters static ScrollContentPresenter() { ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); - ChildProperty.Changed.AddClassHandler((x,e) => x.ChildChanged(e)); + ChildProperty.Changed.AddClassHandler((x, e) => x.ChildChanged(e)); AffectsArrange(OffsetProperty); } @@ -246,7 +246,7 @@ namespace Avalonia.Controls.Presenters if (isLogical) _activeLogicalGestureScrolls?.TryGetValue(e.Id, out delta); delta += e.Delta; - + if (Extent.Height > Viewport.Height) { double dy; @@ -293,7 +293,7 @@ namespace Avalonia.Controls.Presenters } } - private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) + private void OnScrollGestureEnded(object sender, ScrollGestureEndedEventArgs e) => _activeLogicalGestureScrolls?.Remove(e.Id); /// diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index d2d82f5ca1..7fddee1012 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -119,7 +119,7 @@ namespace Avalonia.Controls.Primitives /// static SelectingItemsControl() { - IsSelectedChangedEvent.AddClassHandler((x,e) => x.ContainerSelectionChanged(e)); + IsSelectedChangedEvent.AddClassHandler((x, e) => x.ContainerSelectionChanged(e)); } /// @@ -1088,8 +1088,8 @@ namespace Avalonia.Controls.Primitives } else { - SelectedIndex = _updateSelectedIndex != int.MinValue ? - _updateSelectedIndex : + SelectedIndex = _updateSelectedIndex != int.MinValue ? + _updateSelectedIndex : AlwaysSelected ? 0 : -1; } } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 8963ceeddf..94898951a9 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -39,8 +39,8 @@ namespace Avalonia.Controls PseudoClass(OrientationProperty, o => o == Orientation.Horizontal, ":horizontal"); PseudoClass(IsIndeterminateProperty, ":indeterminate"); - ValueProperty.Changed.AddClassHandler((x,e) => x.UpdateIndicatorWhenPropChanged(e)); - IsIndeterminateProperty.Changed.AddClassHandler((x,e) => x.UpdateIndicatorWhenPropChanged(e)); + ValueProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + IsIndeterminateProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); } public bool IsIndeterminate From adb65f14b1e0fdb86889d2038c5f4dcdf18a312c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 11 Oct 2019 15:13:48 +0200 Subject: [PATCH 081/118] Don't use C#8 features yet. --- src/Avalonia.Controls/ItemsControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 6b81237e6f..11fdd0457d 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -494,7 +494,7 @@ namespace Avalonia.Controls do { result = container.GetControl(direction, c, wrap); - from ??= result; + from = from ?? result; if (result?.Focusable == true) { From 4ab0131f90d5facc62518417f00dae61e561587e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 11 Oct 2019 15:46:17 +0200 Subject: [PATCH 082/118] Don't override key/pointer events in Carousel. Fixes #3098. --- src/Avalonia.Controls/Carousel.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/Avalonia.Controls/Carousel.cs b/src/Avalonia.Controls/Carousel.cs index 069bf40820..ebc8890721 100644 --- a/src/Avalonia.Controls/Carousel.cs +++ b/src/Avalonia.Controls/Carousel.cs @@ -84,17 +84,5 @@ namespace Avalonia.Controls --SelectedIndex; } } - - /// - protected override void OnKeyDown(KeyEventArgs e) - { - // Ignore key presses. - } - - /// - protected override void OnPointerPressed(PointerPressedEventArgs e) - { - // Ignore pointer presses. - } } } From e7b39c1672ecefa99fb5095a17ee56998e0b0681 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Oct 2019 19:34:21 +0100 Subject: [PATCH 083/118] convert osx special key codes. --- .../AvaloniaNativeMenuExporter.cs | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 1a22b95409..a8a1121405 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -5,12 +5,89 @@ using System.Linq; using System.Text; using Avalonia.Controls; using Avalonia.Controls.Platform; +using Avalonia.Input; using Avalonia.Native.Interop; using Avalonia.Platform.Interop; using Avalonia.Threading; namespace Avalonia.Native { + enum OsxUnicodeSpecialKey + { + NSUpArrowFunctionKey = 0xF700, + NSDownArrowFunctionKey = 0xF701, + NSLeftArrowFunctionKey = 0xF702, + NSRightArrowFunctionKey = 0xF703, + NSF1FunctionKey = 0xF704, + NSF2FunctionKey = 0xF705, + NSF3FunctionKey = 0xF706, + NSF4FunctionKey = 0xF707, + NSF5FunctionKey = 0xF708, + NSF6FunctionKey = 0xF709, + NSF7FunctionKey = 0xF70A, + NSF8FunctionKey = 0xF70B, + NSF9FunctionKey = 0xF70C, + NSF10FunctionKey = 0xF70D, + NSF11FunctionKey = 0xF70E, + NSF12FunctionKey = 0xF70F, + NSF13FunctionKey = 0xF710, + NSF14FunctionKey = 0xF711, + NSF15FunctionKey = 0xF712, + NSF16FunctionKey = 0xF713, + NSF17FunctionKey = 0xF714, + NSF18FunctionKey = 0xF715, + NSF19FunctionKey = 0xF716, + NSF20FunctionKey = 0xF717, + NSF21FunctionKey = 0xF718, + NSF22FunctionKey = 0xF719, + NSF23FunctionKey = 0xF71A, + NSF24FunctionKey = 0xF71B, + NSF25FunctionKey = 0xF71C, + NSF26FunctionKey = 0xF71D, + NSF27FunctionKey = 0xF71E, + NSF28FunctionKey = 0xF71F, + NSF29FunctionKey = 0xF720, + NSF30FunctionKey = 0xF721, + NSF31FunctionKey = 0xF722, + NSF32FunctionKey = 0xF723, + NSF33FunctionKey = 0xF724, + NSF34FunctionKey = 0xF725, + NSF35FunctionKey = 0xF726, + NSInsertFunctionKey = 0xF727, + NSDeleteFunctionKey = 0xF728, + NSHomeFunctionKey = 0xF729, + NSBeginFunctionKey = 0xF72A, + NSEndFunctionKey = 0xF72B, + NSPageUpFunctionKey = 0xF72C, + NSPageDownFunctionKey = 0xF72D, + NSPrintScreenFunctionKey = 0xF72E, + NSScrollLockFunctionKey = 0xF72F, + NSPauseFunctionKey = 0xF730, + NSSysReqFunctionKey = 0xF731, + NSBreakFunctionKey = 0xF732, + NSResetFunctionKey = 0xF733, + NSStopFunctionKey = 0xF734, + NSMenuFunctionKey = 0xF735, + NSUserFunctionKey = 0xF736, + NSSystemFunctionKey = 0xF737, + NSPrintFunctionKey = 0xF738, + NSClearLineFunctionKey = 0xF739, + NSClearDisplayFunctionKey = 0xF73A, + NSInsertLineFunctionKey = 0xF73B, + NSDeleteLineFunctionKey = 0xF73C, + NSInsertCharFunctionKey = 0xF73D, + NSDeleteCharFunctionKey = 0xF73E, + NSPrevFunctionKey = 0xF73F, + NSNextFunctionKey = 0xF740, + NSSelectFunctionKey = 0xF741, + NSExecuteFunctionKey = 0xF742, + NSUndoFunctionKey = 0xF743, + NSRedoFunctionKey = 0xF744, + NSFindFunctionKey = 0xF745, + NSHelpFunctionKey = 0xF746, + NSModeSwitchFunctionKey = 0xF747 + } + public class MenuActionCallback : CallbackBase, IAvnActionCallback { private Action _action; @@ -50,6 +127,11 @@ namespace Avalonia.Native private IAvnWindow _nativeWindow; private List _menuItems = new List(); + private static Dictionary osxKeys = new Dictionary + { + { Key.F1, OsxUnicodeSpecialKey.NSF1FunctionKey }, + }; + public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) { _factory = factory; @@ -147,6 +229,18 @@ namespace Avalonia.Native } } + private static string ConvertOSXSpecialKeyCodes(Key key) + { + if (osxKeys.ContainsKey(key)) + { + return Encoding.UTF8.GetString(BitConverter.GetBytes((ushort)osxKeys[key])); + } + else + { + return key.ToString().ToLower(); + } + } + private void SetChildren(IAvnAppMenu menu, ICollection children) { foreach (var i in children) @@ -164,7 +258,7 @@ namespace Avalonia.Native if (item.Gesture != null) { - using (var buffer = new Utf8Buffer(item.Gesture.Key.ToString().ToLower())) + using (var buffer = new Utf8Buffer(ConvertOSXSpecialKeyCodes(item.Gesture.Key))) { menuItem.SetGesture(buffer.DangerousGetHandle(), (AvnInputModifiers)item.Gesture.KeyModifiers); } From 0327d8e56bcf0cffa17f67b12872b0ad6b2bc8e8 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Oct 2019 19:51:21 +0100 Subject: [PATCH 084/118] implement osx keycodes. --- src/Avalonia.Native/AvaloniaNativeMenuExporter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index a8a1121405..3b0c4d94a6 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -130,6 +130,7 @@ namespace Avalonia.Native private static Dictionary osxKeys = new Dictionary { { Key.F1, OsxUnicodeSpecialKey.NSF1FunctionKey }, + { Key.F4, OsxUnicodeSpecialKey.NSF4FunctionKey }, }; public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) @@ -233,7 +234,7 @@ namespace Avalonia.Native { if (osxKeys.ContainsKey(key)) { - return Encoding.UTF8.GetString(BitConverter.GetBytes((ushort)osxKeys[key])); + return ((char)osxKeys[key]).ToString(); } else { From 1474409a5ee6f80135c3234444a5b403dd0738bd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 11 Oct 2019 20:07:11 +0100 Subject: [PATCH 085/118] add missing keycodes for osx gestures. --- .../AvaloniaNativeMenuExporter.cs | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs index 3b0c4d94a6..950943d54a 100644 --- a/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs +++ b/src/Avalonia.Native/AvaloniaNativeMenuExporter.cs @@ -129,8 +129,53 @@ namespace Avalonia.Native private static Dictionary osxKeys = new Dictionary { + {Key.Up, OsxUnicodeSpecialKey.NSUpArrowFunctionKey }, + {Key.Down, OsxUnicodeSpecialKey.NSDownArrowFunctionKey }, + {Key.Left, OsxUnicodeSpecialKey.NSLeftArrowFunctionKey }, + {Key.Right, OsxUnicodeSpecialKey.NSRightArrowFunctionKey }, { Key.F1, OsxUnicodeSpecialKey.NSF1FunctionKey }, + { Key.F2, OsxUnicodeSpecialKey.NSF2FunctionKey }, + { Key.F3, OsxUnicodeSpecialKey.NSF3FunctionKey }, { Key.F4, OsxUnicodeSpecialKey.NSF4FunctionKey }, + { Key.F5, OsxUnicodeSpecialKey.NSF5FunctionKey }, + { Key.F6, OsxUnicodeSpecialKey.NSF6FunctionKey }, + { Key.F7, OsxUnicodeSpecialKey.NSF7FunctionKey }, + { Key.F8, OsxUnicodeSpecialKey.NSF8FunctionKey }, + { Key.F9, OsxUnicodeSpecialKey.NSF9FunctionKey }, + { Key.F10, OsxUnicodeSpecialKey.NSF10FunctionKey }, + { Key.F11, OsxUnicodeSpecialKey.NSF11FunctionKey }, + { Key.F12, OsxUnicodeSpecialKey.NSF12FunctionKey }, + { Key.F13, OsxUnicodeSpecialKey.NSF13FunctionKey }, + { Key.F14, OsxUnicodeSpecialKey.NSF14FunctionKey }, + { Key.F15, OsxUnicodeSpecialKey.NSF15FunctionKey }, + { Key.F16, OsxUnicodeSpecialKey.NSF16FunctionKey }, + { Key.F17, OsxUnicodeSpecialKey.NSF17FunctionKey }, + { Key.F18, OsxUnicodeSpecialKey.NSF18FunctionKey }, + { Key.F19, OsxUnicodeSpecialKey.NSF19FunctionKey }, + { Key.F20, OsxUnicodeSpecialKey.NSF20FunctionKey }, + { Key.F21, OsxUnicodeSpecialKey.NSF21FunctionKey }, + { Key.F22, OsxUnicodeSpecialKey.NSF22FunctionKey }, + { Key.F23, OsxUnicodeSpecialKey.NSF23FunctionKey }, + { Key.F24, OsxUnicodeSpecialKey.NSF24FunctionKey }, + { Key.Insert, OsxUnicodeSpecialKey.NSInsertFunctionKey }, + { Key.Delete, OsxUnicodeSpecialKey.NSDeleteFunctionKey }, + { Key.Home, OsxUnicodeSpecialKey.NSHomeFunctionKey }, + //{ Key.Begin, OsxUnicodeSpecialKey.NSBeginFunctionKey }, + { Key.End, OsxUnicodeSpecialKey.NSEndFunctionKey }, + { Key.PageUp, OsxUnicodeSpecialKey.NSPageUpFunctionKey }, + { Key.PageDown, OsxUnicodeSpecialKey.NSPageDownFunctionKey }, + { Key.PrintScreen, OsxUnicodeSpecialKey.NSPrintScreenFunctionKey }, + { Key.Scroll, OsxUnicodeSpecialKey.NSScrollLockFunctionKey }, + //{ Key.SysReq, OsxUnicodeSpecialKey.NSSysReqFunctionKey }, + //{ Key.Break, OsxUnicodeSpecialKey.NSBreakFunctionKey }, + //{ Key.Reset, OsxUnicodeSpecialKey.NSResetFunctionKey }, + //{ Key.Stop, OsxUnicodeSpecialKey.NSStopFunctionKey }, + //{ Key.Menu, OsxUnicodeSpecialKey.NSMenuFunctionKey }, + //{ Key.UserFunction, OsxUnicodeSpecialKey.NSUserFunctionKey }, + //{ Key.SystemFunction, OsxUnicodeSpecialKey.NSSystemFunctionKey }, + { Key.Print, OsxUnicodeSpecialKey.NSPrintFunctionKey }, + //{ Key.ClearLine, OsxUnicodeSpecialKey.NSClearLineFunctionKey }, + //{ Key.ClearDisplay, OsxUnicodeSpecialKey.NSClearDisplayFunctionKey }, }; public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) From a16e3bff7fd44f3581c4b5df1e0eb9d616c20846 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 12 Oct 2019 10:21:17 +0300 Subject: [PATCH 086/118] Added InitialPressMouseButton to PointerReleasedEventArgs that returns the button that caused the PointerPressed event MouseButton is now deprecated with error, since people need to decide which behavior they want --- .../DataGridColumnHeader.cs | 2 +- src/Avalonia.Controls/Button.cs | 2 +- src/Avalonia.Controls/Calendar/Calendar.cs | 2 +- .../Calendar/CalendarButton.cs | 2 +- .../Calendar/CalendarDayButton.cs | 2 +- src/Avalonia.Controls/ContextMenu.cs | 2 +- .../Platform/DefaultMenuInteractionHandler.cs | 2 +- src/Avalonia.Controls/RepeatButton.cs | 4 +-- src/Avalonia.Controls/TabControl.cs | 2 +- .../SelectingItemsControlSelectionAdapter.cs | 2 +- src/Avalonia.Input/Gestures.cs | 2 +- src/Avalonia.Input/MouseDevice.cs | 5 ++-- src/Avalonia.Input/PointerEventArgs.cs | 16 ++++++++---- src/Avalonia.Input/PointerPoint.cs | 25 +++++++++++-------- src/Avalonia.Input/TouchDevice.cs | 2 +- .../DefaultMenuInteractionHandlerTests.cs | 4 ++- tests/Avalonia.UnitTests/MouseTestHelper.cs | 3 +-- 17 files changed, 45 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 4c77c8b5ae..d1651b2d09 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -468,7 +468,7 @@ namespace Avalonia.Controls private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e) { - if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left) + if (OwningColumn == null || e.Handled || !IsEnabled || e.InitialPressMouseButton != MouseButton.Left) { return; } diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs index d39ea73828..78d02e200f 100644 --- a/src/Avalonia.Controls/Button.cs +++ b/src/Avalonia.Controls/Button.cs @@ -294,7 +294,7 @@ namespace Avalonia.Controls { base.OnPointerReleased(e); - if (IsPressed && e.MouseButton == MouseButton.Left) + if (IsPressed && e.InitialPressMouseButton == MouseButton.Left) { IsPressed = false; e.Handled = true; diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 89b375996b..beafab3edf 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -1565,7 +1565,7 @@ namespace Avalonia.Controls protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); - if (!HasFocusInternal && e.MouseButton == MouseButton.Left) + if (!HasFocusInternal && e.InitialPressMouseButton == MouseButton.Left) { FocusManager.Instance.Focus(this); } diff --git a/src/Avalonia.Controls/Calendar/CalendarButton.cs b/src/Avalonia.Controls/Calendar/CalendarButton.cs index 53852defb3..a273e68d56 100644 --- a/src/Avalonia.Controls/Calendar/CalendarButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarButton.cs @@ -173,7 +173,7 @@ namespace Avalonia.Controls.Primitives protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); - if (e.MouseButton == MouseButton.Left) + if (e.InitialPressMouseButton == MouseButton.Left) CalendarLeftMouseButtonUp?.Invoke(this, e); } } diff --git a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs index cb2a98e5ca..e62a1ce1f4 100644 --- a/src/Avalonia.Controls/Calendar/CalendarDayButton.cs +++ b/src/Avalonia.Controls/Calendar/CalendarDayButton.cs @@ -231,7 +231,7 @@ namespace Avalonia.Controls.Primitives { base.OnPointerReleased(e); - if (e.MouseButton == MouseButton.Left) + if (e.InitialPressMouseButton == MouseButton.Left) CalendarDayButtonMouseUp?.Invoke(this, e); } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a5025df82d..5dfa5863f5 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -192,7 +192,7 @@ namespace Avalonia.Controls e.Handled = true; } - if (e.MouseButton == MouseButton.Right) + if (e.InitialPressMouseButton == MouseButton.Right) { if (contextMenu.CancelOpening()) return; diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 98f925cd0c..b5dbd1e668 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -356,7 +356,7 @@ namespace Avalonia.Controls.Platform { var item = GetMenuItem(e.Source as IControl); - if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == false) + if (e.InitialPressMouseButton == MouseButton.Left && item?.HasSubMenu == false) { Click(item); e.Handled = true; diff --git a/src/Avalonia.Controls/RepeatButton.cs b/src/Avalonia.Controls/RepeatButton.cs index 07a1e82638..a982a0970c 100644 --- a/src/Avalonia.Controls/RepeatButton.cs +++ b/src/Avalonia.Controls/RepeatButton.cs @@ -98,10 +98,10 @@ namespace Avalonia.Controls { base.OnPointerReleased(e); - if (e.MouseButton == MouseButton.Left) + if (e.InitialPressMouseButton == MouseButton.Left) { StopTimer(); } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Controls/TabControl.cs b/src/Avalonia.Controls/TabControl.cs index 50bcb034ac..61ac0822b0 100644 --- a/src/Avalonia.Controls/TabControl.cs +++ b/src/Avalonia.Controls/TabControl.cs @@ -196,7 +196,7 @@ namespace Avalonia.Controls protected override void OnPointerReleased(PointerReleasedEventArgs e) { - if (e.MouseButton == MouseButton.Left && e.Pointer.Type != PointerType.Mouse) + if (e.InitialPressMouseButton == MouseButton.Left && e.Pointer.Type != PointerType.Mouse) { var container = GetContainerFromEventSource(e.Source); if (container != null diff --git a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs index 4d814170c6..78361fcc8f 100644 --- a/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs +++ b/src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs @@ -178,7 +178,7 @@ namespace Avalonia.Controls.Utils /// The event data. private void OnSelectorPointerReleased(object sender, PointerReleasedEventArgs e) { - if (e.MouseButton == MouseButton.Left) + if (e.InitialPressMouseButton == MouseButton.Left) { OnCommit(); } diff --git a/src/Avalonia.Input/Gestures.cs b/src/Avalonia.Input/Gestures.cs index 6b06151773..a5bd4feb64 100644 --- a/src/Avalonia.Input/Gestures.cs +++ b/src/Avalonia.Input/Gestures.cs @@ -97,7 +97,7 @@ namespace Avalonia.Input if (s_lastPress.TryGetTarget(out var target) && target == e.Source) { - var et = e.MouseButton != MouseButton.Right ? TappedEvent : RightTappedEvent; + var et = e.InitialPressMouseButton != MouseButton.Right ? TappedEvent : RightTappedEvent; e.Source.RaiseEvent(new RoutedEventArgs(et)); } } diff --git a/src/Avalonia.Input/MouseDevice.cs b/src/Avalonia.Input/MouseDevice.cs index 0d5471f790..c84596b913 100644 --- a/src/Avalonia.Input/MouseDevice.cs +++ b/src/Avalonia.Input/MouseDevice.cs @@ -221,7 +221,7 @@ namespace Avalonia.Input _lastClickTime = timestamp; _lastClickRect = new Rect(p, new Size()) .Inflate(new Thickness(settings.DoubleClickSize.Width / 2, settings.DoubleClickSize.Height / 2)); - _lastMouseDownButton = properties.GetObsoleteMouseButton(); + _lastMouseDownButton = properties.PointerUpdateKind.GetMouseButton(); var e = new PointerPressedEventArgs(source, _pointer, root, p, timestamp, properties, inputModifiers, _clickCount); source.RaiseEvent(e); return e.Handled; @@ -267,7 +267,8 @@ namespace Avalonia.Input if (hit != null) { var source = GetSource(hit); - var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers); + var e = new PointerReleasedEventArgs(source, _pointer, root, p, timestamp, props, inputModifiers, + _lastMouseDownButton); source?.RaiseEvent(e); _pointer.Capture(null); diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index 5b3c43e4df..f22d5c29fb 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -124,7 +124,7 @@ namespace Avalonia.Input public int ClickCount => _obsoleteClickCount; [Obsolete("Use PointerUpdateKind")] - public MouseButton MouseButton => Properties.GetObsoleteMouseButton(); + public MouseButton MouseButton => Properties.PointerUpdateKind.GetMouseButton(); } public class PointerReleasedEventArgs : PointerEventArgs @@ -132,15 +132,21 @@ namespace Avalonia.Input public PointerReleasedEventArgs( IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, - PointerPointProperties properties, KeyModifiers modifiers) + PointerPointProperties properties, KeyModifiers modifiers, + MouseButton initialPressMouseButton) : base(InputElement.PointerReleasedEvent, source, pointer, rootVisual, rootVisualPosition, timestamp, properties, modifiers) { - + InitialPressMouseButton = initialPressMouseButton; } - [Obsolete("Use PointerUpdateKind")] - public MouseButton MouseButton => Properties.GetObsoleteMouseButton(); + /// + /// Gets the mouse button that triggered the corresponding PointerPressed event + /// + public MouseButton InitialPressMouseButton { get; } + + [Obsolete("Either use GetCurrentPoint(this).Properties.PointerUpdateKind or InitialPressMouseButton, see ", true)] + public MouseButton MouseButton => InitialPressMouseButton; } public class PointerCaptureLostEventArgs : RoutedEventArgs diff --git a/src/Avalonia.Input/PointerPoint.cs b/src/Avalonia.Input/PointerPoint.cs index d823a78090..e9f3c02b7f 100644 --- a/src/Avalonia.Input/PointerPoint.cs +++ b/src/Avalonia.Input/PointerPoint.cs @@ -49,17 +49,6 @@ namespace Avalonia.Input } public static PointerPointProperties None { get; } = new PointerPointProperties(); - - public MouseButton GetObsoleteMouseButton() - { - if (PointerUpdateKind == PointerUpdateKind.LeftButtonPressed || PointerUpdateKind == PointerUpdateKind.LeftButtonReleased) - return MouseButton.Left; - if (PointerUpdateKind == PointerUpdateKind.MiddleButtonPressed || PointerUpdateKind == PointerUpdateKind.MiddleButtonReleased) - return MouseButton.Middle; - if (PointerUpdateKind == PointerUpdateKind.RightButtonPressed || PointerUpdateKind == PointerUpdateKind.RightButtonReleased) - return MouseButton.Right; - return MouseButton.None; - } } public enum PointerUpdateKind @@ -72,4 +61,18 @@ namespace Avalonia.Input RightButtonReleased, Other } + + public static class PointerUpdateKindExtensions + { + public static MouseButton GetMouseButton(this PointerUpdateKind kind) + { + if (kind == PointerUpdateKind.LeftButtonPressed || kind == PointerUpdateKind.LeftButtonReleased) + return MouseButton.Left; + if (kind == PointerUpdateKind.MiddleButtonPressed || kind == PointerUpdateKind.MiddleButtonReleased) + return MouseButton.Middle; + if (kind == PointerUpdateKind.RightButtonPressed || kind == PointerUpdateKind.RightButtonReleased) + return MouseButton.Right; + return MouseButton.None; + } + } } diff --git a/src/Avalonia.Input/TouchDevice.cs b/src/Avalonia.Input/TouchDevice.cs index 765e02848f..b231c9fff4 100644 --- a/src/Avalonia.Input/TouchDevice.cs +++ b/src/Avalonia.Input/TouchDevice.cs @@ -60,7 +60,7 @@ namespace Avalonia.Input args.Root, args.Position, ev.Timestamp, new PointerPointProperties(GetModifiers(args.InputModifiers, false), PointerUpdateKind.LeftButtonReleased), - GetKeyModifiers(args.InputModifiers))); + GetKeyModifiers(args.InputModifiers), MouseButton.Left)); } } diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index ff11bc513d..989bd744a6 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -18,7 +18,9 @@ namespace Avalonia.Controls.UnitTests.Platform default); static PointerReleasedEventArgs CreateReleased(IInteractive source) => new PointerReleasedEventArgs(source, - new FakePointer(), (IVisual)source, default,0, new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonReleased), default); + new FakePointer(), (IVisual)source, default,0, + new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonReleased), + default, MouseButton.Left); public class TopLevel { diff --git a/tests/Avalonia.UnitTests/MouseTestHelper.cs b/tests/Avalonia.UnitTests/MouseTestHelper.cs index d6e64936c7..48c4d73471 100644 --- a/tests/Avalonia.UnitTests/MouseTestHelper.cs +++ b/tests/Avalonia.UnitTests/MouseTestHelper.cs @@ -86,8 +86,7 @@ namespace Avalonia.UnitTests { _pointer.Capture(null); target.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (IVisual)target, position, - Timestamp(), props, - GetModifiers(modifiers))); + Timestamp(), props, GetModifiers(modifiers), _pressedButton)); } else Move(target, source, position); From 1b5f3b6cc8ea613af0521c1df080f4584ab0e5ea Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 12 Oct 2019 10:31:18 +0300 Subject: [PATCH 087/118] Renamed GetPointerPoint to GetCurrentPoint to match UWP --- src/Avalonia.Controls/MenuItem.cs | 4 ++-- src/Avalonia.Input/PointerEventArgs.cs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 8c82fed58d..3ba0007f6b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -337,7 +337,7 @@ namespace Avalonia.Controls { base.OnPointerEnter(e); - var point = e.GetPointerPoint(null); + var point = e.GetCurrentPoint(null); RaiseEvent(new PointerEventArgs(PointerEnterItemEvent, this, e.Pointer, this.VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } @@ -347,7 +347,7 @@ namespace Avalonia.Controls { base.OnPointerLeave(e); - var point = e.GetPointerPoint(null); + var point = e.GetCurrentPoint(null); RaiseEvent(new PointerEventArgs(PointerLeaveItemEvent, this, e.Pointer, this.VisualRoot, point.Position, e.Timestamp, point.Properties, e.KeyModifiers)); } diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index f22d5c29fb..ca3bbc518c 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -88,9 +88,20 @@ namespace Avalonia.Input return _rootVisualPosition * _rootVisual.TransformToVisual(relativeTo) ?? default; } - public PointerPoint GetPointerPoint(IVisual relativeTo) + [Obsolete("Use GetCurrentPoint")] + public PointerPoint GetPointerPoint(IVisual relativeTo) => GetCurrentPoint(relativeTo); + + /// + /// Returns the PointerPoint associated with the current event + /// + /// The visual which coordinate system to use. Pass null for toplevel coordinate system + /// + public PointerPoint GetCurrentPoint(IVisual relativeTo) => new PointerPoint(Pointer, GetPosition(relativeTo), _properties); + /// + /// Returns the current pointer point properties + /// protected PointerPointProperties Properties => _properties; } From 9cb36f2dc4708cc7620f0ec919f320661166bf4e Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 12 Oct 2019 23:28:30 +0300 Subject: [PATCH 088/118] Fixed obsolete comment --- src/Avalonia.Input/PointerEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Input/PointerEventArgs.cs b/src/Avalonia.Input/PointerEventArgs.cs index ca3bbc518c..e12a20f7a2 100644 --- a/src/Avalonia.Input/PointerEventArgs.cs +++ b/src/Avalonia.Input/PointerEventArgs.cs @@ -156,7 +156,7 @@ namespace Avalonia.Input /// public MouseButton InitialPressMouseButton { get; } - [Obsolete("Either use GetCurrentPoint(this).Properties.PointerUpdateKind or InitialPressMouseButton, see ", true)] + [Obsolete("Either use GetCurrentPoint(this).Properties.PointerUpdateKind or InitialPressMouseButton, see https://github.com/AvaloniaUI/Avalonia/wiki/Pointer-events-in-0.9 for more details", true)] public MouseButton MouseButton => InitialPressMouseButton; } From 88446b1e9ead00ad4a8178981e3670d7100eeebc Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 14 Oct 2019 17:33:14 +0800 Subject: [PATCH 089/118] Pass PointerPressed event to BeginModeDrag and BeginResizeDrag. --- samples/ControlCatalog/DecoratedWindow.xaml.cs | 11 ++++++----- src/Avalonia.Controls/Platform/IWindowImpl.cs | 13 +++++++------ src/Avalonia.Controls/Window.cs | 4 ++-- .../Remote/PreviewerWindowImpl.cs | 5 +++-- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 14 +++++++------- src/Avalonia.Native/WindowImplBase.cs | 4 ++-- src/Avalonia.X11/X11Window.cs | 12 +++++++----- src/Windows/Avalonia.Win32/WindowImpl.cs | 4 ++-- 8 files changed, 36 insertions(+), 31 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs index 2e7218b956..d76ef0a7bf 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml.cs +++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs @@ -18,18 +18,18 @@ namespace ControlCatalog { var ctl = this.FindControl(name); ctl.Cursor = new Cursor(cursor); - ctl.PointerPressed += delegate + ctl.PointerPressed += (i, e) => { - PlatformImpl?.BeginResizeDrag(edge); + PlatformImpl?.BeginResizeDrag(edge, e); }; } private void InitializeComponent() { AvaloniaXamlLoader.Load(this); - this.FindControl("TitleBar").PointerPressed += delegate + this.FindControl("TitleBar").PointerPressed += (i, e) => { - PlatformImpl?.BeginMoveDrag(); + PlatformImpl?.BeginMoveDrag(e); }; SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West); SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East); @@ -39,7 +39,8 @@ namespace ControlCatalog SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast); SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest); SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast); - this.FindControl Func Closing { get; set; } - + /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler. /// - void BeginMoveDrag(); + void BeginMoveDrag(PointerPressedEventArgs e); /// /// Starts resizing a window. This function is used if an application has window resizing controls. /// Should be called from left mouse button press event handler /// - void BeginResizeDrag(WindowEdge edge); - + void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e); + /// /// Sets the client size of the top level. /// void Resize(Size clientSize); - + /// /// Sets the client size of the top level. /// void Move(PixelPoint point); - + /// /// Minimum width of the window. /// diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index ec2fca0db5..1816a6c81d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -261,13 +261,13 @@ namespace Avalonia.Controls /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// - public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag(); + public void BeginMoveDrag(PointerPressedEventArgs e) => PlatformImpl?.BeginMoveDrag(e); /// /// Starts resizing a window. This function is used if an application has window resizing controls. /// Should be called from left mouse button press event handler /// - public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge); + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e); /// /// Carries out the arrange pass of the window. diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs index 40524ad4b7..86e34ca6d4 100644 --- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs +++ b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs @@ -2,6 +2,7 @@ using System.Reactive.Disposables; using Avalonia.Controls; using Avalonia.Controls.Remote.Server; +using Avalonia.Input; using Avalonia.Platform; using Avalonia.Remote.Protocol; using Avalonia.Remote.Protocol.Viewport; @@ -27,11 +28,11 @@ namespace Avalonia.DesignerSupport.Remote { } - public void BeginMoveDrag() + public void BeginMoveDrag(PointerPressedEventArgs e) { } - public void BeginResizeDrag(WindowEdge edge) + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { } diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 16d434b614..4bba5ef41b 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -46,7 +46,7 @@ namespace Avalonia.DesignerSupport.Remote Resize(size); })); } - + public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root); public void Dispose() { @@ -75,11 +75,11 @@ namespace Avalonia.DesignerSupport.Remote { } - public void BeginMoveDrag() + public void BeginMoveDrag(PointerPressedEventArgs e) { } - public void BeginResizeDrag(WindowEdge edge) + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { } @@ -93,7 +93,7 @@ namespace Avalonia.DesignerSupport.Remote public void Move(PixelPoint point) { - + } public IScreenImpl Screen { get; } = new ScreenStub(); @@ -153,7 +153,7 @@ namespace Avalonia.DesignerSupport.Remote { public void Save(Stream outputStream) { - + } } @@ -167,10 +167,10 @@ namespace Avalonia.DesignerSupport.Remote class SystemDialogsStub : ISystemDialogImpl { public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => - Task.FromResult((string[]) null); + Task.FromResult((string[])null); public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) => - Task.FromResult((string) null); + Task.FromResult((string)null); } class ScreenStub : IScreenImpl diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index d8ff370c45..209e1bc7ea 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -301,7 +301,7 @@ namespace Avalonia.Native _native.Hide(); } - public void BeginMoveDrag() + public void BeginMoveDrag(PointerPressedEventArgs e) { _native.BeginMoveDrag(); } @@ -343,7 +343,7 @@ namespace Avalonia.Native _native.SetMinMaxSize(minSize.ToAvnSize(), maxSize.ToAvnSize()); } - public void BeginResizeDrag(WindowEdge edge) + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 860456a838..2630f9cf96 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -878,21 +878,23 @@ namespace Avalonia.X11 } - void BeginMoveResize(NetWmMoveResize side) + void BeginMoveResize(NetWmMoveResize side, PointerPressedEventArgs e) { var pos = GetCursorPos(_x11); XUngrabPointer(_x11.Display, new IntPtr(0)); SendNetWMMessage (_x11.Atoms._NET_WM_MOVERESIZE, (IntPtr) pos.x, (IntPtr) pos.y, (IntPtr) side, (IntPtr) 1, (IntPtr)1); // left button + + e.Pointer.Capture(null); } - public void BeginMoveDrag() + public void BeginMoveDrag(PointerPressedEventArgs e) { - BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE); + BeginMoveResize(NetWmMoveResize._NET_WM_MOVERESIZE_MOVE, e); } - public void BeginResizeDrag(WindowEdge edge) + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { var side = NetWmMoveResize._NET_WM_MOVERESIZE_CANCEL; if (edge == WindowEdge.East) @@ -911,7 +913,7 @@ namespace Avalonia.X11 side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT; if (edge == WindowEdge.SouthWest) side = NetWmMoveResize._NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT; - BeginMoveResize(side); + BeginMoveResize(side, e); } public void SetTitle(string title) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index e8c3177ec5..e8117f5533 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -331,7 +331,7 @@ namespace Avalonia.Win32 ShowWindow(_showWindowState); } - public void BeginMoveDrag() + public void BeginMoveDrag(PointerPressedEventArgs e) { WindowsMouseDevice.Instance.Capture(null); UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN, @@ -350,7 +350,7 @@ namespace Avalonia.Win32 {WindowEdge.West, UnmanagedMethods.HitTestValues.HTLEFT} }; - public void BeginResizeDrag(WindowEdge edge) + public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) { #if USE_MANAGED_DRAG _managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position)); From a351e7c523638367d4b361298646f1d21783a68c Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Mon, 14 Oct 2019 20:54:26 +0800 Subject: [PATCH 090/118] Address review. --- samples/ControlCatalog/DecoratedWindow.xaml.cs | 3 +-- src/Windows/Avalonia.Win32/WindowImpl.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/DecoratedWindow.xaml.cs b/samples/ControlCatalog/DecoratedWindow.xaml.cs index d76ef0a7bf..bdf5b8fbee 100644 --- a/samples/ControlCatalog/DecoratedWindow.xaml.cs +++ b/samples/ControlCatalog/DecoratedWindow.xaml.cs @@ -39,8 +39,7 @@ namespace ControlCatalog SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast); SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest); SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast); - this.FindControl /// The associated device. /// The event timestamp. - public RawInputEventArgs(IInputDevice device, ulong timestamp) + /// The root from which the event originates. + public RawInputEventArgs(IInputDevice device, ulong timestamp, IInputRoot root) { Contract.Requires(device != null); Device = device; Timestamp = timestamp; + Root = root; } /// @@ -34,6 +36,11 @@ namespace Avalonia.Input.Raw /// public IInputDevice Device { get; } + /// + /// Gets the root from which the event originates. + /// + public IInputRoot Root { get; } + /// /// Gets or sets a value indicating whether the event was handled. /// diff --git a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs index cd8b2eacf7..c720cf11f8 100644 --- a/src/Avalonia.Input/Raw/RawKeyEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawKeyEventArgs.cs @@ -14,9 +14,10 @@ namespace Avalonia.Input.Raw public RawKeyEventArgs( IKeyboardDevice device, ulong timestamp, + IInputRoot root, RawKeyEventType type, Key key, RawInputModifiers modifiers) - : base(device, timestamp) + : base(device, timestamp, root) { Key = key; Type = type; diff --git a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs index 88f6daf11f..56854c7d29 100644 --- a/src/Avalonia.Input/Raw/RawPointerEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawPointerEventArgs.cs @@ -44,22 +44,16 @@ namespace Avalonia.Input.Raw RawPointerEventType type, Point position, RawInputModifiers inputModifiers) - : base(device, timestamp) + : base(device, timestamp, root) { Contract.Requires(device != null); Contract.Requires(root != null); - Root = root; Position = position; Type = type; InputModifiers = inputModifiers; } - /// - /// Gets the root from which the event originates. - /// - public IInputRoot Root { get; } - /// /// Gets the mouse position, in client DIPs. /// diff --git a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs index 0d1e5d2422..010342da15 100644 --- a/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs +++ b/src/Avalonia.Input/Raw/RawTextInputEventArgs.cs @@ -5,11 +5,16 @@ namespace Avalonia.Input.Raw { public class RawTextInputEventArgs : RawInputEventArgs { - public string Text { get; set; } - - public RawTextInputEventArgs(IKeyboardDevice device, ulong timestamp, string text) : base(device, timestamp) + public RawTextInputEventArgs( + IKeyboardDevice device, + ulong timestamp, + IInputRoot root, + string text) + : base(device, timestamp, root) { Text = text; } + + public string Text { get; set; } } } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 209e1bc7ea..fe7458d583 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -204,7 +204,7 @@ namespace Avalonia.Native { Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - var args = new RawTextInputEventArgs(_keyboard, timeStamp, text); + var args = new RawTextInputEventArgs(_keyboard, timeStamp, _inputRoot, text); Input?.Invoke(args); @@ -215,7 +215,7 @@ namespace Avalonia.Native { Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1); - var args = new RawKeyEventArgs(_keyboard, timeStamp, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers); + var args = new RawKeyEventArgs(_keyboard, timeStamp, _inputRoot, (RawKeyEventType)type, (Key)key, (RawInputModifiers)modifiers); Input?.Invoke(args); diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 2630f9cf96..17471fad10 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -455,7 +455,7 @@ namespace Avalonia.X11 key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32(); - ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), + ScheduleInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp, X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev); @@ -470,7 +470,7 @@ namespace Avalonia.X11 if (text[0] < ' ' || text[0] == 0x7f) //Control codes or DEL return; } - ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), text), + ScheduleInput(new RawTextInputEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot, text), ref ev); } } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 71c398481b..2887468046 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -212,17 +212,17 @@ namespace Avalonia.Win32.Interop.Wpf protected override void OnMouseLeave(MouseEventArgs e) => MouseEvent(RawPointerEventType.LeaveWindow, e); protected override void OnKeyDown(KeyEventArgs e) - => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, RawKeyEventType.KeyDown, + => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, RawKeyEventType.KeyDown, (Key) e.Key, GetModifiers(null))); protected override void OnKeyUp(KeyEventArgs e) - => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, RawKeyEventType.KeyUp, + => _ttl.Input?.Invoke(new RawKeyEventArgs(_keyboard, (uint)e.Timestamp, _inputRoot, RawKeyEventType.KeyUp, (Key)e.Key, GetModifiers(null))); protected override void OnTextInput(TextCompositionEventArgs e) - => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, e.Text)); + => _ttl.Input?.Invoke(new RawTextInputEventArgs(_keyboard, (uint) e.Timestamp, _inputRoot, e.Text)); void ITopLevelImpl.SetCursor(IPlatformHandle cursor) { diff --git a/src/Windows/Avalonia.Win32/OleDropTarget.cs b/src/Windows/Avalonia.Win32/OleDropTarget.cs index 8ac0f25598..b17e0d6c09 100644 --- a/src/Windows/Avalonia.Win32/OleDropTarget.cs +++ b/src/Windows/Avalonia.Win32/OleDropTarget.cs @@ -8,13 +8,13 @@ namespace Avalonia.Win32 { class OleDropTarget : IDropTarget { - private readonly IInputElement _target; + private readonly IInputRoot _target; private readonly ITopLevelImpl _tl; private readonly IDragDropDevice _dragDevice; private IDataObject _currentDrag = null; - public OleDropTarget(ITopLevelImpl tl, IInputElement target) + public OleDropTarget(ITopLevelImpl tl, IInputRoot target) { _dragDevice = AvaloniaLocator.Current.GetService(); _tl = tl; diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4f4c9852e8..04a9303d53 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -508,6 +508,7 @@ namespace Avalonia.Win32 e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, + _owner, RawKeyEventType.KeyDown, KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers); break; @@ -521,6 +522,7 @@ namespace Avalonia.Win32 e = new RawKeyEventArgs( WindowsKeyboardDevice.Instance, timestamp, + _owner, RawKeyEventType.KeyUp, KeyInterop.KeyFromVirtualKey(ToInt32(wParam)), WindowsKeyboardDevice.Instance.Modifiers); break; @@ -528,7 +530,7 @@ namespace Avalonia.Win32 // Ignore control chars if (ToInt32(wParam) >= 32) { - e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, + e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, new string((char)ToInt32(wParam), 1)); } diff --git a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs index 645f87163a..72c81659c3 100644 --- a/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TopLevelTests.cs @@ -182,6 +182,7 @@ namespace Avalonia.Controls.UnitTests var input = new RawKeyEventArgs( new Mock().Object, 0, + target, RawKeyEventType.KeyDown, Key.A, RawInputModifiers.None); impl.Object.Input(input); From 91662d7020cbb8ab3a370c7f67a02faf4da44d31 Mon Sep 17 00:00:00 2001 From: Dariusz Komosinski Date: Thu, 17 Oct 2019 23:29:00 +0200 Subject: [PATCH 105/118] Fix Grid not reacting to cell structure changes during runtime. --- src/Avalonia.Controls/Grid.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 4f41b0bf1e..8ecfe349f8 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -24,13 +24,14 @@ namespace Avalonia.Controls { static Grid() { - IsSharedSizeScopeProperty.Changed.AddClassHandler(DefinitionBase.OnIsSharedSizeScopePropertyChanged); ShowGridLinesProperty.Changed.AddClassHandler(OnShowGridLinesPropertyChanged); - ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); - RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + IsSharedSizeScopeProperty.Changed.AddClassHandler(DefinitionBase.OnIsSharedSizeScopePropertyChanged); + ColumnProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + ColumnSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + RowSpanProperty.Changed.AddClassHandler(OnCellAttachedPropertyChanged); + AffectsParentMeasure(ColumnProperty, ColumnSpanProperty, RowProperty, RowSpanProperty); } From b7e509f04637c9fccec17f38caadd5e68196c055 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 15:35:08 +0100 Subject: [PATCH 106/118] thumb adds :pressed pseudo class. --- src/Avalonia.Controls/Primitives/Thumb.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index 7e9680dc9f..9f5ddb666c 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -83,6 +83,8 @@ namespace Avalonia.Controls.Primitives Vector = (Vector)_lastPoint, }; + PseudoClasses.Add(":pressed"); + RaiseEvent(ev); } @@ -102,6 +104,8 @@ namespace Avalonia.Controls.Primitives RaiseEvent(ev); } + + PseudoClasses.Remove(":pressed"); } } } From 5b1a552127e240b16fb8d34fe9dc0698c79f35ab Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 15:35:51 +0100 Subject: [PATCH 107/118] fill in the bottom right corner of scrollviewer area where the horizontal and vertical scrollbars meet. --- src/Avalonia.Themes.Default/ScrollViewer.xaml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Themes.Default/ScrollViewer.xaml b/src/Avalonia.Themes.Default/ScrollViewer.xaml index 3e130cad67..38f4eef964 100644 --- a/src/Avalonia.Themes.Default/ScrollViewer.xaml +++ b/src/Avalonia.Themes.Default/ScrollViewer.xaml @@ -36,6 +36,7 @@ Visibility="{TemplateBinding VerticalScrollBarVisibility}" Grid.Column="1" Focusable="False"/> + From 1bc56dc777cd1e41eaa72306877add569dd88023 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 15:36:29 +0100 Subject: [PATCH 108/118] overhaul scrollbar templates, to support pointer over, pointer pressed, and nice arrows. basically matches visual studio now. --- src/Avalonia.Themes.Default/RepeatButton.xaml | 6 +- src/Avalonia.Themes.Default/ScrollBar.xaml | 264 +++++++++--------- 2 files changed, 139 insertions(+), 131 deletions(-) diff --git a/src/Avalonia.Themes.Default/RepeatButton.xaml b/src/Avalonia.Themes.Default/RepeatButton.xaml index f555209471..702e4e6ebd 100644 --- a/src/Avalonia.Themes.Default/RepeatButton.xaml +++ b/src/Avalonia.Themes.Default/RepeatButton.xaml @@ -33,12 +33,8 @@ - - \ No newline at end of file + diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index c9552a607c..05689def0a 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -1,128 +1,140 @@ - - - - - - - - + + + + + + + + + + + + + + From 45028cd2eaeb4d56e3d4348fcaead360739438a7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 15:37:06 +0100 Subject: [PATCH 109/118] add 2 extra control colour levels like UWP, will need to rename these in 0.10, right now will be a breaking change. --- .../Accents/BaseDark.xaml | 7 ++++++- .../Accents/BaseLight.xaml | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index 0ed17fae76..fbe74df1b2 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -14,7 +14,9 @@ #FFA0A0A0 #FF282828 #FF505050 + #FF686868 #FF808080 + #FFEFEBEF #FFA8A8A8 #FF828282 #FF505050 @@ -32,7 +34,9 @@ + + @@ -61,6 +65,7 @@ 12 16 - 10 + 20 + 9 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 3a8a8ec446..2643aae7b2 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -12,10 +12,15 @@ #FFAAAAAA #FF888888 #FF333333 - #FFFFFFFF - #FFAAAAAA - #FF888888 - #FFF0F0F0 + + + #FF868999 + #FFF5F5F5 + #FFC2C3C9 + #FF686868 + #FF5B5B5B + + #FFF0F0F0 #FFD0D0D0 #FF808080 #FF000000 @@ -32,7 +37,9 @@ + + @@ -61,6 +68,7 @@ 12 16 - 10 + 20 + 9 From 7658791ab9e49c416b3934c60047862a3d1d5b9a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 16:52:46 +0100 Subject: [PATCH 110/118] fix centralization of buttons. --- src/Avalonia.Themes.Default/ScrollBar.xaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 05689def0a..856fba00c0 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -5,12 +5,12 @@ - - + - - + From ab54a9ced42c7c40c5aa9556e201b4071ccde0b1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 16:57:48 +0100 Subject: [PATCH 111/118] fix indentation --- src/Avalonia.Themes.Default/Accents/BaseDark.xaml | 8 ++++---- src/Avalonia.Themes.Default/Accents/BaseLight.xaml | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index fbe74df1b2..e040ed233e 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -34,9 +34,9 @@ - + - + @@ -65,7 +65,7 @@ 12 16 - 20 - 9 + 20 + 9 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index 2643aae7b2..b56bd3eaf6 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -20,7 +20,7 @@ #FF686868 #FF5B5B5B - #FFF0F0F0 + #FFF0F0F0 #FFD0D0D0 #FF808080 #FF000000 @@ -37,9 +37,9 @@ - + - + @@ -68,7 +68,7 @@ 12 16 - 20 - 9 + 20 + 9 From 31e56bc155ed5fb9fb27b5ba2c56ec059248925f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 17:11:57 +0100 Subject: [PATCH 112/118] fix centralization of scroll buttons --- src/Avalonia.Themes.Default/Accents/BaseDark.xaml | 4 ++-- src/Avalonia.Themes.Default/Accents/BaseLight.xaml | 4 ++-- src/Avalonia.Themes.Default/ScrollBar.xaml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml index e040ed233e..ffe3e92202 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseDark.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseDark.xaml @@ -65,7 +65,7 @@ 12 16 - 20 - 9 + 18 + 8 diff --git a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml index b56bd3eaf6..c0e5f47eed 100644 --- a/src/Avalonia.Themes.Default/Accents/BaseLight.xaml +++ b/src/Avalonia.Themes.Default/Accents/BaseLight.xaml @@ -68,7 +68,7 @@ 12 16 - 20 - 9 + 18 + 8 diff --git a/src/Avalonia.Themes.Default/ScrollBar.xaml b/src/Avalonia.Themes.Default/ScrollBar.xaml index 856fba00c0..64a4399d16 100644 --- a/src/Avalonia.Themes.Default/ScrollBar.xaml +++ b/src/Avalonia.Themes.Default/ScrollBar.xaml @@ -10,7 +10,7 @@ Grid.Row="0" Focusable="False" MinHeight="{DynamicResource ScrollBarThickness}"> - + - + @@ -57,7 +57,7 @@ Grid.Column="0" Focusable="False" MinWidth="{DynamicResource ScrollBarThickness}"> - + - + From c1ce0d6b99745e6c21d3767149bfbf2cad99e4df Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 18 Oct 2019 17:37:21 +0100 Subject: [PATCH 113/118] [OSX] fix scroll wheel speed. --- native/Avalonia.Native/src/OSX/window.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 9c7e9323e0..2a5acaea91 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -855,8 +855,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent if(type == Wheel) { - delta.X = [event scrollingDeltaX] / 5; - delta.Y = [event scrollingDeltaY] / 5; + delta.X = [event scrollingDeltaX] / 50; + delta.Y = [event scrollingDeltaY] / 50; if(delta.X == 0 && delta.Y == 0) { From 8f736db8fc71aa8d1545192559d603dd68245365 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Oct 2019 18:32:45 +0300 Subject: [PATCH 114/118] Updated XamlIl --- src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index c7155c5f6c..6a6a28513e 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit c7155c5f6c1a5153ee2d8cd78e5d1524dd6744cf +Subproject commit 6a6a28513e5abddacaf52e088635ff52d98f079d From 61340acf4b86e1e9dd5e7e769c6b7438cb9104dd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Oct 2019 19:11:21 +0300 Subject: [PATCH 115/118] Disable IL verification for nuget version of compiler by default --- build/BuildTargets.targets | 1 + packages/Avalonia/AvaloniaBuildTasks.targets | 2 ++ src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs | 4 +++- src/Avalonia.Build.Tasks/Program.cs | 3 ++- src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs | 4 ++-- .../XamlIl/AvaloniaXamlIlRuntimeCompiler.cs | 6 +++--- src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- 7 files changed, 14 insertions(+), 8 deletions(-) diff --git a/build/BuildTargets.targets b/build/BuildTargets.targets index 08ec039ec7..a5543cd050 100644 --- a/build/BuildTargets.targets +++ b/build/BuildTargets.targets @@ -2,6 +2,7 @@ $(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll true + true diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets index a455e2b3fc..552713f94b 100644 --- a/packages/Avalonia/AvaloniaBuildTasks.targets +++ b/packages/Avalonia/AvaloniaBuildTasks.targets @@ -53,6 +53,7 @@ $(IntermediateOutputPath)/Avalonia/references $(IntermediateOutputPath)/Avalonia/original.dll + false !string.IsNullOrWhiteSpace(l)).ToArray(), - ProjectDirectory, OutputPath); + ProjectDirectory, OutputPath, VerifyIl); if (!res.Success) return false; if (!res.WrittenFile) @@ -66,6 +66,8 @@ namespace Avalonia.Build.Tasks public string ProjectDirectory { get; set; } public string OutputPath { get; set; } + + public bool VerifyIl { get; set; } public IBuildEngine BuildEngine { get; set; } public ITaskHost HostObject { get; set; } diff --git a/src/Avalonia.Build.Tasks/Program.cs b/src/Avalonia.Build.Tasks/Program.cs index d356b15408..1909c4c6ec 100644 --- a/src/Avalonia.Build.Tasks/Program.cs +++ b/src/Avalonia.Build.Tasks/Program.cs @@ -28,7 +28,8 @@ namespace Avalonia.Build.Tasks ReferencesFilePath = args[1], OutputPath = args[2], BuildEngine = new ConsoleBuildEngine(), - ProjectDirectory = Directory.GetCurrentDirectory() + ProjectDirectory = Directory.GetCurrentDirectory(), + VerifyIl = true }.Execute() ? 0 : 2; diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs index 0dd52c9b48..e348eb0fbc 100644 --- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs +++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs @@ -40,7 +40,7 @@ namespace Avalonia.Build.Tasks } public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory, - string output) + string output, bool verifyIl) { var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input); var asm = typeSystem.TargetAssemblyDefinition; @@ -65,7 +65,7 @@ namespace Avalonia.Build.Tasks var contextClass = XamlIlContextDefinition.GenerateContextClass(typeSystem.CreateTypeBuilder(contextDef), typeSystem, xamlLanguage); - var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass); + var compiler = new AvaloniaXamlIlCompiler(compilerConfig, contextClass) { EnableIlVerification = verifyIl }; var editorBrowsableAttribute = typeSystem .GetTypeReference(typeSystem.FindType("System.ComponentModel.EditorBrowsableAttribute")) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs index b91d679fba..5a5da518d0 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/AvaloniaXamlIlRuntimeCompiler.cs @@ -114,10 +114,10 @@ namespace Avalonia.Markup.Xaml.XamlIl InitializeSre(); var asm = localAssembly == null ? null : _sreTypeSystem.GetAssembly(localAssembly); - + var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_sreTypeSystem, asm, - _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter), - _sreContextType); + _sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter), + _sreContextType) { EnableIlVerification = true }; var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri); IXamlIlType overrideType = null; diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 6a6a28513e..88468ebe90 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 6a6a28513e5abddacaf52e088635ff52d98f079d +Subproject commit 88468ebe90112dc23b04147deba0d9a0dcc99157 From 842958e551aca5e616caf7f70dc3b691eb9c2e90 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 19 Oct 2019 21:26:19 +0300 Subject: [PATCH 116/118] Updated XamlIl --- src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github index 88468ebe90..ad9915e193 160000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml/XamlIl/xamlil.github @@ -1 +1 @@ -Subproject commit 88468ebe90112dc23b04147deba0d9a0dcc99157 +Subproject commit ad9915e19398a49c5a11b66000c361659ca692b3 From 919f6fc5186c31c52c95b4b76c07239d421786b9 Mon Sep 17 00:00:00 2001 From: nishanth2143 Date: Mon, 21 Oct 2019 20:07:33 +0530 Subject: [PATCH 117/118] Updated README.md file Corrected grammatical errors --- readme.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index ee44a0cc3f..512b35a454 100644 --- a/readme.md +++ b/readme.md @@ -8,9 +8,9 @@ ## About -**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS. +**Avalonia** is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), macOS and with experimental support for Android and iOS. -**Avalonia** is ready for **General-Purpose Desktop App Development**. However there may be some bugs and breaking changes as we continue along into this project's development. To see the status for some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239). +**Avalonia** is ready for **General-Purpose Desktop App Development**. However, there may be some bugs and breaking changes as we continue along into this project's development. To see the status of some of our features, please see our [Roadmap here](https://github.com/AvaloniaUI/Avalonia/issues/2239). | Control catalog | Desktop platforms | Mobile platforms | |---|---|---| @@ -20,11 +20,11 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?itemName=AvaloniaTeam.AvaloniaforVisualStudio) contains project and control templates that will help you get started. After installing it, open "New Project" dialog in Visual Studio, choose "Avalonia" in "Visual C#" section, select "Avalonia .NET Core Application" and press OK (screenshot). Now you can write code and markup that will work on multiple platforms! -For those without Visual Studio, starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core). +For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core). Avalonia is delivered via NuGet package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed)) -Use these commands in Package Manager console to install Avalonia manually: +Use these commands in the Package Manager console to install Avalonia manually: ``` Install-Package Avalonia Install-Package Avalonia.Desktop From 47c9f53e9a4aadf9d626541082326259e5684b6d Mon Sep 17 00:00:00 2001 From: Jumar Macato Date: Tue, 22 Oct 2019 00:38:39 +0800 Subject: [PATCH 118/118] Don't reopen an already active submenu on hover. --- .../Platform/DefaultMenuInteractionHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index b5dbd1e668..97aeead608 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -273,7 +273,8 @@ namespace Avalonia.Controls.Platform if (item.IsTopLevel) { - if (item.Parent.SelectedItem?.IsSubMenuOpen == true) + if (item != item.Parent.SelectedItem && + item.Parent.SelectedItem?.IsSubMenuOpen == true) { item.Parent.SelectedItem.Close(); SelectItemAndAncestors(item);