Browse Source

Merge branch 'master' into demo/dynamic-menus

pull/1891/head
Jeremy Koritzinsky 8 years ago
committed by GitHub
parent
commit
60ce87cd03
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/Avalonia.Controls/Border.cs
  2. 1
      src/Avalonia.Controls/Panel.cs
  3. 4
      src/Avalonia.Controls/TextBlock.cs
  4. 36
      src/Avalonia.Visuals/Media/Brush.cs
  5. 65
      src/Avalonia.Visuals/Media/GradientBrush.cs
  6. 40
      src/Avalonia.Visuals/Media/GradientStop.cs
  7. 23
      src/Avalonia.Visuals/Media/GradientStops.cs
  8. 16
      src/Avalonia.Visuals/Media/IAffectsRender.cs
  9. 4
      src/Avalonia.Visuals/Media/IGradientBrush.cs
  10. 18
      src/Avalonia.Visuals/Media/IGradientStop.cs
  11. 6
      src/Avalonia.Visuals/Media/IMutableBrush.cs
  12. 12
      src/Avalonia.Visuals/Media/ImageBrush.cs
  13. 9
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
  14. 20
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs
  15. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
  16. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
  17. 13
      src/Avalonia.Visuals/Media/LinearGradientBrush.cs
  18. 10
      src/Avalonia.Visuals/Media/RadialGradientBrush.cs
  19. 13
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  20. 7
      src/Avalonia.Visuals/Media/TileBrush.cs
  21. 12
      src/Avalonia.Visuals/Media/VisualBrush.cs
  22. 19
      src/Avalonia.Visuals/Visual.cs
  23. 21
      tests/Avalonia.Controls.UnitTests/BorderTests.cs
  24. 21
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  25. 20
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  26. 48
      tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs
  27. 38
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
  28. 2
      tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs
  29. 4
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  30. 2
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
  31. 4
      tests/Avalonia.RenderTests/OpacityMaskTests.cs
  32. 7
      tests/Avalonia.UnitTests/TestRoot.cs
  33. 27
      tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs
  34. 86
      tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs
  35. 21
      tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs

6
src/Avalonia.Controls/Border.cs

@ -43,7 +43,11 @@ namespace Avalonia.Controls
/// </summary>
static Border()
{
AffectsRender<Border>(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsRender<Border>(
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}

1
src/Avalonia.Controls/Panel.cs

@ -30,6 +30,7 @@ namespace Avalonia.Controls
/// </summary>
static Panel()
{
AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
}

4
src/Avalonia.Controls/TextBlock.cs

@ -6,6 +6,7 @@ using System.Reactive;
using System.Reactive.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -65,7 +66,7 @@ namespace Avalonia.Controls
public static readonly AttachedProperty<IBrush> ForegroundProperty =
AvaloniaProperty.RegisterAttached<TextBlock, Control, IBrush>(
nameof(Foreground),
new SolidColorBrush(0xff000000),
Brushes.Black,
inherits: true);
/// <summary>
@ -100,6 +101,7 @@ namespace Avalonia.Controls
{
ClipToBoundsProperty.OverrideDefaultValue<TextBlock>(true);
AffectsRender<TextBlock>(
BackgroundProperty,
ForegroundProperty,
FontWeightProperty,
FontSizeProperty,

36
src/Avalonia.Visuals/Media/Brush.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
/// Describes how an area is painted.
/// </summary>
[TypeConverter(typeof(BrushConverter))]
public abstract class Brush : AvaloniaObject, IBrush
public abstract class Brush : AvaloniaObject, IMutableBrush
{
/// <summary>
/// Defines the <see cref="Opacity"/> property.
@ -18,6 +18,9 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Brush, double>(nameof(Opacity), 1.0);
/// <inheritdoc/>
public event EventHandler Invalidated;
/// <summary>
/// Gets or sets the opacity of the brush.
/// </summary>
@ -50,5 +53,36 @@ namespace Avalonia.Media
throw new FormatException($"Invalid brush string: '{s}'.");
}
/// <inheritdoc/>
public abstract IBrush ToImmutable();
/// <summary>
/// Marks a property as affecting the brush's visual representation.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a brush's static constructor, any change to the
/// property will cause the <see cref="Invalidated"/> event to be raised on the brush.
/// </remarks>
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : Brush
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.RaiseInvalidated(EventArgs.Empty);
}
foreach (var property in properties)
{
property.Changed.Subscribe(Invalidate);
}
}
/// <summary>
/// Raises the <see cref="Invalidated"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
}
}

65
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -1,7 +1,11 @@
// 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.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Media
@ -20,35 +24,74 @@ namespace Avalonia.Media
/// <summary>
/// Defines the <see cref="GradientStops"/> property.
/// </summary>
public static readonly StyledProperty<IList<GradientStop>> GradientStopsProperty =
AvaloniaProperty.Register<GradientBrush, IList<GradientStop>>(nameof(GradientStops));
public static readonly StyledProperty<GradientStops> GradientStopsProperty =
AvaloniaProperty.Register<GradientBrush, GradientStops>(nameof(GradientStops));
private IDisposable _gradientStopsSubscription;
static GradientBrush()
{
GradientStopsProperty.Changed.Subscribe(GradientStopsChanged);
AffectsRender<LinearGradientBrush>(SpreadMethodProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="GradientBrush"/> class.
/// </summary>
public GradientBrush()
{
this.GradientStops = new List<GradientStop>();
this.GradientStops = new GradientStops();
}
/// <summary>
/// Gets or sets the brush's spread method that defines how to draw a gradient that
/// doesn't fill the bounds of the destination control.
/// </summary>
/// <inheritdoc/>
public GradientSpreadMethod SpreadMethod
{
get { return GetValue(SpreadMethodProperty); }
set { SetValue(SpreadMethodProperty, value); }
}
/// <summary>
/// Gets or sets the brush's gradient stops.
/// </summary>
/// <inheritdoc/>
[Content]
public IList<GradientStop> GradientStops
public GradientStops GradientStops
{
get { return GetValue(GradientStopsProperty); }
set { SetValue(GradientStopsProperty, value); }
}
/// <inheritdoc/>
IReadOnlyList<IGradientStop> IGradientBrush.GradientStops => GradientStops;
private static void GradientStopsChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is GradientBrush brush)
{
var oldValue = (GradientStops)e.OldValue;
var newValue = (GradientStops)e.NewValue;
if (oldValue != null)
{
oldValue.CollectionChanged -= brush.GradientStopsChanged;
brush._gradientStopsSubscription.Dispose();
}
if (newValue != null)
{
newValue.CollectionChanged += brush.GradientStopsChanged;
brush._gradientStopsSubscription = newValue.TrackItemPropertyChanged(brush.GradientStopChanged);
}
brush.RaiseInvalidated(EventArgs.Empty);
}
}
private void GradientStopsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaiseInvalidated(EventArgs.Empty);
}
private void GradientStopChanged(Tuple<object, PropertyChangedEventArgs> e)
{
RaiseInvalidated(EventArgs.Empty);
}
}
}

40
src/Avalonia.Visuals/Media/GradientStop.cs

@ -4,10 +4,22 @@
namespace Avalonia.Media
{
/// <summary>
/// GradientStop
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public sealed class GradientStop
public sealed class GradientStop : AvaloniaObject, IGradientStop
{
/// <summary>
/// Describes the <see cref="Offset"/> property.
/// </summary>
public static StyledProperty<double> OffsetProperty =
AvaloniaProperty.Register<GradientStop, double>(nameof(Offset));
/// <summary>
/// Describes the <see cref="Color"/> property.
/// </summary>
public static StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<GradientStop, Color>(nameof(Color));
/// <summary>
/// Initializes a new instance of the <see cref="GradientStop"/> class.
/// </summary>
@ -24,16 +36,18 @@ namespace Avalonia.Media
Offset = offset;
}
// TODO: Make these dependency properties.
/// <summary>
/// The offset
/// </summary>
public double Offset { get; set; }
/// <inheritdoc/>
public double Offset
{
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
/// <summary>
/// The color
/// </summary>
public Color Color { get; set; }
/// <inheritdoc/>
public Color Color
{
get => GetValue(ColorProperty);
set => SetValue(ColorProperty, value);
}
}
}
}

23
src/Avalonia.Visuals/Media/GradientStops.cs

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// A collection of <see cref="GradientStop"/>s.
/// </summary>
public class GradientStops : AvaloniaList<GradientStop>
{
public GradientStops()
{
ResetBehavior = ResetBehavior.Remove;
}
public IReadOnlyList<ImmutableGradientStop> ToImmutable()
{
return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToList();
}
}
}

16
src/Avalonia.Visuals/Media/IAffectsRender.cs

@ -0,0 +1,16 @@
using System;
namespace Avalonia.Media
{
/// <summary>
/// Signals to a self-rendering control that changes to the resource should invoke
/// <see cref="Visual.InvalidateVisual"/>.
/// </summary>
public interface IAffectsRender
{
/// <summary>
/// Raised when the resource changes visually.
/// </summary>
event EventHandler Invalidated;
}
}

4
src/Avalonia.Visuals/Media/IGradientBrush.cs

@ -10,7 +10,7 @@ namespace Avalonia.Media
/// <summary>
/// Gets the brush's gradient stops.
/// </summary>
IList<GradientStop> GradientStops { get; }
IReadOnlyList<IGradientStop> GradientStops { get; }
/// <summary>
/// Gets the brush's spread method that defines how to draw a gradient that doesn't fill
@ -18,4 +18,4 @@ namespace Avalonia.Media
/// </summary>
GradientSpreadMethod SpreadMethod { get; }
}
}
}

18
src/Avalonia.Visuals/Media/IGradientStop.cs

@ -0,0 +1,18 @@
namespace Avalonia.Media
{
/// <summary>
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public interface IGradientStop
{
/// <summary>
/// Gets the gradient stop color.
/// </summary>
Color Color { get; }
/// <summary>
/// Gets the gradient stop offset.
/// </summary>
double Offset { get; }
}
}

6
src/Avalonia.Visuals/Media/IMutableBrush.cs

@ -1,9 +1,11 @@
namespace Avalonia.Media
using System;
namespace Avalonia.Media
{
/// <summary>
/// Represents a mutable brush which can return an immutable clone of itself.
/// </summary>
public interface IMutableBrush : IBrush
public interface IMutableBrush : IBrush, IAffectsRender
{
/// <summary>
/// Creates an immutable clone of the brush.

12
src/Avalonia.Visuals/Media/ImageBrush.cs

@ -2,13 +2,14 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media.Imaging;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with an <see cref="IBitmap"/>.
/// </summary>
public class ImageBrush : TileBrush, IImageBrush, IMutableBrush
public class ImageBrush : TileBrush, IImageBrush
{
/// <summary>
/// Defines the <see cref="Visual"/> property.
@ -16,6 +17,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<IBitmap> SourceProperty =
AvaloniaProperty.Register<ImageBrush, IBitmap>(nameof(Source));
static ImageBrush()
{
AffectsRender<ImageBrush>(SourceProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
/// </summary>
@ -42,9 +48,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableImageBrush(this);
return new ImmutableImageBrush(this);
}
}
}

9
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Media.Immutable
{
@ -15,7 +14,7 @@ namespace Avalonia.Media.Immutable
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
protected ImmutableGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity,
GradientSpreadMethod spreadMethod)
{
@ -28,14 +27,14 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
protected ImmutableGradientBrush(IGradientBrush source)
: this(source.GradientStops.ToList(), source.Opacity, source.SpreadMethod)
protected ImmutableGradientBrush(GradientBrush source)
: this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod)
{
}
/// <inheritdoc/>
public IList<GradientStop> GradientStops { get; }
public IReadOnlyList<IGradientStop> GradientStops { get; }
/// <inheritdoc/>
public double Opacity { get; }

20
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientStop.cs

@ -0,0 +1,20 @@
namespace Avalonia.Media.Immutable
{
/// <summary>
/// Describes the location and color of a transition point in a gradient.
/// </summary>
public class ImmutableGradientStop : IGradientStop
{
public ImmutableGradientStop(double offset, Color color)
{
Offset = offset;
Color = color;
}
/// <inheritdoc/>
public double Offset { get; }
/// <inheritdoc/>
public Color Color { get; }
}
}

4
src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs

@ -16,7 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="startPoint">The start point for the gradient.</param>
/// <param name="endPoint">The end point for the gradient.</param>
public ImmutableLinearGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? startPoint = null,
@ -31,7 +31,7 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableLinearGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableLinearGradientBrush(ILinearGradientBrush source)
public ImmutableLinearGradientBrush(LinearGradientBrush source)
: base(source)
{
StartPoint = source.StartPoint;

4
src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs

@ -21,7 +21,7 @@ namespace Avalonia.Media.Immutable
/// The horizontal and vertical radius of the outermost circle of the radial gradient.
/// </param>
public ImmutableRadialGradientBrush(
IList<GradientStop> gradientStops,
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
@ -38,7 +38,7 @@ namespace Avalonia.Media.Immutable
/// Initializes a new instance of the <see cref="ImmutableRadialGradientBrush"/> class.
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableRadialGradientBrush(IRadialGradientBrush source)
public ImmutableRadialGradientBrush(RadialGradientBrush source)
: base(source)
{
Center = source.Center;

13
src/Avalonia.Visuals/Media/LinearGradientBrush.cs

@ -1,12 +1,14 @@
// 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.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// A brush that draws with a linear gradient.
/// </summary>
public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush, IMutableBrush
public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush
{
/// <summary>
/// Defines the <see cref="StartPoint"/> property.
@ -24,6 +26,11 @@ namespace Avalonia.Media
nameof(EndPoint),
RelativePoint.BottomRight);
static LinearGradientBrush()
{
AffectsRender<LinearGradientBrush>(StartPointProperty, EndPointProperty);
}
/// <summary>
/// Gets or sets the start point for the gradient.
/// </summary>
@ -43,9 +50,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableLinearGradientBrush(this);
return new ImmutableLinearGradientBrush(this);
}
}
}

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

@ -1,12 +1,14 @@
// 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.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Paints an area with a radial gradient.
/// </summary>
public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush, IMutableBrush
public sealed class RadialGradientBrush : GradientBrush, IRadialGradientBrush
{
/// <summary>
/// Defines the <see cref="Center"/> property.
@ -63,9 +65,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableRadialGradientBrush(this);
return new ImmutableRadialGradientBrush(this);
}
}
}
}

13
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -1,12 +1,14 @@
// 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.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Fills an area with a solid color.
/// </summary>
public class SolidColorBrush : Brush, ISolidColorBrush, IMutableBrush
public class SolidColorBrush : Brush, ISolidColorBrush
{
/// <summary>
/// Defines the <see cref="Color"/> property.
@ -14,6 +16,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<Color> ColorProperty =
AvaloniaProperty.Register<SolidColorBrush, Color>(nameof(Color));
static SolidColorBrush()
{
AffectsRender<SolidColorBrush>(ColorProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="SolidColorBrush"/> class.
/// </summary>
@ -75,9 +82,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableSolidColorBrush(this);
return new ImmutableSolidColorBrush(this);
}
}
}

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

@ -79,6 +79,13 @@ namespace Avalonia.Media
static TileBrush()
{
AffectsRender<TileBrush>(
AlignmentXProperty,
AlignmentYProperty,
DestinationRectProperty,
SourceRectProperty,
StretchProperty,
TileModeProperty);
RenderOptions.BitmapInterpolationModeProperty.OverrideDefaultValue<TileBrush>(BitmapInterpolationMode.Default);
}

12
src/Avalonia.Visuals/Media/VisualBrush.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 Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media
@ -8,7 +9,7 @@ namespace Avalonia.Media
/// <summary>
/// Paints an area with an <see cref="IVisual"/>.
/// </summary>
public class VisualBrush : TileBrush, IVisualBrush, IMutableBrush
public class VisualBrush : TileBrush, IVisualBrush
{
/// <summary>
/// Defines the <see cref="Visual"/> property.
@ -16,6 +17,11 @@ namespace Avalonia.Media
public static readonly StyledProperty<IVisual> VisualProperty =
AvaloniaProperty.Register<VisualBrush, IVisual>(nameof(Visual));
static VisualBrush()
{
AffectsRender<VisualBrush>(VisualProperty);
}
/// <summary>
/// Initializes a new instance of the <see cref="VisualBrush"/> class.
/// </summary>
@ -42,9 +48,9 @@ namespace Avalonia.Media
}
/// <inheritdoc/>
IBrush IMutableBrush.ToImmutable()
public override IBrush ToImmutable()
{
return new Immutable.ImmutableVisualBrush(this);
return new ImmutableVisualBrush(this);
}
}
}

19
src/Avalonia.Visuals/Visual.cs

@ -338,11 +338,24 @@ namespace Avalonia
/// FrameworkPropertyMetadata.AffectsRender flag.
/// </remarks>
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : class, IVisual
where T : Visual
{
void Invalidate(AvaloniaPropertyChangedEventArgs e)
{
(e.Sender as T)?.InvalidateVisual();
if (e.Sender is T sender)
{
if (e.OldValue is IAffectsRender oldValue)
{
oldValue.Invalidated -= sender.AffectsRenderInvalidated;
}
if (e.NewValue is IAffectsRender newValue)
{
newValue.Invalidated += sender.AffectsRenderInvalidated;
}
sender.InvalidateVisual();
}
}
foreach (var property in properties)
@ -544,6 +557,8 @@ namespace Avalonia
OnVisualParentChanged(old, value);
}
private void AffectsRenderInvalidated(object sender, EventArgs e) => InvalidateVisual();
/// <summary>
/// Called when the <see cref="VisualChildren"/> collection changes.
/// </summary>

21
tests/Avalonia.Controls.UnitTests/BorderTests.cs

@ -1,6 +1,10 @@
// 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.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -42,5 +46,22 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Rect(6, 6, 0, 0), content.Bounds);
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new Border()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

21
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@ -3,7 +3,11 @@
using System.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -115,5 +119,22 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new Panel()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

20
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@ -13,6 +13,7 @@ using System;
using System.Linq;
using Xunit;
using Avalonia.Rendering;
using Avalonia.Media;
namespace Avalonia.Controls.UnitTests.Presenters
{
@ -203,5 +204,22 @@ namespace Avalonia.Controls.UnitTests.Presenters
Assert.NotEqual(foo, logicalChildren.First());
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new ContentPresenter()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}
}

48
tests/Avalonia.Controls.UnitTests/Shapes/RectangleTests.cs

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests.Shapes
{
public class RectangleTests
{
[Fact]
public void Changing_Fill_Brush_Color_Should_Invalidate_Visual()
{
var target = new Rectangle()
{
Fill = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Fill).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
[Fact]
public void Changing_Stroke_Brush_Color_Should_Invalidate_Visual()
{
var target = new Rectangle()
{
Stroke = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Stroke).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

38
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -2,6 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
@ -25,5 +29,39 @@ namespace Avalonia.Controls.UnitTests
"",
textBlock.Text);
}
[Fact]
public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
{
var target = new TextBlock()
{
Background = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Background).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
[Fact]
public void Changing_Foreground_Brush_Color_Should_Invalidate_Visual()
{
var target = new TextBlock()
{
Foreground = new SolidColorBrush(Colors.Red),
};
var root = new TestRoot(target);
var renderer = Mock.Get(root.Renderer);
renderer.ResetCalls();
((SolidColorBrush)target.Foreground).Color = Colors.Green;
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
}
}

2
tests/Avalonia.RenderTests/Controls/CustomRenderTests.cs

@ -124,7 +124,7 @@ namespace Avalonia.Direct2D1.RenderTests.Controls
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)

4
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -36,7 +36,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
@ -63,7 +63,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
StartPoint = new RelativePoint(0.5, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(0.5, 1, RelativeUnit.Relative),
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }

2
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -34,7 +34,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
Background = new RadialGradientBrush
{
GradientStops = new[]
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }

4
tests/Avalonia.RenderTests/OpacityMaskTests.cs

@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new List<GradientStop>
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)
@ -65,7 +65,7 @@ namespace Avalonia.Direct2D1.RenderTests
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops = new List<GradientStop>
GradientStops =
{
new GradientStop(Color.FromUInt32(0xffffffff), 0),
new GradientStop(Color.FromUInt32(0x00ffffff), 1)

7
tests/Avalonia.UnitTests/TestRoot.cs

@ -19,6 +19,13 @@ namespace Avalonia.UnitTests
public TestRoot()
{
Renderer = Mock.Of<IRenderer>();
}
public TestRoot(IControl child)
: this()
{
Child = child;
}
event EventHandler<NameScopeEventArgs> INameScope.Registered

27
tests/Avalonia.Visuals.UnitTests/Media/ImageBrushTests.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class ImageBrushTests
{
[Fact]
public void Changing_Source_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new ImageBrush(bitmap1);
var raised = false;
target.Invalidated += (s, e) => raised = true;
target.Source = bitmap2;
Assert.True(raised);
}
}
}

86
tests/Avalonia.Visuals.UnitTests/Media/LinearGradientBrushTests.cs

@ -0,0 +1,86 @@
using System;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Moq;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class LinearGradientBrushTests
{
[Fact]
public void Changing_StartPoint_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.StartPoint = new RelativePoint();
target.Invalidated += (s, e) => raised = true;
target.StartPoint = new RelativePoint(10, 10, RelativeUnit.Absolute);
Assert.True(raised);
}
[Fact]
public void Changing_EndPoint_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.EndPoint = new RelativePoint();
target.Invalidated += (s, e) => raised = true;
target.EndPoint = new RelativePoint(10, 10, RelativeUnit.Absolute);
Assert.True(raised);
}
[Fact]
public void Changing_GradientStops_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops = new GradientStops { new GradientStop(Colors.Green, 0) };
Assert.True(raised);
}
[Fact]
public void Adding_GradientStop_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops.Add(new GradientStop(Colors.Green, 1));
Assert.True(raised);
}
[Fact]
public void Changing_GradientStop_Offset_Raises_Invalidated()
{
var bitmap1 = Mock.Of<IBitmap>();
var bitmap2 = Mock.Of<IBitmap>();
var target = new LinearGradientBrush();
var raised = false;
target.GradientStops = new GradientStops { new GradientStop(Colors.Red, 0) };
target.Invalidated += (s, e) => raised = true;
target.GradientStops[0].Offset = 0.5;
Assert.True(raised);
}
}
}

21
tests/Avalonia.Visuals.UnitTests/Media/SolidColorBrushTests.cs

@ -0,0 +1,21 @@
using System;
using Avalonia.Media;
using Xunit;
namespace Avalonia.Visuals.UnitTests.Media
{
public class SolidColorBrushTests
{
[Fact]
public void Changing_Color_Raises_Invalidated()
{
var target = new SolidColorBrush(Colors.Red);
var raised = false;
target.Invalidated += (s, e) => raised = true;
target.Color = Colors.Green;
Assert.True(raised);
}
}
}
Loading…
Cancel
Save