Browse Source

Merge branch 'master' into master

Andrey Alonzov 8 years ago
parent
commit
c6781736e0

+ 0 - 3
build.cake

@@ -378,7 +378,4 @@ Task("Travis")
 // EXECUTE
 ///////////////////////////////////////////////////////////////////////////////
 
-Information("[Setup]");
-
-
 RunTarget(parameters.Target);

+ 15 - 12
samples/ControlCatalog/Pages/TreeViewPage.xaml.cs

@@ -11,7 +11,7 @@ namespace ControlCatalog.Pages
         public TreeViewPage()
         {
             this.InitializeComponent();
-            DataContext = CreateNodes(0);
+            DataContext = new Node().Children;
         }
 
         private void InitializeComponent()
@@ -19,19 +19,22 @@ namespace ControlCatalog.Pages
             AvaloniaXamlLoader.Load(this);
         }
 
-        private IList<Node> CreateNodes(int level)
+        public class Node
         {
-            return Enumerable.Range(0, 10).Select(x => new Node
+            private IList<Node> _children;
+            public string Header { get; private set; }
+            public IList<Node> Children
             {
-                Header = $"Item {x}",
-                Children = level < 5 ? CreateNodes(level + 1) : null,
-            }).ToList();
-        }
-
-        private class Node
-        {
-            public string Header { get; set; }
-            public IList<Node> Children { get; set; }
+                get
+                {
+                    if (_children == null)
+                    {
+                        _children = Enumerable.Range(1, 10).Select(i => new Node() {Header = $"Item {i}"})
+                            .ToArray();
+                    }
+                    return _children;
+                }
+            }
         }
     }
 }

+ 3 - 1
src/Avalonia.Controls/TopLevel.cs

@@ -184,7 +184,7 @@ namespace Avalonia.Controls
         /// <summary>
         /// Gets the renderer for the window.
         /// </summary>
-        public IRenderer Renderer { get; }
+        public IRenderer Renderer { get; private set; }
 
         /// <summary>
         /// Gets the access key handler for the window.
@@ -381,6 +381,8 @@ namespace Avalonia.Controls
         private void HandleClosed()
         {
             Closed?.Invoke(this, EventArgs.Empty);
+            Renderer?.Dispose();
+            Renderer = null;
             _applicationLifecycle.OnExit -= OnApplicationExiting;
         }
 

+ 1 - 0
src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj

@@ -44,6 +44,7 @@
     <Compile Include="..\Avalonia.Gtk\KeyTransform.cs">
       <Link>KeyTransform.cs</Link>
     </Compile>
+    <Compile Include="Interop\CairoSurface.cs" />
     <Compile Include="ClipboardImpl.cs" />
     <Compile Include="CursorFactory.cs" />
     <Compile Include="FramebufferManager.cs" />

+ 2 - 12
src/Gtk/Avalonia.Gtk3/FramebufferManager.cs

@@ -10,7 +10,6 @@ namespace Avalonia.Gtk3
     class FramebufferManager : IFramebufferPlatformSurface, IDisposable
     {
         private readonly TopLevelImpl _window;
-        private ImageSurfaceFramebuffer _fb;
         public FramebufferManager(TopLevelImpl window)
         {
             _window = window;
@@ -18,25 +17,16 @@ namespace Avalonia.Gtk3
 
         public void Dispose()
         {
-            _fb?.Deallocate();
+            //
         }
 
         public ILockedFramebuffer Lock()
         {
             if(_window.CurrentCairoContext == IntPtr.Zero)
                 throw new InvalidOperationException("Window is not in drawing state");
-
-            var ctx = _window.CurrentCairoContext;
             var width = (int) _window.ClientSize.Width;
             var height = (int) _window.ClientSize.Height;
-            if (_fb == null || _fb.Width != width ||
-                _fb.Height != height)
-            {
-                _fb?.Dispose();
-                _fb = new ImageSurfaceFramebuffer(width, height);
-            }
-            _fb.Prepare(ctx);
-            return _fb;
+            return new ImageSurfaceFramebuffer(_window.CurrentCairoContext, width, height);
         }
     }
 }

+ 7 - 16
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@@ -13,10 +13,11 @@ namespace Avalonia.Gtk3
     class ImageSurfaceFramebuffer : ILockedFramebuffer
     {
         private IntPtr _context;
-        private IntPtr _surface;
+        private CairoSurface _surface;
 
-        public ImageSurfaceFramebuffer(int width, int height)
+        public ImageSurfaceFramebuffer(IntPtr context, int width, int height)
         {
+            _context = context;
             _surface = Native.CairoImageSurfaceCreate(1, width, height);
             Width = width;
             Height = height;
@@ -24,27 +25,17 @@ namespace Avalonia.Gtk3
             RowBytes = Native.CairoImageSurfaceGetStride(_surface);
             Native.CairoSurfaceFlush(_surface);
         }
-
-        public void Prepare(IntPtr context)
-        {
-            _context = context;
-        }
-
-        public void Deallocate()
-        {
-            Native.CairoSurfaceDestroy(_surface);
-            _surface = IntPtr.Zero;
-        }
-
+        
         public void Dispose()
         {
-            if(_context == IntPtr.Zero || _surface == IntPtr.Zero)
+            if(_context == IntPtr.Zero || _surface == null)
                 return;
             Native.CairoSurfaceMarkDirty(_surface);
             Native.CairoSetSourceSurface(_context, _surface, 0, 0);
             Native.CairoPaint(_context);
             _context = IntPtr.Zero;
-
+            _surface.Dispose();
+            _surface = null;
         }
 
         public IntPtr Address { get; }

+ 20 - 0
src/Gtk/Avalonia.Gtk3/Interop/CairoSurface.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Avalonia.Gtk3.Interop
+{
+    class CairoSurface : SafeHandle
+    {
+        public CairoSurface() : base(IntPtr.Zero, true)
+        {
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            Native.CairoSurfaceDestroy(handle);
+            return true;
+        }
+
+        public override bool IsInvalid => handle == IntPtr.Zero;
+    }
+}

+ 6 - 6
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@@ -110,25 +110,25 @@ namespace Avalonia.Gtk3.Interop
 
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate IntPtr cairo_image_surface_create(int format, int width, int height);
+            public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate IntPtr cairo_image_surface_get_data(IntPtr surface);
+            public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate int cairo_image_surface_get_stride(IntPtr surface);
+            public delegate int cairo_image_surface_get_stride(CairoSurface surface);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate void cairo_surface_mark_dirty(IntPtr surface);
+            public delegate void cairo_surface_mark_dirty(CairoSurface surface);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate void cairo_surface_flush(IntPtr surface);
+            public delegate void cairo_surface_flush(CairoSurface surface);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_surface_destroy(IntPtr surface);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
-            public delegate void cairo_set_source_surface(IntPtr cr, IntPtr surface, double x, double y);
+            public delegate void cairo_set_source_surface(IntPtr cr, CairoSurface surface, double x, double y);
 
             [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
             public delegate void cairo_paint(IntPtr context);

+ 4 - 2
src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs

@@ -75,7 +75,7 @@ namespace Avalonia.Gtk3
 
         private bool OnDestroy(IntPtr gtkwidget, IntPtr userdata)
         {
-            Closed?.Invoke();
+            Dispose();
             return false;
         }
 
@@ -210,7 +210,9 @@ namespace Avalonia.Gtk3
 
         public void Dispose()
         {
-            Closed?.Invoke();
+            //We are calling it here, since signal handler will be detached
+            if (!GtkWidget.IsClosed)
+                Closed?.Invoke();
             foreach(var d in Disposables.AsEnumerable().Reverse())
                 d.Dispose();
             Disposables.Clear();

+ 19 - 9
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -182,9 +182,18 @@ namespace Avalonia.Win32
 
         public void Dispose()
         {
-            s_instances.Remove(this);
-            _framebuffer.Dispose();
-            UnmanagedMethods.DestroyWindow(_hwnd);
+            _framebuffer?.Dispose();
+            _framebuffer = null;
+            if (_hwnd != IntPtr.Zero)
+            {
+                UnmanagedMethods.DestroyWindow(_hwnd);
+                _hwnd = IntPtr.Zero;
+            }
+            if (_className != null)
+            {
+                UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null));
+                _className = null;
+            }
         }
 
         public void Hide()
@@ -419,12 +428,13 @@ namespace Avalonia.Win32
                     return IntPtr.Zero;
 
                 case UnmanagedMethods.WindowsMessage.WM_DESTROY:
-                    if (Closed != null)
-                    {
-                        UnmanagedMethods.UnregisterClass(_className, UnmanagedMethods.GetModuleHandle(null));
-                        Closed();
-                    }
-
+                    //Window doesn't exist anymore
+                    _hwnd = IntPtr.Zero;
+                    //Remove root reference to this class, so unmanaged delegate can be collected
+                    s_instances.Remove(this);
+                    Closed?.Invoke();
+                    //Free other resources
+                    Dispose();
                     return IntPtr.Zero;
 
                 case UnmanagedMethods.WindowsMessage.WM_DPICHANGED:

+ 28 - 0
tests/Avalonia.LeakTests/ControlTests.cs

@@ -11,6 +11,7 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Diagnostics;
 using Avalonia.Layout;
+using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Styling;
 using Avalonia.UnitTests;
@@ -301,6 +302,33 @@ namespace Avalonia.LeakTests
             }
         }
 
+
+        [Fact]
+        public void RendererIsDisposed()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var renderer = new Mock<IRenderer>();
+                renderer.Setup(x => x.Dispose());
+                var impl = new Mock<IWindowImpl>();
+                impl.SetupGet(x => x.Scaling).Returns(1);
+                impl.SetupProperty(x => x.Closed);
+                impl.Setup(x => x.Dispose()).Callback(() => impl.Object.Closed());
+
+                AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatform>()
+                    .ToConstant(new MockWindowingPlatform(() => impl.Object));
+                AvaloniaLocator.CurrentMutable.Bind<IRendererFactory>()
+                    .ToConstant(new MockRendererFactory(renderer.Object));
+                var window = new Window()
+                {
+                    Content = new Button()
+                };
+                window.Show();
+                window.Close();
+                renderer.Verify(r => r.Dispose());
+            }
+        }
+
         private static void PurgeMoqReferences()
         {
             // Moq holds onto references in its mock of IRenderer in case we want to check if a method has been called;

+ 1 - 0
tests/Avalonia.UnitTests/Avalonia.UnitTests.csproj

@@ -55,6 +55,7 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="InvariantCultureFixture.cs" />
+    <Compile Include="MockRendererFactory.cs" />
     <Compile Include="NotifyingBase.cs" />
     <Compile Include="TestLogSink.cs" />
     <Compile Include="TestTemplatedRoot.cs" />

+ 29 - 0
tests/Avalonia.UnitTests/MockRendererFactory.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Avalonia.Rendering;
+
+namespace Avalonia.UnitTests
+{
+    public class MockRendererFactory : IRendererFactory
+    {
+        private readonly Func<IRenderRoot, IRenderLoop, IRenderer> _cb;
+
+        public MockRendererFactory(Func<IRenderRoot, IRenderLoop, IRenderer> cb = null)
+        {
+            _cb = cb;
+        }
+
+        public MockRendererFactory(IRenderer renderer) : this((_, __) => renderer)
+        {
+
+        }
+
+        public IRenderer CreateRenderer(IRenderRoot root, IRenderLoop renderLoop)
+        {
+            return _cb?.Invoke(root, renderLoop);
+        }
+    }
+}