Browse Source

Merge branch 'master' into fixes/2350-stackpanel-layout

pull/2431/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
71f4cb93fb
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      azure-pipelines.yml
  2. 6
      build/SharedVersion.props
  3. 8
      nukebuild/Build.cs
  4. 2
      nukebuild/Numerge
  5. 1
      samples/BindingDemo/App.xaml.cs
  6. 1
      samples/ControlCatalog.Desktop/Program.cs
  7. 1
      samples/ControlCatalog.NetCore/Program.cs
  8. 1
      samples/RenderDemo/App.xaml.cs
  9. 3
      samples/RenderDemo/MainWindow.xaml
  10. 119
      samples/RenderDemo/Pages/CustomSkiaPage.cs
  11. 1
      samples/VirtualizationDemo/Program.cs
  12. 2
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  13. 4
      src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs
  14. 3
      src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs
  15. 69
      src/Avalonia.Controls/Button.cs
  16. 21
      src/Avalonia.Controls/Grid.cs
  17. 4
      src/Avalonia.Controls/ListBox.cs
  18. 35
      src/Avalonia.Controls/MenuItem.cs
  19. 5
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  20. 4
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  21. 72
      src/Avalonia.Controls/Shapes/Shape.cs
  22. 8
      src/Avalonia.Controls/TreeView.cs
  23. 14
      src/Avalonia.Controls/Window.cs
  24. 7
      src/Avalonia.ReactiveUI/AppBuilderExtensions.cs
  25. 8
      src/Avalonia.ReactiveUI/Attributes.cs
  26. 18
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  27. 2
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  28. 2
      src/Avalonia.ReactiveUI/ReactiveWindow.cs
  29. 8
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  30. 1
      src/Avalonia.Styling/StyledElement.cs
  31. 26
      src/Avalonia.Themes.Default/ScrollBar.xaml
  32. 10
      src/Avalonia.Visuals/Media/BrushExtensions.cs
  33. 7
      src/Avalonia.Visuals/Media/DrawingContext.cs
  34. 31
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  35. 10
      src/Avalonia.Visuals/Media/LineGeometry.cs
  36. 72
      src/Avalonia.Visuals/Media/Pen.cs
  37. 3
      src/Avalonia.Visuals/Media/PenLineCap.cs
  38. 28
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  39. 7
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  40. 22
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  41. 39
      src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs
  42. 9
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  43. 3
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  44. 2
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  45. 42
      src/Skia/Avalonia.Skia/CustomRenderTarget.cs
  46. 26
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  47. 25
      src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs
  48. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  49. 2
      src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs
  50. 2
      src/Skia/Avalonia.Skia/GeometryImpl.cs
  51. 2
      src/Skia/Avalonia.Skia/GlRenderTarget.cs
  52. 26
      src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs
  53. 29
      src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs
  54. 19
      src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs
  55. 10
      src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs
  56. 2
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  57. 29
      src/Skia/Avalonia.Skia/LineGeometryImpl.cs
  58. 38
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  59. 25
      src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs
  60. 5
      src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs
  61. 19
      src/Skia/Avalonia.Skia/SkiaOptions.cs
  62. 10
      src/Skia/Avalonia.Skia/SkiaPlatform.cs
  63. 2
      src/Skia/Avalonia.Skia/StreamGeometryImpl.cs
  64. 2
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  65. 2
      src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs
  66. 2
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  67. 8
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  68. 3
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  69. 27
      src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs
  70. 2
      src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs
  71. 27
      src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs
  72. 26
      src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs
  73. 8
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  74. 2
      src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs
  75. 46
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  76. 54
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  77. 45
      tests/Avalonia.Controls.UnitTests/WindowTests.cs
  78. 1
      tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs
  79. 1
      tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs
  80. 8
      tests/Avalonia.RenderTests/Shapes/PathTests.cs
  81. 3
      tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
  82. 15
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  83. 15
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
  84. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png
  85. BIN
      tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png

4
azure-pipelines.yml

@ -32,7 +32,7 @@ jobs:
- job: macOS
pool:
vmImage: 'xcode9-macos10.13'
vmImage: 'macOS-10.14'
steps:
- task: DotNetCoreInstaller@0
inputs:
@ -49,7 +49,7 @@ jobs:
inputs:
actions: 'build'
scheme: ''
sdk: 'macosx10.13'
sdk: 'macosx10.14'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: 'default' # Options: 8, 9, default, specifyPath

6
build/SharedVersion.props

@ -2,8 +2,8 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.7.1</Version>
<Copyright>Copyright 2018 &#169; The AvaloniaUI Project</Copyright>
<Version>0.8.1</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
@ -11,4 +11,4 @@
<NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion>
</PropertyGroup>
</Project>
</Project>

8
nukebuild/Build.cs

@ -122,6 +122,14 @@ partial class Build : NukeBuild
foreach(var fw in frameworks)
{
if (fw.StartsWith("net4")
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
&& Environment.GetEnvironmentVariable("FORCE_LINUX_TESTS") != "1")
{
Information($"Skipping {fw} tests on Linux - https://github.com/mono/mono/issues/13969");
continue;
}
Information("Running for " + fw);
DotNetTest(c =>
{

2
nukebuild/Numerge

@ -1 +1 @@
Subproject commit 4464343aef5c8ab7a42fcb20a483a6058199f8b8
Subproject commit aef10ae67dc55c95f49b52a505a0be33bfa297a5

1
samples/BindingDemo/App.xaml.cs

@ -3,6 +3,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using Serilog;
namespace BindingDemo

1
samples/ControlCatalog.Desktop/Program.cs

@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.Platform;
using Avalonia.ReactiveUI;
using Serilog;
namespace ControlCatalog

1
samples/ControlCatalog.NetCore/Program.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Skia;
using Avalonia.ReactiveUI;
namespace ControlCatalog.NetCore
{

1
samples/RenderDemo/App.xaml.cs

@ -4,6 +4,7 @@
using Avalonia;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace RenderDemo
{

3
samples/RenderDemo/MainWindow.xaml

@ -33,6 +33,9 @@
<TabItem Header="Drawing">
<pages:DrawingPage/>
</TabItem>
<TabItem Header="SkCanvas">
<pages:CustomSkiaPage/>
</TabItem>
</TabControl>
</DockPanel>
</Window>

119
samples/RenderDemo/Pages/CustomSkiaPage.cs

@ -0,0 +1,119 @@
using System;
using System.Diagnostics;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Skia;
using Avalonia.Threading;
using SkiaSharp;
namespace RenderDemo.Pages
{
public class CustomSkiaPage : Control
{
public CustomSkiaPage()
{
ClipToBounds = true;
}
class CustomDrawOp : ICustomDrawOperation
{
private readonly FormattedText _noSkia;
public CustomDrawOp(Rect bounds, FormattedText noSkia)
{
_noSkia = noSkia;
Bounds = bounds;
}
public void Dispose()
{
// No-op
}
public Rect Bounds { get; }
public bool HitTest(Point p) => false;
public bool Equals(ICustomDrawOperation other) => false;
static Stopwatch St = Stopwatch.StartNew();
public void Render(IDrawingContextImpl context)
{
var canvas = (context as ISkiaDrawingContextImpl)?.SkCanvas;
if (canvas == null)
context.DrawText(Brushes.Black, new Point(), _noSkia.PlatformImpl);
else
{
canvas.Save();
// create the first shader
var colors = new SKColor[] {
new SKColor(0, 255, 255),
new SKColor(255, 0, 255),
new SKColor(255, 255, 0),
new SKColor(0, 255, 255)
};
var sx = Animate(100, 2, 10);
var sy = Animate(1000, 5, 15);
var lightPosition = new SKPoint(
(float)(Bounds.Width / 2 + Math.Cos(St.Elapsed.TotalSeconds) * Bounds.Width / 4),
(float)(Bounds.Height / 2 + Math.Sin(St.Elapsed.TotalSeconds) * Bounds.Height / 4));
using (var sweep =
SKShader.CreateSweepGradient(new SKPoint((int)Bounds.Width / 2, (int)Bounds.Height / 2), colors,
null))
using(var turbulence = SKShader.CreatePerlinNoiseFractalNoise(0.05f, 0.05f, 4, 0))
using(var shader = SKShader.CreateCompose(sweep, turbulence, SKBlendMode.SrcATop))
using(var blur = SKImageFilter.CreateBlur(Animate(100, 2, 10), Animate(100, 5, 15)))
using (var paint = new SKPaint
{
Shader = shader,
ImageFilter = blur
})
canvas.DrawPaint(paint);
using (var pseudoLight = SKShader.CreateRadialGradient(
lightPosition,
(float) (Bounds.Width/3),
new [] {
new SKColor(255, 200, 200, 100),
SKColors.Transparent,
new SKColor(40,40,40, 220),
new SKColor(20,20,20, (byte)Animate(100, 200,220)) },
new float[] { 0.3f, 0.3f, 0.8f, 1 },
SKShaderTileMode.Clamp))
using (var paint = new SKPaint
{
Shader = pseudoLight
})
canvas.DrawPaint(paint);
canvas.Restore();
}
}
static int Animate(int d, int from, int to)
{
var ms = (int)(St.ElapsedMilliseconds / d);
var diff = to - from;
var range = diff * 2;
var v = ms % range;
if (v > diff)
v = range - v;
var rv = v + from;
if (rv < from || rv > to)
throw new Exception("WTF");
return rv;
}
}
public override void Render(DrawingContext context)
{
var noSkia = new FormattedText()
{
Text = "Current rendering API is not Skia"
};
context.Custom(new CustomDrawOp(new Rect(0, 0, Bounds.Width, Bounds.Height), noSkia));
Dispatcher.UIThread.InvokeAsync(InvalidateVisual, DispatcherPriority.Background);
}
}
}

1
samples/VirtualizationDemo/Program.cs

@ -5,6 +5,7 @@ using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Avalonia.ReactiveUI;
using Serilog;
namespace VirtualizationDemo

2
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -19,6 +19,7 @@ namespace Avalonia.Utilities
/// </summary>
/// <typeparam name="TTarget">The type of the target.</typeparam>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <typeparam name="TSubscriber">The type of the subscriber.</typeparam>
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>
@ -40,6 +41,7 @@ namespace Avalonia.Utilities
/// Unsubscribes from an event.
/// </summary>
/// <typeparam name="TEventArgs">The type of the event arguments.</typeparam>
/// <typeparam name="TSubscriber">The type of the subscriber.</typeparam>
/// <param name="target">The event source.</param>
/// <param name="eventName">The name of the event.</param>
/// <param name="subscriber">The subscriber.</param>

4
src/Avalonia.Controls.DataGrid/Collections/DataGridCollectionView.cs

@ -927,7 +927,7 @@ namespace Avalonia.Collections
/// <remarks>
/// <p>
/// Clear a sort criteria by assigning SortDescription.Empty to this property.
/// One or more sort criteria in form of <seealso cref="SortDescription"/>
/// One or more sort criteria in form of <seealso cref="DataGridSortDescription"/>
/// can be used, each specifying a property and direction to sort by.
/// </p>
/// </remarks>
@ -4312,4 +4312,4 @@ namespace Avalonia.Collections
}
}
}
}
}

3
src/Avalonia.Controls.DataGrid/Properties/AssemblyInfo.cs

@ -8,7 +8,6 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Collections")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Primitives")]

69
src/Avalonia.Controls/Button.cs

@ -7,6 +7,7 @@ using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -160,6 +161,40 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (IsDefault)
{
if (e.Root is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged -= CanExecuteChanged;
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -195,20 +230,6 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (IsDefault)
{
if (e.Root is IInputElement inputElement)
{
StopListeningForDefault(inputElement);
}
}
}
/// <summary>
/// Invokes the <see cref="Click"/> event.
/// </summary>
@ -281,17 +302,17 @@ namespace Avalonia.Controls
{
if (e.Sender is Button button)
{
var oldCommand = e.OldValue as ICommand;
var newCommand = e.NewValue as ICommand;
if (oldCommand != null)
{
oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
}
if (newCommand != null)
if (((ILogical)button).IsAttachedToLogicalTree)
{
newCommand.CanExecuteChanged += button.CanExecuteChanged;
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= button.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += button.CanExecuteChanged;
}
}
button.CanExecuteChanged(button, EventArgs.Empty);

21
src/Avalonia.Controls/Grid.cs

@ -177,6 +177,17 @@ namespace Avalonia.Controls
return element.GetValue(RowSpanProperty);
}
/// <summary>
/// Gets the value of the IsSharedSizeScope attached property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <returns>The control's IsSharedSizeScope value.</returns>
public static bool GetIsSharedSizeScope(AvaloniaObject element)
{
return element.GetValue(IsSharedSizeScopeProperty);
}
/// <summary>
/// Sets the value of the Column attached property for a control.
/// </summary>
@ -217,6 +228,16 @@ namespace Avalonia.Controls
element.SetValue(RowSpanProperty, value);
}
/// <summary>
/// Sets the value of IsSharedSizeScope property for a control.
/// </summary>
/// <param name="element">The control.</param>
/// <param name="value">The IsSharedSizeScope value.</param>
public static void SetIsSharedSizeScope(AvaloniaObject element, bool value)
{
element.SetValue(IsSharedSizeScopeProperty, value);
}
/// <summary>
/// Gets the result of the last column measurement.
/// Use this result to reduce the arrange calculation.

4
src/Avalonia.Controls/ListBox.cs

@ -30,13 +30,13 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="SelectedItems"/> property.
/// </summary>
public static readonly new AvaloniaProperty<IList> SelectedItemsProperty =
public static readonly new DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
SelectingItemsControl.SelectedItemsProperty;
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
public static readonly new AvaloniaProperty<SelectionMode> SelectionModeProperty =
public static readonly new StyledProperty<SelectionMode> SelectionModeProperty =
SelectingItemsControl.SelectionModeProperty;
/// <summary>

35
src/Avalonia.Controls/MenuItem.cs

@ -286,6 +286,26 @@ namespace Avalonia.Controls
return new MenuItemContainerGenerator(this);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged += CanExecuteChanged;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
if (Command != null)
{
Command.CanExecuteChanged -= CanExecuteChanged;
}
}
/// <summary>
/// Called when the <see cref="MenuItem"/> is clicked.
/// </summary>
@ -399,14 +419,17 @@ namespace Avalonia.Controls
{
if (e.Sender is MenuItem menuItem)
{
if (e.OldValue is ICommand oldCommand)
if (((ILogical)menuItem).IsAttachedToLogicalTree)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);

5
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -147,7 +147,10 @@ namespace Avalonia.Platform
e.Handled = true;
}
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
}
}
private void ProcessMouseEvents(RawMouseEventArgs e)

4
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -91,12 +91,12 @@ namespace Avalonia.Controls.Primitives
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - screenX - bounds.Width);
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - screenY - bounds.Height);
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
}

72
src/Avalonia.Controls/Shapes/Shape.cs

@ -21,13 +21,19 @@ namespace Avalonia.Controls.Shapes
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
AvaloniaProperty.Register<Shape, AvaloniaList<double>>(nameof(StrokeDashArray));
public static readonly StyledProperty<double> StrokeDashOffsetProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeDashOffset));
public static readonly StyledProperty<double> StrokeThicknessProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeThickness));
public static readonly StyledProperty<PenLineCap> StrokeLineCapProperty =
AvaloniaProperty.Register<Shape, PenLineCap>(nameof(StrokeLineCap), PenLineCap.Flat);
public static readonly StyledProperty<PenLineJoin> StrokeJoinProperty =
AvaloniaProperty.Register<Shape, PenLineJoin>(nameof(StrokeJoin), PenLineJoin.Miter);
private Matrix _transform = Matrix.Identity;
private Geometry _definingGeometry;
private Geometry _renderedGeometry;
@ -36,7 +42,9 @@ namespace Avalonia.Controls.Shapes
static Shape()
{
AffectsMeasure<Shape>(StretchProperty, StrokeThicknessProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty);
AffectsRender<Shape>(FillProperty, StrokeProperty, StrokeDashArrayProperty, StrokeDashOffsetProperty,
StrokeThicknessProperty, StrokeLineCapProperty, StrokeJoinProperty);
}
public Geometry DefiningGeometry
@ -106,7 +114,7 @@ namespace Avalonia.Controls.Shapes
get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
public double StrokeDashOffset
{
get { return GetValue(StrokeDashOffsetProperty); }
@ -119,13 +127,17 @@ namespace Avalonia.Controls.Shapes
set { SetValue(StrokeThicknessProperty, value); }
}
public PenLineCap StrokeDashCap { get; set; } = PenLineCap.Flat;
public PenLineCap StrokeStartLineCap { get; set; } = PenLineCap.Flat;
public PenLineCap StrokeEndLineCap { get; set; } = PenLineCap.Flat;
public PenLineCap StrokeLineCap
{
get { return GetValue(StrokeLineCapProperty); }
set { SetValue(StrokeLineCapProperty, value); }
}
public PenLineJoin StrokeJoin { get; set; } = PenLineJoin.Miter;
public PenLineJoin StrokeJoin
{
get { return GetValue(StrokeJoinProperty); }
set { SetValue(StrokeJoinProperty, value); }
}
public override void Render(DrawingContext context)
{
@ -133,8 +145,8 @@ namespace Avalonia.Controls.Shapes
if (geometry != null)
{
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset),
StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin);
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset),
StrokeLineCap, StrokeJoin);
context.DrawGeometry(Fill, pen, geometry);
}
}
@ -169,11 +181,11 @@ namespace Avalonia.Controls.Shapes
protected void InvalidateGeometry()
{
this._renderedGeometry = null;
this._definingGeometry = null;
_renderedGeometry = null;
_definingGeometry = null;
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
bool deferCalculateTransform;
@ -203,10 +215,10 @@ namespace Avalonia.Controls.Shapes
return CalculateShapeSizeAndSetTransform(availableSize);
}
}
protected override Size ArrangeOverride(Size finalSize)
{
if(_calculateTransformOnArrange)
if (_calculateTransformOnArrange)
{
_calculateTransformOnArrange = false;
CalculateShapeSizeAndSetTransform(finalSize);
@ -312,25 +324,25 @@ namespace Avalonia.Controls.Shapes
private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)
{
var control = e.Sender as Shape;
if (!(e.Sender is Shape control))
{
return;
}
if (control != null)
// If the geometry is invalidated when Bounds changes, only invalidate when the Size
// portion changes.
if (e.Property == BoundsProperty)
{
// If the geometry is invalidated when Bounds changes, only invalidate when the Size
// portion changes.
if (e.Property == BoundsProperty)
{
var oldBounds = (Rect)e.OldValue;
var newBounds = (Rect)e.NewValue;
var oldBounds = (Rect)e.OldValue;
var newBounds = (Rect)e.NewValue;
if (oldBounds.Size == newBounds.Size)
{
return;
}
if (oldBounds.Size == newBounds.Size)
{
return;
}
control.InvalidateGeometry();
}
control.InvalidateGeometry();
}
}
}

8
src/Avalonia.Controls/TreeView.cs

@ -40,17 +40,15 @@ namespace Avalonia.Controls
/// Defines the <see cref="SelectedItems"/> property.
/// </summary>
public static readonly DirectProperty<TreeView, IList> SelectedItemsProperty =
AvaloniaProperty.RegisterDirect<TreeView, IList>(
nameof(SelectedItems),
ListBox.SelectedItemsProperty.AddOwner<TreeView>(
o => o.SelectedItems,
(o, v) => o.SelectedItems = v);
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
protected static readonly StyledProperty<SelectionMode> SelectionModeProperty =
AvaloniaProperty.Register<SelectingItemsControl, SelectionMode>(
nameof(SelectionMode));
public static readonly StyledProperty<SelectionMode> SelectionModeProperty =
ListBox.SelectionModeProperty.AddOwner<TreeView>();
private static readonly IList Empty = new object[0];
private object _selectedItem;

14
src/Avalonia.Controls/Window.cs

@ -291,7 +291,8 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="dialogResult">The dialog result.</param>
/// <remarks>
/// When the window is shown with the <see cref="ShowDialog{TResult}"/> method, the
/// When the window is shown with the <see cref="ShowDialog{TResult}(IWindowImpl)"/>
/// or <see cref="ShowDialog{TResult}(Window)"/> method, the
/// resulting task will produce the <see cref="_dialogResult"/> value when the window
/// is closed.
/// </remarks>
@ -370,8 +371,16 @@ namespace Avalonia.Controls
/// <summary>
/// Shows the window.
/// </summary>
/// <exception cref="InvalidOperationException">
/// The window has already been closed.
/// </exception>
public override void Show()
{
if (PlatformImpl == null)
{
throw new InvalidOperationException("Cannot re-show a closed window.");
}
if (IsVisible)
{
return;
@ -396,6 +405,9 @@ namespace Avalonia.Controls
/// Shows the window as a dialog.
/// </summary>
/// <param name="owner">The dialog's owner window.</param>
/// <exception cref="InvalidOperationException">
/// The window has already been closed.
/// </exception>
/// <returns>
/// A task that can be used to track the lifetime of the dialog.
/// </returns>

7
src/Avalonia.ReactiveUI/AppBuilderExtensions.cs

@ -6,10 +6,15 @@ using Avalonia.Threading;
using ReactiveUI;
using Splat;
namespace Avalonia
namespace Avalonia.ReactiveUI
{
public static class AppBuilderExtensions
{
/// <summary>
/// Initializes ReactiveUI framework to use with Avalonia. Registers Avalonia
/// scheduler and Avalonia activation for view fetcher. Always remember to
/// call this method if you are using ReactiveUI in your application.
/// </summary>
public static TAppBuilder UseReactiveUI<TAppBuilder>(this TAppBuilder builder)
where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{

8
src/Avalonia.ReactiveUI/Attributes.cs

@ -0,0 +1,8 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("http://reactiveui.net", "Avalonia.ReactiveUI")]

18
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -9,15 +9,24 @@ using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
namespace Avalonia
namespace Avalonia.ReactiveUI
{
/// <summary>
/// Determines when Avalonia IVisuals get activated.
/// </summary>
public class AvaloniaActivationForViewFetcher : IActivationForViewFetcher
{
/// <summary>
/// Returns affinity for view.
/// </summary>
public int GetAffinityForView(Type view)
{
return typeof(IVisual).GetTypeInfo().IsAssignableFrom(view.GetTypeInfo()) ? 10 : 0;
}
/// <summary>
/// Returns activation observable for activatable Avalonia view.
/// </summary>
public IObservable<bool> GetActivationForView(IActivatable view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
@ -25,6 +34,9 @@ namespace Avalonia
return GetActivationForVisual(visual);
}
/// <summary>
/// Listens to Opened and Closed events for Avalonia windows.
/// </summary>
private IObservable<bool> GetActivationForWindowBase(WindowBase window)
{
var windowLoaded = Observable
@ -42,6 +54,10 @@ namespace Avalonia
.DistinctUntilChanged();
}
/// <summary>
/// Listens to AttachedToVisualTree and DetachedFromVisualTree
/// events for Avalonia IVisuals.
/// </summary>
private IObservable<bool> GetActivationForVisual(IVisual visual)
{
var visualLoaded = Observable

2
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -6,7 +6,7 @@ using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
namespace Avalonia
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI UserControl that implements <see cref="IViewFor{TViewModel}"/>

2
src/Avalonia.ReactiveUI/ReactiveWindow.cs

@ -6,7 +6,7 @@ using Avalonia.VisualTree;
using Avalonia.Controls;
using ReactiveUI;
namespace Avalonia
namespace Avalonia.ReactiveUI
{
/// <summary>
/// A ReactiveUI Window that implements <see cref="IViewFor{TViewModel}"/>

8
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -1,13 +1,17 @@
// 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.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia;
using ReactiveUI;
using Splat;
namespace Avalonia
namespace Avalonia.ReactiveUI
{
/// <summary>
/// This control hosts the View associated with ReactiveUI RoutingState,
@ -157,7 +161,7 @@ namespace Avalonia
return;
}
var viewLocator = ViewLocator ?? ReactiveUI.ViewLocator.Current;
var viewLocator = ViewLocator ?? global::ReactiveUI.ViewLocator.Current;
var view = viewLocator.ResolveView(viewModel);
if (view == null) throw new Exception($"Couldn't find view for '{viewModel}'. Is it registered?");

1
src/Avalonia.Styling/StyledElement.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;

26
src/Avalonia.Themes.Default/ScrollBar.xaml

@ -30,13 +30,7 @@
Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton>
<Thumb Name="thumb">
<Thumb.Template>
<ControlTemplate>
<Border Background="{DynamicResource ThemeControlHighBrush}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Thumb Name="thumb"/>
</Track>
<RepeatButton Name="PART_LineDownButton"
Classes="repeat"
@ -84,13 +78,7 @@
Classes="repeattrack"
Focusable="False"/>
</Track.IncreaseButton>
<Thumb Name="thumb">
<Thumb.Template>
<ControlTemplate>
<Border Background="{DynamicResource ThemeControlHighBrush}" />
</ControlTemplate>
</Thumb.Template>
</Thumb>
<Thumb Name="thumb"/>
</Track>
<RepeatButton Name="PART_LineDownButton"
Classes="repeat"
@ -106,6 +94,16 @@
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ScrollBar /template/ Thumb#thumb">
<Setter Property="Background" Value="{DynamicResource ThemeControlHighBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style Selector="ScrollBar:horizontal /template/ Thumb#thumb">
<Setter Property="MinWidth" Value="{DynamicResource ScrollBarThickness}" />
</Style>

10
src/Avalonia.Visuals/Media/BrushExtensions.cs

@ -34,16 +34,14 @@ namespace Avalonia.Media
{
Contract.Requires<ArgumentNullException>(pen != null);
var brush = pen?.Brush?.ToImmutable();
return pen == null || ReferenceEquals(pen?.Brush, brush) ?
var brush = pen.Brush?.ToImmutable();
return ReferenceEquals(pen.Brush, brush) ?
pen :
new Pen(
brush,
thickness: pen.Thickness,
dashStyle: pen.DashStyle,
dashCap: pen.DashCap,
startLineCap: pen.StartLineCap,
endLineCap: pen.EndLineCap,
dashStyle: pen.DashStyle,
lineCap: pen.LineCap,
lineJoin: pen.LineJoin,
miterLimit: pen.MiterLimit);
}

7
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Visuals.Media.Imaging;
@ -131,6 +132,12 @@ namespace Avalonia.Media
}
}
/// <summary>
/// Draws a custom drawing operation
/// </summary>
/// <param name="custom">custom operation</param>
public void Custom(ICustomDrawOperation custom) => PlatformImpl.Custom(custom);
/// <summary>
/// Draws text.
/// </summary>

31
src/Avalonia.Visuals/Media/EllipseGeometry.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Platform;
namespace Avalonia.Media
@ -57,36 +56,8 @@ namespace Avalonia.Media
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var geometry = factory.CreateStreamGeometry();
using (var ctx = geometry.Open())
{
var rect = Rect;
double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
var center = rect.Center;
var radius = new Vector(rect.Width / 2, rect.Height / 2);
var x0 = center.X - radius.X;
var x1 = center.X - (radius.X * controlPointRatio);
var x2 = center.X;
var x3 = center.X + (radius.X * controlPointRatio);
var x4 = center.X + radius.X;
var y0 = center.Y - radius.Y;
var y1 = center.Y - (radius.Y * controlPointRatio);
var y2 = center.Y;
var y3 = center.Y + (radius.Y * controlPointRatio);
var y4 = center.Y + radius.Y;
ctx.BeginFigure(new Point(x2, y0), true);
ctx.CubicBezierTo(new Point(x3, y0), new Point(x4, y1), new Point(x4, y2));
ctx.CubicBezierTo(new Point(x4, y3), new Point(x3, y4), new Point(x2, y4));
ctx.CubicBezierTo(new Point(x1, y4), new Point(x0, y3), new Point(x0, y2));
ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
ctx.EndFigure(true);
}
return geometry;
return factory.CreateEllipseGeometry(Rect);
}
}
}

10
src/Avalonia.Visuals/Media/LineGeometry.cs

@ -73,16 +73,8 @@ namespace Avalonia.Media
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var geometry = factory.CreateStreamGeometry();
using (var context = geometry.Open())
{
context.BeginFigure(StartPoint, false);
context.LineTo(EndPoint);
context.EndFigure(false);
}
return geometry;
return factory.CreateLineGeometry(StartPoint, EndPoint);
}
}
}

72
src/Avalonia.Visuals/Media/Pen.cs

@ -11,63 +11,45 @@ namespace Avalonia.Media
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="brush">The brush used to draw.</param>
/// <param name="color">The stroke color.</param>
/// <param name="thickness">The stroke thickness.</param>
/// <param name="dashStyle">The dash style.</param>
/// <param name="dashCap">The dash cap.</param>
/// <param name="startLineCap">The start line cap.</param>
/// <param name="endLineCap">The end line cap.</param>
/// <param name="lineCap">Specifies the type of graphic shape to use on both ends of a line.</param>
/// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param>
public Pen(
IBrush brush,
uint color,
double thickness = 1.0,
DashStyle dashStyle = null,
PenLineCap dashCap = PenLineCap.Flat,
PenLineCap startLineCap = PenLineCap.Flat,
PenLineCap endLineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
DashStyle dashStyle = null,
PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0) : this(new SolidColorBrush(color), thickness, dashStyle, lineCap, lineJoin, miterLimit)
{
Brush = brush;
Thickness = thickness;
DashCap = dashCap;
StartLineCap = startLineCap;
EndLineCap = endLineCap;
LineJoin = lineJoin;
MiterLimit = miterLimit;
DashStyle = dashStyle;
}
/// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary>
/// <param name="color">The stroke color.</param>
/// <param name="brush">The brush used to draw.</param>
/// <param name="thickness">The stroke thickness.</param>
/// <param name="dashStyle">The dash style.</param>
/// <param name="dashCap">The dash cap.</param>
/// <param name="startLineCap">The start line cap.</param>
/// <param name="endLineCap">The end line cap.</param>
/// <param name="lineCap">The line cap.</param>
/// <param name="lineJoin">The line join.</param>
/// <param name="miterLimit">The miter limit.</param>
public Pen(
uint color,
IBrush brush,
double thickness = 1.0,
DashStyle dashStyle = null,
PenLineCap dashCap = PenLineCap.Flat,
PenLineCap startLineCap = PenLineCap.Flat,
PenLineCap endLineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
DashStyle dashStyle = null,
PenLineCap lineCap = PenLineCap.Flat,
PenLineJoin lineJoin = PenLineJoin.Miter,
double miterLimit = 10.0)
{
Brush = new SolidColorBrush(color);
Brush = brush;
Thickness = thickness;
StartLineCap = startLineCap;
EndLineCap = endLineCap;
LineCap = lineCap;
LineJoin = lineJoin;
MiterLimit = miterLimit;
DashStyle = dashStyle;
DashCap = dashCap;
}
/// <summary>
@ -78,18 +60,26 @@ namespace Avalonia.Media
/// <summary>
/// Gets the stroke thickness.
/// </summary>
public double Thickness { get; } = 1.0;
public double Thickness { get; }
/// <summary>
/// Specifies the style of dashed lines drawn with a <see cref="Pen"/> object.
/// </summary>
public DashStyle DashStyle { get; }
public PenLineCap DashCap { get; }
public PenLineCap StartLineCap { get; } = PenLineCap.Flat;
public PenLineCap EndLineCap { get; } = PenLineCap.Flat;
/// <summary>
/// Specifies the type of graphic shape to use on both ends of a line.
/// </summary>
public PenLineCap LineCap { get; }
public PenLineJoin LineJoin { get; } = PenLineJoin.Miter;
/// <summary>
/// Specifies how to join consecutive line or curve segments in a <see cref="PathFigure"/> (subpath) contained in a <see cref="PathGeometry"/> object.
/// </summary>
public PenLineJoin LineJoin { get; }
public double MiterLimit { get; } = 10.0;
/// <summary>
/// The limit on the ratio of the miter length to half this pen's Thickness.
/// </summary>
public double MiterLimit { get; }
}
}

3
src/Avalonia.Visuals/Media/PenLineCap.cs

@ -4,7 +4,6 @@ namespace Avalonia.Media
{
Flat,
Round,
Square,
Triangle
Square
}
}

28
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@ -16,12 +16,6 @@ namespace Avalonia.Media
public static readonly StyledProperty<Rect> RectProperty =
AvaloniaProperty.Register<RectangleGeometry, Rect>(nameof(Rect));
public Rect Rect
{
get => GetValue(RectProperty);
set => SetValue(RectProperty, value);
}
static RectangleGeometry()
{
AffectsGeometry(RectProperty);
@ -43,25 +37,23 @@ namespace Avalonia.Media
Rect = rect;
}
/// <summary>
/// Gets or sets the bounds of the rectangle.
/// </summary>
public Rect Rect
{
get => GetValue(RectProperty);
set => SetValue(RectProperty, value);
}
/// <inheritdoc/>
public override Geometry Clone() => new RectangleGeometry(Rect);
protected override IGeometryImpl CreateDefiningGeometry()
{
var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
var geometry = factory.CreateStreamGeometry();
using (var context = geometry.Open())
{
var rect = Rect;
context.BeginFigure(rect.TopLeft, true);
context.LineTo(rect.TopRight);
context.LineTo(rect.BottomRight);
context.LineTo(rect.BottomLeft);
context.EndFigure(true);
}
return geometry;
return factory.CreateRectangleGeometry(Rect);
}
}
}

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

@ -3,6 +3,7 @@
using System;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
@ -139,5 +140,11 @@ namespace Avalonia.Platform
/// Pops the latest pushed geometry clip.
/// </summary>
void PopGeometryClip();
/// <summary>
/// Adds a custom draw operation
/// </summary>
/// <param name="custom">Custom draw operation</param>
void Custom(ICustomDrawOperation custom);
}
}

22
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -36,6 +36,28 @@ namespace Avalonia.Platform
Size constraint,
IReadOnlyList<FormattedTextStyleSpan> spans);
/// <summary>
/// Creates an ellipse geometry implementation.
/// </summary>
/// <param name="rect">The bounds of the ellipse.</param>
/// <returns>An ellipse geometry..</returns>
IGeometryImpl CreateEllipseGeometry(Rect rect);
/// <summary>
/// Creates a line geometry implementation.
/// </summary>
/// <param name="p1">The start of the line.</param>
/// <param name="p2">The end of the line.</param>
/// <returns>A line geometry.</returns>
IGeometryImpl CreateLineGeometry(Point p1, Point p2);
/// <summary>
/// Creates a rectangle geometry implementation.
/// </summary>
/// <param name="rect">The bounds of the rectangle.</param>
/// <returns>A rectangle.</returns>
IGeometryImpl CreateRectangleGeometry(Rect rect);
/// <summary>
/// Creates a stream geometry implementation.
/// </summary>

39
src/Avalonia.Visuals/Rendering/SceneGraph/CustomDrawOperation.cs

@ -0,0 +1,39 @@
using System;
using Avalonia.Media;
using Avalonia.Platform;
namespace Avalonia.Rendering.SceneGraph
{
internal sealed class CustomDrawOperation : DrawOperation
{
public Matrix Transform { get; }
public ICustomDrawOperation Custom { get; }
public CustomDrawOperation(ICustomDrawOperation custom, Matrix transform)
: base(custom.Bounds, transform, null)
{
Transform = transform;
Custom = custom;
}
public override bool HitTest(Point p)
{
return Custom.HitTest(p * Transform);
}
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
Custom.Render(context);
}
public override void Dispose() => Custom.Dispose();
public bool Equals(Matrix transform, ICustomDrawOperation custom) =>
Transform == transform && Custom?.Equals(custom) == true;
}
public interface ICustomDrawOperation : IDrawOperation, IEquatable<ICustomDrawOperation>
{
}
}

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

@ -165,6 +165,15 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs<CustomDrawOperation>();
if (next == null || !next.Item.Equals(Transform, custom))
Add(new CustomDrawOperation(custom, Transform));
else
++_drawOperationindex;
}
/// <inheritdoc/>
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)

3
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -162,7 +162,8 @@ namespace Avalonia.Markup.Xaml
var readerSettings = new XamlXmlReaderSettings()
{
BaseUri = uri,
LocalAssembly = localAssembly
LocalAssembly = localAssembly,
ProvideLineInfo = true,
};
var context = IsDesignMode ? AvaloniaXamlSchemaContext.DesignInstance : AvaloniaXamlSchemaContext.Instance;

2
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@ -1 +1 @@
Subproject commit ab5526173722b8988bc5ca3c03c8752ce89c0975
Subproject commit 7452b23169e4948907fa10e2c115b672897d0e04

42
src/Skia/Avalonia.Skia/CustomRenderTarget.cs

@ -0,0 +1,42 @@
// 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 Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Skia
{
/// <summary>
/// Adapts <see cref="ICustomSkiaRenderTarget"/> to be used within Skia rendering pipeline.
/// </summary>
internal class CustomRenderTarget : IRenderTarget
{
private readonly ICustomSkiaRenderTarget _renderTarget;
public CustomRenderTarget(ICustomSkiaRenderTarget renderTarget)
{
_renderTarget = renderTarget;
}
public void Dispose()
{
_renderTarget.Dispose();
}
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer)
{
ICustomSkiaRenderSession session = _renderTarget.BeginRendering();
var nfo = new DrawingContextImpl.CreateInfo
{
GrContext = session.GrContext,
Canvas = session.Canvas,
Dpi = SkiaPlatform.DefaultDpi * session.ScaleFactor,
VisualBrushRenderer = visualBrushRenderer,
DisableTextLcdRendering = true
};
return new DrawingContextImpl(nfo, session);
}
}
}

26
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -9,6 +9,7 @@ using System.Threading;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Rendering.Utilities;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
@ -19,7 +20,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia based drawing context.
/// </summary>
public class DrawingContextImpl : IDrawingContextImpl
internal class DrawingContextImpl : IDrawingContextImpl, ISkiaDrawingContextImpl
{
private IDisposable[] _disposables;
private readonly Vector _dpi;
@ -99,6 +100,8 @@ namespace Avalonia.Skia
/// </summary>
public SKCanvas Canvas { get; }
SKCanvas ISkiaDrawingContextImpl.SkCanvas => Canvas;
/// <inheritdoc />
public void Clear(Color color)
{
@ -296,6 +299,8 @@ namespace Avalonia.Skia
Canvas.Restore();
}
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
/// <inheritdoc />
public void PushOpacityMask(IBrush mask, Rect bounds)
{
@ -573,25 +578,17 @@ namespace Avalonia.Skia
// Need to modify dashes due to Skia modifying their lengths
// https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/dots
// TODO: Still something is off, dashes are now present, but don't look the same as D2D ones.
float dashLengthModifier;
float gapLengthModifier;
switch (pen.StartLineCap)
switch (pen.LineCap)
{
case PenLineCap.Round:
paint.StrokeCap = SKStrokeCap.Round;
dashLengthModifier = -paint.StrokeWidth;
gapLengthModifier = paint.StrokeWidth;
break;
case PenLineCap.Square:
paint.StrokeCap = SKStrokeCap.Square;
dashLengthModifier = -paint.StrokeWidth;
gapLengthModifier = paint.StrokeWidth;
break;
default:
paint.StrokeCap = SKStrokeCap.Butt;
dashLengthModifier = 0.0f;
gapLengthModifier = 0.0f;
break;
}
@ -617,13 +614,12 @@ namespace Avalonia.Skia
for (var i = 0; i < srcDashes.Count; ++i)
{
var lengthModifier = i % 2 == 0 ? dashLengthModifier : gapLengthModifier;
// Avalonia dash lengths are relative, but Skia takes absolute sizes - need to scale
dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth + lengthModifier;
dashesArray[i] = (float) srcDashes[i] * paint.StrokeWidth;
}
var pe = SKPathEffect.CreateDash(dashesArray, (float) pen.DashStyle.Offset);
var offset = (float)(pen.DashStyle.Offset * pen.Thickness);
var pe = SKPathEffect.CreateDash(dashesArray, offset);
paint.PathEffect = pe;
rv.AddDisposable(pe);

25
src/Skia/Avalonia.Skia/EllipseGeometryImpl.cs

@ -0,0 +1,25 @@
// 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 SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// A Skia implementation of a <see cref="Avalonia.Media.EllipseGeometry"/>.
/// </summary>
internal class EllipseGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
public override SKPath EffectivePath { get; }
public EllipseGeometryImpl(Rect rect)
{
var path = new SKPath();
path.AddOval(rect.ToSKRect());
EffectivePath = path;
Bounds = rect;
}
}
}

2
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia formatted text implementation.
/// </summary>
public class FormattedTextImpl : IFormattedTextImpl
internal class FormattedTextImpl : IFormattedTextImpl
{
public FormattedTextImpl(
string text,

2
src/Skia/Avalonia.Skia/FramebufferRenderTarget.cs

@ -13,7 +13,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia render target that renders to a framebuffer surface. No gpu acceleration available.
/// </summary>
public class FramebufferRenderTarget : IRenderTarget
internal class FramebufferRenderTarget : IRenderTarget
{
private readonly IFramebufferPlatformSurface _platformSurface;
private SKImageInfo _currentImageInfo;

2
src/Skia/Avalonia.Skia/GeometryImpl.cs

@ -11,7 +11,7 @@ namespace Avalonia.Skia
/// <summary>
/// A Skia implementation of <see cref="IGeometryImpl"/>.
/// </summary>
public abstract class GeometryImpl : IGeometryImpl
internal abstract class GeometryImpl : IGeometryImpl
{
private PathCache _pathCache;

2
src/Skia/Avalonia.Skia/GlRenderTarget.cs

@ -8,7 +8,7 @@ using static Avalonia.OpenGL.GlConsts;
namespace Avalonia.Skia
{
public class GlRenderTarget : IRenderTarget
internal class GlRenderTarget : IRenderTarget
{
private readonly GRContext _grContext;
private IGlPlatformSurfaceRenderTarget _surface;

26
src/Skia/Avalonia.Skia/ICustomSkiaGpu.cs

@ -0,0 +1,26 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Collections.Generic;
using SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Custom Skia gpu instance.
/// </summary>
public interface ICustomSkiaGpu
{
/// <summary>
/// Skia GrContext used.
/// </summary>
GRContext GrContext { get; }
/// <summary>
/// Attempts to create custom render target from given surfaces.
/// </summary>
/// <param name="surfaces">Surfaces.</param>
/// <returns>Created render target or <see langword="null"/> if it fails.</returns>
ICustomSkiaRenderTarget TryCreateRenderTarget(IEnumerable<object> surfaces);
}
}

29
src/Skia/Avalonia.Skia/ICustomSkiaRenderSession.cs

@ -0,0 +1,29 @@
// 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 SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// Custom render session for Skia render target.
/// </summary>
public interface ICustomSkiaRenderSession : IDisposable
{
/// <summary>
/// GrContext used by this session.
/// </summary>
GRContext GrContext { get; }
/// <summary>
/// Canvas that will be used to render.
/// </summary>
SKCanvas Canvas { get; }
/// <summary>
/// Scaling factor.
/// </summary>
double ScaleFactor { get; }
}
}

19
src/Skia/Avalonia.Skia/ICustomSkiaRenderTarget.cs

@ -0,0 +1,19 @@
// 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;
namespace Avalonia.Skia
{
/// <summary>
/// Custom Skia render target.
/// </summary>
public interface ICustomSkiaRenderTarget : IDisposable
{
/// <summary>
/// Start rendering to this render target.
/// </summary>
/// <returns></returns>
ICustomSkiaRenderSession BeginRendering();
}
}

10
src/Skia/Avalonia.Skia/ISkiaDrawingContextImpl.cs

@ -0,0 +1,10 @@
using Avalonia.Platform;
using SkiaSharp;
namespace Avalonia.Skia
{
public interface ISkiaDrawingContextImpl : IDrawingContextImpl
{
SKCanvas SkCanvas { get; }
}
}

2
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -12,7 +12,7 @@ namespace Avalonia.Skia
/// <summary>
/// Immutable Skia bitmap.
/// </summary>
public class ImmutableBitmap : IDrawableBitmapImpl
internal class ImmutableBitmap : IDrawableBitmapImpl
{
private readonly SKImage _image;

29
src/Skia/Avalonia.Skia/LineGeometryImpl.cs

@ -0,0 +1,29 @@
// 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 SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// A Skia implementation of a <see cref="Avalonia.Media.LineGeometry"/>.
/// </summary>
internal class LineGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
public override SKPath EffectivePath { get; }
public LineGeometryImpl(Point p1, Point p2)
{
var path = new SKPath();
path.MoveTo(p1.ToSKPoint());
path.LineTo(p2.ToSKPoint());
EffectivePath = path;
Bounds = new Rect(
new Point(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y)),
new Point(Math.Max(p1.X, p2.X), Math.Max(p1.Y, p2.Y)));
}
}
}

38
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -15,14 +15,25 @@ namespace Avalonia.Skia
/// <summary>
/// Skia platform render interface.
/// </summary>
public class PlatformRenderInterface : IPlatformRenderInterface
internal class PlatformRenderInterface : IPlatformRenderInterface
{
private readonly ICustomSkiaGpu _customSkiaGpu;
private GRContext GrContext { get; }
public IEnumerable<string> InstalledFontNames => SKFontManager.Default.FontFamilies;
public PlatformRenderInterface()
public PlatformRenderInterface(ICustomSkiaGpu customSkiaGpu)
{
if (customSkiaGpu != null)
{
_customSkiaGpu = customSkiaGpu;
GrContext = _customSkiaGpu.GrContext;
return;
}
var gl = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
if (gl != null)
{
@ -32,12 +43,11 @@ namespace Avalonia.Skia
? GRGlInterface.AssembleGlInterface((_, proc) => display.GlInterface.GetProcAddress(proc))
: GRGlInterface.AssembleGlesInterface((_, proc) => display.GlInterface.GetProcAddress(proc)))
{
GrContext = GRContext.Create(GRBackend.OpenGL, iface);
}
}
}
/// <inheritdoc />
public IFormattedTextImpl CreateFormattedText(
string text,
@ -50,6 +60,12 @@ namespace Avalonia.Skia
return new FormattedTextImpl(text, typeface, textAlignment, wrapping, constraint, spans);
}
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
/// <inheritdoc />
public IStreamGeometryImpl CreateStreamGeometry()
{
@ -98,13 +114,23 @@ namespace Avalonia.Skia
DisableTextLcdRendering = false,
GrContext = GrContext
};
return new SurfaceRenderTarget(createInfo);
}
/// <inheritdoc />
public virtual IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
if (_customSkiaGpu != null)
{
ICustomSkiaRenderTarget customRenderTarget = _customSkiaGpu.TryCreateRenderTarget(surfaces);
if (customRenderTarget != null)
{
return new CustomRenderTarget(customRenderTarget);
}
}
foreach (var surface in surfaces)
{
if (surface is IGlPlatformSurface glSurface && GrContext != null)

25
src/Skia/Avalonia.Skia/RectangleGeometryImpl.cs

@ -0,0 +1,25 @@
// 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 SkiaSharp;
namespace Avalonia.Skia
{
/// <summary>
/// A Skia implementation of a <see cref="Avalonia.Media.RectangleGeometry"/>.
/// </summary>
internal class RectangleGeometryImpl : GeometryImpl
{
public override Rect Bounds { get; }
public override SKPath EffectivePath { get; }
public RectangleGeometryImpl(Rect rect)
{
var path = new SKPath();
path.AddRect(rect.ToSKRect());
EffectivePath = path;
Bounds = rect;
}
}
}

5
src/Skia/Avalonia.Skia/SkiaApplicationExtensions.cs

@ -20,8 +20,9 @@ namespace Avalonia
/// <returns>Configure builder.</returns>
public static T UseSkia<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize(), "Skia");
return builder;
return builder.UseRenderingSubsystem(() => SkiaPlatform.Initialize(
AvaloniaLocator.Current.GetService<SkiaOptions>() ?? new SkiaOptions()),
"Skia");
}
}
}

19
src/Skia/Avalonia.Skia/SkiaOptions.cs

@ -0,0 +1,19 @@
// 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 Avalonia.Skia;
namespace Avalonia
{
/// <summary>
/// Options for Skia rendering subsystem.
/// </summary>
public class SkiaOptions
{
/// <summary>
/// Custom gpu factory to use. Can be used to customize behavior of Skia renderer.
/// </summary>
public Func<ICustomSkiaGpu> CustomGpuFactory { get; set; }
}
}

10
src/Skia/Avalonia.Skia/SkiaPlatform.cs

@ -15,8 +15,14 @@ namespace Avalonia.Skia
/// </summary>
public static void Initialize()
{
var renderInterface = new PlatformRenderInterface();
Initialize(new SkiaOptions());
}
public static void Initialize(SkiaOptions options)
{
var customGpu = options.CustomGpuFactory?.Invoke();
var renderInterface = new PlatformRenderInterface(customGpu);
AvaloniaLocator.CurrentMutable
.Bind<IPlatformRenderInterface>().ToConstant(renderInterface);
}

2
src/Skia/Avalonia.Skia/StreamGeometryImpl.cs

@ -10,7 +10,7 @@ namespace Avalonia.Skia
/// <summary>
/// A Skia implementation of a <see cref="IStreamGeometryImpl"/>.
/// </summary>
public class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
internal class StreamGeometryImpl : GeometryImpl, IStreamGeometryImpl
{
private Rect _bounds;
private readonly SKPath _effectivePath;

2
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -14,7 +14,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia render target that writes to a surface.
/// </summary>
public class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
internal class SurfaceRenderTarget : IRenderTargetBitmapImpl, IDrawableBitmapImpl
{
private readonly SKSurface _surface;
private readonly SKCanvas _canvas;

2
src/Skia/Avalonia.Skia/TransformedGeometryImpl.cs

@ -9,7 +9,7 @@ namespace Avalonia.Skia
/// <summary>
/// A Skia implementation of a <see cref="ITransformedGeometryImpl"/>.
/// </summary>
public class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
internal class TransformedGeometryImpl : GeometryImpl, ITransformedGeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="TransformedGeometryImpl"/> class.

2
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -13,7 +13,7 @@ namespace Avalonia.Skia
/// <summary>
/// Skia based writeable bitmap.
/// </summary>
public class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
internal class WriteableBitmapImpl : IWriteableBitmapImpl, IDrawableBitmapImpl
{
private static readonly SKBitmapReleaseDelegate s_releaseDelegate = ReleaseProc;
private readonly SKBitmap _bitmap;

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

@ -182,10 +182,10 @@ namespace Avalonia.Direct2D1
return new WriteableWicBitmapImpl(size, dpi, format);
}
public IStreamGeometryImpl CreateStreamGeometry()
{
return new StreamGeometryImpl();
}
public IGeometryImpl CreateEllipseGeometry(Rect rect) => new EllipseGeometryImpl(rect);
public IGeometryImpl CreateLineGeometry(Point p1, Point p2) => new LineGeometryImpl(p1, p2);
public IGeometryImpl CreateRectangleGeometry(Rect rect) => new RectangleGeometryImpl(rect);
public IStreamGeometryImpl CreateStreamGeometry() => new StreamGeometryImpl();
public IBitmapImpl LoadBitmap(string fileName)
{

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

@ -6,6 +6,7 @@ using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using SharpDX;
using SharpDX.Direct2D1;
@ -508,5 +509,7 @@ namespace Avalonia.Direct2D1.Media
{
PopLayer();
}
public void Custom(ICustomDrawOperation custom) => custom.Render(this);
}
}

27
src/Windows/Avalonia.Direct2D1/Media/EllipseGeometryImpl.cs

@ -0,0 +1,27 @@
// 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 SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.EllipseGeometry"/>.
/// </summary>
internal class EllipseGeometryImpl : GeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public EllipseGeometryImpl(Rect rect)
: base(CreateGeometry(rect))
{
}
private static Geometry CreateGeometry(Rect rect)
{
var ellipse = new Ellipse(rect.Center.ToSharpDX(), (float)rect.Width / 2, (float)rect.Height / 2);
return new EllipseGeometry(Direct2D1Platform.Direct2D1Factory, ellipse);
}
}
}

2
src/Windows/Avalonia.Direct2D1/Media/FormattedTextImpl.cs

@ -9,7 +9,7 @@ using DWrite = SharpDX.DirectWrite;
namespace Avalonia.Direct2D1.Media
{
public class FormattedTextImpl : IFormattedTextImpl
internal class FormattedTextImpl : IFormattedTextImpl
{
public FormattedTextImpl(
string text,

27
src/Windows/Avalonia.Direct2D1/Media/LineGeometryImpl.cs

@ -0,0 +1,27 @@
// 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 SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.LineGeometry"/>.
/// </summary>
internal class LineGeometryImpl : StreamGeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public LineGeometryImpl(Point p1, Point p2)
{
using (var sink = ((PathGeometry)Geometry).Open())
{
sink.BeginFigure(p1.ToSharpDX(), FigureBegin.Hollow);
sink.AddLine(p2.ToSharpDX());
sink.EndFigure(FigureEnd.Open);
sink.Close();
}
}
}
}

26
src/Windows/Avalonia.Direct2D1/Media/RectangleGeometryImpl.cs

@ -0,0 +1,26 @@
// 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 SharpDX.Direct2D1;
namespace Avalonia.Direct2D1.Media
{
/// <summary>
/// A Direct2D implementation of a <see cref="Avalonia.Media.RectangleGeometry"/>.
/// </summary>
internal class RectangleGeometryImpl : GeometryImpl
{
/// <summary>
/// Initializes a new instance of the <see cref="StreamGeometryImpl"/> class.
/// </summary>
public RectangleGeometryImpl(Rect rect)
: base(CreateGeometry(rect))
{
}
private static Geometry CreateGeometry(Rect rect)
{
return new RectangleGeometry(Direct2D1Platform.Direct2D1Factory, rect.ToDirect2D());
}
}
}

8
src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs

@ -122,14 +122,16 @@ namespace Avalonia.Direct2D1
/// <returns>The Direct2D brush.</returns>
public static StrokeStyle ToDirect2DStrokeStyle(this Avalonia.Media.Pen pen, Factory factory)
{
var d2dLineCap = pen.LineCap.ToDirect2D();
var properties = new StrokeStyleProperties
{
DashStyle = DashStyle.Solid,
MiterLimit = (float)pen.MiterLimit,
LineJoin = pen.LineJoin.ToDirect2D(),
StartCap = pen.StartLineCap.ToDirect2D(),
EndCap = pen.EndLineCap.ToDirect2D(),
DashCap = pen.DashCap.ToDirect2D()
StartCap = d2dLineCap,
EndCap = d2dLineCap,
DashCap = d2dLineCap
};
float[] dashes = null;
if (pen.DashStyle?.Dashes != null && pen.DashStyle.Dashes.Count > 0)

2
src/Windows/Avalonia.Win32.Interop/Wpf/Direct2DImageSurface.cs

@ -50,7 +50,7 @@ namespace Avalonia.Win32.Interop.Wpf
{
_resource = texture.QueryInterface<SharpDX.Direct3D11.Resource>();
Target = new RenderTarget(AvaloniaLocator.Current.GetService<SharpDX.Direct2D1.Factory>(), surface,
Target = new RenderTarget(Direct2D1Platform.Direct2D1Factory, surface,
new RenderTargetProperties
{
DpiX = (float) dpi.X,

46
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -5,6 +5,7 @@ using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
@ -21,6 +22,7 @@ namespace Avalonia.Controls.UnitTests
{
Command = command,
};
var root = new TestRoot { Child = target };
Assert.False(target.IsEnabled);
command.IsEnabled = true;
@ -215,6 +217,39 @@ namespace Avalonia.Controls.UnitTests
Assert.True(clicked);
}
[Fact]
public void Button_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button
{
Command = command,
};
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void Button_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button { Command = command };
var root = new TestRoot { Child = target };
Assert.Equal(1, command.SubscriptionCount);
}
[Fact]
public void Button_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
{
var command = new TestCommand(true);
var target = new Button { Command = command };
var root = new TestRoot { Child = target };
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
private class TestButton : Button, IRenderRoot
{
public TestButton()
@ -298,6 +333,7 @@ namespace Avalonia.Controls.UnitTests
private class TestCommand : ICommand
{
private EventHandler _canExecuteChanged;
private bool _enabled;
public TestCommand(bool enabled)
@ -313,12 +349,18 @@ namespace Avalonia.Controls.UnitTests
if (_enabled != value)
{
_enabled = value;
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
_canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
public event EventHandler CanExecuteChanged;
public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged
{
add { _canExecuteChanged += value; ++SubscriptionCount; }
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => _enabled;

54
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -22,5 +24,57 @@ namespace Avalonia.Controls.UnitTests
Assert.False(target.Focusable);
}
[Fact]
public void MenuItem_Does_Not_Subscribe_To_Command_CanExecuteChanged_Until_Added_To_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem
{
Command = command,
};
Assert.Equal(0, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Subscribes_To_Command_CanExecuteChanged_When_Added_To_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem { Command = command };
var root = new TestRoot { Child = target };
Assert.Equal(1, command.SubscriptionCount);
}
[Fact]
public void MenuItem_Unsubscribes_From_Command_CanExecuteChanged_When_Removed_From_Logical_Tree()
{
var command = new TestCommand();
var target = new MenuItem { Command = command };
var root = new TestRoot { Child = target };
root.Child = null;
Assert.Equal(0, command.SubscriptionCount);
}
private class TestCommand : ICommand
{
private EventHandler _canExecuteChanged;
public int SubscriptionCount { get; private set; }
public event EventHandler CanExecuteChanged
{
add { _canExecuteChanged += value; ++SubscriptionCount; }
remove { _canExecuteChanged -= value; --SubscriptionCount; }
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
}
}
}
}

45
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -292,6 +292,51 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Calling_Show_On_Closed_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var windowImpl = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
var target = new Window(windowImpl);
target.Show();
target.Close();
var openedRaised = false;
target.Opened += (s, e) => openedRaised = true;
var ex = Assert.Throws<InvalidOperationException>(() => target.Show());
Assert.Equal("Cannot re-show a closed window.", ex.Message);
Assert.False(openedRaised);
}
}
[Fact]
public async Task Calling_ShowDialog_On_Closed_Window_Should_Throw()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var parent = new Mock<IWindowImpl>();
var windowImpl = new Mock<IWindowImpl>();
windowImpl.SetupProperty(x => x.Closed);
windowImpl.Setup(x => x.Scaling).Returns(1);
var target = new Window(windowImpl.Object);
var task = target.ShowDialog<bool>(parent.Object);
windowImpl.Object.Closed();
await task;
var openedRaised = false;
target.Opened += (s, e) => openedRaised = true;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => target.ShowDialog<bool>(parent.Object));
Assert.Equal("Cannot re-show a closed window.", ex.Message);
Assert.False(openedRaised);
}
}
[Fact]
public void Window_Should_Be_Centered_When_WindowStartupLocation_Is_CenterScreen()
{

1
tests/Avalonia.ReactiveUI.UnitTests/AvaloniaActivationForViewFetcherTest.cs

@ -11,6 +11,7 @@ using DynamicData;
using Xunit;
using Splat;
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Avalonia
{

1
tests/Avalonia.ReactiveUI.UnitTests/RoutedViewHostTest.cs

@ -14,6 +14,7 @@ using Avalonia.Markup.Xaml;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Reactive;
using Avalonia.ReactiveUI;
namespace Avalonia
{

8
tests/Avalonia.RenderTests/Shapes/PathTests.cs

@ -334,11 +334,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
CompareImages();
}
#if AVALONIA_SKIA_SKIP_FAIL
[Fact(Skip = "FIXME")]
#else
[Fact]
#endif
public async Task Path_With_PenLineCap()
{
Decorator target = new Decorator
@ -351,10 +347,8 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
StrokeThickness = 10,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
StrokeDashCap = PenLineCap.Triangle,
StrokeDashArray = new AvaloniaList<double>(3, 1),
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Square,
StrokeLineCap = PenLineCap.Round,
Data = StreamGeometry.Parse("M 20,20 L 180,180"),
}
};

3
tests/Avalonia.RenderTests/Shapes/PolylineTests.cs

@ -61,8 +61,7 @@ namespace Avalonia.Direct2D1.RenderTests.Shapes
Points = polylinePoints,
Stretch = Stretch.Uniform,
StrokeJoin = PenLineJoin.Round,
StrokeStartLineCap = PenLineCap.Round,
StrokeEndLineCap = PenLineCap.Round,
StrokeLineCap = PenLineCap.Round,
StrokeThickness = 10
}
};

15
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -22,6 +22,21 @@ namespace Avalonia.UnitTests
return Mock.Of<IFormattedTextImpl>();
}
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
return Mock.Of<IGeometryImpl>();
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
return Mock.Of<IGeometryImpl>();
}
public IRenderTarget CreateRenderTarget(IEnumerable<object> surfaces)
{
return Mock.Of<IRenderTarget>();

15
tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

@ -56,6 +56,21 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
public IGeometryImpl CreateEllipseGeometry(Rect rect)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateLineGeometry(Point p1, Point p2)
{
throw new NotImplementedException();
}
public IGeometryImpl CreateRectangleGeometry(Rect rect)
{
throw new NotImplementedException();
}
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();

BIN
tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Loading…
Cancel
Save