소스 검색

Merge branch 'master' into fixes/batchupdate-set-cleared-value-end-update

Dan Walmsley 4 년 전
부모
커밋
f731abc34a
24개의 변경된 파일155개의 추가작업 그리고 172개의 파일을 삭제
  1. 3 2
      native/Avalonia.Native/src/OSX/menu.h
  2. 26 0
      native/Avalonia.Native/src/OSX/menu.mm
  3. 5 2
      native/Avalonia.Native/src/OSX/window.mm
  4. 1 1
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  5. 1 1
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  6. 1 21
      src/Android/Avalonia.Android/AndroidPlatform.cs
  7. 8 7
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  8. 1 1
      src/Android/Avalonia.Android/Avalonia.Android.csproj
  9. 2 6
      src/Android/Avalonia.Android/AvaloniaActivity.cs
  10. 0 14
      src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs
  11. 4 2
      src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs
  12. 1 1
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  13. 2 0
      src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
  14. 13 25
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  15. 22 69
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  16. 0 11
      src/Android/Avalonia.Android/app.config
  17. 2 2
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  18. 1 1
      src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
  19. 3 1
      src/Avalonia.Controls/ApiCompatBaseline.txt
  20. 2 0
      src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs
  21. 32 1
      src/Avalonia.Controls/NativeMenu.cs
  22. 1 1
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  23. 22 0
      src/Avalonia.Native/IAvnMenu.cs
  24. 2 3
      src/Avalonia.Native/avn.idl

+ 3 - 2
native/Avalonia.Native/src/OSX/menu.h

@@ -60,7 +60,6 @@ public:
     void RaiseOnClicked();
 };
 
-
 class AvnAppMenu : public ComSingleObject<IAvnMenu, &IID_IAvnMenu>
 {
 private:
@@ -71,10 +70,12 @@ public:
     FORWARD_IUNKNOWN()
     
     AvnAppMenu(IAvnMenuEvents* events);
-        
+
     AvnMenu* GetNative();
     
     void RaiseNeedsUpdate ();
+    void RaiseOpening();
+    void RaiseClosed();
     
     virtual HRESULT InsertItem (int index, IAvnMenuItem* item) override;
     

+ 26 - 0
native/Avalonia.Native/src/OSX/menu.mm

@@ -298,6 +298,23 @@ void AvnAppMenu::RaiseNeedsUpdate()
     }
 }
 
+void AvnAppMenu::RaiseOpening()
+{
+    if(_baseEvents != nullptr)
+    {
+        _baseEvents->Opening();
+    }
+}
+
+void AvnAppMenu::RaiseClosed()
+{
+    if(_baseEvents != nullptr)
+    {
+        _baseEvents->Closed();
+    }
+}
+
+
 HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item)
 {
     @autoreleasepool
@@ -382,6 +399,15 @@ HRESULT AvnAppMenu::Clear()
     _parent->RaiseNeedsUpdate();
 }
 
+- (void)menuWillOpen:(NSMenu *)menu
+{
+    _parent->RaiseOpening();
+}
+
+- (void)menuDidClose:(NSMenu *)menu
+{
+    _parent->RaiseClosed();
+}
 
 @end
 

+ 5 - 2
native/Avalonia.Native/src/OSX/window.mm

@@ -2231,9 +2231,12 @@ protected:
     {
         @autoreleasepool
         {
-            [Window setContentSize:NSSize{x, y}];
+            if (Window != nullptr)
+            {
+                [Window setContentSize:NSSize{x, y}];
             
-            [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
+                [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
+            }
             
             return S_OK;
         }

+ 1 - 1
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@@ -16,7 +16,7 @@
     <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
     <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
     <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
     <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

+ 1 - 1
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
-	<uses-sdk android:targetSdkVersion="29" />
+	<uses-sdk android:targetSdkVersion="30" />
 	<application android:label="ControlCatalog.Android"></application>
 </manifest>

+ 1 - 21
src/Android/Avalonia.Android/AndroidPlatform.cs

@@ -29,21 +29,12 @@ namespace Avalonia
 
 namespace Avalonia.Android
 {
-    class AndroidPlatform : IPlatformSettings, IWindowingPlatform
+    class AndroidPlatform : IPlatformSettings
     {
         public static readonly AndroidPlatform Instance = new AndroidPlatform();
         public static AndroidPlatformOptions Options { get; private set; }
         public Size DoubleClickSize => new Size(4, 4);
         public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200);
-        public double RenderScalingFactor => _scalingFactor;
-        public double LayoutScalingFactor => _scalingFactor;
-
-        private readonly double _scalingFactor = 1;
-
-        public AndroidPlatform()
-        {
-            _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
-        }
 
         public static void Initialize(Type appType, AndroidPlatformOptions options)
         {
@@ -56,7 +47,6 @@ namespace Avalonia.Android
                 .Bind<IPlatformSettings>().ToConstant(Instance)
                 .Bind<IPlatformThreadingInterface>().ToConstant(new AndroidThreadingInterface())
                 .Bind<ISystemDialogImpl>().ToTransient<SystemDialogImpl>()
-                .Bind<IWindowingPlatform>().ToConstant(Instance)
                 .Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
                 .Bind<IRenderTimer>().ToConstant(new ChoreographerTimer())
                 .Bind<IRenderLoop>().ToConstant(new RenderLoop())
@@ -70,16 +60,6 @@ namespace Avalonia.Android
                 EglPlatformOpenGlInterface.TryInitialize();
             }
         }
-
-        public IWindowImpl CreateWindow()
-        {
-            throw new NotSupportedException();
-        }
-
-        public IWindowImpl CreateEmbeddableWindow()
-        {
-            throw new NotSupportedException();
-        }
     }
 
     public sealed class AndroidPlatformOptions

+ 8 - 7
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@@ -1,25 +1,26 @@
 using System;
 using System.Reactive.Disposables;
 using System.Threading;
+
 using Android.OS;
+
 using Avalonia.Platform;
 using Avalonia.Threading;
 
+using App = Android.App.Application;
+
 namespace Avalonia.Android
 {
-    class AndroidThreadingInterface : IPlatformThreadingInterface
+    internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface
     {
         private Handler _handler;
 
         public AndroidThreadingInterface()
         {
-            _handler = new Handler(global::Android.App.Application.Context.MainLooper);
+            _handler = new Handler(App.Context.MainLooper);
         }
 
-        public void RunLoop(CancellationToken cancellationToken)
-        {
-            return;
-        }
+        public void RunLoop(CancellationToken cancellationToken) => throw new NotSupportedException();
 
         public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
         {
@@ -57,7 +58,7 @@ namespace Avalonia.Android
                     });
                 }
             }, null, TimeSpan.Zero, interval);
-            
+
             return Disposable.Create(() =>
             {
                 lock (l)

+ 1 - 1
src/Android/Avalonia.Android/Avalonia.Android.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="MSBuild.Sdk.Extras">
   <PropertyGroup>
-    <TargetFramework>monoandroid90</TargetFramework>
+    <TargetFramework>monoandroid11.0</TargetFramework>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
   <ItemGroup>

+ 2 - 6
src/Android/Avalonia.Android/AvaloniaActivity.cs

@@ -1,4 +1,3 @@
-
 using Android.App;
 using Android.OS;
 using Android.Views;
@@ -7,7 +6,6 @@ namespace Avalonia.Android
 {
     public abstract class AvaloniaActivity : Activity
     {
-        
         internal AvaloniaView View;
         object _content;
 
@@ -35,9 +33,7 @@ namespace Avalonia.Android
             }
         }
 
-        public override bool DispatchKeyEvent(KeyEvent e)
-        {
-            return View.DispatchKeyEvent(e);
-        }
+        public override bool DispatchKeyEvent(KeyEvent e) =>
+            View.DispatchKeyEvent(e) ? true : base.DispatchKeyEvent(e);
     }
 }

+ 0 - 14
src/Android/Avalonia.Android/Platform/Input/AndroidMouseDevice.cs

@@ -1,14 +0,0 @@
-using Avalonia.Input;
-
-namespace Avalonia.Android.Platform.Input
-{
-    public class AndroidMouseDevice : MouseDevice
-    {
-        public static AndroidMouseDevice Instance { get; } = new AndroidMouseDevice();
-
-        public AndroidMouseDevice()
-        {
-
-        }
-    }
-}

+ 4 - 2
src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
     {
         private IntPtr _window;
 
-        public AndroidFramebuffer(Surface surface)
+        public AndroidFramebuffer(Surface surface, double scaling)
         {
             if(surface == null)
                 throw new ArgumentNullException(nameof(surface));
@@ -31,6 +31,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
             RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4);
             Address = buffer.bits;
+
+            Dpi = scaling * new Vector(96, 96);
         }
 
         public void Dispose()
@@ -44,7 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
         public IntPtr Address { get; set; }
         public PixelSize Size { get; }
         public int RowBytes { get; }
-        public Vector Dpi { get; } = new Vector(96, 96);
+        public Vector Dpi { get; }
         public PixelFormat Format { get; }
 
         [DllImport("android")]

+ 1 - 1
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@@ -12,6 +12,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _topLevel = topLevel;
         }
 
-        public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface);
+        public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface, _topLevel.RenderScaling);
     }
 }

+ 2 - 0
src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs

@@ -43,11 +43,13 @@ namespace Avalonia.Android
             }
         }
 
+        [Obsolete("deprecated")]
         public override void Invalidate(global::Android.Graphics.Rect dirty)
         {
             Invalidate();
         }
 
+        [Obsolete("deprecated")]
         public override void Invalidate(int l, int t, int r, int b)
         {
             Invalidate();

+ 13 - 25
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@@ -6,7 +6,6 @@ using Android.Runtime;
 using Android.Views;
 
 using Avalonia.Android.OpenGL;
-using Avalonia.Android.Platform.Input;
 using Avalonia.Android.Platform.Specific;
 using Avalonia.Android.Platform.Specific.Helpers;
 using Avalonia.Controls;
@@ -35,16 +34,16 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _view = new ViewImpl(context, this, placeOnTop);
             _keyboardHelper = new AndroidKeyboardEventsHelper<TopLevelImpl>(this);
             _touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
-                p => GetAvaloniaPointFromEvent(p));
+                GetAvaloniaPointFromEvent);
 
             _gl = GlPlatformSurface.TryCreate(this);
             _framebuffer = new FramebufferManager(this);
 
-            MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
-                _view.Resources.DisplayMetrics.HeightPixels);
-        }
-
+            RenderScaling = (int)_view.Resources.DisplayMetrics.Density;
 
+            MaxClientSize = new PixelSize(_view.Resources.DisplayMetrics.WidthPixels,
+                _view.Resources.DisplayMetrics.HeightPixels).ToSize(RenderScaling);
+        }
 
         private bool _handleEvents;
 
@@ -58,25 +57,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             }
         }
 
-        public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY());
+        public virtual Point GetAvaloniaPointFromEvent(MotionEvent e, int pointerIndex) =>
+            new Point(e.GetX(pointerIndex), e.GetY(pointerIndex)) / RenderScaling;
 
         public IInputRoot InputRoot { get; private set; }
 
-        public virtual Size ClientSize
-        {
-            get
-            {
-                if (_view == null)
-                    return new Size(0, 0);
-                return new Size(_view.Width, _view.Height);
-            }
-            set
-            {
-
-            }
-        }
+        public virtual Size ClientSize => Size.ToSize(RenderScaling);
 
-        public IMouseDevice MouseDevice => AndroidMouseDevice.Instance;
+        public IMouseDevice MouseDevice { get; } = new MouseDevice();
 
         public Action Closed { get; set; }
 
@@ -115,12 +103,12 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
         public Point PointToClient(PixelPoint point)
         {
-            return point.ToPoint(1);
+            return point.ToPoint(RenderScaling);
         }
 
         public PixelPoint PointToScreen(Point point)
         {
-            return PixelPoint.FromPoint(point, 1);
+            return PixelPoint.FromPoint(point, RenderScaling);
         }
 
         public void SetCursor(ICursorImpl cursor)
@@ -138,7 +126,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
             _view.Visibility = ViewStates.Visible;
         }
 
-        public double RenderScaling => 1;
+        public double RenderScaling { get; }
 
         void Draw()
         {
@@ -193,7 +181,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
 
             void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
             {
-                var newSize = new Size(width, height);
+                var newSize = new PixelSize(width, height).ToSize(_tl.RenderScaling);
 
                 if (newSize != _oldSize)
                 {

+ 22 - 69
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@@ -11,7 +11,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
         private TView _view;
         public bool HandleEvents { get; set; }
 
-        public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, Point> getPointfunc)
+        public AndroidTouchEventsHelper(TView view, Func<IInputRoot> getInputRoot, Func<MotionEvent, int, Point> getPointfunc)
         {
             this._view = view;
             HandleEvents = true;
@@ -19,11 +19,9 @@ namespace Avalonia.Android.Platform.Specific.Helpers
             _getInputRoot = getInputRoot;
         }
 
-        private DateTime _lastTouchMoveEventTime = DateTime.Now;
-        private Point? _lastTouchMovePoint;
-        private Func<MotionEvent, Point> _getPointFunc;
+        private TouchDevice _touchDevice = new TouchDevice();
+        private Func<MotionEvent, int, Point> _getPointFunc;
         private Func<IInputRoot> _getInputRoot;
-        private Point _point;
 
         public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
         {
@@ -33,89 +31,44 @@ namespace Avalonia.Android.Platform.Specific.Helpers
                 return null;
             }
 
-            RawPointerEventType? mouseEventType = null;
             var eventTime = DateTime.Now;
+
             //Basic touch support
-            switch (e.Action)
+            var pointerEventType = e.Action switch
             {
-                case MotionEventActions.Move:
-                    //may be bot flood the evnt system with too many event especially on not so powerfull mobile devices
-                    if ((eventTime - _lastTouchMoveEventTime).TotalMilliseconds > 10)
-                    {
-                        mouseEventType = RawPointerEventType.Move;
-                    }
-                    break;
-
-                case MotionEventActions.Down:
-                    mouseEventType = RawPointerEventType.LeftButtonDown;
+                MotionEventActions.Down => RawPointerEventType.TouchBegin,
+                MotionEventActions.Up => RawPointerEventType.TouchEnd,
+                MotionEventActions.Cancel => RawPointerEventType.TouchCancel,
+                _ => RawPointerEventType.TouchUpdate
+            };
 
-                    break;
+            if (e.Action.HasFlag(MotionEventActions.PointerDown))
+            {
+                pointerEventType = RawPointerEventType.TouchBegin;
+            }
 
-                case MotionEventActions.Up:
-                    mouseEventType = RawPointerEventType.LeftButtonUp;
-                    break;
+            if (e.Action.HasFlag(MotionEventActions.PointerUp))
+            {
+                pointerEventType = RawPointerEventType.TouchEnd;
             }
 
-            if (mouseEventType != null)
+            for (int i = 0; i < e.PointerCount; i++)
             {
                 //if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event
-                _point = _getPointFunc(e);
+                var point = _getPointFunc(e, i);
 
                 double x = _view.View.GetX();
                 double y = _view.View.GetY();
                 double r = x + _view.View.Width;
                 double b = y + _view.View.Height;
 
-                if (x <= _point.X && r >= _point.X && y <= _point.Y && b >= _point.Y)
+                if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y)
                 {
                     var inputRoot = _getInputRoot();
-                    var mouseDevice = Avalonia.Android.Platform.Input.AndroidMouseDevice.Instance;
-
-                    //in order the controls to work in a predictable way
-                    //we need to generate mouse move before first mouse down event
-                    //as this is the way buttons are working every time
-                    //otherwise there is a problem sometimes
-                    if (mouseEventType == RawPointerEventType.LeftButtonDown)
-                    {
-                        var me = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
-                                    RawPointerEventType.Move, _point, RawInputModifiers.None);
-                        _view.Input(me);
-                    }
 
-                    var mouseEvent = new RawPointerEventArgs(mouseDevice, (uint)eventTime.Ticks, inputRoot,
-                        mouseEventType.Value, _point, RawInputModifiers.LeftMouseButton);
+                    var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot,
+                        i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i));
                     _view.Input(mouseEvent);
-
-                    if (e.Action == MotionEventActions.Move && mouseDevice.Captured == null)
-                    {
-                        if (_lastTouchMovePoint != null)
-                        {
-                            //raise mouse scroll event so the scrollers
-                            //are moving with the cursor
-                            double vectorX = _point.X - _lastTouchMovePoint.Value.X;
-                            double vectorY = _point.Y - _lastTouchMovePoint.Value.Y;
-                            //based on test correction of 0.02 is working perfect
-                            double correction = 0.02;
-                            var ps = AndroidPlatform.Instance.LayoutScalingFactor;
-                            var mouseWheelEvent = new RawMouseWheelEventArgs(
-                                        mouseDevice,
-                                        (uint)eventTime.Ticks,
-                                        inputRoot,
-                                        _point,
-                                        new Vector(vectorX * correction / ps, vectorY * correction / ps), RawInputModifiers.LeftMouseButton);
-                            _view.Input(mouseWheelEvent);
-                        }
-                        _lastTouchMovePoint = _point;
-                        _lastTouchMoveEventTime = eventTime;
-                    }
-                    else if (e.Action == MotionEventActions.Down)
-                    {
-                        _lastTouchMovePoint = _point;
-                    }
-                    else
-                    {
-                        _lastTouchMovePoint = null;
-                    }
                 }
             }
 

+ 0 - 11
src/Android/Avalonia.Android/app.config

@@ -1,11 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="System.Runtime.InteropServices.WindowsRuntime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>

+ 2 - 2
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@@ -16,7 +16,7 @@
     <AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
     <GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
     <AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
-    <TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
+    <TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
     <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@@ -150,4 +150,4 @@
   <Import Project="..\..\..\build\System.Memory.props" />
   <Import Project="..\..\..\build\AndroidWorkarounds.props" />
   <Import Project="..\..\..\build\LegacyProject.targets" />
-</Project>
+</Project>

+ 1 - 1
src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
-	<uses-sdk android:targetSdkVersion="29" />
+	<uses-sdk android:targetSdkVersion="30" />
 	<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application>
 	<uses-permission android:name="android.permission.INTERNET" />
 </manifest>

+ 3 - 1
src/Avalonia.Controls/ApiCompatBaseline.txt

@@ -1,7 +1,9 @@
 Compat issues with assembly Avalonia.Controls:
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
+InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
 MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
 MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
 InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
 MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 5
+Total Issues: 7

+ 2 - 0
src/Avalonia.Controls/INativeMenuExporterEventsImplBridge.cs

@@ -3,5 +3,7 @@ namespace Avalonia.Controls
     public interface INativeMenuExporterEventsImplBridge
     {
         void RaiseNeedsUpdate ();
+        void RaiseOpening();
+        void RaiseClosed();
     }
 }

+ 32 - 1
src/Avalonia.Controls/NativeMenu.cs

@@ -12,13 +12,34 @@ namespace Avalonia.Controls
         private readonly AvaloniaList<NativeMenuItemBase> _items =
             new AvaloniaList<NativeMenuItemBase> { ResetBehavior = ResetBehavior.Remove };
         private NativeMenuItem _parent;
+
         [Content]
         public IList<NativeMenuItemBase> Items => _items;
 
         /// <summary>
-        /// Raised when the user clicks the menu and before its opened. Use this event to update the menu dynamically.
+        /// Raised when the menu requests an update.
+        /// </summary>
+        /// <remarks>
+        /// Use this event to add, remove or modify menu items before a menu is
+        /// shown or a hotkey is pressed.
+        /// </remarks>
+        public event EventHandler<EventArgs> NeedsUpdate;
+
+        /// <summary>
+        /// Raised before the menu is opened.
         /// </summary>
+        /// <remarks>
+        /// Do not update the menu in this event; use <see cref="NeedsUpdate"/>.
+        /// </remarks>
         public event EventHandler<EventArgs> Opening;
+        
+        /// <summary>
+        /// Raised after the menu is closed.
+        /// </summary>
+        /// <remarks>
+        /// Do not update the menu in this event; use <see cref="NeedsUpdate"/>.
+        /// </remarks>
+        public event EventHandler<EventArgs> Closed;
 
         public NativeMenu()
         {
@@ -27,10 +48,20 @@ namespace Avalonia.Controls
         }
 
         void INativeMenuExporterEventsImplBridge.RaiseNeedsUpdate()
+        {
+            NeedsUpdate?.Invoke(this, EventArgs.Empty);
+        }
+
+        void INativeMenuExporterEventsImplBridge.RaiseOpening()
         {
             Opening?.Invoke(this, EventArgs.Empty);
         }
 
+        void INativeMenuExporterEventsImplBridge.RaiseClosed()
+        {
+            Closed?.Invoke(this, EventArgs.Empty);
+        }
+
         private void Validator(NativeMenuItemBase obj)
         {
             if (obj.Parent != null)

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@@ -169,7 +169,7 @@ namespace Avalonia.DesignerSupport.Remote
             if (entryPoint == null)
                 throw Die($"Assembly {args.AppPath} doesn't have an entry point");
             var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName,
-                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
+                BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Array.Empty<Type>(), null);
             if (builderMethod == null)
                 throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
             Design.IsDesignMode = true;

+ 22 - 0
src/Avalonia.Native/IAvnMenu.cs

@@ -20,11 +20,23 @@ namespace Avalonia.Native.Interop
         {
             _parent?.RaiseNeedsUpdate();
         }
+
+        public void Opening()
+        {
+            _parent?.RaiseOpening();
+        }
+
+        public void Closed()
+        {
+            _parent?.RaiseClosed();
+        }
     }
 
     partial interface IAvnMenu
     {
         void RaiseNeedsUpdate();
+        void RaiseOpening();
+        void RaiseClosed();
         void Deinitialise();
     }
 }
@@ -45,6 +57,16 @@ namespace Avalonia.Native.Interop.Impl
             _exporter.UpdateIfNeeded();
         }
 
+        public void RaiseOpening()
+        {
+            (ManagedMenu as INativeMenuExporterEventsImplBridge).RaiseOpening();
+        }
+
+        public void RaiseClosed()
+        {
+            (ManagedMenu as INativeMenuExporterEventsImplBridge).RaiseClosed();
+        }
+
         internal NativeMenu ManagedMenu { get; private set; }
 
         public static __MicroComIAvnMenuProxy Create(IAvaloniaNativeFactory factory)

+ 2 - 3
src/Avalonia.Native/avn.idl

@@ -685,10 +685,9 @@ interface IAvnMenuItem : IUnknown
 [uuid(0af7df53-7632-42f4-a650-0992c361b477)]
 interface IAvnMenuEvents : IUnknown
 {
-    /**
-     * NeedsUpdate
-     */
      void NeedsUpdate();
+     void Opening();
+     void Closed();
 }
 
 [uuid(5142bb41-66ab-49e7-bb37-cd079c000f27)]