diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 870345e543..c0936356d2 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -513,6 +513,7 @@ private:
bool _fullScreenActive;
SystemDecorations _decorations;
AvnWindowState _lastWindowState;
+ AvnWindowState _actualWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
@@ -539,6 +540,7 @@ private:
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal;
+ _actualWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
@@ -633,7 +635,7 @@ private:
void WindowStateChanged () override
{
- if(!_inSetWindowState && !_transitioningWindowState)
+ if(_shown && !_inSetWindowState && !_transitioningWindowState)
{
AvnWindowState state;
GetWindowState(&state);
@@ -963,14 +965,14 @@ private:
{
@autoreleasepool
{
- if(_lastWindowState == state)
+ if(_actualWindowState == state)
{
return S_OK;
}
_inSetWindowState = true;
- auto currentState = _lastWindowState;
+ auto currentState = _actualWindowState;
_lastWindowState = state;
if(currentState == Normal)
@@ -1049,8 +1051,11 @@ private:
}
break;
}
+
+ _actualWindowState = _lastWindowState;
}
+
_inSetWindowState = false;
return S_OK;
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index 723351ae57..2446c0e1c9 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -17,7 +17,10 @@ namespace ControlCatalog
public MainWindow()
{
this.InitializeComponent();
- this.AttachDevTools();
+ this.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions()
+ {
+ StartupScreenIndex = 1,
+ });
//Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
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.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs
index 6f601a3e13..0e1882ce75 100644
--- a/src/Avalonia.Animation/AnimationInstance`1.cs
+++ b/src/Avalonia.Animation/AnimationInstance`1.cs
@@ -5,6 +5,7 @@ using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
+using JetBrains.Annotations;
namespace Avalonia.Animation
{
@@ -45,8 +46,9 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
- _neutralValue = (T)_targetControl.GetValue(_animator.Property);
+ control.PropertyChanged += ControlPropertyChanged;
+ UpdateNeutralValue();
FetchProperties();
}
@@ -216,5 +218,22 @@ namespace Avalonia.Animation
}
}
}
+
+ private void UpdateNeutralValue()
+ {
+ var property = _animator.Property;
+ var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
+
+ _neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
+ (T)baseValue : (T)_targetControl.GetValue(property);
+ }
+
+ private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation)
+ {
+ UpdateNeutralValue();
+ }
+ }
}
}
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/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index fabb38f87d..234960e87c 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -70,7 +70,7 @@ namespace Avalonia.Controls.Remote
public override void Render(DrawingContext context)
{
- if (_lastFrame != null)
+ if (_lastFrame != null && _lastFrame.Width != 0 && _lastFrame.Height != 0)
{
var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelSize.Width != _lastFrame.Width ||
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.Diagnostics/Diagnostics/DevTools.cs b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
index 0e36c8f9cb..2a386f106e 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
@@ -10,7 +10,8 @@ namespace Avalonia.Diagnostics
{
public static class DevTools
{
- private static readonly Dictionary s_open = new Dictionary();
+ private static readonly Dictionary s_open =
+ new Dictionary();
public static IDisposable Attach(TopLevel root, KeyGesture gesture)
{
@@ -52,6 +53,7 @@ namespace Avalonia.Diagnostics
Width = options.Size.Width,
Height = options.Size.Height,
};
+ window.SetOptions(options);
window.Closed += DevToolsClosed;
s_open.Add(root, window);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
index f9978f3b7e..5336dca65b 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
@@ -22,5 +22,10 @@ namespace Avalonia.Diagnostics
/// Gets or sets the initial size of the DevTools window. The default value is 1280x720.
///
public Size Size { get; set; } = new Size(1280, 720);
+
+ ///
+ /// Get or set the startup screen index where the DevTools window will be displayed.
+ ///
+ public int? StartupScreenIndex { get; set; }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
index 72491bebc2..3f367165ac 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
@@ -151,7 +151,7 @@ namespace Avalonia.Diagnostics.ViewModels
get { return _pointerOverElement; }
private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); }
}
-
+
private void UpdateConsoleContext(ConsoleContext context)
{
context.root = _root;
@@ -213,5 +213,12 @@ namespace Avalonia.Diagnostics.ViewModels
tree.SelectControl(control);
}
}
+
+ public int? StartupScreenIndex { get; private set; } = default;
+
+ public void SetOptions(DevToolsOptions options)
+ {
+ StartupScreenIndex = options.StartupScreenIndex;
+ }
}
}
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
index bbb8e76551..d1232b749a 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -24,6 +24,23 @@ namespace Avalonia.Diagnostics.Views
_keySubscription = InputManager.Instance.Process
.OfType()
.Subscribe(RawKeyDown);
+
+ EventHandler? lh = default;
+ lh = (s, e) =>
+ {
+ this.Opened -= lh;
+ if ((DataContext as MainViewModel)?.StartupScreenIndex is int index)
+ {
+ var screens = this.Screens;
+ if (index > -1 && index < screens.ScreenCount)
+ {
+ var screen = screens.All[index];
+ this.Position = screen.Bounds.TopLeft;
+ this.WindowState = WindowState.Maximized;
+ }
+ }
+ };
+ this.Opened += lh;
}
public TopLevel? Root
@@ -115,5 +132,8 @@ namespace Avalonia.Diagnostics.Views
}
private void RootClosed(object? sender, EventArgs e) => Close();
+
+ public void SetOptions(DevToolsOptions options) =>
+ (DataContext as MainViewModel)?.SetOptions(options);
}
}
diff --git a/src/Avalonia.Input/KeyboardDevice.cs b/src/Avalonia.Input/KeyboardDevice.cs
index 79152e20d2..a159b19026 100644
--- a/src/Avalonia.Input/KeyboardDevice.cs
+++ b/src/Avalonia.Input/KeyboardDevice.cs
@@ -217,12 +217,31 @@ namespace Avalonia.Input
{
var bindings = (currentHandler as IInputElement)?.KeyBindings;
if (bindings != null)
+ {
+ KeyBinding[]? bindingsCopy = null;
+
+ // Create a copy of the KeyBindings list if there's a binding which matches the event.
+ // If we don't do this the foreach loop will throw an InvalidOperationException when the KeyBindings list is changed.
+ // This can happen when a new view is loaded which adds its own KeyBindings to the handler.
foreach (var binding in bindings)
{
- if (ev.Handled)
+ if (binding.Gesture?.Matches(ev) == true)
+ {
+ bindingsCopy = bindings.ToArray();
break;
- binding.TryHandle(ev);
+ }
+ }
+
+ if (bindingsCopy is object)
+ {
+ foreach (var binding in bindingsCopy)
+ {
+ if (ev.Handled)
+ break;
+ binding.TryHandle(ev);
+ }
}
+ }
currentHandler = currentHandler.VisualParent;
}
diff --git a/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs b/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
index 9e30e4fa14..8d446ebc9c 100644
--- a/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
+++ b/src/Avalonia.Styling/Styling/Activators/PropertyEqualsActivator.cs
@@ -33,6 +33,6 @@ namespace Avalonia.Styling.Activators
void IObserver
+ public sealed class PolyLineSegment : PathSegment
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PointsProperty
+ = AvaloniaProperty.Register(nameof(Points));
+
+ ///
+ /// Gets or sets the points.
+ ///
+ ///
+ /// The points.
+ ///
+ public AvaloniaList Points
+ {
+ get => GetValue(PointsProperty);
+ set => SetValue(PointsProperty, value);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public PolyLineSegment()
+ {
+ Points = new Points();
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The points.
+ public PolyLineSegment(IEnumerable points) : this()
+ {
+ Points.AddRange(points);
+ }
+
+ protected internal override void ApplyTo(StreamGeometryContext ctx)
+ {
+ var points = Points;
+ if (points.Count > 0)
+ {
+ for (int i = 0; i < points.Count; i++)
+ {
+ ctx.LineTo(points[i]);
+ }
+ }
+ }
+
+ public override string ToString()
+ => Points.Count >= 1 ? "L " + string.Join(" ", Points) : "";
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 2c5c2acd52..d8bd0607d1 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -164,7 +164,7 @@ namespace Avalonia.Skia
///
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 5e630e54a6..3eca42faa9 100644
--- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
+++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using Avalonia.Media;
using Avalonia.Platform;
@@ -175,7 +176,8 @@ namespace Avalonia.Skia
foreach (var line in _skiaLines.Where(l =>
(l.Start + l.Length) > index &&
- lastIndex >= l.Start))
+ lastIndex >= l.Start &&
+ !l.IsEmptyTrailingLine))
{
int lineEndIndex = line.Start + (line.Length > 0 ? line.Length - 1 : 0);
@@ -276,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
{
@@ -466,7 +468,8 @@ namespace Avalonia.Skia
for (int i = line.Start; i < line.Start + line.TextLength; i++)
{
- float w = _paint.MeasureText(Text[i].ToString());
+ var c = Text[i];
+ var w = line.IsEmptyTrailingLine ? 0 :_paint.MeasureText(Text[i].ToString());
_rects.Add(new Rect(
prevRight,
@@ -611,6 +614,7 @@ namespace Avalonia.Skia
lastLine.Width = lastLineWidth;
lastLine.Height = _lineHeight;
lastLine.Top = curY;
+ lastLine.IsEmptyTrailingLine = true;
_skiaLines.Add(lastLine);
@@ -713,6 +717,7 @@ namespace Avalonia.Skia
public int TextLength;
public float Top;
public float Width;
+ public bool IsEmptyTrailingLine;
};
private struct FBrushRange
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.Input.UnitTests/KeyboardDeviceTests.cs b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
index df0a077c7f..7730cee78c 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardDeviceTests.cs
@@ -1,5 +1,9 @@
-using Avalonia.Input.Raw;
+using System;
+using System.Windows.Input;
+using Avalonia.Controls;
+using Avalonia.Input.Raw;
using Avalonia.Interactivity;
+using Avalonia.UnitTests;
using Moq;
using Xunit;
@@ -86,5 +90,45 @@ namespace Avalonia.Input.UnitTests
focused.Verify(x => x.RaiseEvent(It.IsAny()));
}
+
+ [Fact]
+ public void Can_Change_KeyBindings_In_Keybinding_Event_Handler()
+ {
+ var target = new KeyboardDevice();
+ var button = new Button();
+ var root = new TestRoot(button);
+ var raised = 0;
+
+ button.KeyBindings.Add(new KeyBinding
+ {
+ Gesture = new KeyGesture(Key.O, KeyModifiers.Control),
+ Command = new DelegateCommand(() =>
+ {
+ button.KeyBindings.Clear();
+ ++raised;
+ }),
+ });
+
+ target.SetFocusedElement(button, NavigationMethod.Pointer, 0);
+ target.ProcessRawEvent(
+ new RawKeyEventArgs(
+ target,
+ 0,
+ root,
+ RawKeyEventType.KeyDown,
+ Key.O,
+ RawInputModifiers.Control));
+
+ Assert.Equal(1, raised);
+ }
+
+ private class DelegateCommand : ICommand
+ {
+ private readonly Action _action;
+ public DelegateCommand(Action action) => _action = action;
+ public event EventHandler CanExecuteChanged;
+ public bool CanExecute(object parameter) => true;
+ public void Execute(object parameter) => _action();
+ }
}
}
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/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
index e149410152..7689a458ae 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_PropertyEquals.cs
@@ -22,6 +22,23 @@ namespace Avalonia.Styling.UnitTests
Assert.False(await activator.Take(1));
}
+ [Theory]
+ [InlineData("Bar", FooBar.Bar)]
+ [InlineData("352", 352)]
+ [InlineData("0.1", 0.1)]
+ public async Task PropertyEquals_Matches_When_Property_Has_Matching_Value_And_Different_Type(string literal, object value)
+ {
+ var control = new TextBlock();
+ var target = default(Selector).PropertyEquals(TextBlock.TagProperty, literal);
+ var activator = target.Match(control).Activator.ToObservable();
+
+ Assert.False(await activator.Take(1));
+ control.Tag = value;
+ Assert.True(await activator.Take(1));
+ control.Tag = null;
+ Assert.False(await activator.Take(1));
+ }
+
[Fact]
public void OfType_PropertyEquals_Doesnt_Match_Control_Of_Wrong_Type()
{
@@ -40,5 +57,11 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal("TextBlock[Text=foo]", target.ToString());
}
+
+ private enum FooBar
+ {
+ Foo,
+ Bar
+ }
}
}
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