diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index aa165d13f7..f37df56b73 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -36,6 +36,9 @@
+
+
+
diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
new file mode 100644
index 0000000000..b386636cae
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs
new file mode 100644
index 0000000000..eed8ee29ce
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml.cs
@@ -0,0 +1,27 @@
+using System.Reactive.Linq;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using RenderDemo.ViewModels;
+
+namespace RenderDemo.Pages
+{
+ public class CustomAnimatorPage : UserControl
+ {
+ public CustomAnimatorPage()
+ {
+ InitializeComponent();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/RenderDemo/Pages/CustomStringAnimator.cs b/samples/RenderDemo/Pages/CustomStringAnimator.cs
new file mode 100644
index 0000000000..851a2d0187
--- /dev/null
+++ b/samples/RenderDemo/Pages/CustomStringAnimator.cs
@@ -0,0 +1,16 @@
+using Avalonia.Animation.Animators;
+
+namespace RenderDemo.Pages
+{
+ public class CustomStringAnimator : Animator
+ {
+ public override string Interpolate(double progress, string oldValue, string newValue)
+ {
+ if (newValue.Length == 0) return "";
+ var step = 1.0 / newValue.Length;
+ var length = (int)(progress / step);
+ var result = newValue.Substring(0, length + 1);
+ return result;
+ }
+ }
+}
diff --git a/src/Avalonia.Animation/Animation.cs b/src/Avalonia.Animation/Animation.cs
index c42153ec4f..daa4793ef0 100644
--- a/src/Avalonia.Animation/Animation.cs
+++ b/src/Avalonia.Animation/Animation.cs
@@ -194,6 +194,33 @@ namespace Avalonia.Animation
[Content]
public KeyFrames Children { get; } = new KeyFrames();
+ // Store values for the Animator attached properties for IAnimationSetter objects.
+ private static readonly Dictionary s_animators = new Dictionary();
+
+ ///
+ /// Gets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator type.
+ public static Type GetAnimator(IAnimationSetter setter)
+ {
+ if (s_animators.TryGetValue(setter, out var type))
+ {
+ return type;
+ }
+ return null;
+ }
+
+ ///
+ /// Sets the value of the Animator attached property for a setter.
+ ///
+ /// The animation setter.
+ /// The property animator value.
+ public static void SetAnimator(IAnimationSetter setter, Type value)
+ {
+ s_animators[setter] = value;
+ }
+
private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)>
{
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
@@ -248,7 +275,7 @@ namespace Avalonia.Animation
{
foreach (var setter in keyframe.Setters)
{
- var handler = GetAnimatorType(setter.Property);
+ var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property);
if (handler == null)
{
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index 173c5c1a94..ae61f8f642 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -585,6 +585,30 @@ namespace Avalonia
});
}
+ ///
+ /// Subscribes to a property changed notifications for changes that originate from a
+ /// .
+ ///
+ /// The type of the property change sender.
+ /// /// The type of the property..
+ /// The property changed observable.
+ ///
+ /// The method to call. The parameters are the sender and the event args.
+ ///
+ /// A disposable that can be used to terminate the subscription.
+ public static IDisposable AddClassHandler(
+ this IObservable> observable,
+ Action> action) where TTarget : AvaloniaObject
+ {
+ return observable.Subscribe(e =>
+ {
+ if (e.Sender is TTarget target)
+ {
+ action(target, e);
+ }
+ });
+ }
+
///
/// Subscribes to a property changed notifications for changes that originate from a
/// .
diff --git a/src/Avalonia.Controls/ApiCompatBaseline.txt b/src/Avalonia.Controls/ApiCompatBaseline.txt
index a79b3b4d7b..46c12ebd39 100644
--- a/src/Avalonia.Controls/ApiCompatBaseline.txt
+++ b/src/Avalonia.Controls/ApiCompatBaseline.txt
@@ -4,6 +4,7 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.IMenuItem.StaysOpenOnClick.set(System.Boolean)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseClosed()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.INativeMenuExporterEventsImplBridge.RaiseOpening()' is present in the implementation but not in the contract.
+MembersMustExist : Member 'public Avalonia.StyledProperty Avalonia.StyledProperty Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
@@ -11,4 +12,4 @@ EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
-Total Issues: 12
+Total Issues: 13
diff --git a/src/Avalonia.Controls/ScrollViewer.cs b/src/Avalonia.Controls/ScrollViewer.cs
index 90c4e05943..559edeb204 100644
--- a/src/Avalonia.Controls/ScrollViewer.cs
+++ b/src/Avalonia.Controls/ScrollViewer.cs
@@ -176,8 +176,10 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty AllowAutoHideProperty =
- ScrollBar.AllowAutoHideProperty.AddOwner();
+ public static readonly AttachedProperty AllowAutoHideProperty =
+ AvaloniaProperty.RegisterAttached(
+ nameof(AllowAutoHide),
+ true);
///
/// Defines the event.
@@ -207,8 +209,8 @@ namespace Avalonia.Controls
///
static ScrollViewer()
{
- HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e));
- VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e));
+ HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e));
+ VerticalScrollBarVisibilityProperty.Changed.AddClassHandler((x, e) => x.ScrollBarVisibilityChanged(e));
}
///
@@ -526,6 +528,26 @@ namespace Avalonia.Controls
return control.GetValue(VerticalScrollBarVisibilityProperty);
}
+ ///
+ /// Gets the value of the AllowAutoHideProperty attached property.
+ ///
+ /// The control to set the value on.
+ /// The value of the property.
+ public static void SetAllowAutoHide(Control control, bool value)
+ {
+ control.SetValue(AllowAutoHideProperty, value);
+ }
+
+ ///
+ /// Gets the value of the AllowAutoHideProperty attached property.
+ ///
+ /// The control to read the value from.
+ /// The value of the property.
+ public static bool GetAllowAutoHide(Control control)
+ {
+ return control.GetValue(AllowAutoHideProperty);
+ }
+
///
/// Gets the value of the VerticalScrollBarVisibility attached property.
///
@@ -604,10 +626,10 @@ namespace Avalonia.Controls
CalculatedPropertiesChanged();
}
- private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
+ private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
{
- var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
- var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue);
+ var wasEnabled = e.OldValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
+ var isEnabled = e.NewValue.GetValueOrDefault() != ScrollBarVisibility.Disabled;
if (wasEnabled != isEnabled)
{
diff --git a/src/Avalonia.Themes.Default/ListBox.xaml b/src/Avalonia.Themes.Default/ListBox.xaml
index e91d8a6772..1ad996a1a0 100644
--- a/src/Avalonia.Themes.Default/ListBox.xaml
+++ b/src/Avalonia.Themes.Default/ListBox.xaml
@@ -13,7 +13,8 @@
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
+ VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}"
+ AllowAutoHide="{TemplateBinding (ScrollViewer.AllowAutoHide)}">
public void DrawLine(IPen pen, Point p1, Point p2)
{
- using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
+ using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize()))
{
if (paint.Paint is object)
{
@@ -177,10 +177,10 @@ namespace Avalonia.Skia
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var impl = (GeometryImpl) geometry;
- var size = geometry.Bounds.Size;
+ var rect = geometry.Bounds;
- using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
- using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
+ using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper))
+ using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper))
{
if (fill.Paint != null)
{
@@ -354,7 +354,7 @@ namespace Avalonia.Skia
if (brush != null)
{
- using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
+ using (var paint = CreatePaint(_fillPaint, brush, rect.Rect))
{
if (isRounded)
{
@@ -397,7 +397,7 @@ namespace Avalonia.Skia
if (pen?.Brush != null)
{
- using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
+ using (var paint = CreatePaint(_strokePaint, pen, rect.Rect))
{
if (paint.Paint is object)
{
@@ -417,7 +417,7 @@ namespace Avalonia.Skia
///
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
- using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size))
+ using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds))
{
var textImpl = (FormattedTextImpl) text;
textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering);
@@ -427,7 +427,7 @@ namespace Avalonia.Skia
///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
- using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
+ using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@@ -537,7 +537,7 @@ namespace Avalonia.Skia
var paint = new SKPaint();
Canvas.SaveLayer(paint);
- _maskStack.Push(CreatePaint(paint, mask, bounds.Size, true));
+ _maskStack.Push(CreatePaint(paint, mask, bounds, true));
}
///
@@ -593,18 +593,19 @@ namespace Avalonia.Skia
/// Paint wrapper.
/// Target size.
/// Gradient brush.
- private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
+ private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
{
var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
+ var position = targetRect.Position.ToSKPoint();
switch (gradientBrush)
{
case ILinearGradientBrush linearGradient:
{
- var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
- var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
+ var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint();
+ var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
@@ -617,10 +618,10 @@ namespace Avalonia.Skia
}
case IRadialGradientBrush radialGradient:
{
- var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
- var radius = (float)(radialGradient.Radius * targetSize.Width);
+ var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
+ var radius = (float)(radialGradient.Radius * targetRect.Width);
- var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
+ var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint();
if (origin.Equals(center))
{
@@ -665,7 +666,7 @@ namespace Avalonia.Skia
}
case IConicGradientBrush conicGradient:
{
- var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
+ var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
@@ -867,10 +868,10 @@ namespace Avalonia.Skia
///
/// The paint to wrap.
/// Source brush.
- /// Target size.
+ /// Target rect.
/// Optional dispose of the supplied paint.
/// Paint wrapper for given brush.
- internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false)
+ internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false)
{
var paintWrapper = new PaintWrapper(paint, disposePaint);
@@ -889,7 +890,7 @@ namespace Avalonia.Skia
if (brush is IGradientBrush gradient)
{
- ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
+ ConfigureGradientBrush(ref paintWrapper, targetRect, gradient);
return paintWrapper;
}
@@ -909,7 +910,7 @@ namespace Avalonia.Skia
if (tileBrush != null && tileBrushImage != null)
{
- ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
+ ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage);
}
else
{
@@ -924,10 +925,10 @@ namespace Avalonia.Skia
///
/// The paint to wrap.
/// Source pen.
- /// Target size.
+ /// Target rect.
/// Optional dispose of the supplied paint.
///
- private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false)
+ private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false)
{
// In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered.
@@ -936,7 +937,7 @@ namespace Avalonia.Skia
return default;
}
- var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint);
+ var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint);
paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness;
diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
index 5f4980e461..3eca42faa9 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -278,9 +278,9 @@ namespace Avalonia.Skia
if (fb != null)
{
- //TODO: figure out how to get the brush size
+ //TODO: figure out how to get the brush rect
currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb,
- new Size());
+ default);
}
else
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
index 22c998df93..e4b2405290 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
@@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media
{
var wrapper = clientDrawingEffect as BrushWrapper;
- // TODO: Work out how to get the size below rather than passing new Size().
+ // TODO: Work out how to get the rect below rather than passing default.
var brush = (wrapper == null) ?
_foreground :
- _context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
+ _context.CreateBrush(wrapper.Brush, default).PlatformBrush;
_renderTarget.DrawGlyphRun(
new RawVector2 { X = baselineOriginX, Y = baselineOriginY },
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index af35934785..9336c9a7bb 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
- using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
+ using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
@@ -217,9 +217,7 @@ namespace Avalonia.Direct2D1.Media
{
if (pen != null)
{
- var size = new Rect(p1, p2).Size;
-
- using (var d2dBrush = CreateBrush(pen.Brush, size))
+ using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize()))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@@ -245,7 +243,7 @@ namespace Avalonia.Direct2D1.Media
{
if (brush != null)
{
- using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
+ using (var d2dBrush = CreateBrush(brush, geometry.Bounds))
{
if (d2dBrush.PlatformBrush != null)
{
@@ -257,7 +255,7 @@ namespace Avalonia.Direct2D1.Media
if (pen != null)
{
- using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
+ using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen)))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@@ -282,7 +280,7 @@ namespace Avalonia.Direct2D1.Media
if (brush != null)
{
- using (var b = CreateBrush(brush, rect.Size))
+ using (var b = CreateBrush(brush, rect))
{
if (b.PlatformBrush != null)
{
@@ -311,7 +309,7 @@ namespace Avalonia.Direct2D1.Media
if (pen?.Brush != null)
{
- using (var wrapper = CreateBrush(pen.Brush, rect.Size))
+ using (var wrapper = CreateBrush(pen.Brush, rect))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (wrapper.PlatformBrush != null)
@@ -349,7 +347,7 @@ namespace Avalonia.Direct2D1.Media
{
var impl = (FormattedTextImpl)text;
- using (var brush = CreateBrush(foreground, impl.Bounds.Size))
+ using (var brush = CreateBrush(foreground, impl.Bounds))
using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
{
if (brush.PlatformBrush != null)
@@ -367,7 +365,7 @@ namespace Avalonia.Direct2D1.Media
/// The glyph run.
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
- using (var brush = CreateBrush(foreground, glyphRun.Size))
+ using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size)))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@@ -458,9 +456,9 @@ namespace Avalonia.Direct2D1.Media
/// Creates a Direct2D brush wrapper for a Avalonia brush.
///
/// The avalonia brush.
- /// The size of the brush's target area.
+ /// The brush's target area.
/// The Direct2D brush wrapper.
- public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
+ public BrushImpl CreateBrush(IBrush brush, Rect destinationRect)
{
var solidColorBrush = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
@@ -475,11 +473,11 @@ namespace Avalonia.Direct2D1.Media
}
else if (linearGradientBrush != null)
{
- return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
+ return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect);
}
else if (radialGradientBrush != null)
{
- return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
+ return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect);
}
else if (conicGradientBrush != null)
{
@@ -492,7 +490,7 @@ namespace Avalonia.Direct2D1.Media
imageBrush,
_deviceContext,
(BitmapImpl)imageBrush.Source.PlatformImpl.Item,
- destinationSize);
+ destinationRect.Size);
}
else if (visualBrush != null)
{
@@ -523,7 +521,7 @@ namespace Avalonia.Direct2D1.Media
visualBrush,
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
- destinationSize);
+ destinationRect.Size);
}
}
}
@@ -574,7 +572,7 @@ namespace Avalonia.Direct2D1.Media
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1,
- OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
+ OpacityBrush = CreateBrush(mask, bounds).PlatformBrush
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);
diff --git a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
index 0e63d4cc03..69b45455ac 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public LinearGradientBrushImpl(
ILinearGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
- Size destinationSize)
+ Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@@ -21,8 +21,9 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
- var startPoint = brush.StartPoint.ToPixels(destinationSize);
- var endPoint = brush.EndPoint.ToPixels(destinationSize);
+ var position = destinationRect.Position;
+ var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size);
+ var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size);
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,
diff --git a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
index 1fca6d4e33..7dcfd7e1e0 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public RadialGradientBrushImpl(
IRadialGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
- Size destinationSize)
+ Rect destinationRect)
{
if (brush.GradientStops.Count == 0)
{
@@ -21,12 +21,13 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
- var centerPoint = brush.Center.ToPixels(destinationSize);
- var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint;
+ var position = destinationRect.Position;
+ var centerPoint = position + brush.Center.ToPixels(destinationRect.Size);
+ var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint;
// Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
- var radiusX = brush.Radius * destinationSize.Width;
- var radiusY = brush.Radius * destinationSize.Height;
+ var radiusX = brush.Radius * destinationRect.Width;
+ var radiusY = brush.Radius * destinationRect.Height;
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,
diff --git a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
index db69a0a028..3d2f09e2a8 100644
--- a/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
@@ -1,5 +1,6 @@
using Avalonia.Controls;
using Avalonia.Media;
+using System;
using System.Threading.Tasks;
using Xunit;
@@ -174,5 +175,44 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
+
+ [Fact]
+ public async Task ConicGradientBrush_DrawingContext()
+ {
+ var brush = new ConicGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Yellow, Offset = 0.1667 },
+ new GradientStop { Color = Colors.Lime, Offset = 0.3333 },
+ new GradientStop { Color = Colors.Aqua, Offset = 0.5000 },
+ new GradientStop { Color = Colors.Blue, Offset = 0.6667 },
+ new GradientStop { Color = Colors.Magenta, Offset = 0.8333 },
+ new GradientStop { Color = Colors.Red, Offset = 1 },
+ }
+ };
+
+ Decorator target = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new DrawnControl(c =>
+ {
+ c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+ c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+ }),
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ private class DrawnControl : Control
+ {
+ private readonly Action _render;
+ public DrawnControl(Action render) => _render = render;
+ public override void Render(DrawingContext context) => _render(context);
+ }
}
}
diff --git a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
index af7b4f130f..28701f2f97 100644
--- a/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
@@ -72,5 +72,41 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
+
+ [Fact]
+ public async Task LinearGradientBrush_DrawingContext()
+ {
+ var brush = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ }
+ };
+
+ Decorator target = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new DrawnControl(c =>
+ {
+ c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+ c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+ }),
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ private class DrawnControl : Control
+ {
+ private readonly Action _render;
+ public DrawnControl(Action render) => _render = render;
+ public override void Render(DrawingContext context) => _render(context);
+ }
}
}
diff --git a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
index 2941b8dc34..95fae7f2fa 100644
--- a/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs
@@ -165,5 +165,39 @@ namespace Avalonia.Direct2D1.RenderTests.Media
await RenderToFile(target);
CompareImages();
}
+
+ [Fact]
+ public async Task RadialGradientBrush_DrawingContext()
+ {
+ var brush = new RadialGradientBrush
+ {
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ }
+ };
+
+ Decorator target = new Decorator
+ {
+ Width = 200,
+ Height = 200,
+ Child = new DrawnControl(c =>
+ {
+ c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
+ c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
+ }),
+ };
+
+ await RenderToFile(target);
+ CompareImages();
+ }
+
+ private class DrawnControl : Control
+ {
+ private readonly Action _render;
+ public DrawnControl(Action render) => _render = render;
+ public override void Render(DrawingContext context) => _render(context);
+ }
}
}
diff --git a/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..3a2241b53f
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..7ba0bf1622
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..85d11f2c2b
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..3906d5a181
Binary files /dev/null and b/tests/TestFiles/Skia/Media/ConicGradientBrush/ConicGradientBrush_DrawingContext.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..5ccfc0fcfa
Binary files /dev/null and b/tests/TestFiles/Skia/Media/LinearGradientBrush/LinearGradientBrush_DrawingContext.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png
new file mode 100644
index 0000000000..55533a5828
Binary files /dev/null and b/tests/TestFiles/Skia/Media/RadialGradientBrush/RadialGradientBrush_DrawingContext.expected.png differ