From 374d6002752e966709602b952e9dbf090491e07a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 01:02:24 +0300 Subject: [PATCH 01/61] Added support for multiple drawing methods for window implementations. Added framebuffer support --- .../Platform/SkiaPlatform/WindowImpl.cs | 3 + .../Avalonia.Controls.csproj | 4 + .../Platform/ITopLevelImpl.cs | 6 + .../Surfaces/IFramebufferPlatformSurface.cs | 13 ++ .../Platform/Surfaces/ILockedFramebuffer.cs | 18 +++ .../Surfaces/INativeWindowPlatformSurface.cs | 23 ++++ .../Platform/Surfaces/PixelFormat.cs | 15 ++ src/Avalonia.Controls/TopLevel.cs | 2 +- .../Platform/IPlatformRenderInterface.cs | 5 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 22 +-- src/Gtk/Avalonia.Cairo/RenderTarget.cs | 19 +-- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 2 + src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 42 ++++++ src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs | 58 ++++++++ src/Gtk/Avalonia.Gtk/WindowImpl.cs | 2 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 32 ++++- .../AndroidPlatformRenderInterface.cs | 26 ++++ .../Avalonia.Skia.Android.csproj | 1 + .../Avalonia.Skia.Android/RenderTarget.cs | 16 +-- .../Avalonia.Skia.Android/SkiaRenderView.cs | 2 +- .../Avalonia.Skia.Desktop.csproj | 5 +- .../PlatformRenderInterfaceDesktop.cs | 21 +++ .../Avalonia.Skia.iOS.csproj | 1 + .../PlatformRenderingInterfaceIos.cs | 18 +++ src/Skia/Avalonia.Skia.iOS/RenderTarget.cs | 5 +- .../Avalonia.Skia/Avalonia.Skia.projitems | 1 + .../Avalonia.Skia/FramebufferRenderTarget.cs | 77 +++++++++++ .../Avalonia.Skia/PlatformRenderInterface.cs | 10 +- .../Avalonia.Skia/WindowDrawingContextImpl.cs | 2 + .../Avalonia.Direct2D1/Direct2D1Platform.cs | 18 +-- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 13 +- .../Avalonia.Win32/Avalonia.Win32.csproj | 2 + .../Avalonia.Win32/FramebufferManager.cs | 41 ++++++ .../Interop/UnmanagedMethods.cs | 48 +++++++ .../Avalonia.Win32/WindowFramebuffer.cs | 130 ++++++++++++++++++ src/Windows/Avalonia.Win32/WindowImpl.cs | 10 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 + .../InputElement_HitTesting.cs | 2 +- .../VisualTree/MockRenderInterface.cs | 2 +- 39 files changed, 644 insertions(+), 75 deletions(-) create mode 100644 src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs create mode 100644 src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs create mode 100644 src/Gtk/Avalonia.Gtk/FramebufferManager.cs create mode 100644 src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs create mode 100644 src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs create mode 100644 src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs create mode 100644 src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs create mode 100644 src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs create mode 100644 src/Windows/Avalonia.Win32/FramebufferManager.cs create mode 100644 src/Windows/Avalonia.Win32/WindowFramebuffer.cs diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 0e1540b5fd..7816a8af27 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -9,6 +9,7 @@ using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Skia.Android; using System; +using System.Collections.Generic; using Avalonia.Controls; namespace Avalonia.Android.Platform.SkiaPlatform @@ -193,5 +194,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } + + public IEnumerable Surfaces => new object[] {this}; } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 10fbe746e3..0fe068dab5 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -57,6 +57,10 @@ + + + + diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 087a333e8a..e11f1a3d79 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.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.Collections.Generic; using Avalonia.Controls; using Avalonia.Input; using Avalonia.Input.Raw; @@ -37,6 +38,11 @@ namespace Avalonia.Platform /// IPlatformHandle Handle { get; } + /// + /// Supported methods of image output + /// + IEnumerable Surfaces { get; } + /// /// Gets or sets a method called when the window is activated (receives focus). /// diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs new file mode 100644 index 0000000000..6959947415 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface IFramebufferPlatformSurface + { + ILockedFramebuffer Lock(); + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs new file mode 100644 index 0000000000..1810479a70 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface ILockedFramebuffer : IDisposable + { + IntPtr Address { get; } + int Width { get; } + int Height { get; } + int RowBytes { get; } + Size Dpi { get; } + PixelFormat Format { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs new file mode 100644 index 0000000000..33aa969c72 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public interface INativeWindowPlatformSurface + { + IntPtr Handle { get; } + } + + public class NativeWindowPlatformSurface : INativeWindowPlatformSurface + { + public NativeWindowPlatformSurface(IntPtr handle) + { + Handle = handle; + } + + public IntPtr Handle { get; } + } +} diff --git a/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs new file mode 100644 index 0000000000..c9f8eabe97 --- /dev/null +++ b/src/Avalonia.Controls/Platform/Surfaces/PixelFormat.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Controls.Platform.Surfaces +{ + public enum PixelFormat + { + Rgb565, + Rgba8888, + Bgra8888 + } +} diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 43c59f0b31..f314629d02 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -237,7 +237,7 @@ namespace Avalonia.Controls /// IRenderTarget IRenderRoot.CreateRenderTarget() { - return _renderInterface.CreateRenderTarget(PlatformImpl.Handle); + return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index c129cbd905..ba48a72512 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.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.Collections.Generic; using System.IO; using Avalonia.Media; @@ -40,9 +41,9 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The platform handle for the renderer. + /// The list of surfaces that can be used for output. /// An . - IRenderTarget CreateRenderTarget(IPlatformHandle handle); + IRenderTarget CreateRenderTarget(IEnumerable surfaces); /// /// Creates a render target bitmap implementation. diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index e59bcfffa1..8b3c6899d3 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Linq; using Avalonia.Cairo.Media; using Avalonia.Cairo.Media.Imaging; using Avalonia.Media; @@ -50,24 +52,14 @@ namespace Avalonia.Cairo return new FormattedTextImpl(s_pangoContext, text, fontFamily, fontSize, fontStyle, textAlignment, fontWeight); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - var window = handle as Gtk.Window; - if (window != null) - { - window.DoubleBuffered = true; - return new RenderTarget(window); - } - var area = handle as Gtk.DrawingArea; - if (area != null) - { - area.DoubleBuffered = true; - return new RenderTarget(area); - } + var accessor = surfaces?.OfType>().FirstOrDefault(); + if(accessor!=null) + return new RenderTarget(accessor); throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from a '{0}' handle which isn't Gtk.Window or Gtk.DrawingArea", - handle.HandleDescriptor)); + "Don't know how to create a Cairo renderer from any of provided surfaces")); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Cairo/RenderTarget.cs b/src/Gtk/Avalonia.Cairo/RenderTarget.cs index d285986762..49f5e18dec 100644 --- a/src/Gtk/Avalonia.Cairo/RenderTarget.cs +++ b/src/Gtk/Avalonia.Cairo/RenderTarget.cs @@ -20,8 +20,8 @@ namespace Avalonia.Cairo public class RenderTarget : IRenderTarget { private readonly Surface _surface; - private readonly Gtk.Window _window; - private readonly Gtk.DrawingArea _area; + private readonly Func _drawableAccessor; + /// /// Initializes a new instance of the class. @@ -29,9 +29,9 @@ namespace Avalonia.Cairo /// The window. /// The width of the window. /// The height of the window. - public RenderTarget(Gtk.Window window) + public RenderTarget(Func drawable) { - _window = window; + _drawableAccessor = drawable; } public RenderTarget(ImageSurface surface) @@ -39,11 +39,6 @@ namespace Avalonia.Cairo _surface = surface; } - public RenderTarget(DrawingArea area) - { - _area = area; - } - /// /// Creates a cairo surface that targets a platform-specific resource. /// @@ -52,12 +47,10 @@ namespace Avalonia.Cairo public IDrawingContextImpl CreateMediaDrawingContext() { - if (_window != null) - return new Media.DrawingContext(_window.GdkWindow); + if (_drawableAccessor != null) + return new Media.DrawingContext(_drawableAccessor()); if (_surface != null) return new Media.DrawingContext(_surface); - if (_area != null) - return new Media.DrawingContext(_area.GdkWindow); throw new InvalidOperationException("Unspecified render target"); } diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index b001c7bc19..2c10b1188b 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -48,7 +48,9 @@ + + diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs new file mode 100644 index 0000000000..3da3c3d70c --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; + +namespace Avalonia.Gtk +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly WindowImplBase _window; + private PixbufFramebuffer _fb; + public FramebufferManager(WindowImplBase window) + { + _window = window; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + + public ILockedFramebuffer Lock() + { + if(_window.CurrentDrawable == null) + throw new InvalidOperationException("Window is not in drawing state"); + + var drawable = _window.CurrentDrawable; + 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 PixbufFramebuffer(width, height); + } + _fb.SetDrawable(drawable); + return _fb; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs new file mode 100644 index 0000000000..76e9e8a307 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Gdk; + +namespace Avalonia.Gtk +{ + class PixbufFramebuffer : ILockedFramebuffer + { + private Pixbuf _pixbuf; + private Drawable _drawable; + + public PixbufFramebuffer(int width, int height) + { + _pixbuf = new Pixbuf(Gdk.Colorspace.Rgb, false, 8, width, height); + } + + public void SetDrawable(Drawable drawable) + { + _drawable = drawable; + } + + public void Deallocate() + { + _pixbuf.Dispose(); + _pixbuf = null; + } + + public void Dispose() + { + using (var gc = new Gdk.GC(_drawable)) + _drawable.DrawPixbuf(gc, _pixbuf, 0, 0, 0, 0, Width, Height, RgbDither.None, 0, 0); + _drawable = null; + } + + public IntPtr Address => _pixbuf.Pixels; + public int Width => _pixbuf.Width; + public int Height => _pixbuf.Height; + public int RowBytes => _pixbuf.Rowstride; + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format + { + get + { + if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs index e075a31c4c..eca7c24136 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs @@ -10,7 +10,7 @@ namespace Avalonia.Gtk { private Gtk.Window _window; private Gtk.Window Window => _window ?? (_window = (Gtk.Window) Widget); - + public WindowImpl(Gtk.WindowType type) : base(new PlatformHandleAwareWindow(type)) { Init(); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index 8641f2f431..ba9b984c56 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -2,10 +2,12 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.Reactive.Disposables; using System.Runtime.InteropServices; using Gdk; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Input; @@ -22,7 +24,8 @@ namespace Avalonia.Gtk private IInputRoot _inputRoot; protected Gtk.Widget _window; public Gtk.Widget Widget => _window; - + public Gdk.Drawable CurrentDrawable { get; private set; } + private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -33,6 +36,7 @@ namespace Avalonia.Gtk protected WindowImplBase(Gtk.Widget window) { _window = window; + _framebuffer = new FramebufferManager(this); Init(); } @@ -43,7 +47,6 @@ namespace Avalonia.Gtk _imContext = new Gtk.IMMulticontext(); _imContext.Commit += ImContext_Commit; _window.Realized += OnRealized; - _window.DoubleBuffered = false; _window.Realize(); _window.ButtonPressEvent += OnButtonPressEvent; _window.ButtonReleaseEvent += OnButtonReleaseEvent; @@ -283,7 +286,9 @@ namespace Avalonia.Gtk void OnExposeEvent(object o, Gtk.ExposeEventArgs args) { + CurrentDrawable = args.Event.Window; Paint(args.Event.Area.ToAvalonia()); + CurrentDrawable = null; args.RetVal = true; } @@ -311,9 +316,32 @@ namespace Avalonia.Gtk public void Dispose() { + _framebuffer.Dispose(); _window.Hide(); _window.Dispose(); _window = null; } + + + //We need that for DrawingArea which does not have an HWND until parent window is realized + //and could also *change* it's HWND + class DynamicNativeWindowSurface : INativeWindowPlatformSurface + { + private readonly IPlatformHandle _handle; + + public DynamicNativeWindowSurface(IPlatformHandle handle) + { + _handle = handle; + } + + public IntPtr Handle => _handle.Handle; + } + + public IEnumerable Surfaces => new object[] + { + new DynamicNativeWindowSurface(Handle), + new Func(() => CurrentDrawable), + _framebuffer + }; } } diff --git a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs new file mode 100644 index 0000000000..610d6f5703 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Avalonia.Platform; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + var surfaceView = surfaces?.OfType().FirstOrDefault(); + if (surfaceView == null) + throw new ArgumentException("Avalonia.Skia.Android is only capable to draw on SurfaceView"); + return new WindowRenderTarget(surfaceView); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj index cf352607f0..fdde5553eb 100644 --- a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj +++ b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj @@ -87,6 +87,7 @@ + diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 59ad3b9fbb..03ddf49851 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -26,14 +26,14 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + private readonly SurfaceView _surfaceView; Bitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget(SurfaceView surfaceView) { - _hwnd = hwnd; + _surfaceView = surfaceView; FixSize(); } @@ -63,9 +63,8 @@ namespace Avalonia.Skia private void GetPlatformWindowSize(out int w, out int h) { - var surfaceView = _hwnd as SurfaceView; - w = surfaceView.Width; - h = surfaceView.Height; + w = _surfaceView.Width; + h = _surfaceView.Height; } public override DrawingContext CreateDrawingContext() @@ -85,11 +84,10 @@ namespace Avalonia.Skia public void Present() { - var surfaceView = _hwnd as SurfaceView; Canvas canvas = null; try { - canvas = surfaceView.Holder.LockCanvas(null); + canvas = _surfaceView.Holder.LockCanvas(null); _bitmap.UnlockPixels(); canvas.DrawBitmap(_bitmap, 0, 0, null); } @@ -99,7 +97,7 @@ namespace Avalonia.Skia finally { if (canvas != null) - surfaceView.Holder.UnlockCanvasAndPost(canvas); + _surfaceView.Holder.UnlockCanvasAndPost(canvas); } _bitmap.UnlockPixels(); diff --git a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs index 62d8e7bd18..fc6c994bd5 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs @@ -22,7 +22,7 @@ namespace Avalonia.Skia.Android { _renderTarget = AvaloniaLocator.Current.GetService() - .CreateRenderTarget(this); + .CreateRenderTarget(new object[]{this}); } protected override void Draw() diff --git a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj index d7d865225a..62586f750d 100644 --- a/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj +++ b/src/Skia/Avalonia.Skia.Desktop/Avalonia.Skia.Desktop.csproj @@ -72,11 +72,8 @@ + - - UnmanagedMethods.cs - - diff --git a/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs new file mode 100644 index 0000000000..9382a4f6e2 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Desktop/PlatformRenderInterfaceDesktop.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + var fb = surfaces?.OfType().FirstOrDefault(); + if (fb == null) + throw new Exception("Avalonia.Skia.Deskop currently only supports framebuffer render target"); + return new FramebufferRenderTarget(fb); + } + } +} diff --git a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj index 28892c3c01..c8a7f36317 100644 --- a/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj +++ b/src/Skia/Avalonia.Skia.iOS/Avalonia.Skia.iOS.csproj @@ -37,6 +37,7 @@ true + diff --git a/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs new file mode 100644 index 0000000000..fdc7389800 --- /dev/null +++ b/src/Skia/Avalonia.Skia.iOS/PlatformRenderingInterfaceIos.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Avalonia.Platform; +using Foundation; +using UIKit; + +namespace Avalonia.Skia +{ + partial class PlatformRenderInterface + { + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) + { + return new WindowRenderTarget(); + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs index 083b611d5c..abe663e44b 100644 --- a/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.iOS/RenderTarget.cs @@ -26,14 +26,13 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { - private readonly IPlatformHandle _hwnd; + SKBitmap _bitmap; int Width { get; set; } int Height { get; set; } - public WindowRenderTarget(IPlatformHandle hwnd) + public WindowRenderTarget() { - _hwnd = hwnd; FixSize(); } diff --git a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems index 426be548e2..303de8ee56 100644 --- a/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems +++ b/src/Skia/Avalonia.Skia/Avalonia.Skia.projitems @@ -12,6 +12,7 @@ + diff --git a/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs new file mode 100644 index 0000000000..3d3e322e0d --- /dev/null +++ b/src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Media; +using Avalonia.Platform; +using SkiaSharp; + +namespace Avalonia.Skia +{ + public class FramebufferRenderTarget : IRenderTarget + { + private readonly IFramebufferPlatformSurface _surface; + + public FramebufferRenderTarget(IFramebufferPlatformSurface surface) + { + _surface = surface; + } + + public void Dispose() + { + //Nothing to do here, since we don't own framebuffer + } + + class FramebufferDrawingContextImpl : DrawingContextImpl + { + private readonly SKCanvas _canvas; + private readonly SKSurface _surface; + private readonly ILockedFramebuffer _framebuffer; + + public FramebufferDrawingContextImpl(SKCanvas canvas, SKSurface surface, ILockedFramebuffer framebuffer) : base(canvas) + { + _canvas = canvas; + _surface = surface; + _framebuffer = framebuffer; + } + + public override void Dispose() + { + _canvas.Dispose(); + _surface.Dispose(); + _framebuffer.Dispose(); + base.Dispose(); + } + } + + SKColorType TranslatePixelFormat(PixelFormat fmt) + { + if(fmt == PixelFormat.Rgb565) + return SKColorType.Rgb565; + if(fmt == PixelFormat.Bgra8888) + return SKColorType.Bgra8888; + if (fmt == PixelFormat.Rgba8888) + return SKColorType.Rgba8888; + throw new ArgumentException("Unknown pixel format: " + fmt); + } + + public DrawingContext CreateDrawingContext() + { + var fb = _surface.Lock(); + + SKImageInfo nfo = new SKImageInfo(fb.Width, fb.Height, TranslatePixelFormat(fb.Format), + SKAlphaType.Opaque); + var surface = SKSurface.Create(nfo, fb.Address, fb.RowBytes); + if (surface == null) + throw new Exception("Unable to create a surface for pixel format " + fb.Format); + var canvas = surface.Canvas; + canvas.RestoreToCount(0); + canvas.Save(); + canvas.Clear(SKColors.Red); + canvas.ResetMatrix(); + + return new DrawingContext(new FramebufferDrawingContextImpl(canvas, surface, fb), + Matrix.CreateScale(fb.Dpi.Width / 96, fb.Dpi.Height / 96)); + } + } +} diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index f8558c7428..56b334073b 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Avalonia.Media; using Avalonia.Platform; @@ -7,7 +8,7 @@ using SkiaSharp; namespace Avalonia.Skia { - public class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory + public partial class PlatformRenderInterface : IPlatformRenderInterface, IRendererFactory { public IBitmapImpl CreateBitmap(int width, int height) { @@ -54,6 +55,8 @@ namespace Avalonia.Skia return new Renderer(root, renderLoop); } + + public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { if (width < 1) @@ -63,10 +66,5 @@ namespace Avalonia.Skia return new BitmapImpl(width, height); } - - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) - { - return new WindowRenderTarget(handle); - } } } diff --git a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs index a4e29fc022..db9d10a346 100644 --- a/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/WindowDrawingContextImpl.cs @@ -1,6 +1,7 @@ namespace Avalonia.Skia { +#if !DESKTOP // not sure we need this yet internal class WindowDrawingContextImpl : DrawingContextImpl { @@ -18,4 +19,5 @@ namespace Avalonia.Skia _target.Present(); } } +#endif } \ No newline at end of file diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index e53692bef0..4ef267e0de 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -2,11 +2,14 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using Avalonia.Direct2D1.Media; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Rendering; namespace Avalonia @@ -99,18 +102,15 @@ namespace Avalonia.Direct2D1 return new Renderer(root, renderLoop); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - if (handle.HandleDescriptor == "HWND") - { - return new HwndRenderTarget(handle.Handle); - } - else + var nativeWindow = surfaces?.OfType().FirstOrDefault(); + + if (nativeWindow != null) { - throw new NotSupportedException(string.Format( - "Don't know how to create a Direct2D1 renderer from a '{0}' handle", - handle.HandleDescriptor)); + return new HwndRenderTarget(nativeWindow); } + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 49d4c91c52..317e7fc34f 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -11,16 +12,16 @@ namespace Avalonia.Direct2D1 { class HwndRenderTarget : SwapChainRenderTarget { - private readonly IntPtr _hwnd; + private readonly INativeWindowPlatformSurface _window; - public HwndRenderTarget(IntPtr hwnd) + public HwndRenderTarget(INativeWindowPlatformSurface window) { - _hwnd = hwnd; + _window = window; } protected override SwapChain1 CreateSwapChain(Factory2 dxgiFactory, SwapChainDescription1 swapChainDesc) { - return new SwapChain1(dxgiFactory, DxgiDevice, _hwnd, ref swapChainDesc); + return new SwapChain1(dxgiFactory, DxgiDevice, _window.Handle, ref swapChainDesc); } protected override Size2F GetWindowDpi() @@ -30,7 +31,7 @@ namespace Avalonia.Direct2D1 uint dpix, dpiy; var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd, + _window.Handle, UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); if (UnmanagedMethods.GetDpiForMonitor( @@ -49,7 +50,7 @@ namespace Avalonia.Direct2D1 protected override Size2 GetWindowSize() { UnmanagedMethods.RECT rc; - UnmanagedMethods.GetClientRect(_hwnd, out rc); + UnmanagedMethods.GetClientRect(_window.Handle, out rc); return new Size2(rc.right - rc.left, rc.bottom - rc.top); } } diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 4b190dc469..851233a19e 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -69,6 +69,8 @@ Component + + diff --git a/src/Windows/Avalonia.Win32/FramebufferManager.cs b/src/Windows/Avalonia.Win32/FramebufferManager.cs new file mode 100644 index 0000000000..ecd05f41b4 --- /dev/null +++ b/src/Windows/Avalonia.Win32/FramebufferManager.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Win32.Interop; + +namespace Avalonia.Win32 +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly IntPtr _hwnd; + private WindowFramebuffer _fb; + + public FramebufferManager(IntPtr hwnd) + { + _hwnd = hwnd; + } + + public ILockedFramebuffer Lock() + { + UnmanagedMethods.RECT rc; + UnmanagedMethods.GetClientRect(_hwnd, out rc); + var width = rc.right - rc.left; + var height = rc.bottom - rc.top; + if (_fb == null || _fb.Width != width || _fb.Height != height) + { + _fb?.Deallocate(); + _fb = null; + _fb = new WindowFramebuffer(_hwnd, width, height); + } + return _fb; + } + + public void Dispose() + { + _fb?.Deallocate(); + } + } +} diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index c2785095d0..4d5af748f4 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -568,6 +568,27 @@ namespace Avalonia.Win32.Interop public byte rgbReserved; } + [StructLayout(LayoutKind.Sequential)] + public struct BITMAPINFOHEADER + { + public uint biSize; + public int biWidth; + public int biHeight; + public ushort biPlanes; + public ushort biBitCount; + public uint biCompression; + public uint biSizeImage; + public int biXPelsPerMeter; + public int biYPelsPerMeter; + public uint biClrUsed; + public uint biClrImportant; + + public void Init() + { + biSize = (uint)Marshal.SizeOf(this); + } + } + [StructLayout(LayoutKind.Sequential)] public struct BITMAPINFO { @@ -859,6 +880,31 @@ namespace Avalonia.Win32.Interop [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); + [DllImport("gdi32.dll")] + public static extern int SetDIBitsToDevice(IntPtr hdc, int XDest, int YDest, uint + dwWidth, uint dwHeight, int XSrc, int YSrc, uint uStartScan, uint cScanLines, + IntPtr lpvBits, [In] ref BITMAPINFOHEADER lpbmi, uint fuColorUse); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateDIBSection(IntPtr hDC, ref BITMAPINFOHEADER pBitmapInfo, int un, out IntPtr lplpVoid, IntPtr handle, int dw); + [DllImport("gdi32.dll")] + public static extern int DeleteObject(IntPtr hObject); + [DllImport("gdi32.dll", SetLastError = true)] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + [DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr CreateFileMapping(IntPtr hFile, + IntPtr lpFileMappingAttributes, + uint flProtect, + uint dwMaximumSizeHigh, + uint dwMaximumSizeLow, + string lpName); + public enum MONITOR { MONITOR_DEFAULTTONULL = 0x00000000, @@ -1177,6 +1223,7 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint SetFilter([MarshalAs(UnmanagedType.Interface)] IntPtr pFilter); + } @@ -1197,5 +1244,6 @@ namespace Avalonia.Win32.Interop [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] uint Compare([In, MarshalAs(UnmanagedType.Interface)] IShellItem psi, [In] uint hint, out int piOrder); + } } diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs new file mode 100644 index 0000000000..2731604a22 --- /dev/null +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Interop; +using System.Windows.Media; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Win32.Interop; +using PixelFormat = Avalonia.Controls.Platform.Surfaces.PixelFormat; + +namespace Avalonia.Win32 +{ + public class WindowFramebuffer : ILockedFramebuffer + { + private readonly IntPtr _handle; + private IntPtr _pBitmap; + UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; + + + public WindowFramebuffer(IntPtr handle, int width, int height) + { + + if (width <= 0) + throw new ArgumentException("width is less than zero"); + if (height <= 0) + throw new ArgumentException("height is less than zero"); + _handle = handle; + _bmpInfo.Init(); + _bmpInfo.biPlanes = 1; + _bmpInfo.biBitCount = 32; + _bmpInfo.Init(); + _bmpInfo.biWidth = width; + _bmpInfo.biHeight = -height; + _pBitmap = Marshal.AllocHGlobal(width * height * 4); + } + + + + public int Width => _bmpInfo.biWidth; + + public int Height => -_bmpInfo.biHeight; + + public void DrawToDevice(IntPtr hDC, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, + int height = -1) + { + if(_pBitmap == IntPtr.Zero) + throw new ObjectDisposedException("Framebuffer"); + if (width == -1) + width = Width; + if (height == -1) + height = Height; + UnmanagedMethods.SetDIBitsToDevice(hDC, destX, destY, (uint) width, (uint) height, srcX, srcY, + 0, (uint)Height, _pBitmap, ref _bmpInfo, 0); + } + + public bool DrawToWindow(IntPtr hWnd, int destX = 0, int destY = 0, int srcX = 0, int srcY = 0, int width = -1, + int height = -1) + { + + if (_pBitmap == IntPtr.Zero) + throw new ObjectDisposedException("Framebuffer"); + if (hWnd == IntPtr.Zero) + return false; + IntPtr hDC = UnmanagedMethods.GetDC(hWnd); + if (hDC == IntPtr.Zero) + return false; + DrawToDevice(hDC, destX, destY, srcX, srcY, width, height); + UnmanagedMethods.ReleaseDC(hWnd, hDC); + return true; + } + + + + public void Dispose() + { + //It's not an *actual* dispose. This call meand "We are done drawing" + DrawToWindow(_handle); + } + + public void Deallocate() + { + if (_pBitmap != IntPtr.Zero) + { + Marshal.FreeHGlobal(_pBitmap); + _pBitmap = IntPtr.Zero; + } + } + + ~WindowFramebuffer() + { + Deallocate(); + } + + public IntPtr Address => _pBitmap; + public int RowBytes => Width * 4; + public PixelFormat Format => PixelFormat.Bgra8888; + + //TODO: Proper DPI support here + /* + private Size GetWindowDpiWin32() + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow( + _hwnd.Handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } + } + + return new Size(96, 96); + } + */ + public Size Dpi => new Size(96, 96); + + } +} \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index db46538796..878be61664 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -11,6 +11,7 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Runtime.InteropServices; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; using Avalonia.Input.Raw; using Avalonia.Platform; using Avalonia.Win32.Input; @@ -36,10 +37,11 @@ namespace Avalonia.Win32 private bool _coverTaskBarWhenMaximized = true; private double _scaling = 1; private WindowState _showWindowState; - + private FramebufferManager _framebuffer; public WindowImpl() { CreateWindow(); + _framebuffer = new FramebufferManager(_hwnd); s_instances.Add(this); } @@ -175,6 +177,7 @@ namespace Avalonia.Win32 public void Dispose() { s_instances.Remove(this); + _framebuffer.Dispose(); UnmanagedMethods.DestroyWindow(_hwnd); } @@ -761,5 +764,10 @@ namespace Avalonia.Win32 ShowWindow(WindowState.Maximized); } } + + public IEnumerable Surfaces => new object[] + { + new NativeWindowPlatformSurface(_hwnd), _framebuffer + }; } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 67817ef62a..e9d095483b 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -232,6 +232,8 @@ namespace Avalonia.iOS public void SetIcon(IWindowIconImpl icon) { } + + public IEnumerable Surfaces => new object[]{this}; } class AvaloniaViewController : UIViewController diff --git a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs index e4e7551f5c..e00d504124 100644 --- a/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs +++ b/tests/Avalonia.Input.UnitTests/InputElement_HitTesting.cs @@ -340,7 +340,7 @@ namespace Avalonia.Input.UnitTests throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs index 3d6a9093e4..ac31b3852b 100644 --- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs @@ -20,7 +20,7 @@ namespace Avalonia.Visuals.UnitTests.VisualTree throw new NotImplementedException(); } - public IRenderTarget CreateRenderTarget(IPlatformHandle handle) + public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { throw new NotImplementedException(); } From 154b5836b70a2c31a4635dda11508c1d788d9de4 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 01:17:58 +0300 Subject: [PATCH 02/61] Fixed dpi --- .../Avalonia.Win32/WindowFramebuffer.cs | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index 2731604a22..cd89c01131 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -99,32 +99,28 @@ namespace Avalonia.Win32 public int RowBytes => Width * 4; public PixelFormat Format => PixelFormat.Bgra8888; - //TODO: Proper DPI support here - /* - private Size GetWindowDpiWin32() + public Size Dpi { - if (UnmanagedMethods.ShCoreAvailable) + get { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow( - _hwnd.Handle, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) + if (UnmanagedMethods.ShCoreAvailable) { - return new Size(dpix, dpiy); + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow(_handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } } + return new Size(96, 96); } - - return new Size(96, 96); } - */ - public Size Dpi => new Size(96, 96); - } } \ No newline at end of file From 55eb55dc8842db5072a01976d1b3f677e416aebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 25 Jan 2017 18:44:01 +0300 Subject: [PATCH 03/61] Removed INativeWindowPlatformSurface and added some docs --- .../Avalonia.Controls.csproj | 1 - .../Platform/ITopLevelImpl.cs | 11 +++++++-- .../Surfaces/IFramebufferPlatformSurface.cs | 6 +++++ .../Platform/Surfaces/ILockedFramebuffer.cs | 18 +++++++++++++++ .../Surfaces/INativeWindowPlatformSurface.cs | 23 ------------------- .../Platform/IPlatformRenderInterface.cs | 2 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 17 +------------- .../AndroidPlatformRenderInterface.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 5 ++-- .../Avalonia.Direct2D1/HwndRenderTarget.cs | 5 ++-- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 11 files changed, 43 insertions(+), 49 deletions(-) delete mode 100644 src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs diff --git a/src/Avalonia.Controls/Avalonia.Controls.csproj b/src/Avalonia.Controls/Avalonia.Controls.csproj index 0fe068dab5..5fc7329f97 100644 --- a/src/Avalonia.Controls/Avalonia.Controls.csproj +++ b/src/Avalonia.Controls/Avalonia.Controls.csproj @@ -59,7 +59,6 @@ - diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index e11f1a3d79..77884acf73 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -39,8 +39,15 @@ namespace Avalonia.Platform IPlatformHandle Handle { get; } /// - /// Supported methods of image output - /// + /// The list of native platform's surfaces that can be consumed by rendering subsystems. + /// + /// + /// Rendering platform will check that list and see if it can utilize one of them to output. + /// It should be enough to expose a native window handle via IPlatformHandle + /// and add support for framebuffer (even if it's emulated one) via IFramebufferPlatformSurface. + /// If you have some rendering platform that's tied to your particular windowing platform, + /// just expose some toolkit-specific object (e. g. Func<Gdk.Drawable> in case of GTK#+Cairo) + /// IEnumerable Surfaces { get; } /// diff --git a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs index 6959947415..84988e912f 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/IFramebufferPlatformSurface.cs @@ -8,6 +8,12 @@ namespace Avalonia.Controls.Platform.Surfaces { public interface IFramebufferPlatformSurface { + /// + /// Provides a framebuffer descriptor for drawing. + /// + /// + /// Contents should be drawn on actual window after disposing + /// ILockedFramebuffer Lock(); } } diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs index 1810479a70..e771401dfe 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -8,11 +8,29 @@ namespace Avalonia.Controls.Platform.Surfaces { public interface ILockedFramebuffer : IDisposable { + /// + /// Address of the first pixel + /// IntPtr Address { get; } + /// + /// Framebuffer width + /// int Width { get; } + /// + /// Framebuffer height + /// int Height { get; } + /// + /// Number of bytes per row + /// int RowBytes { get; } + /// + /// DPI of underling screen + /// Size Dpi { get; } + /// + /// Pixel format + /// PixelFormat Format { get; } } } diff --git a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs b/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs deleted file mode 100644 index 33aa969c72..0000000000 --- a/src/Avalonia.Controls/Platform/Surfaces/INativeWindowPlatformSurface.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Avalonia.Controls.Platform.Surfaces -{ - public interface INativeWindowPlatformSurface - { - IntPtr Handle { get; } - } - - public class NativeWindowPlatformSurface : INativeWindowPlatformSurface - { - public NativeWindowPlatformSurface(IntPtr handle) - { - Handle = handle; - } - - public IntPtr Handle { get; } - } -} diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index ba48a72512..34600c145f 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -41,7 +41,7 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The list of surfaces that can be used for output. + /// The list of native platform's surfaces that can be used for output. /// An . IRenderTarget CreateRenderTarget(IEnumerable surfaces); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index ba9b984c56..bd48998dff 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -322,24 +322,9 @@ namespace Avalonia.Gtk _window = null; } - - //We need that for DrawingArea which does not have an HWND until parent window is realized - //and could also *change* it's HWND - class DynamicNativeWindowSurface : INativeWindowPlatformSurface - { - private readonly IPlatformHandle _handle; - - public DynamicNativeWindowSurface(IPlatformHandle handle) - { - _handle = handle; - } - - public IntPtr Handle => _handle.Handle; - } - public IEnumerable Surfaces => new object[] { - new DynamicNativeWindowSurface(Handle), + Handle, new Func(() => CurrentDrawable), _framebuffer }; diff --git a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs index 610d6f5703..3355839314 100644 --- a/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia.Android/AndroidPlatformRenderInterface.cs @@ -19,7 +19,7 @@ namespace Avalonia.Skia { var surfaceView = surfaces?.OfType().FirstOrDefault(); if (surfaceView == null) - throw new ArgumentException("Avalonia.Skia.Android is only capable to draw on SurfaceView"); + throw new ArgumentException("Avalonia.Skia.Android is only capable of drawing on SurfaceView"); return new WindowRenderTarget(surfaceView); } } diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 4ef267e0de..34595fecc8 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -104,10 +104,11 @@ namespace Avalonia.Direct2D1 public IRenderTarget CreateRenderTarget(IEnumerable surfaces) { - var nativeWindow = surfaces?.OfType().FirstOrDefault(); - + var nativeWindow = surfaces?.OfType().FirstOrDefault(); if (nativeWindow != null) { + if(nativeWindow.HandleDescriptor != "HWND") + throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from " + nativeWindow.HandleDescriptor); return new HwndRenderTarget(nativeWindow); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); diff --git a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs index 317e7fc34f..49402d54b9 100644 --- a/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs +++ b/src/Windows/Avalonia.Direct2D1/HwndRenderTarget.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; using Avalonia.Win32.Interop; using SharpDX; using SharpDX.DXGI; @@ -12,9 +13,9 @@ namespace Avalonia.Direct2D1 { class HwndRenderTarget : SwapChainRenderTarget { - private readonly INativeWindowPlatformSurface _window; + private readonly IPlatformHandle _window; - public HwndRenderTarget(INativeWindowPlatformSurface window) + public HwndRenderTarget(IPlatformHandle window) { _window = window; } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 878be61664..19a7ca5e58 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -767,7 +767,7 @@ namespace Avalonia.Win32 public IEnumerable Surfaces => new object[] { - new NativeWindowPlatformSurface(_hwnd), _framebuffer + Handle, _framebuffer }; } } From e35677081ab38b0988c052df765ee3022c4aa587 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 15 Oct 2016 15:05:42 +0300 Subject: [PATCH 04/61] Initial commit of GTK3 backend that shows something on screen --- Avalonia.sln | 43 ++++++ .../ControlCatalog.Desktop.csproj | 4 + samples/ControlCatalog.Desktop/Program.cs | 4 +- src/Gtk/Avalonia.Gtk3/.gitignore | 1 + src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 80 ++++++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 91 +++++++++++ src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs | 142 ++++++++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 87 +++++++++++ .../Avalonia.Gtk3/Interop/NativeException.cs | 24 +++ src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 104 +++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 45 ++++++ src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs | 32 ++++ .../Avalonia.Gtk3/Properties/AssemblyInfo.cs | 30 ++++ src/Gtk/Avalonia.Gtk3/README.md | 9 ++ src/Gtk/Avalonia.Gtk3/Stubs.cs | 47 ++++++ src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 95 ++++++++++++ src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 48 ++++++ src/Gtk/Avalonia.Gtk3/project.json | 10 ++ 18 files changed, 895 insertions(+), 1 deletion(-) create mode 100644 src/Gtk/Avalonia.Gtk3/.gitignore create mode 100644 src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj create mode 100644 src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Native.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Signal.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs create mode 100644 src/Gtk/Avalonia.Gtk3/README.md create mode 100644 src/Gtk/Avalonia.Gtk3/Stubs.cs create mode 100644 src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs create mode 100644 src/Gtk/Avalonia.Gtk3/WindowImpl.cs create mode 100644 src/Gtk/Avalonia.Gtk3/project.json diff --git a/Avalonia.sln b/Avalonia.sln index a6fdcbd421..0eda7e5fc7 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -155,6 +155,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\Rende EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Gtk3", "src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj", "{BB1F7BB5-6AD4-4776-94D9-C09D0A972658}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 @@ -2329,6 +2331,46 @@ Global {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU {29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Ad-Hoc|x86.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.AppStore|x86.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhone.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|Mono.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.ActiveCfg = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Debug|x86.Build.0 = Debug|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Any CPU.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhone.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|Mono.Build.0 = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.ActiveCfg = Release|Any CPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2382,5 +2424,6 @@ Global {BD7F352C-6DC1-4740-BAF2-2D34A038728C} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3} EndGlobalSection EndGlobal diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 53cb277233..6fec660e55 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -72,6 +72,10 @@ {FB05AC90-89BA-4F2F-A924-F37875FB547C} Avalonia.Cairo + + {bb1f7bb5-6ad4-4776-94d9-c09d0a972658} + Avalonia.Gtk3 + {54F237D5-A70A-4752-9656-0C70B1A7B047} Avalonia.Gtk diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index b67c5ea51d..f2634865b9 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Gtk3; using Avalonia.Logging.Serilog; using Avalonia.Platform; using Serilog; @@ -17,7 +18,8 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. AppBuilder.Configure() - .UsePlatformDetect() + .UseDirect2D1() + .UseGtk3() .Start(); } diff --git a/src/Gtk/Avalonia.Gtk3/.gitignore b/src/Gtk/Avalonia.Gtk3/.gitignore new file mode 100644 index 0000000000..ddb0c6248b --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/.gitignore @@ -0,0 +1 @@ +project.lock.json \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj new file mode 100644 index 0000000000..5e645fb542 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -0,0 +1,80 @@ + + + + + 14.0 + Debug + AnyCPU + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} + Library + Properties + Avalonia.Gtk3 + Avalonia.Gtk3 + en-US + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + v5.0 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {B09B78D8-9B26-48B0-9149-D64A2F120F3F} + Avalonia.Base + + + {D2221C82-4A25-4583-9B43-D791E3F6820C} + Avalonia.Controls + + + {62024B2D-53EB-4638-B26B-85EEAA54866E} + Avalonia.Input + + + {EB582467-6ABB-43A1-B052-E981BA910E3A} + Avalonia.SceneGraph + + + + + \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs new file mode 100644 index 0000000000..164e987802 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Gtk3.Interop; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + public class Gtk3Platform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + { + internal static readonly Gtk3Platform Instance = new Gtk3Platform(); + internal static readonly MouseDevice Mouse = new MouseDevice(); + internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); + internal static IntPtr App { get; set; } + public static void Initialize() + { + Resolver.Resolve(); + Native.GtkInit(0, IntPtr.Zero); + App = Native.GtkApplicationNew("avalonia.app." + Guid.NewGuid(), 0); + //Mark current thread as UI thread + s_tlsMarker = true; + + AvaloniaLocator.CurrentMutable.Bind().ToConstant(Instance) + .Bind().ToSingleton() + .Bind().ToConstant(new CursorFactoryStub()) + .Bind().ToConstant(Keyboard) + .Bind().ToConstant(Mouse) + .Bind().ToConstant(Instance) + .Bind().ToConstant(Instance) + .Bind().ToSingleton() + .Bind().ToConstant(new PlatformIconLoaderStub()); + + } + + public IWindowImpl CreateWindow() => new WindowImpl(); + + public IEmbeddableWindowImpl CreateEmbeddableWindow() + { + throw new NotImplementedException(); + } + + public IPopupImpl CreatePopup() + { + throw new NotImplementedException(); + } + + + + public Size DoubleClickSize => new Size(4, 4); + + public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(100); //STUB + public double RenderScalingFactor { get; } = 1; + public double LayoutScalingFactor { get; } = 1; + + public void RunLoop(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested) + Native.GtkMainIteration(); + } + + public IDisposable StartTimer(TimeSpan interval, Action tick) + { + return null; + } + + public void Signal() + { + } + public event Action Signaled; + + + [ThreadStatic] + private static bool s_tlsMarker; + + public bool CurrentThreadIsLoopThread => s_tlsMarker; + + } + + public static class Gtk3AppBuilderExtensions + { + public static T UseGtk3(this AppBuilderBase builder) where T : AppBuilderBase, new() + => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs new file mode 100644 index 0000000000..93ba7cc195 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs @@ -0,0 +1,142 @@ +using System; +using System.Runtime.InteropServices; + +/* + * Source code imported from https://github.com/kekekeks/evhttp-sharp + * Source is provided under MIT license for Avalonia project and derived works + */ + + +namespace Avalonia.Gtk3.Interop +{ + internal interface IDynLoader + { + IntPtr LoadLibrary(string basePath, string dll); + IntPtr GetProcAddress(IntPtr dll, string proc); + + } + + class UnixLoader : IDynLoader + { + // ReSharper disable InconsistentNaming + static class LinuxImports + { + + [DllImport("libdl.so.2")] + private static extern IntPtr dlopen(string path, int flags); + + [DllImport("libdl.so.2")] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport("libdl.so.2")] + private static extern IntPtr dlerror(); + + public static void Init() + { + DlOpen = dlopen; + DlSym = dlsym; + DlError = dlerror; + Postfix = ".so.0"; + } + } + static class OsXImports + { + + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlopen(string path, int flags); + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlsym(IntPtr handle, string symbol); + + [DllImport("/usr/lib/libSystem.dylib")] + private static extern IntPtr dlerror(); + + public static void Init() + { + DlOpen = dlopen; + DlSym = dlsym; + DlError = dlerror; + Postfix = ".dylib"; //TODO + } + + } + + + [DllImport("libc")] + static extern int uname(IntPtr buf); + + + + static UnixLoader() + { + var buffer = Marshal.AllocHGlobal(0x1000); + uname(buffer); + var unixName = Marshal.PtrToStringAnsi(buffer); + Marshal.FreeHGlobal(buffer); + if(unixName == "Darwin") + OsXImports.Init(); + else + LinuxImports.Init(); + } + + private static Func DlOpen; + private static Func DlSym; + private static Func DlError; + private static string Postfix; + // ReSharper restore InconsistentNaming + + static string DlErrorString() + { + + return Marshal.PtrToStringAnsi(DlError()); + } + + public IntPtr LoadLibrary(string basePath, string dll) + { + dll += Postfix; + if (basePath != null) + dll = System.IO.Path.Combine(basePath, dll); + var handle = DlOpen(dll, 1); + if (handle == IntPtr.Zero) + throw new NativeException(DlErrorString()); + return handle; + } + + public IntPtr GetProcAddress(IntPtr dll, string proc) + { + var ptr = DlSym(dll, proc); + if (ptr == IntPtr.Zero) + throw new NativeException(DlErrorString()); + return ptr; + } + } + + internal class Win32Loader : IDynLoader + { + [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] + private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); + + [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr LoadLibrary(string lpszLib); + + IntPtr IDynLoader.LoadLibrary(string basePath, string dll) + { + dll += "-0.dll"; + if (basePath != null) + dll = System.IO.Path.Combine(basePath, dll); + var handle = LoadLibrary(dll); + if (handle == IntPtr.Zero) + throw new NativeException("Error " + Marshal.GetLastWin32Error()); + return handle; + } + + IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc) + { + var ptr = GetProcAddress(dll, proc); + if (ptr == IntPtr.Zero) + throw new NativeException("Error " + Marshal.GetLastWin32Error()); + return ptr; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs new file mode 100644 index 0000000000..a07d84c710 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + static class Native + { + public static class D + { + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_application_new([MarshalAs(UnmanagedType.AnsiBStr)] string appId, int flags); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_main_iteration(); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_window_new(GtkWindowType windowType); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_init(int argc, IntPtr argv); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_present(IntPtr gtkWindow); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_hide(IntPtr gtkWidget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] //No manual import + public delegate IntPtr gdk_get_native_handle(IntPtr gdkWindow); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_get_window(IntPtr gtkWidget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_set_double_buffered(IntPtr gtkWidget, bool value); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_realize(IntPtr gtkWidget); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_title(IntPtr gtkWindow, Utf8Buffer title); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_set_decorated(IntPtr gtkWindow, bool decorated); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); + } + + + public static D.gtk_window_set_decorated GtkWindowSetDecorated; + public static D.gtk_window_set_title GtkWindowSetTitle; + public static D.gtk_application_new GtkApplicationNew; + public static D.gtk_main_iteration GtkMainIteration; + public static D.gtk_window_new GtkWindowNew; + public static D.gtk_init GtkInit; + public static D.gtk_window_present GtkWindowPresent; + public static D.gtk_widget_hide GtkWidgetHide; + public static D.gdk_get_native_handle GetNativeGdkWindowHandle; + public static D.gtk_widget_get_window GtkWidgetGetWindow; + public static D.gtk_widget_realize GtkWidgetRealize; + public static D.gtk_window_get_size GtkWindowGetSize; + public static D.g_signal_connect_object GSignalConnectObject; + public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; + public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; + + + + } + + public enum GtkWindowType + { + TopLevel, + Popup + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs b/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs new file mode 100644 index 0000000000..43659f8f83 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/NativeException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + public class NativeException : Exception + { + public NativeException() + { + } + + public NativeException(string message) : base(message) + { + } + + public NativeException(string message, Exception inner) : base(message, inner) + { + } + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs new file mode 100644 index 0000000000..2410af7591 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + internal class GtkImportAttribute : Attribute + { + public GtkDll Dll { get; set; } + public string Name { get; set; } + public bool Optional { get; set; } + + public GtkImportAttribute(GtkDll dll, string name = null, bool optional = false) + { + Dll = dll; + Name = name; + Optional = optional; + } + } + + internal enum GtkDll + { + Gdk, + Gtk, + Glib, + Gio, + Gobject + } + + static class Resolver + { + [DllImport("kernel32.dll")] + static extern int GetVersion(); + + static bool IsWin32() + { + try + { + GetVersion(); + return true; + } + catch + { + return false; + } + } + + + + public static void Resolve(string basePath = null) + { + var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader(); + + + var gdk = loader.LoadLibrary(basePath, "libgdk-3"); + var gtk = loader.LoadLibrary(basePath, "libgtk-3"); + var gio = loader.LoadLibrary(basePath, "libgio-2.0"); + var glib = loader.LoadLibrary(basePath, "libglib-2.0"); + var gobject = loader.LoadLibrary(basePath, "libgobject-2.0"); + foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) + { + var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); + if(import == null) + continue; + IntPtr lib; + if (import.Dll == GtkDll.Gtk) + lib = gtk; + else if (import.Dll == GtkDll.Gdk) + lib = gdk; + else if (import.Dll == GtkDll.Gio) + lib = gio; + else if (import.Dll == GtkDll.Glib) + lib = glib; + else if (import.Dll == GtkDll.Gobject) + lib = gobject; + else + throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType); + + var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name); + fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); + } + + var nativeHandleNames = new[] {"gdk_x11_window_get_xid", "gdk_win32_window_get_handle"}; + foreach (var name in nativeHandleNames) + { + try + { + Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal + .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + } + catch { } + } + if (Native.GetNativeGdkWindowHandle == null) + throw new Exception($"Unable to locate any of [{string.Join(", ", nativeHandleNames)}] in libgdk"); + + } + + + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs new file mode 100644 index 0000000000..5896c547f2 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + public class Signal + { + class ConnectedSignal : IDisposable + { + private readonly IntPtr _instance; + private GCHandle _handle; + private readonly ulong _id; + + public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id) + { + _instance = instance; + _handle = handle; + _id = id; + } + + public void Dispose() + { + if (_handle.IsAllocated) + { + Native.GSignalHandlerDisconnect(_instance, _id); + _handle.Free(); + } + } + } + + public static IDisposable Connect(IntPtr obj, string name, T handler) + { + var handle = GCHandle.Alloc(handler); + var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); + var id = Native.GSignalConnectObject(obj, name, ptr, IntPtr.Zero, 0); + if (id == 0) + throw new ArgumentException("Unable to connect to signal " + name); + return new ConnectedSignal(obj, handle, id); + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs new file mode 100644 index 0000000000..7a9868857f --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/Utf8Buffer.cs @@ -0,0 +1,32 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Avalonia.Gtk3.Interop +{ + class Utf8Buffer : SafeHandle + { + private GCHandle _gchandle; + private byte[] _data; + + public Utf8Buffer(string s) : base(IntPtr.Zero, true) + { + _data = Encoding.UTF8.GetBytes(s); + _gchandle = GCHandle.Alloc(_data, GCHandleType.Pinned); + handle = _gchandle.AddrOfPinnedObject(); + } + + protected override bool ReleaseHandle() + { + if (handle != IntPtr.Zero) + { + handle = IntPtr.Zero; + _data = null; + _gchandle.Free(); + } + return true; + } + + public override bool IsInvalid => handle == IntPtr.Zero; + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..72e6388531 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Avalonia.Gtk3")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Avalonia.Gtk3")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("en")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Gtk/Avalonia.Gtk3/README.md b/src/Gtk/Avalonia.Gtk3/README.md new file mode 100644 index 0000000000..7f117d24b3 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/README.md @@ -0,0 +1,9 @@ +P/Invoke based GTK3 backend +=========================== + +Code is EXPERIMENTAL at this point. It also needs Direct2D/Skia for rendering. + +Windows GTK3 binaries aren't included in the repo, you need to download them from http://www.tarnyko.net/repo/gtk3_build_system/gtk+-bundle_3.4.2-20130513_win32.zip +Then you need to extract them somewhere and add `bin` directory to PATH. Support for specifying exact path to binaries will be implemented later. + +On Linux it should work out of the box with system-provided GTK3. On OSX you should be able to wire GTK3 using DYLD_LIBRARY_PATH environment variable. \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Stubs.cs b/src/Gtk/Avalonia.Gtk3/Stubs.cs new file mode 100644 index 0000000000..76333aba60 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Stubs.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Input; +using Avalonia.Input.Platform; +using Avalonia.Platform; + +//TODO: This file should be empty once everything is implemented + +namespace Avalonia.Gtk3 +{ + class ClipboardStub : IClipboard + { + public Task GetTextAsync() => Task.FromResult(""); + + public Task SetTextAsync(string text) => Task.FromResult(0); + + public Task ClearAsync() => Task.FromResult(0); + } + + class CursorFactoryStub : IStandardCursorFactory + { + public IPlatformHandle GetCursor(StandardCursorType cursorType) => new PlatformHandle(IntPtr.Zero, "STUB"); + } + + class SystemDialogStub : ISystemDialogImpl + { + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) => Task.FromResult(new string[0]); + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + => Task.FromResult((string) null); + } + + class PlatformIconLoaderStub : IPlatformIconLoader + { + public IWindowIconImpl LoadIcon(string fileName) => null; + + public IWindowIconImpl LoadIcon(Stream stream) => null; + + public IWindowIconImpl LoadIcon(IBitmapImpl bitmap) => null; + } +} diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs new file mode 100644 index 0000000000..2f6181c893 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -0,0 +1,95 @@ +using System; +using Avalonia.Controls; +using Avalonia.Gtk3.Interop; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle + { + protected readonly IntPtr _gtkWidget; + private IInputRoot _inputRoot; + + public TopLevelImpl(IntPtr gtkWidget) + { + _gtkWidget = gtkWidget; + Native.GtkWidgetRealize(gtkWidget); + Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); + Signal.Connect(_gtkWidget, "draw", OnDraw); + } + + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) + { + Paint?.Invoke(new Rect(ClientSize)); + return true; + } + + public void Dispose() + { + //STUB + } + + public abstract Size ClientSize { get; set; } + + public Size MaxClientSize => new Size(1024, 768); //STUB + public double Scaling => 1; //TODO: Implement scaling + public IPlatformHandle Handle => this; + + string IPlatformHandle.HandleDescriptor => "HWND"; + + public Action Activated { get; set; } + public Action Closed { get; set; } + public Action Deactivated { get; set; } + public Action Input { get; set; } + public Action Paint { get; set; } + public Action Resized { get; set; } + public Action ScalingChanged { get; set; } + public Action PositionChanged { get; set; } + public void Activate() + { + throw new NotImplementedException(); + } + + public void Invalidate(Rect rect) + { + throw new NotImplementedException(); + } + + public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; + + public Point PointToClient(Point point) + { + throw new NotImplementedException(); + } + + public Point PointToScreen(Point point) + { + throw new NotImplementedException(); + } + + public void SetCursor(IPlatformHandle cursor) + { + //STUB + } + + public void Show() => Native.GtkWindowPresent(_gtkWidget); + + public void Hide() => Native.GtkWidgetHide(_gtkWidget); + + public void BeginMoveDrag() + { + //STUB + } + + public void BeginResizeDrag(WindowEdge edge) + { + //STUB + } + + public Point Position { get; set; } + + IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(_gtkWidget)); + } +} diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs new file mode 100644 index 0000000000..e3fd52f971 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -0,0 +1,48 @@ +using System; +using Avalonia.Controls; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + class WindowImpl : TopLevelImpl, IWindowImpl + { + public WindowState WindowState { get; set; } //STUB + public void SetTitle(string title) + { + using (var t = new Utf8Buffer(title)) + Native.GtkWindowSetTitle(_gtkWidget, t); + } + + public IDisposable ShowDialog() + { + return null; + //STUB + } + + public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(_gtkWidget, enabled); + + public void SetIcon(IWindowIconImpl icon) + { + //STUB + } + + public WindowImpl() : base(Native.GtkWindowNew(GtkWindowType.TopLevel)) + { + } + + public override Size ClientSize + { + get + { + int w, h; + Native.GtkWindowGetSize(_gtkWidget, out w, out h); + return new Size(w, h); + } + set + { + //STUB + } + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/project.json b/src/Gtk/Avalonia.Gtk3/project.json new file mode 100644 index 0000000000..7af946da1a --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/project.json @@ -0,0 +1,10 @@ +{ + "supports": {}, + "dependencies": { + "Microsoft.NETCore.Portable.Compatibility": "1.0.1", + "NETStandard.Library": "1.6.0" + }, + "frameworks": { + "netstandard1.1": {} + } +} \ No newline at end of file From a85e70a124d15c567bd0ebe1e2c2a95e063f16a1 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 22:57:33 +0000 Subject: [PATCH 05/61] Remove CoverTaskbarWhenMaximized property from Window. --- src/Avalonia.Controls/Window.cs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 6755dee073..40c52a748d 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -64,13 +64,6 @@ namespace Avalonia.Controls public static readonly StyledProperty HasSystemDecorationsProperty = AvaloniaProperty.Register(nameof(HasSystemDecorations), true); - /// - /// Sets if the window should cover the taskbar when maximized. Only applies to Windows - /// with HasSystemDecorations = false. - /// - public static readonly StyledProperty CoverTaskbarOnMaximizeProperty = - AvaloniaProperty.Register(nameof(CoverTaskbarOnMaximize), true); - /// /// Defines the property. /// @@ -97,9 +90,6 @@ namespace Avalonia.Controls HasSystemDecorationsProperty.Changed.AddClassHandler( (s, e) => s.PlatformImpl.SetSystemDecorations((bool) e.NewValue)); - CoverTaskbarOnMaximizeProperty.Changed.AddClassHandler( - (s, e) => s.PlatformImpl.SetCoverTaskbarWhenMaximized((bool)e.NewValue)); - IconProperty.Changed.AddClassHandler((s, e) => s.PlatformImpl.SetIcon(((WindowIcon)e.NewValue).PlatformImpl)); } @@ -168,16 +158,6 @@ namespace Avalonia.Controls set { SetValue(HasSystemDecorationsProperty, value); } } - /// - /// Sets if the window should cover the taskbar when maximized. Only applies to Windows - /// with HasSystemDecorations = false. - /// - public bool CoverTaskbarOnMaximize - { - get { return GetValue(CoverTaskbarOnMaximizeProperty); } - set { SetValue(CoverTaskbarOnMaximizeProperty, value); } - } - /// /// Gets or sets the minimized/maximized state of the window. /// From c09755cfbff589dfbf74ee47bf2501988a24bf9f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 22:57:56 +0000 Subject: [PATCH 06/61] Remove SetCoverTaskbarWhenMaximized method from IWindowImpl --- src/Avalonia.Controls/Platform/IWindowImpl.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs index fd2feb539f..609e9834cb 100644 --- a/src/Avalonia.Controls/Platform/IWindowImpl.cs +++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs @@ -35,11 +35,6 @@ namespace Avalonia.Platform /// void SetSystemDecorations(bool enabled); - /// - /// When system decorations are disabled sets if the maximized state covers the entire screen or just the working area. - /// - void SetCoverTaskbarWhenMaximized(bool enable); - /// /// Sets the icon of this window. /// From 1394995927dc87c017dc3160e5c586e26f10da12 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:02:01 +0000 Subject: [PATCH 07/61] Win32 WindowImpl now obeys taskbar when maximizing by default. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index db46538796..ed6d9c32d3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -33,7 +33,6 @@ namespace Avalonia.Win32 private bool _trackingMouse; private bool _isActive; private bool _decorated = true; - private bool _coverTaskBarWhenMaximized = true; private double _scaling = 1; private WindowState _showWindowState; @@ -678,8 +677,6 @@ namespace Avalonia.Win32 { UnmanagedMethods.ShowWindowCommand command; - bool maximizeFillsDesktop = false; // otherwise we cover entire screen. - switch (state) { case WindowState.Minimized: @@ -687,11 +684,6 @@ namespace Avalonia.Win32 break; case WindowState.Maximized: command = ShowWindowCommand.Maximize; - - if (!_decorated && !_coverTaskBarWhenMaximized) - { - maximizeFillsDesktop = true; - } break; case WindowState.Normal: @@ -704,10 +696,7 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, command); - if (maximizeFillsDesktop) - { - MaximizeWithoutCoveringTaskbar(); - } + MaximizeWithoutCoveringTaskbar(); if (!Design.IsDesignMode) { @@ -751,15 +740,5 @@ namespace Avalonia.Win32 return (int)(ptr.ToInt64() & 0xffffffff); } - - public void SetCoverTaskbarWhenMaximized(bool enable) - { - _coverTaskBarWhenMaximized = enable; - - if (_showWindowState == WindowState.Maximized) - { - ShowWindow(WindowState.Maximized); - } - } } } From 80e42b5882ca5b7be9062b3a871c314a878c4e1e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:04:06 +0000 Subject: [PATCH 08/61] Remove SetCoverTaskbarWhenMaximized from other backends. --- .../Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs | 5 ----- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 5 ----- src/iOS/Avalonia.iOS/AvaloniaView.cs | 5 ----- 3 files changed, 15 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 0e1540b5fd..f1c5d248ac 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -108,11 +108,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - //Not supported - } - public void Invalidate(Rect rect) { if (Holder?.Surface?.IsValid == true) base.Invalidate(); diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index 8641f2f431..a9ecfa4058 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -304,11 +304,6 @@ namespace Avalonia.Gtk args.RetVal = true; } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - // No action neccesary on Gtk. - } - public void Dispose() { _window.Hide(); diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 67817ef62a..8b14d09573 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -170,11 +170,6 @@ namespace Avalonia.iOS //Not supported } - public void SetCoverTaskbarWhenMaximized(bool enable) - { - //Not supported - } - public override void TouchesEnded(NSSet touches, UIEvent evt) { var touch = touches.AnyObject as UITouch; From e486b27357e42b3a19954eee8c7d6c8e09866c9f Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 25 Jan 2017 23:16:59 +0000 Subject: [PATCH 09/61] Only call maximize without covering taskbar if we are in maximized state. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index ed6d9c32d3..1ddd1ed10f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -696,7 +696,10 @@ namespace Avalonia.Win32 UnmanagedMethods.ShowWindow(_hwnd, command); - MaximizeWithoutCoveringTaskbar(); + if (state == WindowState.Maximized) + { + MaximizeWithoutCoveringTaskbar(); + } if (!Design.IsDesignMode) { From f8e392a520caaefc860eb8a05d474a52a1e8279b Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 02:55:48 +0300 Subject: [PATCH 10/61] Updated to the recent code base, implemented threading, resize and invalidate handling --- Avalonia.sln | 3 + .../ControlCatalog.Desktop.csproj | 5 +- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 3 +- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 21 +++++- src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs | 64 +++++++++++++++++++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 55 +++++++++++++++- src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 3 +- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 62 +++++++++++++----- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 16 +++-- 9 files changed, 203 insertions(+), 29 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs diff --git a/Avalonia.sln b/Avalonia.sln index 0eda7e5fc7..46dafa9c76 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -130,6 +130,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog", "samples\ControlCatalog\ControlCatalog.csproj", "{D0A739B9-3C68-4BA6-A328-41606954B6BD}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Desktop", "samples\ControlCatalog.Desktop\ControlCatalog.Desktop.csproj", "{2B888490-D14A-4BCA-AB4B-48676FA93C9B}" + ProjectSection(ProjectDependencies) = postProject + {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {BB1F7BB5-6AD4-4776-94D9-C09D0A972658} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}" EndProject diff --git a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj index 6fec660e55..fb2eba3c2f 100644 --- a/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj +++ b/samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj @@ -5,7 +5,7 @@ Debug AnyCPU {2B888490-D14A-4BCA-AB4B-48676FA93C9B} - WinExe + Exe Properties ControlCatalog.Desktop ControlCatalog.Desktop @@ -33,6 +33,9 @@ prompt 4 + + + ..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index 5e645fb542..df5a712c18 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -41,6 +41,7 @@ + @@ -64,7 +65,7 @@ {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input - + {EB582467-6ABB-43A1-B052-E981BA910E3A} Avalonia.SceneGraph diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 164e987802..faf1983250 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -10,6 +10,7 @@ using Avalonia.Gtk3.Interop; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; +using Avalonia.Rendering; namespace Avalonia.Gtk3 { @@ -35,6 +36,7 @@ namespace Avalonia.Gtk3 .Bind().ToConstant(Instance) .Bind().ToConstant(Instance) .Bind().ToSingleton() + .Bind().ToConstant(new DefaultRenderLoop(60)) .Bind().ToConstant(new PlatformIconLoaderStub()); } @@ -67,11 +69,28 @@ namespace Avalonia.Gtk3 public IDisposable StartTimer(TimeSpan interval, Action tick) { - return null; + return GlibTimeout.StarTimer((uint) interval.TotalMilliseconds, tick); } + private bool _signaled = false; + object _lock = new object(); + public void Signal() { + lock(_lock) + if (!_signaled) + { + _signaled = true; + GlibTimeout.Add(0, () => + { + lock (_lock) + { + _signaled = false; + } + Signaled?.Invoke(); + return false; + }); + } } public event Action Signaled; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs new file mode 100644 index 0000000000..9971d8881d --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Avalonia.Gtk3.Interop +{ + static class GlibTimeout + { + static bool Handler(IntPtr data) + { + var handle = GCHandle.FromIntPtr(data); + var cb = (Func) handle.Target; + if (!cb()) + { + handle.Free(); + return false; + } + return true; + } + + private static readonly GCHandle PinnedHandle; + private static readonly Native.D.timeout_callback PinnedHandler; + static GlibTimeout() + { + PinnedHandler = Handler; + + } + + + public static void Add(uint interval, Func callback) + { + var handle = GCHandle.Alloc(callback); + Native.GTimeoutAdd(interval, PinnedHandler, GCHandle.ToIntPtr(handle)); + } + + class Timer : IDisposable + { + public bool Stopped; + public void Dispose() + { + + Stopped = true; + } + } + + public static IDisposable StarTimer(uint interval, Action tick) + { + var timer = new Timer (); + GlibTimeout.Add(interval, + () => + { + if (timer.Stopped) + return false; + tick(); + return !timer.Stopped; + }); + + return timer; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index a07d84c710..2a5abfe603 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -36,7 +36,19 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate IntPtr gtk_widget_get_window(IntPtr gtkWidget); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_get_screen(IntPtr gtkWidget); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate IntPtr gtk_widget_set_double_buffered(IntPtr gtkWidget, bool value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate IntPtr gtk_widget_set_events(IntPtr gtkWidget, uint flags); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_screen_get_height(IntPtr screen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_screen_get_width(IntPtr screen); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_realize(IntPtr gtkWidget); @@ -50,12 +62,29 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_resize(IntPtr gtkWindow, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate void gdk_window_invalidate_rect(IntPtr window, ref GdkRectangle rect, bool invalidate_children); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] + public delegate ulong g_timeout_add(uint interval, timeout_callback callback, IntPtr data); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool signal_widget_draw(IntPtr gtkWidget, IntPtr cairoContext, IntPtr userData); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData); + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate bool timeout_callback(IntPtr data); } @@ -69,12 +98,19 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_hide GtkWidgetHide; public static D.gdk_get_native_handle GetNativeGdkWindowHandle; public static D.gtk_widget_get_window GtkWidgetGetWindow; + public static D.gtk_widget_get_screen GtkWidgetGetScreen; public static D.gtk_widget_realize GtkWidgetRealize; public static D.gtk_window_get_size GtkWindowGetSize; + public static D.gtk_window_resize GtkWindowResize; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; + public static D.g_timeout_add GTimeoutAdd; public static D.gtk_widget_set_double_buffered GtkWidgetSetDoubleBuffered; - + public static D.gtk_widget_set_events GtkWidgetSetEvents; + public static D.gdk_window_invalidate_rect GdkWindowInvalidateRect; + public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; + public static D.gdk_screen_get_height GdkScreenGetHeight; + public static D.gdk_screen_get_width GdkScreenGetWidth; } @@ -84,4 +120,21 @@ namespace Avalonia.Gtk3.Interop TopLevel, Popup } + + [StructLayout(LayoutKind.Sequential)] + public struct GdkRectangle + { + public int X, Y, Width, Height; + + public static GdkRectangle FromRect(Rect rect) + { + return new GdkRectangle + { + X = (int) rect.X, + Y = (int) rect.Y, + Width = (int) rect.Width, + Height = (int) rect.Height + }; + } + } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 2410af7591..15a2f90ade 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -84,13 +84,14 @@ namespace Avalonia.Gtk3.Interop fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); } - var nativeHandleNames = new[] {"gdk_x11_window_get_xid", "gdk_win32_window_get_handle"}; + var nativeHandleNames = new[] { "gdk_win32_window_get_handle", "gdk_x11_window_get_xid", "gdk_quartz_window_get_nswindow" }; foreach (var name in nativeHandleNames) { try { Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + break; } catch { } } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index 2f6181c893..672e98fe18 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using Avalonia.Controls; using Avalonia.Gtk3.Interop; using Avalonia.Input; @@ -9,44 +11,69 @@ namespace Avalonia.Gtk3 { abstract class TopLevelImpl : ITopLevelImpl, IPlatformHandle { - protected readonly IntPtr _gtkWidget; + protected readonly IntPtr GtkWidget; private IInputRoot _inputRoot; + protected readonly List _disposables = new List(); public TopLevelImpl(IntPtr gtkWidget) { - _gtkWidget = gtkWidget; + GtkWidget = gtkWidget; + Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue); Native.GtkWidgetRealize(gtkWidget); - Native.GtkWidgetSetDoubleBuffered(gtkWidget, false); - Signal.Connect(_gtkWidget, "draw", OnDraw); + Connect("draw", OnDraw); + Connect("configure-event", OnConfigured); } + private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) + { + Debug.WriteLine("Configured"); + Resized?.Invoke(ClientSize); + return false; + } + + + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { + Debug.WriteLine("Draw"); Paint?.Invoke(new Rect(ClientSize)); return true; } public void Dispose() { - //STUB + foreach(var d in _disposables) + d.Dispose(); + _disposables.Clear(); + //TODO } public abstract Size ClientSize { get; set; } - public Size MaxClientSize => new Size(1024, 768); //STUB + public Size MaxClientSize + { + get + { + var s = Native.GtkWidgetGetScreen(GtkWidget); + return new Size(Native.GdkScreenGetWidth(s), Native.GdkScreenGetHeight(s)); + } + } + + public double Scaling => 1; //TODO: Implement scaling public IPlatformHandle Handle => this; string IPlatformHandle.HandleDescriptor => "HWND"; - public Action Activated { get; set; } - public Action Closed { get; set; } - public Action Deactivated { get; set; } - public Action Input { get; set; } + public Action Activated { get; set; } //TODO + public Action Closed { get; set; } //TODO + public Action Deactivated { get; set; } //TODO + public Action Input { get; set; } //TODO public Action Paint { get; set; } - public Action Resized { get; set; } - public Action ScalingChanged { get; set; } - public Action PositionChanged { get; set; } + public Action Resized { get; set; } //TODO + public Action ScalingChanged { get; set; } //TODO + public Action PositionChanged { get; set; } //TODO public void Activate() { throw new NotImplementedException(); @@ -54,7 +81,7 @@ namespace Avalonia.Gtk3 public void Invalidate(Rect rect) { - throw new NotImplementedException(); + Native.GtkWidgetQueueDrawArea(GtkWidget, (int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); } public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot; @@ -74,9 +101,9 @@ namespace Avalonia.Gtk3 //STUB } - public void Show() => Native.GtkWindowPresent(_gtkWidget); + public void Show() => Native.GtkWindowPresent(GtkWidget); - public void Hide() => Native.GtkWidgetHide(_gtkWidget); + public void Hide() => Native.GtkWidgetHide(GtkWidget); public void BeginMoveDrag() { @@ -90,6 +117,7 @@ namespace Avalonia.Gtk3 public Point Position { get; set; } - IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(_gtkWidget)); + IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); + public IEnumerable Surfaces => new object[] {Handle}; } } diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index e3fd52f971..2cd1c5fd6a 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -11,7 +11,7 @@ namespace Avalonia.Gtk3 public void SetTitle(string title) { using (var t = new Utf8Buffer(title)) - Native.GtkWindowSetTitle(_gtkWidget, t); + Native.GtkWindowSetTitle(GtkWidget, t); } public IDisposable ShowDialog() @@ -20,7 +20,7 @@ namespace Avalonia.Gtk3 //STUB } - public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(_gtkWidget, enabled); + public void SetSystemDecorations(bool enabled) => Native.GtkWindowSetDecorated(GtkWidget, enabled); public void SetIcon(IWindowIconImpl icon) { @@ -36,13 +36,15 @@ namespace Avalonia.Gtk3 get { int w, h; - Native.GtkWindowGetSize(_gtkWidget, out w, out h); + Native.GtkWindowGetSize(GtkWidget, out w, out h); return new Size(w, h); } - set - { - //STUB - } + set { Native.GtkWindowResize(GtkWidget, (int) value.Width, (int) value.Height); } + } + + public void SetCoverTaskbarWhenMaximized(bool enable) + { + //Why do we even have that? } } } From 04359041d7aef90f20ef87dc7f5a6acaa400ad9d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 03:42:54 +0300 Subject: [PATCH 11/61] Implemented click support --- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 6 ++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 96 +++++++++++++++++++++ src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 97 +++++++++++++++++++--- src/Gtk/Avalonia.Gtk3/WindowImpl.cs | 11 --- 4 files changed, 189 insertions(+), 21 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index df5a712c18..ab52c8f9c4 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -25,6 +25,7 @@ DEBUG;TRACE prompt 4 + true pdbonly @@ -33,6 +34,7 @@ TRACE prompt 4 + true @@ -65,6 +67,10 @@ {62024B2D-53EB-4638-B26B-85EEAA54866E} Avalonia.Input + + {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} + Avalonia.Interactivity + {EB582467-6ABB-43A1-B052-E981BA910E3A} Avalonia.SceneGraph diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 2a5abfe603..3ee8871b40 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -4,6 +4,12 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using gint8 = System.Byte; +using gint32 = System.Int32; +using gint = System.Int32; +using guint32 = System.UInt32; +using guint = System.UInt32; +using gdouble = System.Double; namespace Avalonia.Gtk3.Interop { @@ -48,6 +54,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate int gdk_screen_get_width(IntPtr screen); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] + public delegate int gdk_window_get_origin(IntPtr gdkWindow, out int x, out int y); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] @@ -64,6 +73,10 @@ namespace Avalonia.Gtk3.Interop public delegate void gtk_window_get_size(IntPtr gtkWindow, out int width, out int height); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_resize(IntPtr gtkWindow, int width, int height); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_get_position(IntPtr gtkWindow, out int x, out int y); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] + public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); @@ -102,6 +115,8 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_realize GtkWidgetRealize; public static D.gtk_window_get_size GtkWindowGetSize; public static D.gtk_window_resize GtkWindowResize; + public static D.gtk_window_get_position GtkWindowGetPosition; + public static D.gtk_window_move GtkWindowMove; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; @@ -111,6 +126,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_widget_queue_draw_area GtkWidgetQueueDrawArea; public static D.gdk_screen_get_height GdkScreenGetHeight; public static D.gdk_screen_get_width GdkScreenGetWidth; + public static D.gdk_window_get_origin GdkWindowGetOrigin; } @@ -137,4 +153,84 @@ namespace Avalonia.Gtk3.Interop }; } } + + enum GdkEventType + { + Nothing = -1, + Delete = 0, + Destroy = 1, + Expose = 2, + MotionNotify = 3, + ButtonPress = 4, + TwoButtonPress = 5, + ThreeButtonPress = 6, + ButtonRelease = 7, + KeyPress = 8, + KeyRelease = 9, + EnterNotify = 10, + LeaveNotify = 11, + FocusChange = 12, + Configure = 13, + Map = 14, + Unmap = 15, + PropertyNotify = 16, + SelectionClear = 17, + SelectionRequest = 18, + SelectionNotify = 19, + ProximityIn = 20, + ProximityOut = 21, + DragEnter = 22, + DragLeave = 23, + DragMotion = 24, + DragStatus = 25, + DropStart = 26, + DropFinished = 27, + ClientEvent = 28, + VisibilityNotify = 29, + NoExpose = 30, + Scroll = 31, + WindowState = 32, + Setting = 33, + OwnerChange = 34, + GrabBroken = 35, + } + + public enum GdkModifierType + { + ShiftMask = 1, + LockMask = 2, + ControlMask = 4, + Mod1Mask = 8, + Mod2Mask = 16, + Mod3Mask = 32, + Mod4Mask = 64, + Mod5Mask = 128, + Button1Mask = 256, + Button2Mask = 512, + Button3Mask = 1024, + Button4Mask = 2048, + Button5Mask = 4096, + SuperMask = 67108864, + HyperMask = 134217728, + MetaMask = 268435456, + ReleaseMask = 1073741824, + ModifierMask = ReleaseMask | Button5Mask | Button4Mask | Button3Mask | Button2Mask | Button1Mask | Mod5Mask | Mod4Mask | Mod3Mask | Mod2Mask | Mod1Mask | ControlMask | LockMask | ShiftMask, + None = 0, + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventButton + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public gdouble* axes; + public GdkModifierType state; + public guint button; + public IntPtr device; + public gdouble x_root, y_root; + }; } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index 672e98fe18..c16415828f 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -22,21 +22,73 @@ namespace Avalonia.Gtk3 Native.GtkWidgetRealize(gtkWidget); Connect("draw", OnDraw); Connect("configure-event", OnConfigured); + Connect("button-press-event", OnButton); + Connect("button-release-event", OnButton); } + private Size _lastSize; + private Point _lastPosition; + private bool OnConfigured(IntPtr gtkwidget, IntPtr ev, IntPtr userdata) { - Debug.WriteLine("Configured"); - Resized?.Invoke(ClientSize); + var size = ClientSize; + if (_lastSize != size) + { + _lastSize = size; + Resized?.Invoke(size); + } + var pos = Position; + if (_lastPosition != pos) + { + _lastPosition = pos; + PositionChanged?.Invoke(pos); + } + return false; } + private static InputModifiers GetModifierKeys(GdkModifierType state) + { + var rv = InputModifiers.None; + if (state.HasFlag(GdkModifierType.ControlMask)) + rv |= InputModifiers.Control; + if (state.HasFlag(GdkModifierType.ShiftMask)) + rv |= InputModifiers.Shift; + if (state.HasFlag(GdkModifierType.Mod1Mask)) + rv |= InputModifiers.Control; + if (state.HasFlag(GdkModifierType.Button1Mask)) + rv |= InputModifiers.LeftMouseButton; + if (state.HasFlag(GdkModifierType.Button2Mask)) + rv |= InputModifiers.RightMouseButton; + if (state.HasFlag(GdkModifierType.Button3Mask)) + rv |= InputModifiers.MiddleMouseButton; + return rv; + } + + private unsafe bool OnButton(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventButton*)ev; + var e = new RawMouseEventArgs( + Gtk3Platform.Mouse, + evnt->time, + _inputRoot, + evnt->type == GdkEventType.ButtonRelease + ? evnt->button == 1 + ? RawMouseEventType.LeftButtonUp + : evnt->button == 3 ? RawMouseEventType.RightButtonUp : RawMouseEventType.MiddleButtonUp + : evnt->button == 1 + ? RawMouseEventType.LeftButtonDown + : evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown, + new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state)); + Input?.Invoke(e); + return false; + } + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { - Debug.WriteLine("Draw"); Paint?.Invoke(new Rect(ClientSize)); return true; } @@ -49,8 +101,6 @@ namespace Avalonia.Gtk3 //TODO } - public abstract Size ClientSize { get; set; } - public Size MaxClientSize { get @@ -71,9 +121,10 @@ namespace Avalonia.Gtk3 public Action Deactivated { get; set; } //TODO public Action Input { get; set; } //TODO public Action Paint { get; set; } - public Action Resized { get; set; } //TODO + public Action Resized { get; set; } public Action ScalingChanged { get; set; } //TODO - public Action PositionChanged { get; set; } //TODO + public Action PositionChanged { get; set; } + public void Activate() { throw new NotImplementedException(); @@ -88,12 +139,17 @@ namespace Avalonia.Gtk3 public Point PointToClient(Point point) { - throw new NotImplementedException(); + int x, y; + Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); + + return new Point(point.X - x, point.Y - y); } public Point PointToScreen(Point point) { - throw new NotImplementedException(); + int x, y; + Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y); + return new Point(point.X + x, point.Y + y); } public void SetCursor(IPlatformHandle cursor) @@ -115,7 +171,28 @@ namespace Avalonia.Gtk3 //STUB } - public Point Position { get; set; } + + public Size ClientSize + { + get + { + int w, h; + Native.GtkWindowGetSize(GtkWidget, out w, out h); + return new Size(w, h); + } + set { Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height); } + } + + public Point Position + { + get + { + int x, y; + Native.GtkWindowGetPosition(GtkWidget, out x, out y); + return new Point(x, y); + } + set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); } + } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); public IEnumerable Surfaces => new object[] {Handle}; diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs index 2cd1c5fd6a..b8c561c56c 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs @@ -31,17 +31,6 @@ namespace Avalonia.Gtk3 { } - public override Size ClientSize - { - get - { - int w, h; - Native.GtkWindowGetSize(GtkWidget, out w, out h); - return new Size(w, h); - } - set { Native.GtkWindowResize(GtkWidget, (int) value.Width, (int) value.Height); } - } - public void SetCoverTaskbarWhenMaximized(bool enable) { //Why do we even have that? From f55a49729831db65472e26f31fbea0944ba0ae49 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 04:01:38 +0300 Subject: [PATCH 12/61] Implemented motion and scroll events --- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 48 +++++++++++++++++++++++-- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 39 ++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 3ee8871b40..d9cf9fff05 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -5,8 +5,10 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using gint8 = System.Byte; +using gint16 = System.Int16; using gint32 = System.Int32; using gint = System.Int32; +using guint16 = System.UInt16; using guint32 = System.UInt32; using guint = System.UInt32; using gdouble = System.Double; @@ -195,7 +197,7 @@ namespace Avalonia.Gtk3.Interop GrabBroken = 35, } - public enum GdkModifierType + enum GdkModifierType { ShiftMask = 1, LockMask = 2, @@ -218,6 +220,15 @@ namespace Avalonia.Gtk3.Interop None = 0, } + enum GdkScrollDirection + { + Up, + Down, + Left, + Right, + Smooth + } + [StructLayout(LayoutKind.Sequential)] unsafe struct GdkEventButton { @@ -232,5 +243,38 @@ namespace Avalonia.Gtk3.Interop public guint button; public IntPtr device; public gdouble x_root, y_root; - }; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventMotion + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public gdouble* axes; + public GdkModifierType state; + public gint16 is_hint; + public IntPtr device; + public gdouble x_root, y_root; + } + + [StructLayout(LayoutKind.Sequential)] + unsafe struct GdkEventScroll + { + public GdkEventType type; + public IntPtr window; + public gint8 send_event; + public guint32 time; + public gdouble x; + public gdouble y; + public GdkModifierType state; + public GdkScrollDirection direction; + public IntPtr device; + public gdouble x_root, y_root; + public gdouble delta_x; + public gdouble delta_y; + } } diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index c16415828f..fc0893bdb8 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -24,6 +24,8 @@ namespace Avalonia.Gtk3 Connect("configure-event", OnConfigured); Connect("button-press-event", OnButton); Connect("button-release-event", OnButton); + Connect("motion-notify-event", OnMotion); + Connect("scroll-event", OnScroll); } private Size _lastSize; @@ -85,6 +87,43 @@ namespace Avalonia.Gtk3 return false; } + private unsafe bool OnMotion(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventMotion*)ev; + var position = new Point(evnt->x, evnt->y); + + + var e = new RawMouseEventArgs( + Gtk3Platform.Mouse, + evnt->time, + _inputRoot, + RawMouseEventType.Move, + position, GetModifierKeys(evnt->state)); + Input(e); + return false; + } + private unsafe bool OnScroll(IntPtr w, IntPtr ev, IntPtr userdata) + { + var evnt = (GdkEventScroll*)ev; + var delta = new Vector(); + var step = (double) 1; + if (evnt->direction == GdkScrollDirection.Down) + delta = new Vector(0, -step); + else if (evnt->direction == GdkScrollDirection.Up) + delta = new Vector(0, step); + else if (evnt->direction == GdkScrollDirection.Right) + delta = new Vector(-step, 0); + else if (evnt->direction == GdkScrollDirection.Left) + delta = new Vector(step, 0); + else if (evnt->direction == GdkScrollDirection.Smooth) + delta = new Vector(evnt->delta_x, evnt->delta_y); + + var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot, + new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state)); + Input(e); + return false; + } + void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) From 5bb9e6913ed756fe91a3316a3479fe65d35a550d Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 22:21:53 +0300 Subject: [PATCH 13/61] Implemented framebuffer and smarter dll search logic --- samples/ControlCatalog.Desktop/Program.cs | 2 +- src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj | 3 + src/Gtk/Avalonia.Gtk3/FramebufferManager.cs | 42 +++++++ src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 8 +- .../Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 71 +++++++++++ src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs | 33 ++--- .../ICustomGtk3NativeLibraryResolver.cs | 17 +++ src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 36 +++++- src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs | 113 +++++++++++++----- src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs | 8 +- 10 files changed, 272 insertions(+), 61 deletions(-) create mode 100644 src/Gtk/Avalonia.Gtk3/FramebufferManager.cs create mode 100644 src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs create mode 100644 src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index f2634865b9..4abdce2657 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -18,7 +18,7 @@ namespace ControlCatalog // TODO: Make this work with GTK/Skia/Cairo depending on command-line args // again. AppBuilder.Configure() - .UseDirect2D1() + .UseSkia() .UseGtk3() .Start(); } diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj index ab52c8f9c4..06fc40458e 100644 --- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj +++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj @@ -41,13 +41,16 @@ + + + diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs new file mode 100644 index 0000000000..c2685e8791 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; + +namespace Avalonia.Gtk3 +{ + class FramebufferManager : IFramebufferPlatformSurface, IDisposable + { + private readonly TopLevelImpl _window; + private ImageSurfaceFramebuffer _fb; + public FramebufferManager(TopLevelImpl window) + { + _window = window; + } + + 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; + } + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index faf1983250..625cd7096a 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -104,7 +104,11 @@ namespace Avalonia.Gtk3 public static class Gtk3AppBuilderExtensions { - public static T UseGtk3(this AppBuilderBase builder) where T : AppBuilderBase, new() - => builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + public static T UseGtk3(this AppBuilderBase builder, ICustomGtk3NativeLibraryResolver resolver = null) + where T : AppBuilderBase, new() + { + Resolver.Custom = resolver; + return builder.UseWindowingSubsystem(Gtk3Platform.Initialize, "GTK3"); + } } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs new file mode 100644 index 0000000000..e955f82764 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Gtk3.Interop; +using Avalonia.Platform; + + +namespace Avalonia.Gtk3 +{ + class ImageSurfaceFramebuffer : ILockedFramebuffer + { + private IntPtr _context; + private IntPtr _surface; + + public ImageSurfaceFramebuffer(int width, int height) + { + _surface = Native.CairoImageSurfaceCreate(1, width, height); + Width = width; + Height = height; + Address = Native.CairoImageSurfaceGetData(_surface); + 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) + return; + Native.CairoSurfaceMarkDirty(_surface); + Native.CairoSetSourceSurface(_context, _surface, 0, 0); + Native.CairoPaint(_context); + _context = IntPtr.Zero; + + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format + { + get + { + if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == + OperatingSystemType.WinNT) + return PixelFormat.Bgra8888; + return PixelFormat.Rgba8888; + } + } + } +} + + + diff --git a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs index 93ba7cc195..afe526e2c3 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/DynLoader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; /* @@ -11,7 +12,7 @@ namespace Avalonia.Gtk3.Interop { internal interface IDynLoader { - IntPtr LoadLibrary(string basePath, string dll); + IntPtr LoadLibrary(string dll); IntPtr GetProcAddress(IntPtr dll, string proc); } @@ -21,7 +22,6 @@ namespace Avalonia.Gtk3.Interop // ReSharper disable InconsistentNaming static class LinuxImports { - [DllImport("libdl.so.2")] private static extern IntPtr dlopen(string path, int flags); @@ -36,7 +36,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".so.0"; } } static class OsXImports @@ -57,7 +56,6 @@ namespace Avalonia.Gtk3.Interop DlOpen = dlopen; DlSym = dlsym; DlError = dlerror; - Postfix = ".dylib"; //TODO } } @@ -66,8 +64,6 @@ namespace Avalonia.Gtk3.Interop [DllImport("libc")] static extern int uname(IntPtr buf); - - static UnixLoader() { var buffer = Marshal.AllocHGlobal(0x1000); @@ -83,20 +79,12 @@ namespace Avalonia.Gtk3.Interop private static Func DlOpen; private static Func DlSym; private static Func DlError; - private static string Postfix; // ReSharper restore InconsistentNaming - static string DlErrorString() - { - - return Marshal.PtrToStringAnsi(DlError()); - } + static string DlErrorString() => Marshal.PtrToStringAnsi(DlError()); - public IntPtr LoadLibrary(string basePath, string dll) + public IntPtr LoadLibrary(string dll) { - dll += Postfix; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = DlOpen(dll, 1); if (handle == IntPtr.Zero) throw new NativeException(DlErrorString()); @@ -120,15 +108,14 @@ namespace Avalonia.Gtk3.Interop [DllImport("kernel32", EntryPoint = "LoadLibraryW", SetLastError = true, CharSet = CharSet.Unicode)] private static extern IntPtr LoadLibrary(string lpszLib); - IntPtr IDynLoader.LoadLibrary(string basePath, string dll) + IntPtr IDynLoader.LoadLibrary(string dll) { - dll += "-0.dll"; - if (basePath != null) - dll = System.IO.Path.Combine(basePath, dll); var handle = LoadLibrary(dll); - if (handle == IntPtr.Zero) - throw new NativeException("Error " + Marshal.GetLastWin32Error()); - return handle; + if (handle != IntPtr.Zero) + return handle; + var err = Marshal.GetLastWin32Error(); + + throw new NativeException("Error loading " + dll + " error " + err); } IntPtr IDynLoader.GetProcAddress(IntPtr dll, string proc) diff --git a/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs new file mode 100644 index 0000000000..2f88b09896 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/Interop/ICustomGtk3NativeLibraryResolver.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Gtk3.Interop; + +namespace Avalonia.Gtk3 +{ + public interface ICustomGtk3NativeLibraryResolver + { + string GetName(GtkDll dll); + string BasePath { get; } + bool TrySystemFirst { get; } + string Lookup(GtkDll dll); + } +} diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index d9cf9fff05..4ed7474fb1 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -80,6 +80,33 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_window_move(IntPtr gtkWindow, int x, int y); + + + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate IntPtr 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); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate int cairo_image_surface_get_stride(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_mark_dirty(IntPtr surface); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_surface_flush(IntPtr 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); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)] + public delegate void cairo_paint(IntPtr context); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_widget_queue_draw_area(IntPtr gtkWindow, int x, int y, int width, int height); @@ -130,7 +157,14 @@ namespace Avalonia.Gtk3.Interop public static D.gdk_screen_get_width GdkScreenGetWidth; public static D.gdk_window_get_origin GdkWindowGetOrigin; - + public static D.cairo_image_surface_create CairoImageSurfaceCreate; + public static D.cairo_image_surface_get_data CairoImageSurfaceGetData; + public static D.cairo_image_surface_get_stride CairoImageSurfaceGetStride; + public static D.cairo_surface_mark_dirty CairoSurfaceMarkDirty; + public static D.cairo_surface_flush CairoSurfaceFlush; + public static D.cairo_surface_destroy CairoSurfaceDestroy; + public static D.cairo_set_source_surface CairoSetSourceSurface; + public static D.cairo_paint CairoPaint; } public enum GtkWindowType diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs index 15a2f90ade..a965453841 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Resolver.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Avalonia.Platform; namespace Avalonia.Gtk3.Interop { @@ -22,64 +24,108 @@ namespace Avalonia.Gtk3.Interop } } - internal enum GtkDll + public enum GtkDll { Gdk, Gtk, Glib, Gio, - Gobject + Gobject, + Cairo } static class Resolver { - [DllImport("kernel32.dll")] - static extern int GetVersion(); + private static Lazy Platform = + new Lazy( + () => AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem); - static bool IsWin32() + public static ICustomGtk3NativeLibraryResolver Custom { get; set; } + + + static string FormatName(string name, int version = 0) + { + if (Platform.Value == OperatingSystemType.WinNT) + return "lib" + name + "-" + version + ".dll"; + if (Platform.Value == OperatingSystemType.Linux) + return "lib" + name + ".so" + "." + version; + if (Platform.Value == OperatingSystemType.OSX) + return "lib" + name + "." + version + ".dylib"; + throw new Exception("Unknown platform, use custom name resolver"); + } + + + + static string GetDllName(GtkDll dll) { + var name = Custom?.GetName(dll); + if (name != null) + return name; + + switch (dll) + { + case GtkDll.Cairo: + return FormatName("cairo", 2); + case GtkDll.Gdk: + return FormatName("gdk-3"); + case GtkDll.Glib: + return FormatName("glib-2.0"); + case GtkDll.Gio: + return FormatName("gio-2.0"); + case GtkDll.Gtk: + return FormatName("gtk-3"); + case GtkDll.Gobject: + return FormatName("gobject-2.0"); + default: + throw new ArgumentException("Unknown lib: " + dll); + } + } + + static IntPtr LoadDll(IDynLoader loader, GtkDll dll) + { + + var exceptions = new List(); + + var name = GetDllName(dll); + if (Custom?.TrySystemFirst != false) + { + try + { + return loader.LoadLibrary(name); + } + catch (Exception e) + { + exceptions.Add(e); + } + } + var path = Custom?.Lookup(dll); + if (path == null && Custom?.BasePath != null) + path = Path.Combine(Custom.BasePath, name); + try { - GetVersion(); - return true; + return loader.LoadLibrary(path); } - catch + catch (Exception e) { - return false; + exceptions.Add(e); } + throw new AggregateException("Unable to load " + dll, exceptions); } - - public static void Resolve(string basePath = null) { - var loader = IsWin32() ? (IDynLoader)new Win32Loader() : new UnixLoader(); - + var loader = Platform.Value == OperatingSystemType.WinNT ? (IDynLoader)new Win32Loader() : new UnixLoader(); - var gdk = loader.LoadLibrary(basePath, "libgdk-3"); - var gtk = loader.LoadLibrary(basePath, "libgtk-3"); - var gio = loader.LoadLibrary(basePath, "libgio-2.0"); - var glib = loader.LoadLibrary(basePath, "libglib-2.0"); - var gobject = loader.LoadLibrary(basePath, "libgobject-2.0"); + var dlls = Enum.GetValues(typeof(GtkDll)).Cast().ToDictionary(x => x, x => LoadDll(loader, x)); + foreach (var fieldInfo in typeof(Native).GetTypeInfo().DeclaredFields) { var import = fieldInfo.FieldType.GetTypeInfo().GetCustomAttributes(typeof(GtkImportAttribute), true).Cast().FirstOrDefault(); if(import == null) continue; - IntPtr lib; - if (import.Dll == GtkDll.Gtk) - lib = gtk; - else if (import.Dll == GtkDll.Gdk) - lib = gdk; - else if (import.Dll == GtkDll.Gio) - lib = gio; - else if (import.Dll == GtkDll.Glib) - lib = glib; - else if (import.Dll == GtkDll.Gobject) - lib = gobject; - else - throw new ArgumentException("Invalid GtkImportAttribute for " + fieldInfo.FieldType); - + IntPtr lib = dlls[import.Dll]; + var funcPtr = loader.GetProcAddress(lib, import.Name ?? fieldInfo.FieldType.Name); fieldInfo.SetValue(null, Marshal.GetDelegateForFunctionPointer(funcPtr, fieldInfo.FieldType)); } @@ -90,7 +136,7 @@ namespace Avalonia.Gtk3.Interop try { Native.GetNativeGdkWindowHandle = (Native.D.gdk_get_native_handle)Marshal - .GetDelegateForFunctionPointer(loader.GetProcAddress(gdk, name), typeof(Native.D.gdk_get_native_handle)); + .GetDelegateForFunctionPointer(loader.GetProcAddress(dlls[GtkDll.Gdk], name), typeof(Native.D.gdk_get_native_handle)); break; } catch { } @@ -103,3 +149,4 @@ namespace Avalonia.Gtk3.Interop } } + diff --git a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs index fc0893bdb8..4d5509f4f7 100644 --- a/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/TopLevelImpl.cs @@ -13,11 +13,13 @@ namespace Avalonia.Gtk3 { protected readonly IntPtr GtkWidget; private IInputRoot _inputRoot; + private readonly FramebufferManager _framebuffer; protected readonly List _disposables = new List(); public TopLevelImpl(IntPtr gtkWidget) { GtkWidget = gtkWidget; + _framebuffer = new FramebufferManager(this); Native.GtkWidgetSetEvents(gtkWidget, uint.MaxValue); Native.GtkWidgetRealize(gtkWidget); Connect("draw", OnDraw); @@ -126,9 +128,13 @@ namespace Avalonia.Gtk3 void Connect(string name, T handler) => _disposables.Add(Signal.Connect(GtkWidget, name, handler)); + internal IntPtr CurrentCairoContext { get; private set; } + private bool OnDraw(IntPtr gtkwidget, IntPtr cairocontext, IntPtr userdata) { + CurrentCairoContext = cairocontext; Paint?.Invoke(new Rect(ClientSize)); + CurrentCairoContext = IntPtr.Zero; return true; } @@ -234,6 +240,6 @@ namespace Avalonia.Gtk3 } IntPtr IPlatformHandle.Handle => Native.GetNativeGdkWindowHandle(Native.GtkWidgetGetWindow(GtkWidget)); - public IEnumerable Surfaces => new object[] {Handle}; + public IEnumerable Surfaces => new object[] {Handle, _framebuffer}; } } From 480801864a1b5d422dbeed7c8a87a327a60d980a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 Jan 2017 23:15:52 +0300 Subject: [PATCH 14/61] Mono support --- src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs | 3 ++- src/Gtk/Avalonia.Gtk3/Interop/Native.cs | 4 ++-- src/Gtk/Avalonia.Gtk3/Interop/Signal.cs | 11 +++++++---- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index 625cd7096a..2aee41e617 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -24,7 +24,8 @@ namespace Avalonia.Gtk3 { Resolver.Resolve(); Native.GtkInit(0, IntPtr.Zero); - App = Native.GtkApplicationNew("avalonia.app." + Guid.NewGuid(), 0); + using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) + App = Native.GtkApplicationNew(utf, 0); //Mark current thread as UI thread s_tlsMarker = true; diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 4ed7474fb1..aa3e73f7c4 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -20,7 +20,7 @@ namespace Avalonia.Gtk3.Interop public static class D { [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] - public delegate IntPtr gtk_application_new([MarshalAs(UnmanagedType.AnsiBStr)] string appId, int flags); + public delegate IntPtr gtk_application_new(Utf8Buffer appId, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)] public delegate void gtk_main_iteration(); @@ -113,7 +113,7 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)] public delegate void gdk_window_invalidate_rect(IntPtr window, ref GdkRectangle rect, bool invalidate_children); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] - public delegate ulong g_signal_connect_object(IntPtr instance, [MarshalAs(UnmanagedType.AnsiBStr)]string signal, IntPtr handler, IntPtr userData, int flags); + public delegate ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId); [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Glib)] diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs index 5896c547f2..b2ccdcf408 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Signal.cs @@ -36,10 +36,13 @@ namespace Avalonia.Gtk3.Interop { var handle = GCHandle.Alloc(handler); var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler); - var id = Native.GSignalConnectObject(obj, name, ptr, IntPtr.Zero, 0); - if (id == 0) - throw new ArgumentException("Unable to connect to signal " + name); - return new ConnectedSignal(obj, handle, id); + using (var utf = new Utf8Buffer(name)) + { + var id = Native.GSignalConnectObject(obj, utf, ptr, IntPtr.Zero, 0); + if (id == 0) + throw new ArgumentException("Unable to connect to signal " + name); + return new ConnectedSignal(obj, handle, id); + } } } } From 8f3e56963a05ee6260a789ea802de39ea0e0c803 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 26 Jan 2017 23:52:59 +0100 Subject: [PATCH 15/61] Picked some style nits. --- .../Platform/SkiaPlatform/WindowImpl.cs | 6 +- .../Platform/Surfaces/ILockedFramebuffer.cs | 9 +-- .../Platform/IPlatformRenderInterface.cs | 4 +- src/Gtk/Avalonia.Cairo/CairoPlatform.cs | 2 +- src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 5 +- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 13 ++-- .../Avalonia.Skia/PlatformRenderInterface.cs | 2 - .../Avalonia.Win32/WindowFramebuffer.cs | 71 +++++++++---------- src/Windows/Avalonia.Win32/WindowImpl.cs | 11 +-- src/iOS/Avalonia.iOS/AvaloniaView.cs | 5 +- 10 files changed, 59 insertions(+), 69 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs index 7816a8af27..0ce7bdca22 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs @@ -96,6 +96,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform IPlatformHandle ITopLevelImpl.Handle => this; + public IEnumerable Surfaces => new object[] { this }; + public void Activate() { } @@ -194,7 +196,5 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } - - public IEnumerable Surfaces => new object[] {this}; - } + } } \ No newline at end of file diff --git a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs index e771401dfe..d6402d170d 100644 --- a/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs +++ b/src/Avalonia.Controls/Platform/Surfaces/ILockedFramebuffer.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Avalonia.Controls.Platform.Surfaces { @@ -12,22 +8,27 @@ namespace Avalonia.Controls.Platform.Surfaces /// Address of the first pixel /// IntPtr Address { get; } + /// /// Framebuffer width /// int Width { get; } + /// /// Framebuffer height /// int Height { get; } + /// /// Number of bytes per row /// int RowBytes { get; } + /// /// DPI of underling screen /// Size Dpi { get; } + /// /// Pixel format /// diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs index 34600c145f..cdec0a07a1 100644 --- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs @@ -41,7 +41,9 @@ namespace Avalonia.Platform /// /// Creates a renderer. /// - /// The list of native platform's surfaces that can be used for output. + /// + /// The list of native platform surfaces that can be used for output. + /// /// An . IRenderTarget CreateRenderTarget(IEnumerable surfaces); diff --git a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs index 8b3c6899d3..e6c493320f 100644 --- a/src/Gtk/Avalonia.Cairo/CairoPlatform.cs +++ b/src/Gtk/Avalonia.Cairo/CairoPlatform.cs @@ -59,7 +59,7 @@ namespace Avalonia.Cairo return new RenderTarget(accessor); throw new NotSupportedException(string.Format( - "Don't know how to create a Cairo renderer from any of provided surfaces")); + "Don't know how to create a Cairo renderer from any of the provided surfaces.")); } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs index 3da3c3d70c..72c977b2b4 100644 --- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; namespace Avalonia.Gtk @@ -11,6 +7,7 @@ namespace Avalonia.Gtk { private readonly WindowImplBase _window; private PixbufFramebuffer _fb; + public FramebufferManager(WindowImplBase window) { _window = window; diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index bd48998dff..a8bd7836ec 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -3,15 +3,10 @@ using System; using System.Collections.Generic; -using System.Reactive.Disposables; -using System.Runtime.InteropServices; -using Gdk; -using Avalonia.Controls; -using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Input; -using Avalonia.Threading; +using Gdk; using Action = System.Action; using WindowEdge = Avalonia.Controls.WindowEdge; @@ -23,8 +18,6 @@ namespace Avalonia.Gtk { private IInputRoot _inputRoot; protected Gtk.Widget _window; - public Gtk.Widget Widget => _window; - public Gdk.Drawable CurrentDrawable { get; private set; } private FramebufferManager _framebuffer; private Gtk.IMContext _imContext; @@ -60,6 +53,8 @@ namespace Avalonia.Gtk } public IPlatformHandle Handle { get; private set; } + public Gtk.Widget Widget => _window; + public Gdk.Drawable CurrentDrawable { get; private set; } void OnRealized (object sender, EventArgs eventArgs) { diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index 56b334073b..4cfe1e3e40 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -55,8 +55,6 @@ namespace Avalonia.Skia return new Renderer(root, renderLoop); } - - public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) { if (width < 1) diff --git a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs index cd89c01131..f7ea79e78a 100644 --- a/src/Windows/Avalonia.Win32/WindowFramebuffer.cs +++ b/src/Windows/Avalonia.Win32/WindowFramebuffer.cs @@ -18,8 +18,7 @@ namespace Avalonia.Win32 { private readonly IntPtr _handle; private IntPtr _pBitmap; - UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; - + private UnmanagedMethods.BITMAPINFOHEADER _bmpInfo; public WindowFramebuffer(IntPtr handle, int width, int height) { @@ -38,7 +37,38 @@ namespace Avalonia.Win32 _pBitmap = Marshal.AllocHGlobal(width * height * 4); } + ~WindowFramebuffer() + { + Deallocate(); + } + + public IntPtr Address => _pBitmap; + public int RowBytes => Width * 4; + public PixelFormat Format => PixelFormat.Bgra8888; + + public Size Dpi + { + get + { + if (UnmanagedMethods.ShCoreAvailable) + { + uint dpix, dpiy; + + var monitor = UnmanagedMethods.MonitorFromWindow(_handle, + UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); + if (UnmanagedMethods.GetDpiForMonitor( + monitor, + UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, + out dpix, + out dpiy) == 0) + { + return new Size(dpix, dpiy); + } + } + return new Size(96, 96); + } + } public int Width => _bmpInfo.biWidth; @@ -73,11 +103,9 @@ namespace Avalonia.Win32 return true; } - - public void Dispose() { - //It's not an *actual* dispose. This call meand "We are done drawing" + //It's not an *actual* dispose. This call means "We are done drawing" DrawToWindow(_handle); } @@ -89,38 +117,5 @@ namespace Avalonia.Win32 _pBitmap = IntPtr.Zero; } } - - ~WindowFramebuffer() - { - Deallocate(); - } - - public IntPtr Address => _pBitmap; - public int RowBytes => Width * 4; - public PixelFormat Format => PixelFormat.Bgra8888; - - public Size Dpi - { - get - { - if (UnmanagedMethods.ShCoreAvailable) - { - uint dpix, dpiy; - - var monitor = UnmanagedMethods.MonitorFromWindow(_handle, - UnmanagedMethods.MONITOR.MONITOR_DEFAULTTONEAREST); - - if (UnmanagedMethods.GetDpiForMonitor( - monitor, - UnmanagedMethods.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, - out dpix, - out dpiy) == 0) - { - return new Size(dpix, dpiy); - } - } - return new Size(96, 96); - } - } } } \ No newline at end of file diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 19a7ca5e58..90bc684292 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -38,6 +38,7 @@ namespace Avalonia.Win32 private double _scaling = 1; private WindowState _showWindowState; private FramebufferManager _framebuffer; + public WindowImpl() { CreateWindow(); @@ -164,6 +165,11 @@ namespace Avalonia.Win32 } } + public IEnumerable Surfaces => new object[] + { + Handle, _framebuffer + }; + public void Activate() { UnmanagedMethods.SetActiveWindow(_hwnd); @@ -764,10 +770,5 @@ namespace Avalonia.Win32 ShowWindow(WindowState.Maximized); } } - - public IEnumerable Surfaces => new object[] - { - Handle, _framebuffer - }; } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index e9d095483b..1ef034b87c 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -149,6 +149,9 @@ namespace Avalonia.iOS } public Size MaxClientSize => Bounds.Size.ToAvalonia(); + + public IEnumerable Surfaces => new object[] { this }; + public void SetTitle(string title) { //Not supported @@ -232,8 +235,6 @@ namespace Avalonia.iOS public void SetIcon(IWindowIconImpl icon) { } - - public IEnumerable Surfaces => new object[]{this}; } class AvaloniaViewController : UIViewController From d4a967d0626a07c56b48e8b7dbba31ea2a0d52d6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Jan 2017 00:08:09 +0100 Subject: [PATCH 16/61] Missed a nit. --- src/Gtk/Avalonia.Gtk/WindowImplBase.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs index a8bd7836ec..c82d510c64 100644 --- a/src/Gtk/Avalonia.Gtk/WindowImplBase.cs +++ b/src/Gtk/Avalonia.Gtk/WindowImplBase.cs @@ -125,6 +125,13 @@ namespace Avalonia.Gtk public Action ScalingChanged { get; set; } + public IEnumerable Surfaces => new object[] + { + Handle, + new Func(() => CurrentDrawable), + _framebuffer + }; + public IPopupImpl CreatePopup() { return new PopupImpl(); @@ -316,12 +323,5 @@ namespace Avalonia.Gtk _window.Dispose(); _window = null; } - - public IEnumerable Surfaces => new object[] - { - Handle, - new Func(() => CurrentDrawable), - _framebuffer - }; } } From 714145b13d914c18319105b4bbac7bef98ba1fb0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 27 Jan 2017 00:58:24 +0100 Subject: [PATCH 17/61] Added SharpDX.Direct3D11 to nuspec. --- build.cake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.cake b/build.cake index e902c5d24e..a4be2dc9e0 100644 --- a/build.cake +++ b/build.cake @@ -197,6 +197,7 @@ var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault(). var SkiaSharpVersion = packageVersions["SkiaSharp"].FirstOrDefault().Item1; var SharpDXVersion = packageVersions["SharpDX"].FirstOrDefault().Item1; var SharpDXDirect2D1Version = packageVersions["SharpDX.Direct2D1"].FirstOrDefault().Item1; +var SharpDXDirect3D11Version = packageVersions["SharpDX.Direct3D11"].FirstOrDefault().Item1; var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1; Information("Package: Serilog, version: {0}", SerilogVersion); @@ -206,6 +207,7 @@ Information("Package: System.Reactive, version: {0}", SystemReactiveVersion); Information("Package: SkiaSharp, version: {0}", SkiaSharpVersion); Information("Package: SharpDX, version: {0}", SharpDXVersion); Information("Package: SharpDX.Direct2D1, version: {0}", SharpDXDirect2D1Version); +Information("Package: SharpDX.Direct3D11, version: {0}", SharpDXDirect3D11Version); Information("Package: SharpDX.DXGI, version: {0}", SharpDXDXGIVersion); var SetNuGetNuspecCommonProperties = new Action ((nuspec) => { @@ -441,6 +443,7 @@ var nuspecNuGetSettingsDesktop = new [] new NuSpecDependency() { Id = "Avalonia", Version = version }, new NuSpecDependency() { Id = "SharpDX", Version = SharpDXVersion }, new NuSpecDependency() { Id = "SharpDX.Direct2D1", Version = SharpDXDirect2D1Version }, + new NuSpecDependency() { Id = "SharpDX.Direct3D11", Version = SharpDXDirect3D11Version }, new NuSpecDependency() { Id = "SharpDX.DXGI", Version = SharpDXDXGIVersion } }, Files = new [] From 54721a6941679b55f2afa6a8bf3fa913e4cfb99a Mon Sep 17 00:00:00 2001 From: Ivan Kochurkin Date: Fri, 27 Jan 2017 22:30:43 +0300 Subject: [PATCH 18/61] TwoWay binding by default for IsExpanded property in Expander control. fixes #865. --- src/Avalonia.Controls/Expander.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Expander.cs b/src/Avalonia.Controls/Expander.cs index 87eb427f3c..e7f75336f5 100644 --- a/src/Avalonia.Controls/Expander.cs +++ b/src/Avalonia.Controls/Expander.cs @@ -31,7 +31,8 @@ namespace Avalonia.Controls AvaloniaProperty.RegisterDirect( nameof(IsExpanded), o => o.IsExpanded, - (o, v) => o.IsExpanded = v); + (o, v) => o.IsExpanded = v, + defaultBindingMode: Data.BindingMode.TwoWay); static Expander() { From 7ad30a564c1eefdc6caf2d988b356b0e91cc0e60 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 16:43:37 +0300 Subject: [PATCH 19/61] Cairo surface format seems to be always Rgba8888 for some reason --- src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index e955f82764..a2b06d0d5e 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -54,16 +54,8 @@ namespace Avalonia.Gtk3 //TODO: Proper DPI detect public Size Dpi => new Size(96, 96); - public PixelFormat Format - { - get - { - if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == - OperatingSystemType.WinNT) - return PixelFormat.Bgra8888; - return PixelFormat.Rgba8888; - } - } + + public PixelFormat Format => PixelFormat.Bgra8888; } } From 65686fc00293bac46cea236b7d79a4d980c734d1 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 18:17:30 +0300 Subject: [PATCH 20/61] Use ImageSurface instead of Pixbuf --- src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj | 4 +- src/Gtk/Avalonia.Gtk/FramebufferManager.cs | 6 +-- src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs | 58 ---------------------- src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs | 55 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs create mode 100644 src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj index 2c10b1188b..540e70cee0 100644 --- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj +++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj @@ -30,6 +30,8 @@ false + + @@ -50,7 +52,7 @@ - + diff --git a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs index 72c977b2b4..00a4727769 100644 --- a/src/Gtk/Avalonia.Gtk/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk/FramebufferManager.cs @@ -6,7 +6,7 @@ namespace Avalonia.Gtk class FramebufferManager : IFramebufferPlatformSurface, IDisposable { private readonly WindowImplBase _window; - private PixbufFramebuffer _fb; + private SurfaceFramebuffer _fb; public FramebufferManager(WindowImplBase window) { @@ -29,8 +29,8 @@ namespace Avalonia.Gtk if (_fb == null || _fb.Width != width || _fb.Height != height) { - _fb?.Dispose(); - _fb = new PixbufFramebuffer(width, height); + _fb?.Deallocate(); + _fb = new SurfaceFramebuffer(width, height); } _fb.SetDrawable(drawable); return _fb; diff --git a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs b/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs deleted file mode 100644 index 76e9e8a307..0000000000 --- a/src/Gtk/Avalonia.Gtk/PixbufFramebuffer.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Avalonia.Controls.Platform.Surfaces; -using Avalonia.Platform; -using Gdk; - -namespace Avalonia.Gtk -{ - class PixbufFramebuffer : ILockedFramebuffer - { - private Pixbuf _pixbuf; - private Drawable _drawable; - - public PixbufFramebuffer(int width, int height) - { - _pixbuf = new Pixbuf(Gdk.Colorspace.Rgb, false, 8, width, height); - } - - public void SetDrawable(Drawable drawable) - { - _drawable = drawable; - } - - public void Deallocate() - { - _pixbuf.Dispose(); - _pixbuf = null; - } - - public void Dispose() - { - using (var gc = new Gdk.GC(_drawable)) - _drawable.DrawPixbuf(gc, _pixbuf, 0, 0, 0, 0, Width, Height, RgbDither.None, 0, 0); - _drawable = null; - } - - public IntPtr Address => _pixbuf.Pixels; - public int Width => _pixbuf.Width; - public int Height => _pixbuf.Height; - public int RowBytes => _pixbuf.Rowstride; - //TODO: Proper DPI detect - public Size Dpi => new Size(96, 96); - public PixelFormat Format - { - get - { - if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().OperatingSystem == - OperatingSystemType.WinNT) - return PixelFormat.Bgra8888; - return PixelFormat.Rgba8888; - } - } - } -} - diff --git a/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs new file mode 100644 index 0000000000..7e6da0e76a --- /dev/null +++ b/src/Gtk/Avalonia.Gtk/SurfaceFramebuffer.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Platform; +using Cairo; +using Gdk; + +namespace Avalonia.Gtk +{ + class SurfaceFramebuffer : ILockedFramebuffer + { + private Drawable _drawable; + private ImageSurface _surface; + + public SurfaceFramebuffer(int width, int height) + { + _surface = new ImageSurface(Cairo.Format.RGB24, width, height); + } + + public void SetDrawable(Drawable drawable) + { + _drawable = drawable; + _surface.Flush(); + } + + public void Deallocate() + { + _surface.Dispose(); + _surface = null; + } + + public void Dispose() + { + using (var ctx = CairoHelper.Create(_drawable)) + { + _surface.MarkDirty(); + ctx.SetSourceSurface(_surface, 0, 0); + ctx.Paint(); + } + _drawable = null; + } + + public IntPtr Address => _surface.DataPtr; + public int Width => _surface.Width; + public int Height => _surface.Height; + public int RowBytes => _surface.Stride; + //TODO: Proper DPI detect + public Size Dpi => new Size(96, 96); + public PixelFormat Format => PixelFormat.Bgra8888; + } +} + From c446aaf5a6a364d26a32d9c92ba126d58c50ca41 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 28 Jan 2017 23:22:53 +0300 Subject: [PATCH 21/61] Removed IWindowImpl which was a lie, removed initialization hack, fixed rendering --- .../ControlCatalog.Android.csproj | 7 +- .../ControlCatalog.Android/MainActivity.cs | 24 ++---- .../Avalonia.Android/AndroidPlatform.cs | 25 +++--- .../AndroidThreadingInterface.cs | 12 ++- .../Avalonia.Android/Avalonia.Android.csproj | 7 +- .../Avalonia.Android/AvaloniaActivity.cs | 50 ++++++++++++ src/Android/Avalonia.Android/AvaloniaView.cs | 59 ++++++++++++++ .../Platform/SkiaPlatform/MainWindowImpl.cs | 44 ----------- .../{WindowImpl.cs => TopLevelImpl.cs} | 24 ++---- .../Platform/Specific/AvaloniaActivity.cs | 60 --------------- .../Helpers/AndroidKeyboardEventsHelper.cs | 2 +- .../Helpers/AndroidTouchEventsHelper.cs | 2 +- .../Platform/Specific/IAndroidActivity.cs | 12 --- .../Avalonia.AndroidTestApplication.csproj | 12 +-- .../MainActivity.cs | 32 ++------ .../Avalonia.Skia.Android.csproj | 1 + .../Avalonia.Skia.Android/NativeMethods.cs | 71 +++++++++++++++++ .../Avalonia.Skia.Android/RenderTarget.cs | 77 +++++++------------ src/Skia/Avalonia.Skia.Android/SkiaView.cs | 4 +- 19 files changed, 271 insertions(+), 254 deletions(-) create mode 100644 src/Android/Avalonia.Android/AvaloniaActivity.cs create mode 100644 src/Android/Avalonia.Android/AvaloniaView.cs delete mode 100644 src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs rename src/Android/Avalonia.Android/Platform/SkiaPlatform/{WindowImpl.cs => TopLevelImpl.cs} (86%) delete mode 100644 src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs delete mode 100644 src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs create mode 100644 src/Skia/Avalonia.Skia.Android/NativeMethods.cs diff --git a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj index 5b39aa3dfb..7dd09df73c 100644 --- a/samples/ControlCatalog.Android/ControlCatalog.Android.csproj +++ b/samples/ControlCatalog.Android/ControlCatalog.Android.csproj @@ -29,12 +29,15 @@ 4 True None - False + True False False - armeabi,armeabi-v7a,x86 + armeabi;armeabi-v7a;x86 Xamarin False + False + False + False pdbonly diff --git a/samples/ControlCatalog.Android/MainActivity.cs b/samples/ControlCatalog.Android/MainActivity.cs index 3f357b0e70..157609088f 100644 --- a/samples/ControlCatalog.Android/MainActivity.cs +++ b/samples/ControlCatalog.Android/MainActivity.cs @@ -1,9 +1,8 @@ using System; using Android.App; - using Android.OS; using Android.Content.PM; -using Avalonia.Android.Platform.Specific; +using Avalonia.Android; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; @@ -17,29 +16,16 @@ namespace ControlCatalog.Android [Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)] public class MainActivity : AvaloniaActivity { - public MainActivity() : base(typeof (App)) - { - - } - protected override void OnCreate(Bundle savedInstanceState) { - base.OnCreate(savedInstanceState); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else + if (Avalonia.Application.Current == null) { - app = new App(); - AppBuilder.Configure(app) + AppBuilder.Configure(new App()) .UseAndroid() - .UseSkia() .SetupWithoutStarting(); + Content = new MainView(); } - - var mainWindow = new MainWindow(); - app.Run(mainWindow); + base.OnCreate(savedInstanceState); } } } diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index afaa314e6c..5b3170a0c7 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -1,5 +1,8 @@ using System; using System.IO; +using System.Linq; +using Android.Content; +using Android.Views; using Avalonia.Android.Platform; using Avalonia.Android.Platform.Input; using Avalonia.Android.Platform.SkiaPlatform; @@ -8,6 +11,7 @@ using Avalonia.Controls.Platform; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Platform; +using Avalonia.Rendering; using Avalonia.Shared.PlatformSupport; using Avalonia.Skia; @@ -17,7 +21,8 @@ namespace Avalonia { public static T UseAndroid(this T builder) where T : AppBuilderBase, new() { - builder.UseWindowingSubsystem(Android.AndroidPlatform.Initialize, "Android"); + builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.Instance), "Android"); + builder.UseSkia(); return builder; } } @@ -25,7 +30,7 @@ namespace Avalonia namespace Avalonia.Android { - public class AndroidPlatform : IPlatformSettings, IWindowingPlatform + class AndroidPlatform : IPlatformSettings, IWindowingPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public Size DoubleClickSize => new Size(4, 4); @@ -40,7 +45,7 @@ namespace Avalonia.Android _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; } - public static void Initialize() + public static void Initialize(Avalonia.Application app) { AvaloniaLocator.CurrentMutable .Bind().ToTransient() @@ -51,24 +56,22 @@ namespace Avalonia.Android .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToTransient() .Bind().ToConstant(Instance) - .Bind().ToSingleton(); + .Bind().ToSingleton() + .Bind().ToConstant(new DefaultRenderLoop(60)) - SkiaPlatform.Initialize(); - } + .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); - public void Init(Type applicationType) - { - StandardRuntimePlatformServices.Register(applicationType.Assembly); + SkiaPlatform.Initialize(); } public IWindowImpl CreateWindow() { - return new WindowImpl(); + throw new NotSupportedException(); } public IEmbeddableWindowImpl CreateEmbeddableWindow() { - throw new NotImplementedException(); + throw new NotSupportedException(); } public IPopupImpl CreatePopup() diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs index b4059e3114..6327be12a5 100644 --- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs +++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs @@ -51,10 +51,16 @@ namespace Avalonia.Android scheduled = true; EnsureInvokeOnMainThread(() => { - tick(); - lock (l) + try { - scheduled = false; + tick(); + } + finally + { + lock (l) + { + scheduled = false; + } } }); } diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index 654cb13678..e84146ffb3 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -63,16 +63,15 @@ + + - - + - - diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs new file mode 100644 index 0000000000..a9b04c882b --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace Avalonia.Android +{ + public abstract class AvaloniaActivity : Activity + { + AvaloniaView _view; + object _content; + + protected override void OnCreate(Bundle savedInstanceState) + { + RequestWindowFeature(WindowFeatures.NoTitle); + _view = new AvaloniaView(this); + if(_content != null) + _view.Content = _content; + SetContentView(_view); + TakeKeyEvents(true); + base.OnCreate(savedInstanceState); + } + + public object Content + { + get + { + return _content; + } + set + { + _content = value; + if (_view != null) + _view.Content = value; + } + } + + public override bool DispatchKeyEvent(KeyEvent e) + { + return _view.DispatchKeyEvent(e); + } + } +} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs new file mode 100644 index 0000000000..71eea14a1d --- /dev/null +++ b/src/Android/Avalonia.Android/AvaloniaView.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; +using Avalonia.Android.Platform.SkiaPlatform; +using Avalonia.Controls.Embedding; +using Avalonia.Platform; + +namespace Avalonia.Android +{ + public class AvaloniaView : FrameLayout + { + private readonly EmbeddableControlRoot _root; + private readonly ViewImpl _view; + + public AvaloniaView(Context context) : base(context) + { + _view = new ViewImpl(context); + AddView(_view); + _root = new EmbeddableControlRoot(_view); + _root.Prepare(); + } + + public object Content + { + get { return _root.Content; } + set { _root.Content = value; } + } + + public override bool DispatchKeyEvent(KeyEvent e) + { + return _view.DispatchKeyEvent(e); + } + + class ViewImpl : TopLevelImpl, IEmbeddableWindowImpl + { + public event Action LostFocus; + + public ViewImpl(Context context) : base(context) + { + Focusable = true; + FocusChange += ViewImpl_FocusChange; + } + + private void ViewImpl_FocusChange(object sender, FocusChangeEventArgs e) + { + if(!e.HasFocus) + LostFocus?.Invoke(); + } + } + } +} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs deleted file mode 100644 index 690c509b53..0000000000 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/MainWindowImpl.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Android.Views; -using Avalonia.Android.Platform.Specific; -using Avalonia.Controls; -using Avalonia.Input; -using Avalonia.Platform; - -namespace Avalonia.Android.Platform.SkiaPlatform -{ - public class MainWindowImpl : - WindowImpl - , IWindowImpl - { - public MainWindowImpl() - { - } - - public new WindowState WindowState - { - get { return WindowState.Normal; } - set { } - } - - protected override void Init() - { - base.Init(); - - HandleEvents = true; - _keyboardHelper.ActivateAutoShowKeybord(); - } - - void ITopLevelImpl.Show() - { - (Parent as ViewGroup)?.RemoveAllViews(); - AvaloniaLocator.Current.GetService().ContentView = this; - //this.Visibility = ViewStates.Visible; - } - - void ITopLevelImpl.SetInputRoot(IInputRoot inputRoot) - { - base.SetInputRoot(inputRoot); - _keyboardHelper.UpdateKeyboardState(inputRoot); - } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs similarity index 86% rename from src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 17b1cfba0a..1687432bb3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/WindowImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -14,25 +14,21 @@ using Avalonia.Controls; namespace Avalonia.Android.Platform.SkiaPlatform { - public class WindowImpl : SkiaView, IAndroidView, IWindowImpl, ISurfaceHolderCallback + class TopLevelImpl : SkiaView, IAndroidView, ITopLevelImpl, ISurfaceHolderCallback { - protected AndroidKeyboardEventsHelper _keyboardHelper; + protected AndroidKeyboardEventsHelper _keyboardHelper; - private AndroidTouchEventsHelper _touchHelper; + private AndroidTouchEventsHelper _touchHelper; - public WindowImpl(Context context) : base((Activity)context) + public TopLevelImpl(Context context) : base(context) { - _keyboardHelper = new AndroidKeyboardEventsHelper(this); - _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); + _keyboardHelper = new AndroidKeyboardEventsHelper(this); + _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot, p => GetAvaloniaPointFromEvent(p)); MaxClientSize = new Size(Resources.DisplayMetrics.WidthPixels, Resources.DisplayMetrics.HeightPixels); ClientSize = MaxClientSize; - Init(); - } - - public WindowImpl() : this(AvaloniaLocator.Current.GetService().Activity) - { } + void ISurfaceHolderCallback.SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height) { @@ -46,11 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform base.SurfaceChanged(holder, format, width, height); } - - protected virtual void Init() - { - } - + private bool _handleEvents; public bool HandleEvents diff --git a/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs b/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs deleted file mode 100644 index 4ccff10455..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/AvaloniaActivity.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Android.App; -using Android.OS; -using Android.Views; -using Android.Widget; - -namespace Avalonia.Android.Platform.Specific -{ - public class AvaloniaActivity : Activity, IAndroidActivity - { - private IAndroidView _contentView; - - public AvaloniaActivity(Type applicationType) - { - AndroidPlatform.Instance.Init(applicationType); - } - - public Activity Activity => this; - - public IAndroidView ContentView - { - get - { - return this._contentView; - } - - set - { - this._contentView = value; - var fl = new FrameLayout(this); - fl.AddView(this._contentView.View); - //this.SetContentView(value.View); - this.SetContentView(fl); - } - } - - protected override void OnCreate(Bundle savedInstanceState) - { - AvaloniaLocator.CurrentMutable.Bind().ToConstant(this); - RequestWindowFeature(WindowFeatures.NoTitle); - base.OnCreate(savedInstanceState); - } - - public override void SetContentView(View view) - { - base.SetContentView(view); - TakeKeyEvents(true); - } - - public override bool DispatchKeyEvent(KeyEvent e) - { - if (_contentView != null) - { - _contentView.View.DispatchKeyEvent(e); - } - - return base.DispatchKeyEvent(e); - } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs index 17e1f62e8c..7bac0ff814 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs @@ -12,7 +12,7 @@ using System.ComponentModel; namespace Avalonia.Android.Platform.Specific.Helpers { - public class AndroidKeyboardEventsHelper : IDisposable where TView : View, IWindowImpl, IAndroidView + public class AndroidKeyboardEventsHelper : IDisposable where TView : View, ITopLevelImpl, IAndroidView { private TView _view; private IInputElement _lastFocusedElement; diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs index c6d1833d2d..a448044ee6 100644 --- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs +++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs @@ -8,7 +8,7 @@ using System; namespace Avalonia.Android.Platform.Specific.Helpers { - public class AndroidTouchEventsHelper : IDisposable where TView : View, IWindowImpl, IAndroidView + public class AndroidTouchEventsHelper : IDisposable where TView : View, ITopLevelImpl, IAndroidView { private TView _view; public bool HandleEvents { get; set; } diff --git a/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs b/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs deleted file mode 100644 index b2a999d4be..0000000000 --- a/src/Android/Avalonia.Android/Platform/Specific/IAndroidActivity.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Android.App; -using Android.Views; - -namespace Avalonia.Android.Platform.Specific -{ - public interface IAndroidActivity - { - Activity Activity { get; } - - IAndroidView ContentView { get; set; } - } -} \ No newline at end of file diff --git a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj index e004121323..ebe268adcf 100644 --- a/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj +++ b/src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj @@ -20,7 +20,7 @@ Properties\AndroidManifest.xml - true + True full false bin\Debug\ @@ -29,16 +29,16 @@ 4 True None - - False + True False False - armeabi,armeabi-v7a,x86 - - + armeabi;armeabi-v7a;x86 Xamarin False True + False + False + False pdbonly diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs index ff27e12f6e..7973ad72e5 100644 --- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs +++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs @@ -2,7 +2,7 @@ using System; using Android.App; using Android.Content.PM; using Android.OS; -using Avalonia.Android.Platform.Specific; +using Avalonia.Android; using Avalonia.Controls; using Avalonia.Controls.Templates; using Avalonia.Markup.Xaml; @@ -17,36 +17,24 @@ namespace Avalonia.AndroidTestApplication Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance/*, ScreenOrientation = ScreenOrientation.Landscape*/)] - public class MainBaseActivity : AvaloniaActivity + public class MainBaseActivity : Activity { - public MainBaseActivity() : base(typeof (App)) - { - - } - protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else + if (Avalonia.Application.Current == null) { - app = new App(); - AppBuilder.Configure(app) + AppBuilder.Configure() .UseAndroid() - .UseSkia() .SetupWithoutStarting(); } - - app.Run(); + SetContentView(new AvaloniaView(this) { Content = App.CreateSimpleWindow() }); } } public class App : Application { - public void Run() + public override void Initialize() { Styles.Add(new DefaultTheme()); @@ -55,18 +43,14 @@ namespace Avalonia.AndroidTestApplication new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default")); Styles.Add(baseLight); - var wnd = App.CreateSimpleWindow(); - wnd.AttachDevTools(); - Run(wnd); } // This provides a simple UI tree for testing input handling, drawing, etc - public static Window CreateSimpleWindow() + public static ContentControl CreateSimpleWindow() { - Window window = new Window + ContentControl window = new ContentControl() { - Title = "Avalonia Test Application", Background = Brushes.Red, Content = new StackPanel { diff --git a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj index fdde5553eb..35c7af454b 100644 --- a/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj +++ b/src/Skia/Avalonia.Skia.Android/Avalonia.Skia.Android.csproj @@ -89,6 +89,7 @@ + diff --git a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs b/src/Skia/Avalonia.Skia.Android/NativeMethods.cs new file mode 100644 index 0000000000..b9557c00c7 --- /dev/null +++ b/src/Skia/Avalonia.Skia.Android/NativeMethods.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; + +using Android.App; +using Android.Content; +using Android.OS; +using Android.Runtime; +using Android.Views; +using Android.Widget; + +namespace Avalonia.Skia.Android +{ + static class NativeMethods + { + [DllImport("android")] + internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle); + + [DllImport("android")] + internal static extern void ANativeWindow_release(IntPtr window); + [DllImport("android")] + internal static extern void ANativeWindow_unlockAndPost(IntPtr window); + + [DllImport("android")] + internal static extern int ANativeWindow_lock(IntPtr window, out ANativeWindow_Buffer outBuffer, ref ARect inOutDirtyBounds); + public enum AndroidPixelFormat + { + WINDOW_FORMAT_RGBA_8888 = 1, + WINDOW_FORMAT_RGBX_8888 = 2, + WINDOW_FORMAT_RGB_565 = 4, + } + + internal struct ARect + { + public int left; + public int top; + public int right; + public int bottom; + } + + + internal struct ANativeWindow_Buffer + { + // The number of pixels that are show horizontally. + public int width; + + // The number of pixels that are shown vertically. + public int height; + + // The number of *pixels* that a line in the buffer takes in + // memory. This may be >= width. + public int stride; + + // The format of the buffer. One of WINDOW_FORMAT_* + public AndroidPixelFormat format; + + // The actual bits. + public IntPtr bits; + + // Do not touch. + uint reserved1; + uint reserved2; + uint reserved3; + uint reserved4; + uint reserved5; + uint reserved6; + } + } +} \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs index 03ddf49851..2ee8d5a839 100644 --- a/src/Skia/Avalonia.Skia.Android/RenderTarget.cs +++ b/src/Skia/Avalonia.Skia.Android/RenderTarget.cs @@ -3,7 +3,9 @@ using Avalonia.Media; using Avalonia.Platform; using SkiaSharp; using Android.Graphics; +using Android.Runtime; using Android.Views; +using Avalonia.Skia.Android; namespace Avalonia.Skia { @@ -27,49 +29,38 @@ namespace Avalonia.Skia internal class WindowRenderTarget : RenderTarget { private readonly SurfaceView _surfaceView; - Bitmap _bitmap; - int Width { get; set; } - int Height { get; set; } + private IntPtr _window; public WindowRenderTarget(SurfaceView surfaceView) { _surfaceView = surfaceView; - FixSize(); } - private void FixSize() + private void PrepareForDraw() { - int width, height; - GetPlatformWindowSize(out width, out height); - if (Width == width && Height == height) - return; - - Width = width; - Height = height; - - if (Surface != null) - { - Surface.Dispose(); - } - - if (_bitmap != null) - { - _bitmap.Dispose(); - } - - _bitmap = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888); - Surface = SKSurface.Create(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul, _bitmap.LockPixels(), width * 4); - } - - private void GetPlatformWindowSize(out int w, out int h) - { - w = _surfaceView.Width; - h = _surfaceView.Height; + int width = _surfaceView.Width; + var height = _surfaceView.Height; + + _window = NativeMethods.ANativeWindow_fromSurface(JNIEnv.Handle, _surfaceView.Holder.Surface.Handle); + var buffer = new NativeMethods.ANativeWindow_Buffer(); + var rc = new NativeMethods.ARect() {right = width, bottom = height}; + NativeMethods.ANativeWindow_lock(_window, out buffer, ref rc); + + var colorType = buffer.format == NativeMethods.AndroidPixelFormat.WINDOW_FORMAT_RGB_565 + ? SKColorType.Rgb565 : SKImageInfo.PlatformColorType; + + var stride = buffer.stride * (colorType == SKColorType.Rgb565 ? 2 : 4); + + Surface = SKSurface.Create(buffer.width, buffer.height, colorType, + SKAlphaType.Premul, buffer.bits, stride); + + if (Surface == null) + throw new Exception("Unable to create Skia surface"); } public override DrawingContext CreateDrawingContext() { - FixSize(); + PrepareForDraw(); var canvas = Surface.Canvas; canvas.RestoreToCount(0); @@ -84,23 +75,11 @@ namespace Avalonia.Skia public void Present() { - Canvas canvas = null; - try - { - canvas = _surfaceView.Holder.LockCanvas(null); - _bitmap.UnlockPixels(); - canvas.DrawBitmap(_bitmap, 0, 0, null); - } - catch (Exception) - { - } - finally - { - if (canvas != null) - _surfaceView.Holder.UnlockCanvasAndPost(canvas); - } - - _bitmap.UnlockPixels(); + Surface?.Dispose(); + Surface = null; + NativeMethods.ANativeWindow_unlockAndPost(_window); + NativeMethods.ANativeWindow_release(_window); + _window = IntPtr.Zero; } } } diff --git a/src/Skia/Avalonia.Skia.Android/SkiaView.cs b/src/Skia/Avalonia.Skia.Android/SkiaView.cs index a45a80cf18..f69462f087 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaView.cs +++ b/src/Skia/Avalonia.Skia.Android/SkiaView.cs @@ -18,12 +18,12 @@ namespace Avalonia.Skia.Android { public abstract class SkiaView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle { - private readonly Activity _context; + private readonly Context _context; bool _invalidateQueued; readonly object _lock = new object(); private readonly Handler _handler; - public SkiaView(Activity context) : base(context) + public SkiaView(Context context) : base(context) { _context = context; Holder.AddCallback(this); From 87de5719253cff9f82701ac0b0f51e16acb516de Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 29 Jan 2017 00:43:22 +0300 Subject: [PATCH 22/61] Use IFramebufferSurface for android. --- Avalonia.sln | 39 +---- .../Avalonia.Android/Avalonia.Android.csproj | 2 + .../SkiaPlatform/AndroidFramebuffer.cs} | 56 +++++-- .../InvalidationAwareSurfaceView.cs} | 6 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 10 +- src/Skia/Avalonia.Skia.Android.TestApp/App.cs | 7 - .../Avalonia.Skia.Android.TestApp.csproj | 138 ------------------ ...nia.Skia.Android.TestApp.v2.ncrunchproject | 26 ---- .../MainActivity.cs | 78 ---------- .../Properties/AndroidManifest.xml | 5 - .../Properties/AssemblyInfo.cs | 30 ---- .../Resources/Resource.Designer.cs | 114 --------------- .../Resources/drawable/Icon.png | Bin 4147 -> 0 bytes .../Resources/layout/Main.axml | 13 -- .../Resources/values/Strings.xml | 5 - .../AndroidPlatformRenderInterface.cs | 9 +- .../AndroidRenderTarget.cs | 24 --- .../Avalonia.Skia.Android.csproj | 5 - .../Avalonia.Skia.Android/RenderTarget.cs | 85 ----------- .../Avalonia.Skia.Android/SkiaRenderView.cs | 39 ----- .../Avalonia.Skia.iOS.csproj | 1 + .../WindowDrawingContextImpl.cs | 0 .../Avalonia.Skia/Avalonia.Skia.projitems | 1 - 23 files changed, 63 insertions(+), 630 deletions(-) rename src/{Skia/Avalonia.Skia.Android/NativeMethods.cs => Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs} (52%) rename src/{Skia/Avalonia.Skia.Android/SkiaView.cs => Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs} (90%) delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/App.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml delete mode 100644 src/Skia/Avalonia.Skia.Android.TestApp/Resources/values/Strings.xml delete mode 100644 src/Skia/Avalonia.Skia.Android/AndroidRenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia.Android/RenderTarget.cs delete mode 100644 src/Skia/Avalonia.Skia.Android/SkiaRenderView.cs rename src/Skia/{Avalonia.Skia => Avalonia.Skia.iOS}/WindowDrawingContextImpl.cs (100%) diff --git a/Avalonia.sln b/Avalonia.sln index a6fdcbd421..d204e4116b 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.25420.1 +# Visual Studio 15 +VisualStudioVersion = 15.0.26014.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" EndProject @@ -99,8 +99,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Desktop", "sr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Android", "src\Skia\Avalonia.Skia.Android\Avalonia.Skia.Android.csproj", "{BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.Android.TestApp", "src\Skia\Avalonia.Skia.Android.TestApp\Avalonia.Skia.Android.TestApp.csproj", "{F92E55A5-ED73-4CCB-AB4B-0541B6757F31}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.iOS.TestApp", "src\Skia\Avalonia.Skia.iOS.TestApp\Avalonia.Skia.iOS.TestApp.csproj", "{DA49C5F3-BE95-461C-B999-072128CCF59E}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Skia.iOS", "src\Skia\Avalonia.Skia.iOS\Avalonia.Skia.iOS.csproj", "{47BE08A7-5985-410B-9FFC-2264B8EA595F}" @@ -1469,38 +1467,6 @@ Global {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|Mono.ActiveCfg = Release|Any CPU {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|x86.ActiveCfg = Release|Any CPU {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298}.Release|x86.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Ad-Hoc|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.AppStore|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Any CPU.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|Mono.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.ActiveCfg = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.Build.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Debug|x86.Deploy.0 = Debug|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Any CPU.Deploy.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|iPhone.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|Mono.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.ActiveCfg = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.Build.0 = Release|Any CPU - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31}.Release|x86.Deploy.0 = Release|Any CPU {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone {DA49C5F3-BE95-461C-B999-072128CCF59E}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone @@ -2359,7 +2325,6 @@ Global {2F59F3D0-748D-4652-B01E-E0D954756308} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {925DD807-B651-475F-9F7C-CBEB974CE43D} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {BD43F7C0-396B-4AA1-BAD9-DFDE54D51298} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {DA49C5F3-BE95-461C-B999-072128CCF59E} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {47BE08A7-5985-410B-9FFC-2264B8EA595F} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E} {7B92AF71-6287-4693-9DCB-BD5B6E927E23} = {7CF9789C-F1D3-4D0E-90E5-F1DF67A2753F} diff --git a/src/Android/Avalonia.Android/Avalonia.Android.csproj b/src/Android/Avalonia.Android/Avalonia.Android.csproj index e84146ffb3..0dfab3f518 100644 --- a/src/Android/Avalonia.Android/Avalonia.Android.csproj +++ b/src/Android/Avalonia.Android/Avalonia.Android.csproj @@ -70,6 +70,8 @@ + + diff --git a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs similarity index 52% rename from src/Skia/Avalonia.Skia.Android/NativeMethods.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs index b9557c00c7..982c79560b 100644 --- a/src/Skia/Avalonia.Skia.Android/NativeMethods.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/AndroidFramebuffer.cs @@ -1,23 +1,54 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; - -using Android.App; -using Android.Content; -using Android.OS; using Android.Runtime; using Android.Views; -using Android.Widget; +using Avalonia.Controls.Platform.Surfaces; -namespace Avalonia.Skia.Android +namespace Avalonia.Android.Platform.SkiaPlatform { - static class NativeMethods + class AndroidFramebuffer : ILockedFramebuffer { + private IntPtr _window; + + public AndroidFramebuffer(Surface surface) + { + _window = ANativeWindow_fromSurface(JNIEnv.Handle, surface.Handle); + ANativeWindow_Buffer buffer; + var rc = new ARect() + { + right = Width = ANativeWindow_getWidth(_window), + bottom = Height = ANativeWindow_getHeight(_window) + }; + ANativeWindow_lock(_window, out buffer, ref rc); + + Format = buffer.format == AndroidPixelFormat.WINDOW_FORMAT_RGB_565 + ? PixelFormat.Rgb565 : PixelFormat.Rgba8888; + + RowBytes = buffer.stride * (Format == PixelFormat.Rgb565 ? 2 : 4); + Address = buffer.bits; + } + + public void Dispose() + { + ANativeWindow_unlockAndPost(_window); + ANativeWindow_release(_window); + _window = IntPtr.Zero; + Address = IntPtr.Zero; + } + + public IntPtr Address { get; set; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Size Dpi { get; } = new Size(96, 96); + public PixelFormat Format { get; } + [DllImport("android")] internal static extern IntPtr ANativeWindow_fromSurface(IntPtr jniEnv, IntPtr handle); - + [DllImport("android")] + internal static extern int ANativeWindow_getWidth(IntPtr window); + [DllImport("android")] + internal static extern int ANativeWindow_getHeight(IntPtr window); [DllImport("android")] internal static extern void ANativeWindow_release(IntPtr window); [DllImport("android")] @@ -39,8 +70,7 @@ namespace Avalonia.Skia.Android public int right; public int bottom; } - - + internal struct ANativeWindow_Buffer { // The number of pixels that are show horizontally. diff --git a/src/Skia/Avalonia.Skia.Android/SkiaView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs similarity index 90% rename from src/Skia/Avalonia.Skia.Android/SkiaView.cs rename to src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs index f69462f087..2213ebddcc 100644 --- a/src/Skia/Avalonia.Skia.Android/SkiaView.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs @@ -14,16 +14,16 @@ using Android.Widget; using Avalonia.Media; using Avalonia.Platform; -namespace Avalonia.Skia.Android +namespace Avalonia.Android { - public abstract class SkiaView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle + public abstract class InvalidationAwareSurfaceView : SurfaceView, ISurfaceHolderCallback, IPlatformHandle { private readonly Context _context; bool _invalidateQueued; readonly object _lock = new object(); private readonly Handler _handler; - public SkiaView(Context context) : base(context) + public InvalidationAwareSurfaceView(Context context) : base(context) { _context = context; Holder.AddCallback(this); diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 1687432bb3..26d2b0aad9 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -7,14 +7,16 @@ using Avalonia.Android.Platform.Specific.Helpers; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Platform; -using Avalonia.Skia.Android; using System; using System.Collections.Generic; using Avalonia.Controls; +using Avalonia.Controls.Platform.Surfaces; namespace Avalonia.Android.Platform.SkiaPlatform { - class TopLevelImpl : SkiaView, IAndroidView, ITopLevelImpl, ISurfaceHolderCallback + class TopLevelImpl : InvalidationAwareSurfaceView, IAndroidView, ITopLevelImpl, + ISurfaceHolderCallback, IFramebufferPlatformSurface + { protected AndroidKeyboardEventsHelper _keyboardHelper; @@ -183,5 +185,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform { // No window icons for mobile platforms } - } + + ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(Holder.Surface); + } } \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/App.cs b/src/Skia/Avalonia.Skia.Android.TestApp/App.cs deleted file mode 100644 index ca4d36b820..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/App.cs +++ /dev/null @@ -1,7 +0,0 @@ - -namespace Avalonia.Skia.Android.TestApp -{ - public class App : Application - { - } -} diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj b/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj deleted file mode 100644 index 041c8d9ecb..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.csproj +++ /dev/null @@ -1,138 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {F92E55A5-ED73-4CCB-AB4B-0541B6757F31} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Library - Properties - Avalonia.Skia.Android.TestApp - Avalonia.Skia.Android.TestApp - 512 - true - Resources\Resource.Designer.cs - Off - False - v4.4 - Properties\AndroidManifest.xml - - - True - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - True - None - False - False - False - armeabi;armeabi-v7a;x86 - Xamarin - False - True - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - False - Full - True - False - False - Xamarin - False - False - False - False - False - armeabi;armeabi-v7a;x86 - - - - - - - - - - - - - - - - - - - - - - - - {7b92af71-6287-4693-9dcb-bd5b6e927e23} - Avalonia.Android - - - {d211e587-d8bc-45b9-95a4-f297c8fa5200} - Avalonia.Animation - - - {b09b78d8-9b26-48b0-9149-d64a2f120f3f} - Avalonia.Base - - - {d2221c82-4a25-4583-9b43-d791e3f6820c} - Avalonia.Controls - - - {4a1abb09-9047-4bd5-a4ad-a055e52c5ee0} - Avalonia.DotNetFrameworkRuntime - - - {62024b2d-53eb-4638-b26b-85eeaa54866e} - Avalonia.Input - - - {6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b} - Avalonia.Interactivity - - - {42472427-4774-4c81-8aff-9f27b8e31721} - Avalonia.Layout - - - {eb582467-6abb-43a1-b052-e981ba910e3a} - Avalonia.Visuals - - - {f1baa01a-f176-4c6a-b39d-5b40bb1b148f} - Avalonia.Styling - - - {bd43f7c0-396b-4aa1-bad9-dfde54d51298} - Avalonia.Skia.Android - - - - - - - - - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject b/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject deleted file mode 100644 index e1b4d7cf28..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Avalonia.Skia.Android.TestApp.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - true - false - false - false - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs b/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs deleted file mode 100644 index 9ac833a559..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/MainActivity.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Android.App; -using Android.OS; -using Android.Views; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Media; - -namespace Avalonia.Skia.Android.TestApp -{ - [Activity(Label = "Avalonia.Skia.Android.TestApp", MainLauncher = true, Icon = "@drawable/icon")] - public class MainActivity : Activity - { - - protected override void OnCreate(Bundle bundle) - { - base.OnCreate(bundle); - - App app; - if (Avalonia.Application.Current != null) - app = (App)Avalonia.Application.Current; - else - { - app = new App(); - AppBuilder.Configure(app) - .UseAndroid() - .UseSkia() - .SetupWithoutStarting(); - } - - SetContentView(new MainView(this)); - } - - class MainView : SkiaRenderView - { - float _radians = 0; - public MainView(Activity context) : base(context) - { - } - - protected override void OnRender(DrawingContext ctx) - { - ctx.FillRectangle(Brushes.Green, new Rect(0, 0, Width, Height)); - - var rc = new Rect(0, 0, Width/3, Height/3); - using (ctx.PushPostTransform( - Avalonia.Matrix.CreateTranslation(-Width/6, -Width/6)* - Avalonia.Matrix.CreateRotation(_radians)* - Avalonia.Matrix.CreateTranslation(Width/2, Height/2))) - { - ctx.FillRectangle(new LinearGradientBrush() - { - GradientStops = - { - new GradientStop() {Color = Colors.Blue}, - new GradientStop(Colors.Red, 1) - } - }, rc, 5); - } - - - } - - public override bool OnTouchEvent(MotionEvent e) - { - if (e.Action == MotionEventActions.Down) - return true; - if (e.Action == MotionEventActions.Move) - { - _radians = (e.RawY + e.RawY)/100; - Invalidate(); - return true; - } - return base.OnTouchEvent(e); - } - } - } -} - diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml b/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml deleted file mode 100644 index 5af8811830..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AndroidManifest.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs b/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs deleted file mode 100644 index 68efca2668..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using Android.App; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Avalonia.Skia.Android.TestApp")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Avalonia.Skia.Android.TestApp")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs deleted file mode 100644 index 7d9036603f..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/Resource.Designer.cs +++ /dev/null @@ -1,114 +0,0 @@ -#pragma warning disable 1591 -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.Skia.Android.TestApp.Resource", IsApplication=true)] - -namespace Avalonia.Skia.Android.TestApp -{ - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")] - public partial class Resource - { - - static Resource() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - public static void UpdateIdValues() - { - global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.Skia.Android.TestApp.Resource.String.ApplicationName; - global::Avalonia.Android.Resource.String.Hello = global::Avalonia.Skia.Android.TestApp.Resource.String.Hello; - } - - public partial class Attribute - { - - static Attribute() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Attribute() - { - } - } - - public partial class Drawable - { - - // aapt resource value: 0x7f020000 - public const int Icon = 2130837504; - - static Drawable() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Drawable() - { - } - } - - public partial class Id - { - - // aapt resource value: 0x7f050000 - public const int MyButton = 2131034112; - - static Id() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Id() - { - } - } - - public partial class Layout - { - - // aapt resource value: 0x7f030000 - public const int Main = 2130903040; - - static Layout() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private Layout() - { - } - } - - public partial class String - { - - // aapt resource value: 0x7f040001 - public const int ApplicationName = 2130968577; - - // aapt resource value: 0x7f040000 - public const int Hello = 2130968576; - - static String() - { - global::Android.Runtime.ResourceIdManager.UpdateIdValues(); - } - - private String() - { - } - } - } -} -#pragma warning restore 1591 diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/drawable/Icon.png deleted file mode 100644 index 8074c4c571b8cd19e27f4ee5545df367420686d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4147 zcmV-35X|q1P)OwvMs$Q8_8nISM!^>PxsujeDCl4&hPxrxkp%Qc^^|l zp6LqAcf3zf1H4aA1Gv-O6ha)ktct9Y+VA@N^9i;p0H%6v>ZJZYQ`zEa396z-gi{r_ zDz)D=vgRv62GCVeRjK{15j7V@v6|2nafFX6W7z2j1_T0a zLyT3pGTubf1lB5)32>bl0*BflrA!$|_(WD2)iJIfV}37=ZKAC zSe3boYtQ=;o0i>)RtBvsI#iT{0!oF1VFeW`jDjF2Q4aE?{pGCAd>o8Kg#neIh*AMY zLl{;F!vLiem7s*x0<9FKAd6LoPz3~G32P+F+cuGOJ5gcC@pU_?C2fmix7g2)SUaQO$NS07~H)#fn!Q<}KQWtX}wW`g2>cMld+`7Rxgq zChaey66SG560JhO66zA!;sK1cWa2AG$9k~VQY??6bOmJsw9@3uL*z;WWa7(Nm{^TA zilc?y#N9O3LcTo2c)6d}SQl-v-pE4^#wb=s(RxaE28f3FQW(yp$ulG9{KcQ7r>7mQ zE!HYxUYex~*7IinL+l*>HR*UaD;HkQhkL(5I@UwN%Wz504M^d!ylo>ANvKPF_TvA< zkugG5;F6x}$s~J8cnev->_(Ic7%lGQgUi3n#XVo36lUpcS9s z)ympRr7}@|6WF)Ae;D{owN1;aZSR50al9h~?-WhbtKK%bDd zhML131oi1Bu1&Qb$Cp199LJ#;j5d|FhW8_i4KO1OI>}J^p2DfreMSVGY9aFlr&90t zyI2FvxQiKMFviSQeP$Ixh#70qj5O%I+O_I2t2XHWqmh2!1~tHpN3kA4n=1iHj?`@c<~3q^X6_Q$AqTDjBU`|!y<&lkqL|m5tG(b z8a!z&j^m(|;?SW(l*?tZ*{m2H9d&3jqBtXh>O-5e4Qp-W*a5=2NL&Oi62BUM)>zE3 zbSHb>aU3d@3cGggA`C-PsT9^)oy}%dHCaO~nwOrm5E54=aDg(&HR4S23Oa#-a^=}w%g?ZP-1iq8PSjE8jYaGZu z$I)?YN8he?F9>)2d$G6a*zm0XB*Rf&gZAjq(8l@CUDSY1tB#!i> zW$VfG%#SYSiZ};)>pHA`qlfDTEYQEwN6>NNEp+uxuqx({Fgr zjI@!4xRc?vk^9+~eU|mzH__dCDI=xb{Cd}4bELS9xRaS!*FXMwtMR-RR%SLMh0Cjl zencr8#Su<4(%}$yGVBU-HX{18v=yPH*+%^Vtknc>2A;%-~DrYFx^3XfuVgvZ{#1tA== zm3>IzAM2{3Iv_d1XG{P6^tN3|PkJMnjs&CWN7%7_CmjoVakUhsa&dMv==2~^ri?&x zVdv*rnfVyM+I1^Kg*S=23mR@+0T9BWFZUu~@toA8d)fw6be=`Yb6DSX6D?jB%2YT~ z*aHjtIOozfMhA!Jd*?u5_n!SnX>vX`=Ti-1HA4RiE>eI3vTn zz+>Ccf0HX6Ans-ebOB>RJST-Cyr#4XAk+mAlJgdQnoE{^iIN)OcYFSpgJUmXtl@tT z-^ZuUeSj5hSFrQwqX>~EtZ*{>Gi8Bu9_|o06oNtaXP?E936!a@DsvS*tsB@fa6kEA z5GkjwmH?EgpiG&itsB_Tb1NxtFnvxh_s@9KYX1Sttf?AlI~)z zT=6Y7ulx=}<8Scr_UqU-_z)5gPo%050PsbM*ZLno;_-ow&k?FZJtYmb2hPA$LkP)8 z=^d0Q6PImh6Y|QT?{grxj)S=uBKvY2EQUbm@ns9^yKiP~$DcD)c$5Em`zDSScH%iH zVov&m=cMo`1tYwA=!a}vb_ef_{)Q2?FUqn>BR$6phXQRv^1%=YfyE-F$AR4Q?9D!f zCzB^^#td~4u&l~l#rp2QLfe3+_ub9@+|x+m;=2(sQ`s%gO|j$XBb>A7Q(UydipiMw%igcweV#Cr~SP);q>w`bxts_4} znKHg?X==JDkQl3Y>Ckt%`s{n?Nq-1Fw5~%Mq$CAsi-`yu_bKm zxs#QdE7&vgJD%M84f4SNzSDv)S|V?|$!d5a#lhT5>>YWE4NGqa9-fbmV$=)@k&32kdEYetna>=j@0>V8+wRsL;po!3ivVwh<9tn z2S<1u9DAAQ>x1Sn=fk`)At|quvleV($B|#Kap_lB-F^*yV=wZ{9baUu(uXfokr95^ zA*!*W=5a>$2Ps`-F^+qRQT^{*cN>vipT*4!r#p%{(#I7s z0NN94*q?ib$KJjfDI_sjHNdmEVp5wB&j54O#VoFqBwy)gfA$%)4d_X4q${L9Xom2R3xy&ZBSNgt4a1d7K^CDWa9r zVb-_52m}Vp)`9;ZSKd#|U4ZYj5}Gp49{4utST|=c`~(#>KHF6}CCov1iHYw zt{bWo)A@yF2$~c(nR$rSAaFQ$(Wh{vkG1AlutDMw=mM`C`T=X&|Ad9fb5Od}ROt1z zOpczHqrb4Jo^rSCiW#&o(m7jFamnrsTpQb;*h4o8r#$aZ}2RaT-x2u^^ z%u@YyIv$U^u~@9(XGbSwU@fk6SikH>j+D1jQrYTKGJpW%vUT{!d}7THI5&Sa?~MKy zS0-mvMl+BOcroEJ@hN!2H_?coTEJ5Q<;Nd?yx;eIj4{$$E2?YUO|NtNPJ-PdDf;s} zab;}Mz0kbOI}5*w@3gROcnl#5)wQnEhDBfn!Xhy`u>C}*E~vWpO^HS)FC>8^umI=+ z&H;LW6w#;EF`}vQd_9Muru`KnQVPI9U?(sD)&Dg-0j3#(!fNKVZ_GoYH{la~d*1Yh$TI-TL>mI4vpNb@sU2=IZ8vL%AXUx0 zz{K0|nK(yizLHaeW#ZhRfQXoK^}1$=$#1{Yn002ovPDHLkV1n#w+^+xt diff --git a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml b/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml deleted file mode 100644 index 98be1643ef..0000000000 --- a/src/Skia/Avalonia.Skia.Android.TestApp/Resources/layout/Main.axml +++ /dev/null @@ -1,13 +0,0 @@ - - -