diff --git a/src/Avalonia.Controls/MenuItem.cs b/src/Avalonia.Controls/MenuItem.cs index 926c240e57..dadd3b910b 100644 --- a/src/Avalonia.Controls/MenuItem.cs +++ b/src/Avalonia.Controls/MenuItem.cs @@ -307,6 +307,21 @@ namespace Avalonia.Controls () => IsSubMenuOpen = true, TimeSpan.FromMilliseconds(400)); } + else + { + var parentItem = Parent as MenuItem; + if (parentItem != null) + { + foreach (var sibling in parentItem.Items + .OfType() + .Where(x => x != this && x.IsSubMenuOpen)) + { + sibling.CloseSubmenus(); + sibling.IsSubMenuOpen = false; + sibling.IsSelected = false; + } + } + } } /// diff --git a/src/Avalonia.Controls/StackPanel.cs b/src/Avalonia.Controls/StackPanel.cs index 0e12fb3283..a6fe35d668 100644 --- a/src/Avalonia.Controls/StackPanel.cs +++ b/src/Avalonia.Controls/StackPanel.cs @@ -170,6 +170,15 @@ namespace Avalonia.Controls } } + if (Orientation == Orientation.Vertical) + { + measuredHeight -= gap; + } + else + { + measuredWidth -= gap; + } + return new Size(measuredWidth, measuredHeight); } diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs index 5d1b9a1462..079e571d29 100644 --- a/src/Avalonia.Controls/TreeView.cs +++ b/src/Avalonia.Controls/TreeView.cs @@ -253,9 +253,7 @@ namespace Avalonia.Controls if (AutoScrollToSelectedItem) { - DispatcherTimer.RunOnce( - container.ContainerControl.BringIntoView, - TimeSpan.Zero); + Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView); } break; diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs index 59a895a22f..f56e7448a7 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Avalonia.Media; -using Avalonia.Platform; using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph @@ -12,20 +11,16 @@ namespace Avalonia.Rendering.SceneGraph /// /// Base class for draw operations that can use a brush. /// - internal abstract class BrushDrawOperation : IDrawOperation + internal abstract class BrushDrawOperation : DrawOperation { - /// - public abstract Rect Bounds { get; } - - /// - public abstract bool HitTest(Point p); + public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen) + : base(bounds, transform, pen) + { + } /// /// Gets a collection of child scenes that are needed to draw visual brushes. /// public abstract IDictionary ChildScenes { get; } - - /// - public abstract void Render(IDrawingContextImpl context); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs new file mode 100644 index 0000000000..4c6ed189ff --- /dev/null +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; + +namespace Avalonia.Rendering.SceneGraph +{ + /// + /// Base class for draw operations that have bounds. + /// + internal abstract class DrawOperation : IDrawOperation + { + public DrawOperation(Rect bounds, Matrix transform, Pen pen) + { + bounds = bounds.Inflate((pen?.Thickness ?? 0) / 2).TransformToAABB(transform); + Bounds = new Rect( + new Point(Math.Floor(bounds.X), Math.Floor(bounds.Y)), + new Point(Math.Ceiling(bounds.Right), Math.Ceiling(bounds.Bottom))); + } + + public Rect Bounds { get; } + + public abstract bool HitTest(Point p); + + public abstract void Render(IDrawingContextImpl context); + } +} diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs index b884c42d99..6310122183 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Pen pen, IGeometryImpl geometry, IDictionary childScenes = null) + : base(geometry.GetRenderBounds(pen?.Thickness ?? 0), transform, null) { - Bounds = geometry.GetRenderBounds(pen?.Thickness ?? 0).TransformToAABB(transform); Transform = transform; Brush = brush?.ToImmutable(); Pen = pen?.ToImmutable(); @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs index 8c3bb72463..839fd9b0e5 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph public interface IDrawOperation { /// - /// Gets the bounds of the visible content in the node. + /// Gets the bounds of the visible content in the node in global coordinates. /// Rect Bounds { get; } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs index 4a50f12095..8291d1c0bb 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs @@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph /// /// A node in the scene graph which represents an image draw. /// - internal class ImageNode : IDrawOperation + internal class ImageNode : DrawOperation { /// /// Initializes a new instance of the class. @@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph /// The source rect. /// The destination rect. public ImageNode(Matrix transform, IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect) + : base(destRect, transform, null) { - Bounds = destRect.TransformToAABB(transform); Transform = transform; Source = source; Opacity = opacity; @@ -29,9 +29,6 @@ namespace Avalonia.Rendering.SceneGraph DestRect = destRect; } - /// - public Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// @@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph } /// - public void Render(IDrawingContextImpl context) + public override void Render(IDrawingContextImpl context) { // TODO: Probably need to introduce some kind of locking mechanism in the case of // WriteableBitmap. @@ -89,6 +86,6 @@ namespace Avalonia.Rendering.SceneGraph } /// - public bool HitTest(Point p) => Bounds.Contains(p); + public override bool HitTest(Point p) => Bounds.Contains(p); } } diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs index e39335b5b6..d3df478a63 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point p1, Point p2, IDictionary childScenes = null) + : base(new Rect(p1, p2), transform, pen) { - Bounds = new Rect(p1, p2).TransformToAABB(transform).Inflate(pen?.Thickness ?? 0); Transform = transform; Pen = pen?.ToImmutable(); P1 = p1; @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs index c40869724f..28b8f53e26 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs @@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph /// The bounds of the mask. /// Child scenes for drawing visual brushes. public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary childScenes = null) + : base(Rect.Empty, Matrix.Identity, null) { Mask = mask?.ToImmutable(); MaskBounds = bounds; @@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph /// opacity mask pop. /// public OpacityMaskNode() + : base(Rect.Empty, Matrix.Identity, null) { } - /// - public override Rect Bounds => Rect.Empty; - /// /// Gets the mask to be pushed or null if the operation represents a pop. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs index 2affc454b5..1730621c55 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs @@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph Rect rect, float cornerRadius, IDictionary childScenes = null) + : base(rect, transform, pen) { - Bounds = rect.TransformToAABB(transform).Inflate(pen?.Thickness ?? 0); Transform = transform; Brush = brush?.ToImmutable(); Pen = pen?.ToImmutable(); @@ -40,9 +40,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs index 058f3b1c22..6328d7dd14 100644 --- a/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs +++ b/src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs @@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph Point origin, IFormattedTextImpl text, IDictionary childScenes = null) + : base(new Rect(origin, text.Size), transform, null) { - Bounds = new Rect(origin, text.Size).TransformToAABB(transform); Transform = transform; Foreground = foreground?.ToImmutable(); Origin = origin; @@ -37,9 +37,6 @@ namespace Avalonia.Rendering.SceneGraph ChildScenes = childScenes; } - /// - public override Rect Bounds { get; } - /// /// Gets the transform with which the node will be drawn. /// diff --git a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs index b0cc668569..455b63b89e 100644 --- a/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs +++ b/src/Gtk/Avalonia.Gtk3/FramebufferManager.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading.Tasks; using Avalonia.Controls.Platform.Surfaces; using Avalonia.Platform; +using Avalonia.Threading; namespace Avalonia.Gtk3 { @@ -27,7 +28,38 @@ namespace Avalonia.Gtk3 var s = _window.ClientSize; var width = (int) s.Width; var height = (int) s.Height; + + if (!Dispatcher.UIThread.CheckAccess() && Gtk3Platform.DisplayClassName.ToLower().Contains("x11")) + { + var x11 = LockX11Framebuffer(width, height); + if (x11 != null) + return x11; + } + + return new ImageSurfaceFramebuffer(_window, width, height, _window.LastKnownScaleFactor); } + + private static int X11ErrorHandler(IntPtr d, IntPtr e) + { + return 0; + } + + private static X11.XErrorHandler X11ErrorHandlerDelegate = X11ErrorHandler; + + private static IntPtr X11Display; + private ILockedFramebuffer LockX11Framebuffer(int width, int height) + { + if (!_window.GdkWindowHandle.HasValue) + return null; + if (X11Display == IntPtr.Zero) + { + X11Display = X11.XOpenDisplay(IntPtr.Zero); + if (X11Display == IntPtr.Zero) + return null; + X11.XSetErrorHandler(X11ErrorHandlerDelegate); + } + return new X11Framebuffer(X11Display, _window.GdkWindowHandle.Value, width, height, _window.LastKnownScaleFactor); + } } } diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index b36a1cda91..10405037ab 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -22,11 +23,15 @@ namespace Avalonia.Gtk3 internal static readonly MouseDevice Mouse = new MouseDevice(); internal static readonly KeyboardDevice Keyboard = new KeyboardDevice(); internal static IntPtr App { get; set; } + internal static string DisplayClassName; public static bool UseDeferredRendering = true; public static void Initialize() { Resolver.Resolve(); Native.GtkInit(0, IntPtr.Zero); + var disp = Native.GdkGetDefaultDisplay(); + DisplayClassName = Utf8Buffer.StringFromPtr(Native.GTypeName(Marshal.ReadIntPtr(Marshal.ReadIntPtr(disp)))); + using (var utf = new Utf8Buffer("avalonia.app." + Guid.NewGuid())) App = Native.GtkApplicationNew(utf, 0); //Mark current thread as UI thread diff --git a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs index 8c1456726c..e16463a2ef 100644 --- a/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs +++ b/src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs @@ -4,6 +4,6 @@ namespace Avalonia.Gtk3 { public interface IDeferredRenderOperation : IDisposable { - void RenderNow(); + void RenderNow(IntPtr? ctx); } } \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs index 4ebb080dd0..efda21c753 100644 --- a/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs +++ b/src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs @@ -88,10 +88,10 @@ namespace Avalonia.Gtk3 private readonly int _width; private readonly int _height; - public RenderOp(GtkWidget widget, ManagedCairoSurface _surface, double factor, int width, int height) + public RenderOp(GtkWidget widget, ManagedCairoSurface surface, double factor, int width, int height) { _widget = widget; - this._surface = _surface; + _surface = surface ?? throw new ArgumentNullException(); _factor = factor; _width = width; _height = height; @@ -103,9 +103,12 @@ namespace Avalonia.Gtk3 _surface = null; } - public void RenderNow() + public void RenderNow(IntPtr? ctx) { - DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); + if(ctx.HasValue) + Draw(ctx.Value, _surface.Surface, _factor); + else + DrawToWidget(_widget, _surface.Surface, _width, _height, _factor); } } diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs index 433094e0ae..15b3a11fbb 100644 --- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs +++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs @@ -318,6 +318,9 @@ namespace Avalonia.Gtk3.Interop [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate void g_object_ref(GObject instance); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] + public delegate IntPtr g_type_name(IntPtr instance); + [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gobject)] public delegate ulong g_signal_connect_object(GObject instance, Utf8Buffer signal, IntPtr handler, IntPtr userData, int flags); @@ -410,6 +413,7 @@ namespace Avalonia.Gtk3.Interop public static D.gtk_dialog_add_button GtkDialogAddButton; public static D.g_object_unref GObjectUnref; public static D.g_object_ref GObjectRef; + public static D.g_type_name GTypeName; public static D.g_signal_connect_object GSignalConnectObject; public static D.g_signal_handler_disconnect GSignalHandlerDisconnect; public static D.g_timeout_add GTimeoutAdd; diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 8771106895..e14ed77877 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs @@ -31,7 +31,7 @@ namespace Avalonia.Gtk3 private object _lock = new object(); private IDeferredRenderOperation _nextRenderOperation; private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true); - + internal IntPtr? GdkWindowHandle; public WindowBaseImpl(GtkWindow gtkWidget) { @@ -55,6 +55,7 @@ namespace Avalonia.Gtk3 ConnectEvent("leave-notify-event", OnLeaveNotifyEvent); Connect("destroy", OnDestroy); Native.GtkWidgetRealize(gtkWidget); + GdkWindowHandle = this.Handle.Handle; _lastSize = ClientSize; if (Gtk3Platform.UseDeferredRendering) { @@ -286,7 +287,7 @@ namespace Avalonia.Gtk3 } if (op != null) { - op?.RenderNow(); + op?.RenderNow(null); op?.Dispose(); } } diff --git a/src/Gtk/Avalonia.Gtk3/X11.cs b/src/Gtk/Avalonia.Gtk3/X11.cs new file mode 100644 index 0000000000..6708ece17b --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/X11.cs @@ -0,0 +1,54 @@ +using System; +using System.Runtime.InteropServices; + +namespace Avalonia.Gtk3 +{ + class X11 + { + [DllImport("libX11.so.6")] + public static extern IntPtr XOpenDisplay(IntPtr name); + + [DllImport("libX11.so.6")] + public static extern IntPtr XFreeGC(IntPtr display, IntPtr gc); + + [DllImport("libX11.so.6")] + public static extern IntPtr XCreateGC(IntPtr display, IntPtr drawable, ulong valuemask, IntPtr values); + + [DllImport("libX11.so.6")] + public static extern int XInitImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern int XDestroyImage(ref XImage image); + + [DllImport("libX11.so.6")] + public static extern IntPtr XSetErrorHandler(XErrorHandler handler); + + public delegate int XErrorHandler(IntPtr display, IntPtr error); + + [DllImport("libX11.so.6")] + public static extern int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, ref XImage image, + int srcx, int srcy, int destx, int desty, uint width, uint height); + + + public unsafe struct XImage + { + public int width, height; /* size of image */ + public int xoffset; /* number of pixels offset in X direction */ + public int format; /* XYBitmap, XYPixmap, ZPixmap */ + public IntPtr data; /* pointer to image data */ + public int byte_order; /* data byte order, LSBFirst, MSBFirst */ + public int bitmap_unit; /* quant. of scanline 8, 16, 32 */ + public int bitmap_bit_order; /* LSBFirst, MSBFirst */ + public int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */ + public int depth; /* depth of image */ + public int bytes_per_line; /* accelerator to next scanline */ + public int bits_per_pixel; /* bits per pixel (ZPixmap) */ + public ulong red_mask; /* bits in z arrangement */ + public ulong green_mask; + public ulong blue_mask; + private fixed byte funcs[128]; + } + + + } +} \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs new file mode 100644 index 0000000000..2bf08bddf1 --- /dev/null +++ b/src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; +using Avalonia.Platform; + +namespace Avalonia.Gtk3 +{ + class X11Framebuffer : ILockedFramebuffer + { + private readonly IntPtr _display; + private readonly IntPtr _xid; + private IUnmanagedBlob _blob; + + public X11Framebuffer(IntPtr display, IntPtr xid, int width, int height, int factor) + { + _display = display; + _xid = xid; + Width = width*factor; + Height = height*factor; + RowBytes = Width * 4; + Dpi = new Vector(96, 96) * factor; + Format = PixelFormat.Bgra8888; + _blob = AvaloniaLocator.Current.GetService().AllocBlob(RowBytes * Height); + Address = _blob.Address; + } + + public void Dispose() + { + var image = new X11.XImage(); + int bitsPerPixel = 32; + image.width = Width; + image.height = Height; + image.format = 2; //ZPixmap; + image.data = Address; + image.byte_order = 0;// LSBFirst; + image.bitmap_unit = bitsPerPixel; + image.bitmap_bit_order = 0;// LSBFirst; + image.bitmap_pad = bitsPerPixel; + image.depth = 24; + image.bytes_per_line = RowBytes - Width * 4; + image.bits_per_pixel = bitsPerPixel; + X11.XInitImage(ref image); + var gc = X11.XCreateGC(_display, _xid, 0, IntPtr.Zero); + X11.XPutImage(_display, _xid, gc, ref image, 0, 0, 0, 0, (uint) Width, (uint) Height); + X11.XFreeGC(_display, gc); + _blob.Dispose(); + } + + public IntPtr Address { get; } + public int Width { get; } + public int Height { get; } + public int RowBytes { get; } + public Vector Dpi { get; } + public PixelFormat Format { get; } + } +} \ No newline at end of file diff --git a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs index fafa17a810..b777736f06 100644 --- a/src/Shared/PlatformSupport/StandardRuntimePlatform.cs +++ b/src/Shared/PlatformSupport/StandardRuntimePlatform.cs @@ -76,6 +76,7 @@ namespace Avalonia.Shared.PlatformSupport GC.RemoveMemoryPressure(Size); IsDisposed = true; Address = IntPtr.Zero; + Size = 0; } } diff --git a/src/Skia/Avalonia.Skia/BitmapImpl.cs b/src/Skia/Avalonia.Skia/BitmapImpl.cs index d13190deff..00ab770e01 100644 --- a/src/Skia/Avalonia.Skia/BitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/BitmapImpl.cs @@ -33,16 +33,22 @@ namespace Avalonia.Skia PixelWidth = width; _dpi = dpi; var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType; - var runtime = AvaloniaLocator.Current?.GetService()?.GetRuntimeInfo(); + var runtimePlatform = AvaloniaLocator.Current?.GetService(); + var runtime = runtimePlatform?.GetRuntimeInfo(); if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux) colorType = SKColorType.Bgra8888; - - Bitmap = new SKBitmap(); - var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); - var plat = AvaloniaLocator.Current.GetService(); - var blob = plat.AllocBlob(nfo.BytesSize); - Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); - Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); + + if (runtimePlatform != null) + { + Bitmap = new SKBitmap(); + var nfo = new SKImageInfo(width, height, colorType, SKAlphaType.Premul); + var plat = AvaloniaLocator.Current.GetService(); + var blob = plat.AllocBlob(nfo.BytesSize); + Bitmap.InstallPixels(nfo, blob.Address, nfo.RowBytes, null, ReleaseDelegate, blob); + + } + else + Bitmap = new SKBitmap(width, height, colorType, SKAlphaType.Premul); Bitmap.Erase(SKColor.Empty); } diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs index f0a08ca583..dd3ced1d89 100644 --- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs +++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs @@ -112,6 +112,7 @@ namespace Avalonia.Skia public readonly SKPaint Paint; private IDisposable _disposable1; + private IDisposable _disposable2; public IDisposable ApplyTo(SKPaint paint) { @@ -127,6 +128,8 @@ namespace Avalonia.Skia { if (_disposable1 == null) _disposable1 = disposable; + else if (_disposable2 == null) + _disposable2 = disposable; else throw new InvalidOperationException(); } @@ -135,12 +138,14 @@ namespace Avalonia.Skia { Paint = paint; _disposable1 = null; + _disposable2 = null; } public void Dispose() { Paint?.Dispose(); _disposable1?.Dispose(); + _disposable2?.Dispose(); } } @@ -221,8 +226,8 @@ namespace Avalonia.Skia _visualBrushRenderer.RenderVisualBrush(ctx, visualBrush); } - rv.AddDisposable(tileBrushImage); tileBrushImage = intermediate; + rv.AddDisposable(tileBrushImage); } } else diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index eedb8a0c4e..a47c871f5a 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -146,6 +146,8 @@ namespace Avalonia.Direct2D1 } if (s is IExternalDirect2DRenderTargetSurface external) return new ExternalRenderTarget(external, s_dwfactory, s_imagingFactory); + if (s is IFramebufferPlatformSurface fb) + return new FramebufferShimRenderTarget(fb, s_imagingFactory, s_d2D1Factory, s_dwfactory); } throw new NotSupportedException("Don't know how to create a Direct2D1 renderer from any of provided surfaces"); } diff --git a/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs new file mode 100644 index 0000000000..83bd4d2957 --- /dev/null +++ b/src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Avalonia.Controls.Platform.Surfaces; +using Avalonia.Direct2D1.Media; +using Avalonia.Direct2D1.Media.Imaging; +using Avalonia.Platform; +using Avalonia.Rendering; +using Avalonia.Win32.Interop; +using SharpDX.Direct2D1; +using SharpDX.WIC; +using PixelFormat = Avalonia.Platform.PixelFormat; + +namespace Avalonia.Direct2D1 +{ + class FramebufferShimRenderTarget : IRenderTarget + { + private readonly IFramebufferPlatformSurface _surface; + private readonly ImagingFactory _imagingFactory; + private readonly Factory _d2DFactory; + private readonly SharpDX.DirectWrite.Factory _dwriteFactory; + + public FramebufferShimRenderTarget(IFramebufferPlatformSurface surface, + ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory) + { + _surface = surface; + _imagingFactory = imagingFactory; + _d2DFactory = d2dFactory; + _dwriteFactory = dwriteFactory; + } + + public void Dispose() + { + + } + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + var locked = _surface.Lock(); + if (locked.Format == PixelFormat.Rgb565) + { + locked.Dispose(); + throw new ArgumentException("Unsupported pixel format: " + locked.Format); + } + + return new FramebufferShim(locked, _imagingFactory, _d2DFactory, _dwriteFactory) + .CreateDrawingContext(visualBrushRenderer); + } + + class FramebufferShim : WicRenderTargetBitmapImpl + { + private readonly ILockedFramebuffer _target; + + public FramebufferShim(ILockedFramebuffer target, + ImagingFactory imagingFactory, Factory d2dFactory, SharpDX.DirectWrite.Factory dwriteFactory + ) : base(imagingFactory, d2dFactory, dwriteFactory, + target.Width, target.Height, target.Dpi.X, target.Dpi.Y, target.Format) + { + _target = target; + } + + public override IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + { + return base.CreateDrawingContext(visualBrushRenderer, () => + { + using (var l = WicImpl.Lock(BitmapLockFlags.Read)) + { + for (var y = 0; y < _target.Height; y++) + { + UnmanagedMethods.CopyMemory( + _target.Address + _target.RowBytes * y, + l.Data.DataPointer + l.Stride * y, + (uint) Math.Min(l.Stride, _target.RowBytes)); + } + } + Dispose(); + _target.Dispose(); + + }); + } + } + + } +} diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs index f1065370b5..0eb2608047 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs @@ -22,8 +22,9 @@ namespace Avalonia.Direct2D1.Media int width, int height, double dpiX, - double dpiY) - : base(imagingFactory, width, height) + double dpiY, + Platform.PixelFormat? pixelFormat = null) + : base(imagingFactory, width, height, pixelFormat) { var props = new RenderTargetProperties { @@ -45,14 +46,13 @@ namespace Avalonia.Direct2D1.Media base.Dispose(); } - public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + public virtual IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) + => CreateDrawingContext(visualBrushRenderer, null); + + public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer, Action finishedCallback) { - return new DrawingContextImpl( - visualBrushRenderer, - null, - _target, - _dwriteFactory, - WicImagingFactory); + return new DrawingContextImpl(visualBrushRenderer, null, _target, _dwriteFactory, WicImagingFactory, + finishedCallback: finishedCallback); } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 05339c43b0..e9cb2bf450 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -187,7 +187,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(45); - Assert.Equal(null, target.Foo); + Assert.Null(target.Foo); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs index 01b550fb3b..9b4be59647 100644 --- a/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs @@ -66,7 +66,7 @@ namespace Avalonia.Controls.UnitTests.Generators target.Dematerialize(1, 1); Assert.Equal(containers[0].ContainerControl, target.ContainerFromIndex(0)); - Assert.Equal(null, target.ContainerFromIndex(1)); + Assert.Null(target.ContainerFromIndex(1)); Assert.Equal(containers[2].ContainerControl, target.ContainerFromIndex(2)); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs index e32c703409..a524ca3e89 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs @@ -107,7 +107,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetLogicalParent()); + Assert.Null(child.GetLogicalParent()); Assert.Empty(target.GetLogicalChildren()); } @@ -120,7 +120,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = child; target.Content = null; - Assert.Equal(null, child.GetVisualParent()); + Assert.Null(child.GetVisualParent()); Assert.Empty(target.GetVisualChildren()); } diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs index 589b1d67d2..032928d673 100644 --- a/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs +++ b/tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs @@ -191,7 +191,7 @@ namespace Avalonia.Controls.UnitTests.Presenters target.Content = "bar"; target.UpdateChild(); - Assert.Equal(null, foo.Parent); + Assert.Null(foo.Parent); logicalChildren = target.GetLogicalChildren(); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 7ee9fbbf52..a60074fa43 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -171,7 +171,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItem = new Item(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -278,7 +278,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.Items = null; - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -305,7 +305,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.RemoveAt(1); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } @@ -334,7 +334,7 @@ namespace Avalonia.Controls.UnitTests.Primitives items.Clear(); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); Assert.Equal(-1, target.SelectedIndex); } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index d8600f472d..642f594e4d 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives target.SelectedItems = new AvaloniaList(); Assert.Equal(-1, target.SelectedIndex); - Assert.Equal(null, target.SelectedItem); + Assert.Null(target.SelectedItem); } [Fact] diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs index f75c126ff1..56412d732b 100644 --- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs +++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs @@ -70,7 +70,7 @@ namespace Avalonia.Controls.UnitTests target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); - Assert.Equal(new Size(120, 130), target.Bounds.Size); + Assert.Equal(new Size(120, 120), target.Bounds.Size); Assert.Equal(new Rect(0, 0, 120, 20), target.Children[0].Bounds); Assert.Equal(new Rect(0, 30, 120, 30), target.Children[1].Bounds); Assert.Equal(new Rect(0, 70, 120, 50), target.Children[2].Bounds); @@ -94,7 +94,7 @@ namespace Avalonia.Controls.UnitTests target.Measure(Size.Infinity); target.Arrange(new Rect(target.DesiredSize)); - Assert.Equal(new Size(130, 120), target.Bounds.Size); + Assert.Equal(new Size(120, 120), target.Bounds.Size); Assert.Equal(new Rect(0, 0, 20, 120), target.Children[0].Bounds); Assert.Equal(new Rect(30, 0, 30, 120), target.Children[1].Bounds); Assert.Equal(new Rect(70, 0, 50, 120), target.Children[2].Bounds); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs index e912770470..c46fb6fce2 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs @@ -117,7 +117,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data }; target.Bind(TextBox.TextProperty, binding); - Assert.Equal(null, target.Text); + Assert.Null(target.Text); } [Fact] diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs index 49a88e8fae..aa1e56f2a5 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -63,7 +63,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates { var selector = new MemberSelector() { MemberName = "StringValue" }; - Assert.Equal(null, selector.Select(null)); + Assert.Null(selector.Select(null)); } [Fact] @@ -73,7 +73,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Templates var data = new Item() { StringValue = "Value1" }; - Assert.Same(null, selector.Select(data)); + Assert.Null(selector.Select(data)); } [Fact] diff --git a/tests/Avalonia.RenderTests/Media/BitmapTests.cs b/tests/Avalonia.RenderTests/Media/BitmapTests.cs index f01f78ae94..a7cd06a894 100644 --- a/tests/Avalonia.RenderTests/Media/BitmapTests.cs +++ b/tests/Avalonia.RenderTests/Media/BitmapTests.cs @@ -64,13 +64,13 @@ namespace Avalonia.Direct2D1.RenderTests.Media public void Deallocate() => Marshal.FreeHGlobal(Address); } - -#if AVALONIA_SKIA + [Theory] -#else - [Theory(Skip = "Framebuffer not supported")] + [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), +#if SKIA + InlineData(PixelFormat.Rgb565) #endif - [InlineData(PixelFormat.Rgba8888), InlineData(PixelFormat.Bgra8888), InlineData(PixelFormat.Rgb565)] + ] public void FramebufferRenderResultsShouldBeUsableAsBitmap(PixelFormat fmt) { var testName = nameof(FramebufferRenderResultsShouldBeUsableAsBitmap) + "_" + fmt; @@ -84,6 +84,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media ctx.FillRectangle(Brushes.Chartreuse, new Rect(0, 0, 20, 100)); ctx.FillRectangle(Brushes.Crimson, new Rect(20, 0, 20, 100)); ctx.FillRectangle(Brushes.Gold, new Rect(40, 0, 20, 100)); + ctx.PopOpacity(); } var bmp = new Bitmap(fmt, fb.Address, fb.Width, fb.Height, fb.RowBytes); diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs new file mode 100644 index 0000000000..76fe103c1b --- /dev/null +++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs @@ -0,0 +1,55 @@ +using System; +using Avalonia.Media; +using Avalonia.Platform; +using Avalonia.Rendering.SceneGraph; +using Xunit; + +namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph +{ + public class DrawOperationTests + { + [Fact] + public void Empty_Bounds_Remain_Empty() + { + var target = new TestDrawOperation(Rect.Empty, Matrix.Identity, null); + + Assert.Equal(Rect.Empty, target.Bounds); + } + + [Theory] + [InlineData(10, 10, 10, 10, 1, 1, 1, 9, 9, 12, 12)] + [InlineData(10, 10, 10, 10, 1, 1, 2, 9, 9, 12, 12)] + [InlineData(10, 10, 10, 10, 1.5, 1.5, 1, 14, 14, 17, 17)] + public void Rectangle_Bounds_Are_Snapped_To_Pixels( + double x, + double y, + double width, + double height, + double scaleX, + double scaleY, + double? penThickness, + double expectedX, + double expectedY, + double expectedWidth, + double expectedHeight) + { + var target = new TestDrawOperation( + new Rect(x, y, width, height), + Matrix.CreateScale(scaleX, scaleY), + penThickness.HasValue ? new Pen(Brushes.Black, penThickness.Value) : null); + Assert.Equal(new Rect(expectedX, expectedY, expectedWidth, expectedHeight), target.Bounds); + } + + private class TestDrawOperation : DrawOperation + { + public TestDrawOperation(Rect bounds, Matrix transform, Pen pen) + :base(bounds, transform, pen) + { + } + + public override bool HitTest(Point p) => false; + + public override void Render(IDrawingContextImpl context) { } + } + } +} diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png new file mode 100644 index 0000000000..19686464c5 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png new file mode 100644 index 0000000000..f3d20008a1 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png differ diff --git a/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png new file mode 100644 index 0000000000..19686464c5 Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png differ