Browse Source

Merge branch 'master' into MethodsAsCommands

pull/1179/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
f6208c100a
  1. 3
      appveyor.yml
  2. 2
      build/NetCore.props
  3. 6
      docs/guidelines/build.md
  4. 4
      readme.md
  5. BIN
      samples/ControlCatalog/Assets/test_icon.ico
  6. 6
      samples/ControlCatalog/ControlCatalog.csproj
  7. 1
      samples/ControlCatalog/MainView.xaml
  8. 24
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  9. 18
      samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs
  10. 4
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  11. 5
      samples/interop/Direct3DInteropSample/MainWindow.cs
  12. 2
      src/Android/Avalonia.Android/AndroidThreadingInterface.cs
  13. 3
      src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs
  14. 9
      src/Avalonia.Base/Platform/IRuntimePlatform.cs
  15. 13
      src/Avalonia.Base/Threading/Dispatcher.cs
  16. 18
      src/Avalonia.Base/Threading/DispatcherTimer.cs
  17. 15
      src/Avalonia.Controls/MenuItem.cs
  18. 21
      src/Avalonia.Controls/Orientation.cs
  19. 2
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  20. 139
      src/Avalonia.Controls/ProgressBar.cs
  21. 27
      src/Avalonia.Controls/StackPanel.cs
  22. 4
      src/Avalonia.Controls/TreeView.cs
  23. 3
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  24. 42
      src/Avalonia.DotNetCoreRuntime/NetCoreRuntimePlatform.cs
  25. 5
      src/Avalonia.Themes.Default/ProgressBar.xaml
  26. 14
      src/Avalonia.Themes.Default/RepeatButton.xaml
  27. 29
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  28. 34
      src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs
  29. 161
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  30. 11
      src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs
  31. 10
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  32. 9
      src/Avalonia.Visuals/Rendering/RenderLayers.cs
  33. 15
      src/Avalonia.Visuals/Rendering/SceneGraph/BrushDrawOperation.cs
  34. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  35. 26
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs
  36. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  37. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/IDrawOperation.cs
  38. 11
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  39. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/LineNode.cs
  40. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/OpacityMaskNode.cs
  41. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  42. 5
      src/Avalonia.Visuals/Rendering/SceneGraph/TextNode.cs
  43. 34
      src/Gtk/Avalonia.Gtk3/FramebufferManager.cs
  44. 11
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  45. 2
      src/Gtk/Avalonia.Gtk3/IDeferredRenderOperation.cs
  46. 31
      src/Gtk/Avalonia.Gtk3/ImageSurfaceFramebuffer.cs
  47. 6
      src/Gtk/Avalonia.Gtk3/Interop/GlibTimeout.cs
  48. 38
      src/Gtk/Avalonia.Gtk3/Interop/ManagedCairoSurface.cs
  49. 34
      src/Gtk/Avalonia.Gtk3/Interop/Native.cs
  50. 47
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  51. 9
      src/Gtk/Avalonia.Gtk3/WindowImpl.cs
  52. 54
      src/Gtk/Avalonia.Gtk3/X11.cs
  53. 55
      src/Gtk/Avalonia.Gtk3/X11Framebuffer.cs
  54. 4
      src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs
  55. 2
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  56. 142
      src/Shared/PlatformSupport/StandardRuntimePlatform.cs
  57. 23
      src/Skia/Avalonia.Skia/BitmapImpl.cs
  58. 15
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  59. 6
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  60. 28
      src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs
  61. 84
      src/Windows/Avalonia.Direct2D1/FramebufferShimRenderTarget.cs
  62. 10
      src/Windows/Avalonia.Direct2D1/ILayerFactory.cs
  63. 39
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  64. 7
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  65. 32
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  66. 47
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  67. 68
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  68. 57
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  69. 17
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs
  70. 22
      src/Windows/Avalonia.Direct2D1/OptionalDispose.cs
  71. 34
      src/Windows/Avalonia.Direct2D1/RenderTarget.cs
  72. 40
      src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs
  73. 2
      src/Windows/Avalonia.Win32/Win32Platform.cs
  74. 2
      src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs
  75. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  76. 2
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Threading.cs
  77. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  78. 4
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  79. 2
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  80. 8
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs
  81. 2
      tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs
  82. 4
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  83. 2
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_RelativeSource.cs
  84. 4
      tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs
  85. 11
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  86. 2
      tests/Avalonia.RenderTests/TestBase.cs
  87. 2
      tests/Avalonia.UnitTests/TestRoot.cs
  88. 70
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  89. 55
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  90. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Bgra8888.expected.png
  91. BIN
      tests/TestFiles/Direct2D1/Media/Bitmap/FramebufferRenderResultsShouldBeUsableAsBitmap_Rgb565.expected.png
  92. 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

BIN
samples/ControlCatalog/Assets/test_icon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 172 KiB

6
samples/ControlCatalog/ControlCatalog.csproj

@ -69,6 +69,9 @@
<EmbeddedResource Include="Pages\MenuPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ProgressBarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\RadioButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
@ -131,6 +134,9 @@
<Compile Include="Pages\MenuPage.xaml.cs">
<DependentUpon>MenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ProgressBarPage.xaml.cs">
<DependentUpon>ProgressBarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RadioButtonPage.xaml.cs">
<DependentUpon>RadioButtonPage.xaml</DependentUpon>
</Compile>

1
samples/ControlCatalog/MainView.xaml

@ -16,6 +16,7 @@
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>

24
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@ -0,0 +1,24 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">ProgressBar</TextBlock>
<TextBlock Classes="h2">A progress bar control</TextBlock>
<StackPanel>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Gap="16">
<ProgressBar Value="{Binding #hprogress.Value}" />
<ProgressBar IsIndeterminate="True"/>
</StackPanel>
<ProgressBar Value="{Binding #vprogress.Value}" Orientation="Vertical" />
<ProgressBar Orientation="Vertical" IsIndeterminate="True" />
</StackPanel>
<StackPanel Margin="16">
<Slider Name="hprogress" Maximum="100" Value="40"/>
<Slider Name="vprogress" Maximum="100" Value="60"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

18
samples/ControlCatalog/Pages/ProgressBarPage.xaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ProgressBarPage : UserControl
{
public ProgressBarPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

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">

5
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -253,8 +253,9 @@ namespace Direct3DInteropSample
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>());
return new DrawingContextImpl(visualBrushRenderer, null, _window._d2dRenderTarget,
AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>(),
AvaloniaLocator.Current.GetService<ImagingFactory>());
}
}

2
src/Android/Avalonia.Android/AndroidThreadingInterface.cs

@ -30,7 +30,7 @@ namespace Avalonia.Android
return;
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);

3
src/Avalonia.Base/Platform/IPlatformThreadingInterface.cs

@ -17,10 +17,11 @@ namespace Avalonia.Platform
/// <summary>
/// Starts a timer.
/// </summary>
/// <param name="priority"></param>
/// <param name="interval">The interval.</param>
/// <param name="tick">The action to call on each tick.</param>
/// <returns>An <see cref="IDisposable"/> used to stop the timer.</returns>
IDisposable StartTimer(TimeSpan interval, Action tick);
IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick);
void Signal(DispatcherPriority priority);

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

13
src/Avalonia.Base/Threading/Dispatcher.cs

@ -84,6 +84,19 @@ namespace Avalonia.Threading
_jobRunner?.Post(action, priority);
}
/// <summary>
/// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority
/// </summary>
/// <param name="currentPriority"></param>
internal void EnsurePriority(DispatcherPriority currentPriority)
{
if (currentPriority == DispatcherPriority.MaxValue)
return;
currentPriority += 1;
_jobRunner.RunJobs(currentPriority);
}
/// <summary>
/// Allows unit tests to change the platform threading interface.
/// </summary>

18
src/Avalonia.Base/Threading/DispatcherTimer.cs

@ -17,13 +17,11 @@ namespace Avalonia.Threading
private readonly DispatcherPriority _priority;
private TimeSpan _interval;
private readonly Action _raiseTickAction;
/// <summary>
/// Initializes a new instance of the <see cref="DispatcherTimer"/> class.
/// </summary>
public DispatcherTimer() : this(DispatcherPriority.Normal)
public DispatcherTimer() : this(DispatcherPriority.Background)
{
}
@ -34,7 +32,6 @@ namespace Avalonia.Threading
public DispatcherTimer(DispatcherPriority priority)
{
_priority = priority;
_raiseTickAction = RaiseTick;
}
/// <summary>
@ -187,7 +184,7 @@ namespace Avalonia.Threading
throw new Exception("Could not start timer: IPlatformThreadingInterface is not registered.");
}
_timer = threading.StartTimer(Interval, InternalTick);
_timer = threading.StartTimer(_priority, Interval, InternalTick);
}
}
@ -210,14 +207,7 @@ namespace Avalonia.Threading
/// </summary>
private void InternalTick()
{
Dispatcher.UIThread.InvokeAsync(_raiseTickAction, _priority);
}
/// <summary>
/// Raises the <see cref="Tick"/> event.
/// </summary>
private void RaiseTick()
{
Dispatcher.UIThread.EnsurePriority(_priority);
Tick?.Invoke(this, EventArgs.Empty);
}
}

15
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<MenuItem>()
.Where(x => x != this && x.IsSubMenuOpen))
{
sibling.CloseSubmenus();
sibling.IsSubMenuOpen = false;
sibling.IsSelected = false;
}
}
}
}
/// <summary>

21
src/Avalonia.Controls/Orientation.cs

@ -0,0 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Controls
{
/// <summary>
/// Defines vertical or horizontal orientation.
/// </summary>
public enum Orientation
{
/// <summary>
/// Horizontal orientation.
/// </summary>
Horizontal,
/// <summary>
/// Vertical orientation.
/// </summary>
Vertical,
}
}

2
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -30,7 +30,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation));
AvaloniaProperty.Register<ScrollBar, Orientation>(nameof(Orientation), Orientation.Vertical);
private Button _lineUpButton;
private Button _lineDownButton;

139
src/Avalonia.Controls/ProgressBar.cs

@ -1,8 +1,12 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -11,11 +15,38 @@ namespace Avalonia.Controls
/// </summary>
public class ProgressBar : RangeBase
{
public static readonly StyledProperty<bool> IsIndeterminateProperty =
AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
private Border _indicator;
private IndeterminateAnimation _indeterminateAnimation;
static ProgressBar()
{
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
}
public bool IsIndeterminate
{
get => GetValue(IsIndeterminateProperty);
set => SetValue(IsIndeterminateProperty, value);
}
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
/// <inheritdoc/>
@ -29,21 +60,123 @@ namespace Avalonia.Controls
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
_indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
UpdateOrientation(Orientation);
UpdateIsIndeterminate(IsIndeterminate);
}
private void UpdateIndicator(Size bounds)
{
if (_indicator != null)
{
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
_indicator.Width = bounds.Width * percent;
if (IsIndeterminate)
{
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width / 5.0;
else
_indicator.Height = bounds.Height / 5.0;
}
else
{
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
if (Orientation == Orientation.Horizontal)
_indicator.Width = bounds.Width * percent;
else
_indicator.Height = bounds.Height * percent;
}
}
}
private void UpdateOrientation(Orientation orientation)
{
if (orientation == Orientation.Horizontal)
{
MinHeight = 14;
MinWidth = 200;
_indicator.HorizontalAlignment = HorizontalAlignment.Left;
_indicator.VerticalAlignment = VerticalAlignment.Stretch;
}
else
{
MinHeight = 200;
MinWidth = 14;
_indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
_indicator.VerticalAlignment = VerticalAlignment.Bottom;
}
}
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)
if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
_indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
else
_indeterminateAnimation?.Dispose();
}
private void ValueChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateIndicator(Bounds.Size);
}
private class IndeterminateAnimation : IDisposable
{
private WeakReference<ProgressBar> _progressBar;
private IDisposable _indeterminateBindSubscription;
private TimeSpan _startTime;
private bool _disposed;
public bool Disposed => _disposed;
private IndeterminateAnimation(ProgressBar progressBar)
{
_progressBar = new WeakReference<ProgressBar>(progressBar);
_startTime = Animate.Stopwatch.Elapsed;
_indeterminateBindSubscription = Animate.Timer.TakeWhile(x => (x - _startTime).TotalSeconds <= 4.0)
.Select(GetAnimationRect)
.Finally(() => _startTime = Animate.Stopwatch.Elapsed)
.Repeat()
.Subscribe(AnimationTick);
}
public static IndeterminateAnimation StartAnimation(ProgressBar progressBar)
{
return new IndeterminateAnimation(progressBar);
}
private Rect GetAnimationRect(TimeSpan time)
{
if (_progressBar.TryGetTarget(out var progressBar))
{
if (progressBar.Orientation == Orientation.Horizontal)
return new Rect(-progressBar._indicator.Width - 5 + (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Width + progressBar._indicator.Width + 10), 0, progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
else
return new Rect(0, progressBar.Bounds.Height + 5 - (time - _startTime).TotalSeconds / 4.0 * (progressBar.Bounds.Height + progressBar._indicator.Height + 10), progressBar._indicator.Bounds.Width, progressBar._indicator.Bounds.Height);
}
else
{
_indeterminateBindSubscription.Dispose();
return Rect.Empty;
}
}
private void AnimationTick(Rect rect)
{
if (_progressBar.TryGetTarget(out var progressBar))
progressBar._indicator.Arrange(rect);
else
_indeterminateBindSubscription.Dispose();
}
public void Dispose()
{
_indeterminateBindSubscription?.Dispose();
_disposed = true;
}
}
}
}

27
src/Avalonia.Controls/StackPanel.cs

@ -6,22 +6,6 @@ using Avalonia.Input;
namespace Avalonia.Controls
{
/// <summary>
/// Defines vertical or horizontal orientation.
/// </summary>
public enum Orientation
{
/// <summary>
/// Vertical orientation.
/// </summary>
Vertical,
/// <summary>
/// Horizontal orientation.
/// </summary>
Horizontal,
}
/// <summary>
/// A panel which lays out its children horizontally or vertically.
/// </summary>
@ -37,7 +21,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="Orientation"/> property.
/// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation));
AvaloniaProperty.Register<StackPanel, Orientation>(nameof(Orientation), Orientation.Vertical);
/// <summary>
/// Initializes static members of the <see cref="StackPanel"/> class.
@ -186,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();
}
}
}

5
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -1,8 +1,6 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ProgressBar">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
<Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
<Setter Property="MinHeight" Value="14"/>
<Setter Property="MinWidth" Value="200"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
@ -14,8 +12,7 @@
BorderBrush="{TemplateBinding Background}"/>
<Border Name="PART_Indicator"
BorderThickness="1"
Background="{TemplateBinding Foreground}"
HorizontalAlignment="Left"/>
Background="{TemplateBinding Foreground}"/>
</Grid>
</Border>
</ControlTemplate>

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>

29
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -80,12 +80,29 @@ namespace Avalonia.Platform
/// <param name="cornerRadius">The corner radius.</param>
void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f);
/// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer
/// for the current render target.
/// </summary>
/// <param name="size">The size of the layer in DIPs.</param>
/// <returns>An <see cref="IRenderTargetBitmapImpl"/></returns>
/// <remarks>
/// Depending on the rendering backend used, a layer created via this method may be more
/// performant than a standard render target bitmap. In particular the Direct2D backend
/// has to do a format conversion each time a standard render target bitmap is rendered,
/// but a layer created via this method has no such overhead.
/// </remarks>
IRenderTargetBitmapImpl CreateLayer(Size size);
/// <summary>
/// Pushes a clip rectange.
/// </summary>
/// <param name="clip">The clip rectangle.</param>
void PushClip(Rect clip);
/// <summary>
/// Pops the latest pushed clip rectangle.
/// </summary>
void PopClip();
/// <summary>
@ -94,10 +111,19 @@ namespace Avalonia.Platform
/// <param name="opacity">The opacity.</param>
void PushOpacity(double opacity);
/// <summary>
/// Pops the latest pushed opacity value.
/// </summary>
void PopOpacity();
/// <summary>
/// Pushes an opacity mask
/// </summary>
void PushOpacityMask(IBrush mask, Rect bounds);
/// <summary>
/// Pops the latest pushed opacity mask.
/// </summary>
void PopOpacityMask();
/// <summary>
@ -106,6 +132,9 @@ namespace Avalonia.Platform
/// <param name="clip">The clip geometry.</param>
void PushGeometryClip(IGeometryImpl clip);
/// <summary>
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip();
}
}

34
src/Avalonia.Visuals/Rendering/DefaultRenderLayerFactory.cs

@ -1,34 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public class DefaultRenderLayerFactory : IRenderLayerFactory
{
private IPlatformRenderInterface _renderInterface;
public DefaultRenderLayerFactory()
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>())
{
}
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface)
{
_renderInterface = renderInterface;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _renderInterface.CreateRenderTargetBitmap(
(int)Math.Ceiling(size.Width),
(int)Math.Ceiling(size.Height),
dpiX,
dpiY);
}
}
}

161
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -26,7 +26,6 @@ namespace Avalonia.Rendering
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
private readonly RenderLayers _layers;
private readonly IRenderLayerFactory _layerFactory;
private bool _running;
private Scene _scene;
@ -45,13 +44,11 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderLoop">The render loop.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <param name="dispatcher">The dispatcher to use. Optional.</param>
public DeferredRenderer(
IRenderRoot root,
IRenderLoop renderLoop,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null,
IDispatcher dispatcher = null)
{
Contract.Requires<ArgumentNullException>(root != null);
@ -59,8 +56,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
_renderLoop = renderLoop;
}
@ -70,15 +66,13 @@ namespace Avalonia.Rendering
/// <param name="root">The control to render.</param>
/// <param name="renderTarget">The render target.</param>
/// <param name="sceneBuilder">The scene builder to use. Optional.</param>
/// <param name="layerFactory">The layer factory to use. Optional.</param>
/// <remarks>
/// This constructor is intended to be used for unit testing.
/// </remarks>
public DeferredRenderer(
IVisual root,
IRenderTarget renderTarget,
ISceneBuilder sceneBuilder = null,
IRenderLayerFactory layerFactory = null)
ISceneBuilder sceneBuilder = null)
{
Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(renderTarget != null);
@ -86,8 +80,7 @@ namespace Avalonia.Rendering
_root = root;
_renderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
_layerFactory = layerFactory ?? new DefaultRenderLayerFactory();
_layers = new RenderLayers(_layerFactory);
_layers = new RenderLayers();
}
/// <inheritdoc/>
@ -180,38 +173,60 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
if (_renderTarget == null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
{
_dirtyRectsDisplay.Tick();
}
if (scene != null && scene.Size != Size.Empty)
try
{
if (scene.Generation != _lastSceneId)
if (scene != null && scene.Size != Size.Empty)
{
_layers.Update(scene);
RenderToLayers(scene);
IDrawingContextImpl context = null;
if (DebugFramesPath != null)
if (scene.Generation != _lastSceneId)
{
SaveDebugFrames(scene.Generation);
}
context = _renderTarget.CreateDrawingContext(this);
_layers.Update(scene, context);
_lastSceneId = scene.Generation;
RenderToLayers(scene);
composite = true;
}
if (DebugFramesPath != null)
{
SaveDebugFrames(scene.Generation);
}
if (renderOverlay)
{
RenderOverlay(scene);
RenderComposite(scene);
}
else if(composite)
{
RenderComposite(scene);
_lastSceneId = scene.Generation;
composite = true;
}
if (renderOverlay)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
context = context ?? _renderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
context?.Dispose();
}
}
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
}
}
private void Render(IDrawingContextImpl context, VisualNode node, IVisual layer, Rect clipBounds)
@ -273,11 +288,11 @@ namespace Avalonia.Rendering
}
}
private void RenderOverlay(Scene scene)
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
{
if (DrawDirtyRects)
{
var overlay = GetOverlay(scene.Size, scene.Scaling);
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
using (var context = overlay.CreateDrawingContext(this))
{
@ -301,61 +316,44 @@ namespace Avalonia.Rendering
}
}
private void RenderComposite(Scene scene)
private void RenderComposite(Scene scene, IDrawingContextImpl context)
{
try
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{
if (_renderTarget == null)
var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{
_renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
context.PushGeometryClip(layer.GeometryClip);
}
using (var context = _renderTarget.CreateDrawingContext(this))
if (layer.OpacityMask == null)
{
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)
{
var bitmap = _layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
{
context.PushGeometryClip(layer.GeometryClip);
}
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
if (layer.GeometryClip != null)
{
context.PopGeometryClip();
}
}
catch (RenderTargetCorruptedException ex)
if (_overlay != null)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
_renderTarget?.Dispose();
_renderTarget = null;
var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps)
{
RenderFps(context, clientRect, true);
}
}
@ -422,16 +420,19 @@ namespace Avalonia.Rendering
}
}
private IRenderTargetBitmapImpl GetOverlay(Size size, double scaling)
private IRenderTargetBitmapImpl GetOverlay(
IDrawingContextImpl parentContext,
Size size,
double scaling)
{
size = new Size(size.Width * scaling, size.Height * scaling);
var pixelSize = size * scaling;
if (_overlay == null ||
_overlay.PixelWidth != size.Width ||
_overlay.PixelHeight != size.Height)
_overlay.PixelWidth != pixelSize.Width ||
_overlay.PixelHeight != pixelSize.Height)
{
_overlay?.Dispose();
_overlay = _layerFactory.CreateLayer(null, size, 96 * scaling, 96 * scaling);
_overlay = parentContext.CreateLayer(size);
}
return _overlay;

11
src/Avalonia.Visuals/Rendering/IRenderLayerFactory.cs

@ -1,11 +0,0 @@
using System;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Rendering
{
public interface IRenderLayerFactory
{
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY);
}
}

10
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@ -7,16 +7,16 @@ namespace Avalonia.Rendering
{
public class RenderLayer
{
private readonly IRenderLayerFactory _factory;
private readonly IDrawingContextImpl _drawingContext;
public RenderLayer(
IRenderLayerFactory factory,
IDrawingContextImpl drawingContext,
Size size,
double scaling,
IVisual layerRoot)
{
_factory = factory;
Bitmap = factory.CreateLayer(layerRoot, size * scaling, 96 * scaling, 96 * scaling);
_drawingContext = drawingContext;
Bitmap = drawingContext.CreateLayer(size);
Size = size;
Scaling = scaling;
LayerRoot = layerRoot;
@ -31,7 +31,7 @@ namespace Avalonia.Rendering
{
if (Size != size || Scaling != scaling)
{
var resized = _factory.CreateLayer(LayerRoot, size * scaling, 96 * scaling, 96 * scaling);
var resized = _drawingContext.CreateLayer(size);
using (var context = resized.CreateDrawingContext(null))
{

9
src/Avalonia.Visuals/Rendering/RenderLayers.cs

@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.VisualTree;
@ -8,19 +9,17 @@ namespace Avalonia.Rendering
{
public class RenderLayers : IEnumerable<RenderLayer>
{
private readonly IRenderLayerFactory _factory;
private List<RenderLayer> _inner = new List<RenderLayer>();
private Dictionary<IVisual, RenderLayer> _index = new Dictionary<IVisual, RenderLayer>();
public RenderLayers(IRenderLayerFactory factory)
public RenderLayers()
{
_factory = factory;
}
public int Count => _inner.Count;
public RenderLayer this[IVisual layerRoot] => _index[layerRoot];
public void Update(Scene scene)
public void Update(Scene scene, IDrawingContextImpl context)
{
for (var i = scene.Layers.Count - 1; i >= 0; --i)
{
@ -29,7 +28,7 @@ namespace Avalonia.Rendering
if (!_index.TryGetValue(src.LayerRoot, out layer))
{
layer = new RenderLayer(_factory, scene.Size, scene.Scaling, src.LayerRoot);
layer = new RenderLayer(context, scene.Size, scene.Scaling, src.LayerRoot);
_inner.Add(layer);
_index.Add(src.LayerRoot, layer);
}

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

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

@ -194,6 +194,11 @@ namespace Avalonia.Rendering.SceneGraph
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
/// <inheritdoc/>
public void PopClip()
{

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

11
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
@ -65,15 +70,13 @@ namespace Avalonia.Gtk3
Native.GtkMainIteration();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
var msec = interval.TotalMilliseconds;
if (msec <= 0)
throw new ArgumentException("Don't know how to create a timer with zero or negative interval");
var imsec = (uint) msec;
if (imsec == 0)
imsec = 1;
return GlibTimeout.StarTimer(imsec, tick);
return GlibTimeout.StartTimer(GlibPriority.FromDispatcherPriority(priority), imsec, tick);
}
private bool[] _signaled = new bool[(int) DispatcherPriority.MaxValue + 1];

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

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

@ -48,12 +48,10 @@ namespace Avalonia.Gtk3.Interop
}
}
public static IDisposable StarTimer(uint interval, Action tick)
public static IDisposable StartTimer(int priority, uint interval, Action tick)
{
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(priority, 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; }
}
}

4
src/Linux/Avalonia.LinuxFramebuffer/PlatformThreadingInterface.cs

@ -17,7 +17,7 @@ namespace Avalonia.LinuxFramebuffer
public PlatformThreadingInterface()
{
TlsCurrentThreadIsLoopThread = true;
StartTimer(new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
StartTimer(DispatcherPriority.Render, new TimeSpan(0, 0, 0, 0, 66), () => Tick?.Invoke(this, new EventArgs()));
}
private readonly AutoResetEvent _signaled = new AutoResetEvent(false);
@ -74,7 +74,7 @@ namespace Avalonia.LinuxFramebuffer
}
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
return new WatTimer(new System.Threading.Timer(delegate
{

2
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -16,7 +16,7 @@ namespace Avalonia.MonoMac
public event Action<DispatcherPriority?> Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal(DispatcherPriority prio)

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

15
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
@ -349,6 +354,12 @@ namespace Avalonia.Skia
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var pixelSize = size * (_dpi / 96);
return new BitmapImpl((int)pixelSize.Width, (int)pixelSize.Height, _dpi);
}
public void PushClip(Rect clip)
{
Canvas.Save();

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

@ -145,7 +145,9 @@ namespace Avalonia.Direct2D1
return new HwndRenderTarget(nativeWindow);
}
if (s is IExternalDirect2DRenderTargetSurface external)
return new ExternalRenderTarget(external, s_dwfactory);
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");
}
@ -156,7 +158,7 @@ namespace Avalonia.Direct2D1
double dpiX,
double dpiY)
{
return new RenderTargetBitmapImpl(
return new WicRenderTargetBitmapImpl(
s_imagingFactory,
s_d2D1Factory,
s_dwfactory,

28
src/Windows/Avalonia.Direct2D1/ExternalRenderTarget.cs

@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
@ -11,15 +8,20 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1
{
class ExternalRenderTarget : IRenderTarget
class ExternalRenderTarget : IRenderTarget, ILayerFactory
{
private readonly IExternalDirect2DRenderTargetSurface _externalRenderTargetProvider;
private readonly DirectWriteFactory _dwFactory;
public ExternalRenderTarget(IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory)
private readonly SharpDX.WIC.ImagingFactory _wicFactory;
public ExternalRenderTarget(
IExternalDirect2DRenderTargetSurface externalRenderTargetProvider,
DirectWriteFactory dwFactory,
SharpDX.WIC.ImagingFactory wicFactory)
{
_externalRenderTargetProvider = externalRenderTargetProvider;
_dwFactory = dwFactory;
_wicFactory = wicFactory;
}
public void Dispose()
@ -31,7 +33,7 @@ namespace Avalonia.Direct2D1
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
_externalRenderTargetProvider.BeforeDrawing();
return new DrawingContextImpl(visualBrushRenderer, target, _dwFactory, null, () =>
return new DrawingContextImpl(visualBrushRenderer, null, target, _dwFactory, _wicFactory, null, () =>
{
try
{
@ -43,5 +45,15 @@ namespace Avalonia.Direct2D1
}
});
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
var target = _externalRenderTargetProvider.GetOrCreateRenderTarget();
return D2DRenderTargetBitmapImpl.CreateCompatible(
_wicFactory,
_dwFactory,
target,
size);
}
}
}

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

10
src/Windows/Avalonia.Direct2D1/ILayerFactory.cs

@ -0,0 +1,10 @@
using System;
using Avalonia.Platform;
namespace Avalonia.Direct2D1
{
public interface ILayerFactory
{
IRenderTargetBitmapImpl CreateLayer(Size size);
}
}

39
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -2,16 +2,13 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.RenderHelpers;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.Mathematics.Interop;
using IBitmap = Avalonia.Media.Imaging.IBitmap;
namespace Avalonia.Direct2D1.Media
{
@ -21,9 +18,11 @@ namespace Avalonia.Direct2D1.Media
public class DrawingContextImpl : IDrawingContextImpl, IDisposable
{
private readonly IVisualBrushRenderer _visualBrushRenderer;
private readonly ILayerFactory _layerFactory;
private readonly SharpDX.Direct2D1.RenderTarget _renderTarget;
private readonly SharpDX.DXGI.SwapChain1 _swapChain;
private readonly Action _finishedCallback;
private readonly SharpDX.WIC.ImagingFactory _imagingFactory;
private SharpDX.DirectWrite.Factory _directWriteFactory;
/// <summary>
@ -31,21 +30,30 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="visualBrushRenderer">The visual brush renderer.</param>
/// <param name="renderTarget">The render target to draw to.</param>
/// <param name="layerFactory">
/// An object to use to create layers. May be null, in which case a
/// <see cref="WicRenderTargetBitmapImpl"/> will created when a new layer is requested.
/// </param>
/// <param name="directWriteFactory">The DirectWrite factory.</param>
/// <param name="imagingFactory">The WIC imaging factory.</param>
/// <param name="swapChain">An optional swap chain associated with this drawing context.</param>
/// <param name="finishedCallback">An optional delegate to be called when context is disposed.</param>
public DrawingContextImpl(
IVisualBrushRenderer visualBrushRenderer,
ILayerFactory layerFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
SharpDX.DirectWrite.Factory directWriteFactory,
SharpDX.WIC.ImagingFactory imagingFactory,
SharpDX.DXGI.SwapChain1 swapChain = null,
Action finishedCallback = null)
{
_visualBrushRenderer = visualBrushRenderer;
_layerFactory = layerFactory;
_renderTarget = renderTarget;
_swapChain = swapChain;
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
_swapChain = swapChain;
_renderTarget.BeginDraw();
}
@ -97,7 +105,7 @@ namespace Avalonia.Direct2D1.Media
using (var d2d = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
{
_renderTarget.DrawBitmap(
d2d,
d2d.Value,
destRect.ToSharpDX(),
(float)opacity,
BitmapInterpolationMode.Linear,
@ -115,7 +123,7 @@ namespace Avalonia.Direct2D1.Media
public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
using (var d2dSource = ((BitmapImpl)source).GetDirect2DBitmap(_renderTarget))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource))
using (var sourceBrush = new BitmapBrush(_renderTarget, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(_renderTarget.Factory, destRect.ToDirect2D()))
{
@ -284,6 +292,25 @@ namespace Avalonia.Direct2D1.Media
}
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_layerFactory != null)
{
return _layerFactory.CreateLayer(size);
}
else
{
var platform = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var dpi = new Vector(_renderTarget.DotsPerInch.Width, _renderTarget.DotsPerInch.Height);
var pixelSize = size * (dpi / 96);
return platform.CreateRenderTargetBitmap(
(int)pixelSize.Width,
(int)pixelSize.Height,
dpi.X,
dpi.Y);
}
}
/// <summary>
/// Pushes a clip rectange.
/// </summary>
@ -397,7 +424,7 @@ namespace Avalonia.Direct2D1.Media
return new ImageBrushImpl(
visualBrush,
_renderTarget,
new D2DBitmapImpl(intermediate.Bitmap),
new D2DBitmapImpl(_imagingFactory, intermediate.Bitmap),
destinationSize);
}
}

7
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -10,6 +10,8 @@ namespace Avalonia.Direct2D1.Media
{
public sealed class ImageBrushImpl : BrushImpl
{
OptionalDispose<Bitmap> _bitmap;
public ImageBrushImpl(
ITileBrush brush,
SharpDX.Direct2D1.RenderTarget target,
@ -20,9 +22,10 @@ namespace Avalonia.Direct2D1.Media
if (!calc.NeedsIntermediate)
{
_bitmap = bitmap.GetDirect2DBitmap(target);
PlatformBrush = new BitmapBrush(
target,
bitmap.GetDirect2DBitmap(target),
_bitmap.Value,
GetBitmapBrushProperties(brush),
GetBrushProperties(brush, calc.DestinationRect));
}
@ -41,7 +44,7 @@ namespace Avalonia.Direct2D1.Media
public override void Dispose()
{
((BitmapBrush)PlatformBrush)?.Bitmap.Dispose();
_bitmap.Dispose();
base.Dispose();
}

32
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -1,20 +1,38 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
public abstract class BitmapImpl : IBitmapImpl, IDisposable
{
public abstract Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public BitmapImpl(ImagingFactory imagingFactory)
{
WicImagingFactory = imagingFactory;
}
public ImagingFactory WicImagingFactory { get; }
public abstract int PixelWidth { get; }
public abstract int PixelHeight { get; }
public abstract void Save(string fileName);
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public void Save(string fileName)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
Save(s);
}
}
public abstract void Save(Stream stream);
public virtual void Dispose()

47
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -1,11 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Platform;
using SharpDX.Direct2D1;
using WICFactory = SharpDX.WIC.ImagingFactory;
using ImagingFactory2 = SharpDX.WIC.ImagingFactory2;
using ImageParameters = SharpDX.WIC.ImageParameters;
using PngBitmapEncoder = SharpDX.WIC.PngBitmapEncoder;
namespace Avalonia.Direct2D1.Media
{
@ -26,32 +25,42 @@ namespace Avalonia.Direct2D1.Media
/// or if the render target is a <see cref="SharpDX.Direct2D1.DeviceContext"/>,
/// the device associated with this context, to be renderable.
/// </remarks>
public D2DBitmapImpl(Bitmap d2DBitmap)
public D2DBitmapImpl(WICFactory imagingFactory, Bitmap d2DBitmap)
: base(imagingFactory)
{
if (d2DBitmap == null) throw new ArgumentNullException(nameof(d2DBitmap));
_direct2D = d2DBitmap;
_direct2D = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) => _direct2D;
public override int PixelWidth => _direct2D.PixelSize.Width;
public override int PixelHeight => _direct2D.PixelSize.Height;
public override void Save(string fileName)
public override void Dispose()
{
throw new NotImplementedException();
base.Dispose();
_direct2D.Dispose();
}
public override void Save(Stream stream)
public override OptionalDispose<Bitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
throw new NotImplementedException();
return new OptionalDispose<Bitmap>(_direct2D, false);
}
public override void Dispose()
public override void Save(Stream stream)
{
base.Dispose();
_direct2D.Dispose();
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frameEncode = new SharpDX.WIC.BitmapFrameEncode(encoder))
using (var imageEncoder = new SharpDX.WIC.ImageEncoder((ImagingFactory2)WicImagingFactory, null))
{
var parameters = new ImageParameters(
new PixelFormat(SharpDX.DXGI.Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
_direct2D.DotsPerInch.Width,
_direct2D.DotsPerInch.Height,
0, 0, PixelWidth, PixelHeight);
imageEncoder.WriteFrame(_direct2D, frameEncode, parameters);
frameEncode.Commit();
encoder.Commit();
}
}
}
}

68
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -0,0 +1,68 @@
using System;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX;
using SharpDX.Direct2D1;
using SharpDX.WIC;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media.Imaging
{
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly BitmapRenderTarget _target;
public D2DRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
BitmapRenderTarget target)
: base(imagingFactory, target.Bitmap)
{
_dwriteFactory = dwriteFactory;
_target = target;
}
public override int PixelWidth => _target.PixelSize.Width;
public override int PixelHeight => _target.PixelSize.Height;
public static D2DRenderTargetBitmapImpl CreateCompatible(
ImagingFactory imagingFactory,
DirectWriteFactory dwriteFactory,
SharpDX.Direct2D1.RenderTarget renderTarget,
Size size)
{
var bitmapRenderTarget = new BitmapRenderTarget(
renderTarget,
CompatibleRenderTargetOptions.None,
new Size2F((float)size.Width, (float)size.Height));
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget);
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(
visualBrushRenderer,
this,
_target,
_dwriteFactory,
WicImagingFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size);
}
public override void Dispose()
{
_target.Dispose();
}
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target)
{
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false);
}
}
}

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

@ -3,11 +3,10 @@
using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
using PixelFormat = SharpDX.WIC.PixelFormat;
using APixelFormat = Avalonia.Platform.PixelFormat;
using SharpDX.WIC;
using APixelFormat = Avalonia.Platform.PixelFormat;
using D2DBitmap = SharpDX.Direct2D1.Bitmap;
namespace Avalonia.Direct2D1.Media
{
@ -16,18 +15,14 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
public class WicBitmapImpl : BitmapImpl
{
private readonly ImagingFactory _factory;
/// <summary>
/// Initializes a new instance of the <see cref="WicBitmapImpl"/> class.
/// </summary>
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="fileName">The filename of the bitmap to load.</param>
public WicBitmapImpl(ImagingFactory factory, string fileName)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
@ -40,9 +35,8 @@ namespace Avalonia.Direct2D1.Media
/// <param name="factory">The WIC imaging factory to use.</param>
/// <param name="stream">The stream to read the bitmap from.</param>
public WicBitmapImpl(ImagingFactory factory, Stream stream)
: base(factory)
{
_factory = factory;
using (BitmapDecoder decoder = new BitmapDecoder(factory, stream, DecodeOptions.CacheOnLoad))
{
WicImpl = new Bitmap(factory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
@ -57,11 +51,11 @@ namespace Avalonia.Direct2D1.Media
/// <param name="height">The height of the bitmap.</param>
/// <param name="pixelFormat">Pixel format</param>
public WicBitmapImpl(ImagingFactory factory, int width, int height, APixelFormat? pixelFormat = null)
: base(factory)
{
if (!pixelFormat.HasValue)
pixelFormat = APixelFormat.Bgra8888;
_factory = factory;
PixelFormat = pixelFormat;
WicImpl = new Bitmap(
factory,
@ -71,7 +65,8 @@ namespace Avalonia.Direct2D1.Media
BitmapCreateCacheOption.CacheOnLoad);
}
public WicBitmapImpl(ImagingFactory factory, Platform.PixelFormat format, IntPtr data, int width, int height, int stride)
public WicBitmapImpl(ImagingFactory factory, APixelFormat format, IntPtr data, int width, int height, int stride)
: base(factory)
{
WicImpl = new Bitmap(factory, width, height, format.ToWic(), BitmapCreateCacheOption.CacheOnDemand);
PixelFormat = format;
@ -112,41 +107,23 @@ namespace Avalonia.Direct2D1.Media
/// </summary>
/// <param name="renderTarget">The render target.</param>
/// <returns>The Direct2D bitmap.</returns>
public override SharpDX.Direct2D1.Bitmap GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget renderTarget)
{
FormatConverter converter = new FormatConverter(_factory);
FormatConverter converter = new FormatConverter(WicImagingFactory);
converter.Initialize(WicImpl, SharpDX.WIC.PixelFormat.Format32bppPBGRA);
return SharpDX.Direct2D1.Bitmap.FromWicBitmap(renderTarget, converter);
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
/// <summary>
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public override void Save(string fileName)
public override void Save(Stream stream)
{
if (Path.GetExtension(fileName) != ".png")
{
// Yeah, we need to support other formats.
throw new NotSupportedException("Use PNG, stoopid.");
}
using (FileStream s = new FileStream(fileName, FileMode.Create))
using (var encoder = new PngBitmapEncoder(WicImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
{
Save(s);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
}
public override void Save(Stream stream)
{
PngBitmapEncoder encoder = new PngBitmapEncoder(_factory);
encoder.Initialize(stream);
BitmapFrameEncode frame = new BitmapFrameEncode(encoder);
frame.Initialize();
frame.WriteSource(WicImpl);
frame.Commit();
encoder.Commit();
}
}
}

17
src/Windows/Avalonia.Direct2D1/Media/Imaging/RenderTargetBitmapImpl.cs → src/Windows/Avalonia.Direct2D1/Media/Imaging/WicRenderTargetBitmapImpl.cs

@ -10,20 +10,21 @@ using DirectWriteFactory = SharpDX.DirectWrite.Factory;
namespace Avalonia.Direct2D1.Media
{
public class RenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
public class WicRenderTargetBitmapImpl : WicBitmapImpl, IRenderTargetBitmapImpl
{
private readonly DirectWriteFactory _dwriteFactory;
private readonly WicRenderTarget _target;
public RenderTargetBitmapImpl(
public WicRenderTargetBitmapImpl(
ImagingFactory imagingFactory,
Factory d2dFactory,
DirectWriteFactory dwriteFactory,
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, null, _target, _dwriteFactory, WicImagingFactory,
finishedCallback: finishedCallback);
}
}
}

22
src/Windows/Avalonia.Direct2D1/OptionalDispose.cs

@ -0,0 +1,22 @@
using System;
namespace Avalonia.Direct2D1
{
public struct OptionalDispose<T> : IDisposable where T : IDisposable
{
private readonly bool _dispose;
public OptionalDispose(T value, bool dispose)
{
Value = value;
_dispose = dispose;
}
public T Value { get; }
public void Dispose()
{
if (_dispose) Value?.Dispose();
}
}
}

34
src/Windows/Avalonia.Direct2D1/RenderTarget.cs

@ -3,14 +3,16 @@
using System;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering;
using SharpDX.Direct2D1;
using DwFactory = SharpDX.DirectWrite.Factory;
using WicFactory = SharpDX.WIC.ImagingFactory;
namespace Avalonia.Direct2D1
{
public class RenderTarget : IRenderTarget
public class RenderTarget : IRenderTarget, ILayerFactory
{
/// <summary>
/// The render target.
@ -25,24 +27,13 @@ namespace Avalonia.Direct2D1
{
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<DwFactory>();
WicFactory = AvaloniaLocator.Current.GetService<WicFactory>();
_renderTarget = renderTarget;
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public DwFactory DirectWriteFactory
{
get;
}
public Factory Direct2DFactory { get; }
public DwFactory DirectWriteFactory { get; }
public WicFactory WicFactory { get; }
/// <summary>
/// Creates a drawing context for a rendering session.
@ -50,7 +41,16 @@ namespace Avalonia.Direct2D1
/// <returns>An <see cref="Avalonia.Platform.IDrawingContextImpl"/>.</returns>
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
return new DrawingContextImpl(visualBrushRenderer, _renderTarget, DirectWriteFactory);
return new DrawingContextImpl(visualBrushRenderer, this, _renderTarget, DirectWriteFactory, WicFactory);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicFactory,
DirectWriteFactory,
_renderTarget,
size);
}
public void Dispose()

40
src/Windows/Avalonia.Direct2D1/SwapChainRenderTarget.cs

@ -10,10 +10,11 @@ using Factory = SharpDX.Direct2D1.Factory;
using Factory2 = SharpDX.DXGI.Factory2;
using Avalonia.Rendering;
using Avalonia.Direct2D1.Media;
using Avalonia.Direct2D1.Media.Imaging;
namespace Avalonia.Direct2D1
{
public abstract class SwapChainRenderTarget : IRenderTarget
public abstract class SwapChainRenderTarget : IRenderTarget, ILayerFactory
{
private Size2 _savedSize;
private Size2F _savedDpi;
@ -26,24 +27,12 @@ namespace Avalonia.Direct2D1
D2DDevice = AvaloniaLocator.Current.GetService<Device>();
Direct2DFactory = AvaloniaLocator.Current.GetService<Factory>();
DirectWriteFactory = AvaloniaLocator.Current.GetService<SharpDX.DirectWrite.Factory>();
WicImagingFactory = AvaloniaLocator.Current.GetService<SharpDX.WIC.ImagingFactory>();
}
/// <summary>
/// Gets the Direct2D factory.
/// </summary>
public Factory Direct2DFactory
{
get;
}
/// <summary>
/// Gets the DirectWrite factory.
/// </summary>
public SharpDX.DirectWrite.Factory DirectWriteFactory
{
get;
}
public Factory Direct2DFactory { get; }
public SharpDX.DirectWrite.Factory DirectWriteFactory { get; }
public SharpDX.WIC.ImagingFactory WicImagingFactory { get; }
protected SharpDX.DXGI.Device DxgiDevice { get; }
@ -67,11 +56,27 @@ namespace Avalonia.Direct2D1
return new DrawingContextImpl(
visualBrushRenderer,
this,
_deviceContext,
DirectWriteFactory,
WicImagingFactory,
_swapChain);
}
public IRenderTargetBitmapImpl CreateLayer(Size size)
{
if (_deviceContext == null)
{
CreateSwapChain();
}
return D2DRenderTargetBitmapImpl.CreateCompatible(
WicImagingFactory,
DirectWriteFactory,
_deviceContext,
size);
}
public void Dispose()
{
_deviceContext?.Dispose();
@ -86,7 +91,6 @@ namespace Avalonia.Direct2D1
_deviceContext?.Dispose();
_deviceContext = new DeviceContext(D2DDevice, DeviceContextOptions.None) {DotsPerInch = _savedDpi};
var swapChainDesc = new SwapChainDescription1
{
Width = _savedSize.Width,

2
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -114,7 +114,7 @@ namespace Avalonia.Win32
}
}
public IDisposable StartTimer(TimeSpan interval, Action callback)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action callback)
{
UnmanagedMethods.TimerProc timerDelegate =
(hWnd, uMsg, nIDEvent, dwTime) => callback();

2
src/iOS/Avalonia.iOS/PlatformThreadingInterface.cs

@ -51,7 +51,7 @@ namespace Avalonia.iOS
}
}*/
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, _ => tick());
public void Signal(DispatcherPriority prio)

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.Base.UnitTests/AvaloniaObjectTests_Threading.cs

@ -160,7 +160,7 @@ namespace Avalonia.Base.UnitTests
throw new NotImplementedException();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
throw new NotImplementedException();
}

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

2
tests/Avalonia.RenderTests/TestBase.cs

@ -161,7 +161,7 @@ namespace Avalonia.Direct2D1.RenderTests
throw new NotImplementedException();
}
public IDisposable StartTimer(TimeSpan interval, Action tick)
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
throw new NotImplementedException();
}

2
tests/Avalonia.UnitTests/TestRoot.cs

@ -49,8 +49,6 @@ namespace Avalonia.UnitTests
public ILayoutManager LayoutManager => AvaloniaLocator.Current.GetService<ILayoutManager>();
public IRenderTarget RenderTarget => null;
public IRenderer Renderer { get; set; }
public IAccessKeyHandler AccessKeyHandler => null;

70
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -30,7 +30,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: MockSceneBuilder(root).Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher.Object);
target.Start();
@ -55,7 +54,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -75,7 +73,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -111,7 +108,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: MockLayerFactory(root).Object,
dispatcher: dispatcher);
target.Start();
@ -146,22 +142,20 @@ namespace Avalonia.Visuals.UnitTests.Rendering
scene.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize));
});
var layers = new Mock<IRenderLayerFactory>();
layers.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
var renderInterface = new Mock<IPlatformRenderInterface>();
var target = new DeferredRenderer(
root,
loop.Object,
sceneBuilder: sceneBuilder.Object,
layerFactory: layers.Object,
//layerFactory: layers.Object,
dispatcher: dispatcher);
target.Start();
RunFrame(loop);
layers.Verify(x => x.CreateLayer(root, root.ClientSize, 96, 96));
var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
context.Verify(x => x.CreateLayer(root.ClientSize));
}
[Fact]
@ -185,25 +179,25 @@ namespace Avalonia.Visuals.UnitTests.Rendering
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
var loop = new Mock<IRenderLoop>();
var layerFactory = new MockRenderLayerFactory(new Dictionary<IVisual, IRenderTargetBitmapImpl>
{
{ root, CreateLayer() },
{ border, CreateLayer() },
});
var rootLayer = CreateLayer();
var borderLayer = CreateLayer();
var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny<Size>()))
.Returns(rootLayer)
.Returns(borderLayer);
var loop = new Mock<IRenderLoop>();
var target = new DeferredRenderer(
root,
root,
loop.Object,
layerFactory: layerFactory,
dispatcher: new ImmediateDispatcher());
root.Renderer = target;
target.Start();
RunFrame(loop);
var rootContext = layerFactory.GetMockDrawingContext(root);
var borderContext = layerFactory.GetMockDrawingContext(border);
var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
@ -223,7 +217,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
border.Opacity = 1;
RunFrame(loop);
layerFactory.GetMockBitmap(border).Verify(x => x.Dispose());
Mock.Get(borderLayer).Verify(x => x.Dispose());
rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
borderContext.Verify(x => x.FillRectangle(It.IsAny<IBrush>(), It.IsAny<Rect>(), It.IsAny<float>()), Times.Never);
@ -246,13 +240,6 @@ namespace Avalonia.Visuals.UnitTests.Rendering
x.CreateDrawingContext(It.IsAny<IVisualBrushRenderer>()) == Mock.Of<IDrawingContextImpl>());
}
private Mock<IRenderLayerFactory> MockLayerFactory(IRenderRoot root)
{
var result = new Mock<IRenderLayerFactory>();
result.Setup(x => x.CreateLayer(root, root.ClientSize, 96, 96)).Returns(CreateLayer());
return result;
}
private Mock<ISceneBuilder> MockSceneBuilder(IRenderRoot root)
{
var result = new Mock<ISceneBuilder>();
@ -260,34 +247,5 @@ namespace Avalonia.Visuals.UnitTests.Rendering
.Callback<Scene>(x => x.Layers.Add(root).Dirty.Add(new Rect(root.ClientSize)));
return result;
}
private class MockRenderLayerFactory : IRenderLayerFactory
{
private IDictionary<IVisual, IRenderTargetBitmapImpl> _layers;
public MockRenderLayerFactory(IDictionary<IVisual, IRenderTargetBitmapImpl> layers)
{
_layers = layers;
}
public IRenderTargetBitmapImpl CreateLayer(
IVisual layerRoot,
Size size,
double dpiX,
double dpiY)
{
return _layers[layerRoot];
}
public Mock<IRenderTargetBitmapImpl> GetMockBitmap(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot]);
}
public Mock<IDrawingContextImpl> GetMockDrawingContext(IVisual layerRoot)
{
return Mock.Get(_layers[layerRoot].CreateDrawingContext(null));
}
}
}
}

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