Browse Source

Merge remote-tracking branch 'origin/master' into features/managed-notifications

pull/2453/head
Dan Walmsley 7 years ago
parent
commit
f491fbb784
  1. 9
      native/Avalonia.Native/src/OSX/SystemDialogs.mm
  2. 10
      src/Avalonia.Controls/Control.cs
  3. 11
      src/Avalonia.Controls/Image.cs
  4. 13
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  5. 72
      src/Avalonia.Controls/Shapes/Shape.cs
  6. 54
      src/Avalonia.Controls/StackPanel.cs
  7. 2
      src/Avalonia.Controls/Viewbox.cs
  8. 62
      src/Avalonia.Layout/LayoutExtensions.cs
  9. 2
      src/Avalonia.Layout/Layoutable.cs
  10. 10
      src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs
  11. 13
      src/Avalonia.Styling/Styling/ISetterValue.cs
  12. 8
      src/Avalonia.Styling/Styling/Setter.cs
  13. 26
      src/Avalonia.Themes.Default/ScrollBar.xaml
  14. 10
      src/Avalonia.Visuals/Media/BrushExtensions.cs
  15. 72
      src/Avalonia.Visuals/Media/Pen.cs
  16. 3
      src/Avalonia.Visuals/Media/PenLineCap.cs
  17. 13
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  18. 17
      src/Markup/Avalonia.Markup/Data/TemplateBinding.cs
  19. 19
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  20. 8
      src/Windows/Avalonia.Direct2D1/PrimitiveExtensions.cs
  21. 17
      tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs
  22. 160
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  23. 33
      tests/Avalonia.LeakTests/ControlTests.cs
  24. 8
      tests/Avalonia.RenderTests/Shapes/PathTests.cs
  25. 3
      tests/Avalonia.RenderTests/Shapes/PolylineTests.cs
  26. 14
      tests/Avalonia.Styling.UnitTests/SetterTests.cs
  27. BIN
      tests/TestFiles/Direct2D1/Shapes/Path/Path_With_PenLineCap.expected.png
  28. BIN
      tests/TestFiles/Skia/Shapes/Path/Path_With_PenLineCap.expected.png

9
native/Avalonia.Native/src/OSX/SystemDialogs.mm

@ -45,8 +45,7 @@ public:
{
auto url = [urls objectAtIndex:i];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
auto string = [url path];
strings[i] = (void*)[string UTF8String];
}
@ -137,8 +136,7 @@ public:
{
auto url = [urls objectAtIndex:i];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
auto string = [url path];
strings[i] = (void*)[string UTF8String];
}
@ -220,8 +218,7 @@ public:
auto url = [panel URL];
auto string = [url absoluteString];
string = [string substringFromIndex:7];
auto string = [url path];
strings[0] = (void*)[string UTF8String];
events->OnCompleted(1, &strings[0]);

10
src/Avalonia.Controls/Control.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -20,7 +21,7 @@ namespace Avalonia.Controls
///
/// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
/// </remarks>
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInSetter
public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, ISetterValue
{
/// <summary>
/// Defines the <see cref="FocusAdorner"/> property.
@ -90,6 +91,13 @@ namespace Avalonia.Controls
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
/// <inheritdoc/>
void IVisualBrushInitialize.EnsureInitialized()
{

11
src/Avalonia.Controls/Image.cs

@ -81,23 +81,22 @@ namespace Avalonia.Controls
protected override Size MeasureOverride(Size availableSize)
{
var source = Source;
var result = new Size();
if (source != null)
{
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{
return sourceSize;
result = sourceSize;
}
else
{
return Stretch.CalculateSize(availableSize, sourceSize);
result = Stretch.CalculateSize(availableSize, sourceSize);
}
}
else
{
return new Size();
}
return result.Constrain(availableSize);
}
/// <inheritdoc/>

13
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -336,7 +336,18 @@ namespace Avalonia.Controls.Platform
if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == true)
{
Open(item, false);
if (item.IsSubMenuOpen)
{
if (item.IsTopLevel)
{
CloseMenu(item);
}
}
else
{
Open(item, false);
}
e.Handled = true;
}
}

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

54
src/Avalonia.Controls/StackPanel.cs

@ -4,6 +4,7 @@
using System;
using System.Linq;
using Avalonia.Input;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -234,30 +235,16 @@ namespace Avalonia.Controls
measuredWidth -= (hasVisibleChild ? spacing : 0);
}
return new Size(measuredWidth, measuredHeight);
return new Size(measuredWidth, measuredHeight).Constrain(availableSize);
}
/// <summary>
/// Arranges the control's children.
/// </summary>
/// <param name="finalSize">The size allocated to the control.</param>
/// <returns>The space taken.</returns>
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var orientation = Orientation;
double arrangedWidth = finalSize.Width;
double arrangedHeight = finalSize.Height;
double spacing = Spacing;
bool hasVisibleChild = Children.Any(c => c.IsVisible);
if (Orientation == Orientation.Vertical)
{
arrangedHeight = 0;
}
else
{
arrangedWidth = 0;
}
var spacing = Spacing;
var finalRect = new Rect(finalSize);
var pos = 0.0;
var children = ReverseOrder ? Children.Reverse() : Children;
@ -268,32 +255,21 @@ namespace Avalonia.Controls
if (orientation == Orientation.Vertical)
{
double width = Math.Max(childWidth, arrangedWidth);
Rect childFinal = new Rect(0, arrangedHeight, width, childHeight);
ArrangeChild(child, childFinal, finalSize, orientation);
arrangedWidth = Math.Max(arrangedWidth, childWidth);
arrangedHeight += childHeight + (child.IsVisible ? spacing : 0);
var rect = new Rect(0, pos, childWidth, childHeight)
.Align(finalRect, child.HorizontalAlignment, VerticalAlignment.Top);
ArrangeChild(child, rect, finalSize, orientation);
pos += childHeight + spacing;
}
else
{
double height = Math.Max(childHeight, arrangedHeight);
Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height);
ArrangeChild(child, childFinal, finalSize, orientation);
arrangedWidth += childWidth + (child.IsVisible ? spacing : 0);
arrangedHeight = Math.Max(arrangedHeight, childHeight);
var rect = new Rect(pos, 0, childWidth, childHeight)
.Align(finalRect, HorizontalAlignment.Left, child.VerticalAlignment);
ArrangeChild(child, rect, finalSize, orientation);
pos += childWidth + spacing;
}
}
if (orientation == Orientation.Vertical)
{
arrangedHeight = Math.Max(arrangedHeight - (hasVisibleChild ? spacing : 0), finalSize.Height);
}
else
{
arrangedWidth = Math.Max(arrangedWidth - (hasVisibleChild ? spacing : 0), finalSize.Width);
}
return new Size(arrangedWidth, arrangedHeight);
return finalSize;
}
internal virtual void ArrangeChild(

2
src/Avalonia.Controls/Viewbox.cs

@ -49,7 +49,7 @@ namespace Avalonia.Controls
var scale = GetScale(availableSize, childSize, Stretch);
return childSize * scale;
return (childSize * scale).Constrain(availableSize);
}
return new Size();

62
src/Avalonia.Layout/LayoutExtensions.cs

@ -0,0 +1,62 @@
using System;
namespace Avalonia.Layout
{
/// <summary>
/// Extension methods for layout types.
/// </summary>
public static class LayoutExtensions
{
/// <summary>
/// Aligns a rect in a constraining rect according to horizontal and vertical alignment
/// settings.
/// </summary>
/// <param name="rect">The rect to align.</param>
/// <param name="constraint">The constraining rect.</param>
/// <param name="horizontalAlignment">The horizontal alignment.</param>
/// <param name="verticalAlignment">The vertical alignment.</param>
/// <returns></returns>
public static Rect Align(
this Rect rect,
Rect constraint,
HorizontalAlignment horizontalAlignment,
VerticalAlignment verticalAlignment)
{
switch (horizontalAlignment)
{
case HorizontalAlignment.Center:
rect = rect.WithX((constraint.Width - rect.Width) / 2);
break;
case HorizontalAlignment.Right:
rect = rect.WithX(constraint.Width - rect.Width);
break;
case HorizontalAlignment.Stretch:
rect = new Rect(
0,
rect.Y,
Math.Max(constraint.Width, rect.Width),
rect.Height);
break;
}
switch (verticalAlignment)
{
case VerticalAlignment.Center:
rect = rect.WithY((constraint.Height - rect.Height) / 2);
break;
case VerticalAlignment.Bottom:
rect = rect.WithY(constraint.Height - rect.Height);
break;
case VerticalAlignment.Stretch:
rect = new Rect(
rect.X,
0,
rect.Width,
Math.Max(constraint.Height, rect.Height));
break;
}
return rect;
}
}
}

2
src/Avalonia.Layout/Layoutable.cs

@ -314,7 +314,7 @@ namespace Avalonia.Layout
try
{
_measuring = true;
desiredSize = MeasureCore(availableSize).Constrain(availableSize);
desiredSize = MeasureCore(availableSize);
}
finally
{

10
src/Avalonia.Styling/Styling/IRequiresTemplateInSetter.cs

@ -1,10 +0,0 @@
namespace Avalonia.Styling
{
/// <summary>
/// This is an interface for advanced scenarios to assist users in correct style development.
/// You as a user will not need to use this interface directly.
/// </summary>
public interface IRequiresTemplateInSetter
{
}
}

13
src/Avalonia.Styling/Styling/ISetterValue.cs

@ -0,0 +1,13 @@
namespace Avalonia.Styling
{
/// <summary>
/// Customizes the behavior of a class when added as a value to an <see cref="ISetter"/>.
/// </summary>
public interface ISetterValue
{
/// <summary>
/// Notifies that the object has been added as a setter value.
/// </summary>
void Initialize(ISetter setter);
}
}

8
src/Avalonia.Styling/Styling/Setter.cs

@ -65,13 +65,7 @@ namespace Avalonia.Styling
set
{
if (value is IRequiresTemplateInSetter)
{
throw new ArgumentException(
"Cannot assign a control to Setter.Value. Wrap the control in a <Template>.",
nameof(value));
}
(value as ISetterValue)?.Initialize(this);
_value = value;
}
}

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

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

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

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
@ -13,7 +12,6 @@ using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using System.Threading.Tasks;
namespace Avalonia.Rendering
{
@ -33,7 +31,6 @@ namespace Avalonia.Rendering
private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay;
private object _rendering = new object();
private int _lastSceneId = -1;
private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
private IRef<IDrawOperation> _currentDraw;
@ -267,7 +264,7 @@ namespace Avalonia.Rendering
RenderOverlay(scene.Item, GetContext());
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
}
}
}
}
finally
@ -321,15 +318,15 @@ namespace Avalonia.Rendering
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
return (rs, true);
}
// Indicate that we have updated the layers
return (sceneRef.Clone(), true);
}
// Just return scene, layers weren't updated
return (sceneRef.Clone(), false);
}
}
@ -456,7 +453,7 @@ namespace Avalonia.Rendering
private void RenderComposite(Scene scene, IDrawingContextImpl context)
{
context.Clear(Colors.Transparent);
var clientRect = new Rect(scene.Size);
foreach (var layer in scene.Layers)

17
src/Markup/Avalonia.Markup/Data/TemplateBinding.cs

@ -3,6 +3,7 @@ using System.Globalization;
using System.Reactive.Subjects;
using Avalonia.Data.Converters;
using Avalonia.Reactive;
using Avalonia.Styling;
namespace Avalonia.Data
{
@ -12,8 +13,10 @@ namespace Avalonia.Data
public class TemplateBinding : SingleSubscriberObservableBase<object>,
IBinding,
IDescription,
ISubject<object>
ISubject<object>,
ISetterValue
{
private bool _isSetterValue;
private IStyledElement _target;
private Type _targetType;
@ -35,10 +38,11 @@ namespace Avalonia.Data
{
// Usually each `TemplateBinding` will only be instantiated once; in this case we can
// use the `TemplateBinding` object itself as the instanced binding in order to save
// allocating a new object. If the binding *is* instantiated more than once (which can
// happen if it appears in a `Setter` for example, then just make a clone and instantiate
// that.
if (_target == null)
// allocating a new object.
//
// If the binding appears in a `Setter`, then make a clone and instantiate that because
// because the setter can outlive the control and cause a leak.
if (_target == null && !_isSetterValue)
{
_target = (IStyledElement)target;
_targetType = targetProperty?.PropertyType;
@ -106,6 +110,9 @@ namespace Avalonia.Data
}
}
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter) => _isSetterValue = true;
protected override void Subscribed()
{
TemplatedParentChanged();

19
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -578,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;
}
@ -622,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);

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)

17
tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Interactivity;
using Moq;
using Xunit;
@ -110,6 +111,22 @@ namespace Avalonia.Controls.UnitTests.Platform
Assert.True(e.Handled);
}
[Fact]
public void Click_On_Open_TopLevel_Menu_Closes_Menu()
{
var target = new DefaultMenuInteractionHandler(false);
var menu = Mock.Of<IMenu>();
var item = Mock.Of<IMenuItem>(x =>
x.IsSubMenuOpen == true &&
x.IsTopLevel == true &&
x.HasSubMenu == true &&
x.Parent == menu);
var e = new PointerPressedEventArgs { MouseButton = MouseButton.Left, Source = item };
target.PointerPressed(item, e);
Mock.Get(menu).Verify(x => x.Close());
}
[Fact]
public void PointerEnter_Opens_Item_When_Old_Item_Is_Open()
{

160
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -1,7 +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 Avalonia.Controls;
using System.Linq;
using Avalonia.Layout;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -147,6 +148,151 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(50, 0, 50, 120), target.Children[2].Bounds);
}
[Fact]
public void Arranges_Vertical_Children_With_Correct_Bounds()
{
var target = new StackPanel
{
Orientation = Orientation.Vertical,
Children =
{
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Left,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Left,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Center,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Center,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Right,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Right,
MeasureSize = new Size(150, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
MeasureSize = new Size(50, 10),
},
new TestControl
{
HorizontalAlignment = HorizontalAlignment.Stretch,
MeasureSize = new Size(150, 10),
},
}
};
target.Measure(new Size(100, 150));
Assert.Equal(new Size(100, 80), target.DesiredSize);
target.Arrange(new Rect(target.DesiredSize));
var bounds = target.Children.Select(x => x.Bounds).ToArray();
Assert.Equal(
new[]
{
new Rect(0, 0, 50, 10),
new Rect(0, 10, 150, 10),
new Rect(25, 20, 50, 10),
new Rect(-25, 30, 150, 10),
new Rect(50, 40, 50, 10),
new Rect(-50, 50, 150, 10),
new Rect(0, 60, 100, 10),
new Rect(0, 70, 150, 10),
}, bounds);
}
[Fact]
public void Arranges_Horizontal_Children_With_Correct_Bounds()
{
var target = new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new TestControl
{
VerticalAlignment = VerticalAlignment.Top,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Top,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Center,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Center,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Bottom,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Bottom,
MeasureSize = new Size(10, 150),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Stretch,
MeasureSize = new Size(10, 50),
},
new TestControl
{
VerticalAlignment = VerticalAlignment.Stretch,
MeasureSize = new Size(10, 150),
},
}
};
target.Measure(new Size(150, 100));
Assert.Equal(new Size(80, 100), target.DesiredSize);
target.Arrange(new Rect(target.DesiredSize));
var bounds = target.Children.Select(x => x.Bounds).ToArray();
Assert.Equal(
new[]
{
new Rect(0, 0, 10, 50),
new Rect(10, 0, 10, 150),
new Rect(20, 25, 10, 50),
new Rect(30, -25, 10, 150),
new Rect(40, 50, 10, 50),
new Rect(50, -50, 10, 150),
new Rect(60, 0, 10, 100),
new Rect(70, 0, 10, 150),
}, bounds);
}
[Theory]
[InlineData(Orientation.Horizontal)]
[InlineData(Orientation.Vertical)]
@ -185,5 +331,17 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(sizeWithTwoChildren, sizeWithThreeChildren);
}
private class TestControl : Control
{
public Size MeasureConstraint { get; private set; }
public Size MeasureSize { get; set; }
protected override Size MeasureOverride(Size availableSize)
{
MeasureConstraint = availableSize;
return MeasureSize;
}
}
}
}

33
tests/Avalonia.LeakTests/ControlTests.cs

@ -308,6 +308,39 @@ namespace Avalonia.LeakTests
}
[Fact]
public void Slider_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window
{
Content = new Slider()
};
window.Show();
// Do a layout and make sure that Slider gets added to visual tree.
window.LayoutManager.ExecuteInitialLayoutPass(window);
Assert.IsType<Slider>(window.Presenter.Child);
// Clear the content and ensure the Slider is removed.
window.Content = null;
window.LayoutManager.ExecuteLayoutPass();
Assert.Null(window.Presenter.Child);
return window;
};
var result = run();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Slider>()).ObjectsCount));
}
}
[Fact]
public void RendererIsDisposed()
{

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

14
tests/Avalonia.Styling.UnitTests/SetterTests.cs

@ -1,17 +1,15 @@
// 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.Globalization;
using System.Reactive.Subjects;
using Moq;
using Avalonia.Controls;
using Avalonia.Data;
using Xunit;
using System;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Data;
using Avalonia.Markup;
using System.Globalization;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Moq;
using Xunit;
namespace Avalonia.Styling.UnitTests
{
@ -22,7 +20,7 @@ namespace Avalonia.Styling.UnitTests
{
var target = new Setter();
Assert.Throws<ArgumentException>(() => target.Value = new Border());
Assert.Throws<InvalidOperationException>(() => target.Value = new Border());
}
[Fact]

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