Browse Source

Merge remote-tracking branch 'origin/master' into pr/1161-fixed-226

pull/1161/head
Steven Kirk 9 years ago
parent
commit
b133cb8588
  1. 3
      appveyor.yml
  2. 2
      build/NetCore.props
  3. 6
      docs/guidelines/build.md
  4. 4
      readme.md
  5. 4
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  6. 9
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  7. 9
      src/Avalonia.Controls/StackPanel.cs
  8. 4
      src/Avalonia.Controls/TreeView.cs
  9. 3
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  10. 42
      src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs
  11. 14
      src/Avalonia.Themes.Default/RepeatButton.xaml
  12. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  13. 26
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  14. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  15. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  16. 11
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  17. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  18. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  19. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  20. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  21. 34
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  22. 5
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  23. 2
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  24. 31
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  25. 2
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  26. 38
      src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs
  27. 34
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  28. 47
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  29. 9
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  30. 54
      src/Gtk/Avalonia.Gtk3/X11.cs
  31. 55
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  32. 142
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  33. 23
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  34. 9
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  35. 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  36. 84
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  37. 13
      src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs
  38. 1
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  39. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  40. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  41. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  42. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  43. 8
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  44. 2
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  45. 4
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  46. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  47. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  48. 11
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  49. 55
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  50. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  51. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  52. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

3
appveyor.yml

@ -16,9 +16,7 @@ environment:
init:
- ps: if (Test-Path env:nuget_address) {[System.IO.File]::AppendAllText("C:\Windows\System32\drivers\etc\hosts", "`n$($env:nuget_address)`tapi.nuget.org")}
install:
- if not exist gtk-sharp-2.12.26.msi appveyor DownloadFile http://download.xamarin.com/GTKforWindows/Windows/gtk-sharp-2.12.26.msi
- if not exist dotnet-2.0.0.exe appveyor DownloadFile https://download.microsoft.com/download/0/F/D/0FD852A4-7EA1-4E2A-983A-0484AC19B92C/dotnet-sdk-2.0.0-win-x64.exe -FileName "dotnet-2.0.0.exe"
- ps: Start-Process -FilePath "msiexec" -ArgumentList "/i gtk-sharp-2.12.26.msi /quiet /qn /norestart" -Wait
- ps: Start-Process -FilePath "dotnet-2.0.0.exe" -ArgumentList "/quiet" -Wait
- cmd: set PATH=%programfiles(x86)%\GtkSharp\2.12\bin\;%PATH%
before_build:
@ -36,5 +34,4 @@ artifacts:
- path: artifacts\zip\*.zip
- path: artifacts\inspectcode.xml
cache:
- gtk-sharp-2.12.26.msi
- dotnet-2.0.0.exe

2
build/NetCore.props

@ -1,6 +1,4 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Threading.ThreadPool" Version="4.3.0" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.1.0" />
</ItemGroup>
</Project>

6
docs/guidelines/build.md

@ -4,12 +4,6 @@
Avalonia requires at least Visual Studio 2017 and .NET Core SDK 2.0 to build on Windows.
### Install GTK Sharp
For the moment under windows, you must have [gtk-sharp](http://www.mono-project.com/download/#download-win)
installed. Note that after installing the package your machine may require a restart before GTK# is
added to your path. We hope to remove or make this dependency optional at some point in the future.
### Clone the Avalonia repository
```

4
readme.md

@ -7,7 +7,7 @@
A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
![](docs/images/screen.png)
[![](docs/images/screen.png)](https://youtu.be/wHcB3sGLVYg)
Desktop platforms:
@ -36,7 +36,7 @@ Try out the ControlCatalog to give it a quick demo.
Avalonia is a multi-platform windowing toolkit - somewhat like WPF - that is intended to be multi-
platform. It supports XAML, lookless controls and a flexible styling system, and runs on Windows
using Direct2D and other operating systems using Gtk & Cairo.
using Direct2D and other operating systems using Skia and OS-specific windowing backend (GTK, Cocoa, etc).
## Current Status

4
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center">
<Border Grid.Column="0"
Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}"
Background="{DynamicResource ThemeAccentBrush}"
Margin="5"
Padding="50"
ToolTip.Tip="This is a ToolTip">
@ -24,7 +24,7 @@
<Border Name="Border"
Grid.Column="1"
Grid.Row="1"
Background="{StyleResource ThemeAccentBrush}"
Background="{DynamicResource ThemeAccentBrush}"
Margin="5"
Padding="50"
ToolTip.Placement="Bottom">

9
src/Avalonia.Base/Platform/IRuntimePlatform.cs

@ -14,6 +14,15 @@ namespace Avalonia.Platform
IDisposable StartSystemTimer(TimeSpan interval, Action tick);
string GetStackTrace();
RuntimePlatformInfo GetRuntimeInfo();
IUnmanagedBlob AllocBlob(int size);
}
public interface IUnmanagedBlob : IDisposable
{
IntPtr Address { get; }
int Size { get; }
bool IsDisposed { get; }
}
public struct RuntimePlatformInfo

9
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);
}

4
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;

3
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DefineConstants>$(DefineConstants);DOTNETCORE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DocumentationFile>bin\$(Configuration)\Avalonia.DotNetCoreRuntime.XML</DocumentationFile>
@ -21,5 +22,5 @@
<ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
</ItemGroup>
<Import Project="..\..\build\NetCore.props" />
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" Label="Shared" />
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" />
</Project>

42
src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs

@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Microsoft.DotNet.PlatformAbstractions;
using Microsoft.Extensions.DependencyModel;
namespace Avalonia.Shared.PlatformSupport
{
internal partial class StandardRuntimePlatform
{
private static readonly Lazy<Assembly[]> Assemblies = new Lazy<Assembly[]>(LoadAssemblies);
public Assembly[] GetLoadedAssemblies() => Assemblies.Value;
static Assembly[] LoadAssemblies()
{
var assemblies = new List<Assembly>();
// Mostly copy-pasted from (MIT):
// https://github.com/StefH/System.AppDomain.Core/blob/0b35e676c2721aa367b96e62eb52c97ee0b43a70/src/System.AppDomain.NetCoreApp/AppDomain.cs
foreach (var assemblyName in
DependencyContext.Default.GetRuntimeAssemblyNames(RuntimeEnvironment.GetRuntimeIdentifier()))
{
try
{
var assembly = Assembly.Load(assemblyName);
// just load all types and skip this assembly if one or more types cannot be resolved
assembly.DefinedTypes.ToArray();
assemblies.Add(assembly);
}
catch (Exception ex)
{
Debug.Write(ex.Message);
}
}
return assemblies.ToArray();
}
}
}

14
src/Avalonia.Themes.Default/RepeatButton.xaml

@ -1,13 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="RepeatButton">
<Setter Property="Background"
Value="{StyleResource ThemeControlMidBrush}" />
Value="{DynamicResource ThemeControlMidBrush}" />
<Setter Property="BorderBrush"
Value="{StyleResource ThemeBorderLightBrush}" />
Value="{DynamicResource ThemeBorderLightBrush}" />
<Setter Property="BorderThickness"
Value="{StyleResource ThemeBorderThickness}" />
Value="{DynamicResource ThemeBorderThickness}" />
<Setter Property="Foreground"
Value="{StyleResource ThemeForegroundBrush}" />
Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
@ -31,14 +31,14 @@
</Style>
<Style Selector="RepeatButton:pointerover /template/ ContentPresenter">
<Setter Property="BorderBrush"
Value="{StyleResource ThemeBorderMidBrush}" />
Value="{DynamicResource ThemeBorderMidBrush}" />
</Style>
<Style Selector="RepeatButton:pressed /template/ ContentPresenter">
<Setter Property="Background"
Value="{StyleResource ThemeControlDarkBrush}" />
Value="{DynamicResource ThemeControlDarkBrush}" />
</Style>
<Style Selector="RepeatButton:disabled">
<Setter Property="Opacity"
Value="{StyleResource ThemeDisabledOpacity}" />
Value="{DynamicResource ThemeDisabledOpacity}" />
</Style>
</Styles>

15
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
/// <summary>
/// Base class for draw operations that can use a brush.
/// </summary>
internal abstract class BrushDrawOperation : IDrawOperation
internal abstract class BrushDrawOperation : DrawOperation
{
/// <inheritdoc/>
public abstract Rect Bounds { get; }
/// <inheritdoc/>
public abstract bool HitTest(Point p);
public BrushDrawOperation(Rect bounds, Matrix transform, Pen pen)
: base(bounds, transform, pen)
{
}
/// <summary>
/// Gets a collection of child scenes that are needed to draw visual brushes.
/// </summary>
public abstract IDictionary<IVisual, Scene> ChildScenes { get; }
/// <inheritdoc/>
public abstract void Render(IDrawingContextImpl context);
}
}

26
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Base class for draw operations that have bounds.
/// </summary>
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);
}
}

5
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Pen pen,
IGeometryImpl geometry,
IDictionary<IVisual, Scene> 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;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>

2
src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs

@ -12,7 +12,7 @@ namespace Avalonia.Rendering.SceneGraph
public interface IDrawOperation
{
/// <summary>
/// Gets the bounds of the visible content in the node.
/// Gets the bounds of the visible content in the node in global coordinates.
/// </summary>
Rect Bounds { get; }

11
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -9,7 +9,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary>
/// A node in the scene graph which represents an image draw.
/// </summary>
internal class ImageNode : IDrawOperation
internal class ImageNode : DrawOperation
{
/// <summary>
/// Initializes a new instance of the <see cref="ImageNode"/> class.
@ -20,8 +20,8 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="sourceRect">The source rect.</param>
/// <param name="destRect">The destination rect.</param>
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;
}
/// <inheritdoc/>
public Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>
@ -80,7 +77,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
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
}
/// <inheritdoc/>
public bool HitTest(Point p) => Bounds.Contains(p);
public override bool HitTest(Point p) => Bounds.Contains(p);
}
}

5
src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Point p1,
Point p2,
IDictionary<IVisual, Scene> 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;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs

@ -19,6 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="bounds">The bounds of the mask.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public OpacityMaskNode(IBrush mask, Rect bounds, IDictionary<IVisual, Scene> childScenes = null)
: base(Rect.Empty, Matrix.Identity, null)
{
Mask = mask?.ToImmutable();
MaskBounds = bounds;
@ -30,12 +31,10 @@ namespace Avalonia.Rendering.SceneGraph
/// opacity mask pop.
/// </summary>
public OpacityMaskNode()
: base(Rect.Empty, Matrix.Identity, null)
{
}
/// <inheritdoc/>
public override Rect Bounds => Rect.Empty;
/// <summary>
/// Gets the mask to be pushed or null if the operation represents a pop.
/// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -30,8 +30,8 @@ namespace Avalonia.Rendering.SceneGraph
Rect rect,
float cornerRadius,
IDictionary<IVisual, Scene> 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;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>

5
src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs

@ -28,8 +28,8 @@ namespace Avalonia.Rendering.SceneGraph
Point origin,
IFormattedTextImpl text,
IDictionary<IVisual, Scene> 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;
}
/// <inheritdoc/>
public override Rect Bounds { get; }
/// <summary>
/// Gets the transform with which the node will be drawn.
/// </summary>

34
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;
return new ImageSurfaceFramebuffer(_window, width, 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);
}
}
}

5
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

2
src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs

@ -4,6 +4,6 @@ namespace Avalonia.Gtk3
{
public interface IDeferredRenderOperation : IDisposable
{
void RenderNow();
void RenderNow(IntPtr? ctx);
}
}

31
src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs

@ -16,23 +16,23 @@ namespace Avalonia.Gtk3
{
private readonly WindowBaseImpl _impl;
private readonly GtkWidget _widget;
private CairoSurface _surface;
private ManagedCairoSurface _surface;
private int _factor;
private object _lock = new object();
public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height)
public ImageSurfaceFramebuffer(WindowBaseImpl impl, int width, int height, int factor)
{
_impl = impl;
_widget = impl.GtkWidget;
_factor = (int)(Native.GtkWidgetGetScaleFactor?.Invoke(_widget) ?? 1u);
_factor = factor;
width *= _factor;
height *= _factor;
_surface = Native.CairoImageSurfaceCreate(1, width, height);
_surface = new ManagedCairoSurface(width, height);
Width = width;
Height = height;
Address = Native.CairoImageSurfaceGetData(_surface);
RowBytes = Native.CairoImageSurfaceGetStride(_surface);
Native.CairoSurfaceFlush(_surface);
Address = _surface.Buffer;
RowBytes = _surface.Stride;
Native.CairoSurfaceFlush(_surface.Surface);
}
static void Draw(IntPtr context, CairoSurface surface, double factor)
@ -83,15 +83,15 @@ namespace Avalonia.Gtk3
class RenderOp : IDeferredRenderOperation
{
private readonly GtkWidget _widget;
private CairoSurface _surface;
private ManagedCairoSurface _surface;
private readonly double _factor;
private readonly int _width;
private readonly int _height;
public RenderOp(GtkWidget widget, CairoSurface _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, _width, _height, _factor);
if(ctx.HasValue)
Draw(ctx.Value, _surface.Surface, _factor);
else
DrawToWidget(_widget, _surface.Surface, _width, _height, _factor);
}
}
@ -116,9 +119,9 @@ namespace Avalonia.Gtk3
if (Dispatcher.UIThread.CheckAccess())
{
if (_impl.CurrentCairoContext != IntPtr.Zero)
Draw(_impl.CurrentCairoContext, _surface, _factor);
Draw(_impl.CurrentCairoContext, _surface.Surface, _factor);
else
DrawToWidget(_widget, _surface, Width, Height, _factor);
DrawToWidget(_widget, _surface.Surface, Width, Height, _factor);
_surface.Dispose();
}
else

2
src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs

@ -53,7 +53,7 @@ namespace Avalonia.Gtk3.Interop
if (interval == 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var timer = new Timer ();
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Background), interval,
GlibTimeout.Add(GlibPriority.FromDispatcherPriority(DispatcherPriority.Normal), interval,
() =>
{
if (timer.Stopped)

38
src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs

@ -0,0 +1,38 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform;
namespace Avalonia.Gtk3.Interop
{
class ManagedCairoSurface : IDisposable
{
public IntPtr Buffer { get; private set; }
public CairoSurface Surface { get; private set; }
public int Stride { get; private set; }
private int _size;
private IRuntimePlatform _plat;
private IUnmanagedBlob _blob;
public ManagedCairoSurface(int width, int height)
{
_plat = AvaloniaLocator.Current.GetService<IRuntimePlatform>();
Stride = width * 4;
_size = height * Stride;
_blob = _plat.AllocBlob(_size * 2);
Buffer = _blob.Address;
Surface = Native.CairoImageSurfaceCreateForData(Buffer, 1, width, height, Stride);
}
public void Dispose()
{
if (Buffer != IntPtr.Zero)
{
Surface.Dispose();
_blob.Dispose();
Buffer = IntPtr.Zero;
}
}
}
}

34
src/Gtk/Avalonia.Gtk3/Interop/Native.cs

@ -160,6 +160,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate CairoSurface cairo_image_surface_create(int format, int width, int height);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate CairoSurface cairo_image_surface_create_for_data(IntPtr data, int format, int width, int height, int stride);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Cairo)]
public delegate IntPtr cairo_image_surface_get_data(CairoSurface surface);
@ -178,7 +181,7 @@ namespace Avalonia.Gtk3.Interop
[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, CairoSurface surface, double x, double y);
@ -236,17 +239,17 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate GdkWindowState gdk_window_get_state(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_iconify(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_iconify(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_deiconify(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_deiconify(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_maximize(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_maximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gdk_window_unmaximize(IntPtr window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate void gtk_window_unmaximize(GtkWindow window);
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
public delegate void gtk_window_set_geometry_hints(GtkWindow window, IntPtr geometry_widget, ref GdkGeometry geometry, GdkWindowHints geom_mask);
@ -315,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);
@ -407,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;
@ -437,10 +444,10 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_window_get_origin GdkWindowGetOrigin;
public static D.gdk_window_get_pointer GdkWindowGetPointer;
public static D.gdk_window_get_state GdkWindowGetState;
public static D.gdk_window_iconify GdkWindowIconify;
public static D.gdk_window_deiconify GdkWindowDeiconify;
public static D.gdk_window_maximize GdkWindowMaximize;
public static D.gdk_window_unmaximize GdkWindowUnmaximize;
public static D.gtk_window_iconify GtkWindowIconify;
public static D.gtk_window_deiconify GtkWindowDeiconify;
public static D.gtk_window_maximize GtkWindowMaximize;
public static D.gtk_window_unmaximize GtkWindowUnmaximize;
public static D.gdk_window_begin_move_drag GdkWindowBeginMoveDrag;
public static D.gdk_window_begin_resize_drag GdkWindowBeginResizeDrag;
public static D.gdk_event_request_motions GdkEventRequestMotions;
@ -459,6 +466,7 @@ namespace Avalonia.Gtk3.Interop
public static D.gdk_cairo_create GdkCairoCreate;
public static D.cairo_image_surface_create CairoImageSurfaceCreate;
public static D.cairo_image_surface_create_for_data CairoImageSurfaceCreateForData;
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;

47
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Gtk3.Interop;
using Avalonia.Input;
@ -29,7 +30,8 @@ namespace Avalonia.Gtk3
private GCHandle _gcHandle;
private object _lock = new object();
private IDeferredRenderOperation _nextRenderOperation;
private readonly AutoResetEvent _canSetNextOperation = new AutoResetEvent(true);
internal IntPtr? GdkWindowHandle;
public WindowBaseImpl(GtkWindow gtkWidget)
{
@ -53,12 +55,8 @@ namespace Avalonia.Gtk3
ConnectEvent("leave-notify-event", OnLeaveNotifyEvent);
Connect<Native.D.signal_generic>("destroy", OnDestroy);
Native.GtkWidgetRealize(gtkWidget);
GdkWindowHandle = this.Handle.Handle;
_lastSize = ClientSize;
GlibTimeout.Add(0, 16, () =>
{
Invalidate(default(Rect));
return true;
});
if (Gtk3Platform.UseDeferredRendering)
{
Native.GtkWidgetSetDoubleBuffered(gtkWidget, false);
@ -138,7 +136,7 @@ namespace Avalonia.Gtk3
? RawMouseEventType.LeftButtonDown
: evnt->button == 3 ? RawMouseEventType.RightButtonDown : RawMouseEventType.MiddleButtonDown,
new Point(evnt->x, evnt->y), GetModifierKeys(evnt->state));
Input?.Invoke(e);
OnInput(e);
return true;
}
@ -166,7 +164,7 @@ namespace Avalonia.Gtk3
_inputRoot,
RawMouseEventType.Move,
position, GetModifierKeys(evnt->state));
Input(e);
OnInput(e);
return true;
}
@ -195,7 +193,7 @@ namespace Avalonia.Gtk3
}
var e = new RawMouseWheelEventArgs(Gtk3Platform.Mouse, evnt->time, _inputRoot,
new Point(evnt->x, evnt->y), delta, GetModifierKeys(evnt->state));
Input(e);
OnInput(e);
return true;
}
@ -210,7 +208,7 @@ namespace Avalonia.Gtk3
evnt->time,
evnt->type == GdkEventType.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
Avalonia.Gtk.Common.KeyTransform.ConvertKey((GdkKey)evnt->keyval), GetModifierKeys((GdkModifierType)evnt->state));
Input(e);
OnInput(e);
return true;
}
@ -218,7 +216,7 @@ namespace Avalonia.Gtk3
{
var evnt = (GdkEventCrossing*) pev;
var position = new Point(evnt->x, evnt->y);
Input(new RawMouseEventArgs(Gtk3Platform.Mouse,
OnInput(new RawMouseEventArgs(Gtk3Platform.Mouse,
evnt->time,
_inputRoot,
RawMouseEventType.Move,
@ -228,7 +226,7 @@ namespace Avalonia.Gtk3
private unsafe bool OnCommit(IntPtr gtkwidget, IntPtr utf8string, IntPtr userdata)
{
Input(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
OnInput(new RawTextInputEventArgs(Gtk3Platform.Keyboard, _lastKbdEvent, Utf8Buffer.StringFromPtr(utf8string)));
return true;
}
@ -260,11 +258,19 @@ namespace Avalonia.Gtk3
public void SetNextRenderOperation(IDeferredRenderOperation op)
{
lock (_lock)
while (true)
{
_nextRenderOperation?.Dispose();
_nextRenderOperation = op;
lock (_lock)
{
if (_nextRenderOperation == null)
{
_nextRenderOperation = op;
return;
}
}
_canSetNextOperation.WaitOne();
}
}
private void OnRenderTick()
@ -277,10 +283,11 @@ namespace Avalonia.Gtk3
op = _nextRenderOperation;
_nextRenderOperation = null;
}
_canSetNextOperation.Set();
}
if (op != null)
{
op?.RenderNow();
op?.RenderNow(null);
op?.Dispose();
}
}
@ -311,7 +318,7 @@ namespace Avalonia.Gtk3
public IMouseDevice MouseDevice => Gtk3Platform.Mouse;
public double Scaling => (double) 1 / (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
public double Scaling => LastKnownScaleFactor = (int) (Native.GtkWidgetGetScaleFactor?.Invoke(GtkWidget) ?? 1);
public IPlatformHandle Handle => this;
@ -338,6 +345,11 @@ namespace Avalonia.Gtk3
public void SetInputRoot(IInputRoot inputRoot) => _inputRoot = inputRoot;
void OnInput(RawInputEventArgs args)
{
Dispatcher.UIThread.InvokeAsync(() => Input?.Invoke(args), DispatcherPriority.Input);
}
public Point PointToClient(Point point)
{
int x, y;
@ -387,6 +399,7 @@ namespace Avalonia.Gtk3
public Size ClientSize { get; private set; }
public int LastKnownScaleFactor { get; private set; }
public void Resize(Size value)
{

9
src/Gtk/Avalonia.Gtk3/WindowImpl.cs

@ -32,15 +32,14 @@ namespace Avalonia.Gtk3
}
set
{
var w = Native.GtkWidgetGetWindow(GtkWidget);
if (value == WindowState.Minimized)
Native.GdkWindowIconify(w);
Native.GtkWindowIconify(GtkWidget);
else if (value == WindowState.Maximized)
Native.GdkWindowMaximize(w);
Native.GtkWindowMaximize(GtkWidget);
else
{
Native.GdkWindowUnmaximize(w);
Native.GdkWindowDeiconify(w);
Native.GtkWindowUnmaximize(GtkWidget);
Native.GtkWindowDeiconify(GtkWidget);
}
}
}

54
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];
}
}
}

55
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<IRuntimePlatform>().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; }
}
}

142
src/Shared/PlatformSupport/StandardRuntimePlatform.cs

@ -2,8 +2,11 @@
// 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.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia.Platform;
@ -11,20 +14,145 @@ namespace Avalonia.Shared.PlatformSupport
{
internal partial class StandardRuntimePlatform : IRuntimePlatform
{
#if NETCOREAPP2_0
public void PostThreadPoolItem(Action cb) => ThreadPool.QueueUserWorkItem(_ => cb(), null);
#else
public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
public void PostThreadPoolItem(Action cb) => ThreadPool.UnsafeQueueUserWorkItem(_ => cb(), null);
#endif
public Assembly[] GetLoadedAssemblies() => AppDomain.CurrentDomain.GetAssemblies();
public IDisposable StartSystemTimer(TimeSpan interval, Action tick)
{
return new Timer(_ => tick(), null, interval, interval);
}
public string GetStackTrace() => Environment.StackTrace;
public IUnmanagedBlob AllocBlob(int size) => new UnmanagedBlob(this, size);
class UnmanagedBlob : IUnmanagedBlob
{
private readonly StandardRuntimePlatform _plat;
#if DEBUG
private static readonly List<string> Backtraces = new List<string>();
private static Thread GCThread;
private readonly string _backtrace;
public string GetStackTrace() => Environment.StackTrace;
class GCThreadDetector
{
~GCThreadDetector()
{
GCThread = Thread.CurrentThread;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Spawn() => new GCThreadDetector();
static UnmanagedBlob()
{
Spawn();
GC.WaitForPendingFinalizers();
}
#endif
public UnmanagedBlob(StandardRuntimePlatform plat, int size)
{
_plat = plat;
Address = plat.Alloc(size);
GC.AddMemoryPressure(size);
Size = size;
#if DEBUG
_backtrace = Environment.StackTrace;
Backtraces.Add(_backtrace);
#endif
}
void DoDispose()
{
if (!IsDisposed)
{
#if DEBUG
Backtraces.Remove(_backtrace);
#endif
_plat.Free(Address, Size);
GC.RemoveMemoryPressure(Size);
IsDisposed = true;
Address = IntPtr.Zero;
Size = 0;
}
}
public void Dispose()
{
#if DEBUG
if (Thread.CurrentThread.ManagedThreadId == GCThread?.ManagedThreadId)
{
Console.Error.WriteLine("Native blob disposal from finalizer thread\nBacktrace: "
+ Environment.StackTrace
+ "\n\nBlob created by " + _backtrace);
}
#endif
DoDispose();
GC.SuppressFinalize(this);
}
~UnmanagedBlob()
{
#if DEBUG
Console.Error.WriteLine("Undisposed native blob created by " + _backtrace);
#endif
DoDispose();
}
public IntPtr Address { get; private set; }
public int Size { get; private set; }
public bool IsDisposed { get; private set; }
}
#if FULLDOTNET || DOTNETCORE
[DllImport("libc", SetLastError = true)]
private static extern IntPtr mmap(IntPtr addr, IntPtr length, int prot, int flags, int fd, IntPtr offset);
[DllImport("libc", SetLastError = true)]
private static extern int munmap(IntPtr addr, IntPtr length);
[DllImport("libc", SetLastError = true)]
private static extern long sysconf(int name);
private bool? _useMmap;
private bool UseMmap
=> _useMmap ?? ((_useMmap = GetRuntimeInfo().OperatingSystem == OperatingSystemType.Linux)).Value;
IntPtr Alloc(int size)
{
if (UseMmap)
{
var rv = mmap(IntPtr.Zero, new IntPtr(size), 3, 0x22, -1, IntPtr.Zero);
if (rv.ToInt64() == -1 || (ulong) rv.ToInt64() == 0xffffffff)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to allocate memory: " + errno);
}
return rv;
}
else
return Marshal.AllocHGlobal(size);
}
void Free(IntPtr ptr, int len)
{
if (UseMmap)
{
if (munmap(ptr, new IntPtr(len)) == -1)
{
var errno = Marshal.GetLastWin32Error();
throw new Exception("Unable to free memory: " + errno);
}
}
else
Marshal.FreeHGlobal(ptr);
}
#else
IntPtr Alloc(int size) => Marshal.AllocHGlobal(size);
void Free(IntPtr ptr, int len) => Marshal.FreeHGlobal(ptr);
#endif
}
}

23
src/Skia/Avalonia.Skia/BitmapImpl.cs

@ -20,16 +20,35 @@ namespace Avalonia.Skia
_dpi = new Vector(96, 96);
}
static void ReleaseProc(IntPtr address, object ctx)
{
((IUnmanagedBlob) ctx).Dispose();
}
private static readonly SKBitmapReleaseDelegate ReleaseDelegate = ReleaseProc;
public BitmapImpl(int width, int height, Vector dpi, PixelFormat? fmt = null)
{
PixelHeight = height;
PixelWidth = width;
_dpi = dpi;
var colorType = fmt?.ToSkColorType() ?? SKImageInfo.PlatformColorType;
var runtime = AvaloniaLocator.Current?.GetService<IRuntimePlatform>()?.GetRuntimeInfo();
var runtimePlatform = AvaloniaLocator.Current?.GetService<IRuntimePlatform>();
var runtime = runtimePlatform?.GetRuntimeInfo();
if (runtime?.IsDesktop == true && runtime?.OperatingSystem == OperatingSystemType.Linux)
colorType = SKColorType.Bgra8888;
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<IRuntimePlatform>();
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);
}

9
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -45,7 +45,7 @@ namespace Avalonia.Skia
var s = sourceRect.ToSKRect();
var d = destRect.ToSKRect();
using (var paint = new SKPaint()
{ Color = new SKColor(255, 255, 255, (byte)(255 * opacity)) })
{ Color = new SKColor(255, 255, 255, (byte)(255 * opacity * _currentOpacity)) })
{
Canvas.DrawBitmap(impl.Bitmap, s, d, paint);
}
@ -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

2
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -146,6 +146,8 @@ namespace Avalonia.Direct2D1
}
if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory);
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");
}

84
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 : RenderTargetBitmapImpl
{
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();
});
}
}
}
}

13
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.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,9 +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, _target, _dwriteFactory);
return new DrawingContextImpl(visualBrushRenderer, _target, _dwriteFactory,
finishedCallback: finishedCallback);
}
}
}

1
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -74,6 +74,7 @@ namespace Avalonia.Direct2D1.Media
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
{
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
_factory = factory;
PixelFormat = format;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{

2
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]

2
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));
}

4
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());
}

2
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();

8
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);
}

2
tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs

@ -76,7 +76,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
target.SelectedItems = new AvaloniaList<object>();
Assert.Equal(-1, target.SelectedIndex);
Assert.Equal(null, target.SelectedItem);
Assert.Null(target.SelectedItem);
}
[Fact]

4
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);

2
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]

4
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]

11
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);

55
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) { }
}
}
}

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

BIN
tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgba8888.expected.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 800 B

Loading…
Cancel
Save