Browse Source

Merge branch 'master' into fixes/x11-transparency-crash

pull/7370/head
Max Katz 5 years ago
committed by GitHub
parent
commit
a02c2fa234
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      samples/ControlCatalog/App.xaml
  2. 15
      samples/ControlCatalog/Models/Person.cs
  3. 12
      samples/ControlCatalog/Pages/DataGridPage.xaml
  4. 22
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  5. 16
      src/Avalonia.Animation/Animatable.cs
  6. 46
      src/Avalonia.Animation/Animation.cs
  7. 33
      src/Avalonia.Animation/AnimationInstance`1.cs
  8. 20
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  9. 9
      src/Avalonia.Animation/Animators/Animator`1.cs
  10. 4
      src/Avalonia.Animation/Avalonia.Animation.csproj
  11. 2
      src/Avalonia.Animation/Clock.cs
  12. 8
      src/Avalonia.Animation/Cue.cs
  13. 8
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  14. 4
      src/Avalonia.Animation/Easing/Easing.cs
  15. 4
      src/Avalonia.Animation/Easing/EasingTypeConverter.cs
  16. 2
      src/Avalonia.Animation/IAnimation.cs
  17. 4
      src/Avalonia.Animation/IAnimationSetter.cs
  18. 4
      src/Avalonia.Animation/IAnimator.cs
  19. 2
      src/Avalonia.Animation/ITransition.cs
  20. 2
      src/Avalonia.Animation/IterationCount.cs
  21. 4
      src/Avalonia.Animation/IterationCountTypeConverter.cs
  22. 4
      src/Avalonia.Animation/KeyFrame.cs
  23. 4
      src/Avalonia.Animation/KeySplineTypeConverter.cs
  24. 21
      src/Avalonia.Animation/Transition.cs
  25. 6
      src/Avalonia.Animation/TransitionInstance.cs
  26. 2
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  27. 1
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  28. 2
      src/Avalonia.Controls.DataGrid/DataGridColumn.cs
  29. 72
      src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs
  30. 2
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  31. 1
      src/Avalonia.Dialogs/Avalonia.Dialogs.csproj
  32. 6
      src/Avalonia.Input/KeyBinding.cs
  33. 111
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  34. 2
      src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml
  35. 2
      src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml
  36. 1
      src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml
  37. 1
      src/Avalonia.Themes.Fluent/Controls/ListBox.xaml
  38. 7
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  39. 5
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  40. 5
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  41. 7
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  42. 5
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  43. 118
      src/Avalonia.Visuals/Media/Pen.cs
  44. 2
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  45. 25
      tests/Avalonia.Animation.UnitTests/AnimatableTests.cs

1
samples/ControlCatalog/App.xaml

@ -3,6 +3,7 @@
xmlns:vm="using:ControlCatalog.ViewModels" xmlns:vm="using:ControlCatalog.ViewModels"
x:DataType="vm:ApplicationViewModel" x:DataType="vm:ApplicationViewModel"
x:CompileBindings="True" x:CompileBindings="True"
Name="Avalonia ControlCatalog"
x:Class="ControlCatalog.App"> x:Class="ControlCatalog.App">
<Application.Styles> <Application.Styles>
<Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3"> <Style Selector="TextBlock.h1, TextBlock.h2, TextBlock.h3">

15
samples/ControlCatalog/Models/Person.cs

@ -16,6 +16,7 @@ namespace ControlCatalog.Models
string _firstName; string _firstName;
string _lastName; string _lastName;
bool _isBanned; bool _isBanned;
private int _age;
public string FirstName public string FirstName
{ {
@ -59,6 +60,20 @@ namespace ControlCatalog.Models
} }
} }
/// <summary>
/// Gets or sets the age of the person
/// </summary>
public int Age
{
get => _age;
set
{
_age = value;
OnPropertyChanged(nameof(Age));
}
}
Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>(); Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
void SetError(string propertyName, string error) void SetError(string propertyName, string error)

12
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -64,6 +64,18 @@
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" /> <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" /> <DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" IsThreeState="{Binding #IsThreeStateCheckBox.IsChecked, Mode=OneWay}" /> <DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" IsThreeState="{Binding #IsThreeStateCheckBox.IsChecked, Mode=OneWay}" />
<DataGridTemplateColumn Header="Age" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Person">
<TextBlock Text="{Binding Age, StringFormat='{}{0} years'}" VerticalAlignment="Center" HorizontalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate DataType="local:Person">
<NumericUpDown Value="{Binding Age}" FormatString="N0" HorizontalAlignment="Stretch" Minimum="0" Maximum="120" TemplateApplied="NumericUpDown_OnTemplateApplied" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" /> <Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" />

22
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -6,7 +6,9 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using ControlCatalog.Models; using ControlCatalog.Models;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.Primitives;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
namespace ControlCatalog.Pages namespace ControlCatalog.Pages
{ {
@ -48,9 +50,9 @@ namespace ControlCatalog.Pages
var items = new List<Person> var items = new List<Person>
{ {
new Person { FirstName = "John", LastName = "Doe" }, new Person { FirstName = "John", LastName = "Doe" , Age = 30},
new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true }, new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true , Age = 40 },
new Person { FirstName = "Zack", LastName = "Ward" } new Person { FirstName = "Zack", LastName = "Ward" , Age = 50 }
}; };
var collectionView3 = new DataGridCollectionView(items); var collectionView3 = new DataGridCollectionView(items);
@ -84,5 +86,19 @@ namespace ControlCatalog.Pages
return Comparer.Default.Compare(x, y); return Comparer.Default.Compare(x, y);
} }
} }
private void NumericUpDown_OnTemplateApplied(object sender, TemplateAppliedEventArgs e)
{
// We want to focus the TextBox of the NumericUpDown. To do so we search for this control when the template
// is applied, but we postpone the action until the control is actually loaded.
if (e.NameScope.Find<TextBox>("PART_TextBox") is {} textBox)
{
Dispatcher.UIThread.InvokeAsync(() =>
{
textBox.Focus();
textBox.SelectAll();
}, DispatcherPriority.Loaded);
}
}
} }
} }

16
src/Avalonia.Animation/Animatable.cs

@ -157,7 +157,7 @@ namespace Avalonia.Animation
state.Instance?.Dispose(); state.Instance?.Dispose();
state.Instance = transition.Apply( state.Instance = transition.Apply(
this, this,
Clock ?? AvaloniaLocator.Current.GetService<IGlobalClock>(), Clock ?? AvaloniaLocator.Current.GetRequiredService<IGlobalClock>(),
oldValue, oldValue,
newValue); newValue);
return; return;
@ -169,7 +169,7 @@ namespace Avalonia.Animation
base.OnPropertyChangedCore(change); base.OnPropertyChangedCore(change);
} }
private void TransitionsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private void TransitionsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
if (!_transitionsEnabled) if (!_transitionsEnabled)
{ {
@ -179,14 +179,14 @@ namespace Avalonia.Animation
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
AddTransitions(e.NewItems); AddTransitions(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
RemoveTransitions(e.OldItems); RemoveTransitions(e.OldItems!);
break; break;
case NotifyCollectionChangedAction.Replace: case NotifyCollectionChangedAction.Replace:
RemoveTransitions(e.OldItems); RemoveTransitions(e.OldItems!);
AddTransitions(e.NewItems); AddTransitions(e.NewItems!);
break; break;
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException("Transitions collection cannot be reset."); throw new NotSupportedException("Transitions collection cannot be reset.");
@ -204,7 +204,7 @@ namespace Avalonia.Animation
for (var i = 0; i < items.Count; ++i) for (var i = 0; i < items.Count; ++i)
{ {
var t = (ITransition)items[i]; var t = (ITransition)items[i]!;
_transitionState.Add(t, new TransitionState _transitionState.Add(t, new TransitionState
{ {
@ -222,7 +222,7 @@ namespace Avalonia.Animation
for (var i = 0; i < items.Count; ++i) for (var i = 0; i < items.Count; ++i)
{ {
var t = (ITransition)items[i]; var t = (ITransition)items[i]!;
if (_transitionState.TryGetValue(t, out var state)) if (_transitionState.TryGetValue(t, out var state))
{ {

46
src/Avalonia.Animation/Animation.cs

@ -203,7 +203,7 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
/// <param name="setter">The animation setter.</param> /// <param name="setter">The animation setter.</param>
/// <returns>The property animator type.</returns> /// <returns>The property animator type.</returns>
public static Type GetAnimator(IAnimationSetter setter) public static Type? GetAnimator(IAnimationSetter setter)
{ {
if (s_animators.TryGetValue(setter, out var type)) if (s_animators.TryGetValue(setter, out var type))
{ {
@ -254,7 +254,7 @@ namespace Avalonia.Animation
Animators.Insert(0, (condition, typeof(TAnimator))); Animators.Insert(0, (condition, typeof(TAnimator)));
} }
private static Type GetAnimatorType(AvaloniaProperty property) private static Type? GetAnimatorType(AvaloniaProperty property)
{ {
foreach (var (condition, type) in Animators) foreach (var (condition, type) in Animators)
{ {
@ -276,6 +276,11 @@ namespace Avalonia.Animation
{ {
foreach (var setter in keyframe.Setters) foreach (var setter in keyframe.Setters)
{ {
if (setter.Property is null)
{
throw new InvalidOperationException("No Setter property assigned.");
}
var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property); var handler = Animation.GetAnimator(setter) ?? GetAnimatorType(setter.Property);
if (handler == null) if (handler == null)
@ -305,7 +310,7 @@ namespace Avalonia.Animation
foreach (var (handlerType, property) in handlerList) foreach (var (handlerType, property) in handlerList)
{ {
var newInstance = (IAnimator)Activator.CreateInstance(handlerType); var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!;
newInstance.Property = property; newInstance.Property = property;
newAnimatorInstances.Add(newInstance); newAnimatorInstances.Add(newInstance);
} }
@ -321,32 +326,43 @@ namespace Avalonia.Animation
} }
/// <inheritdoc/> /// <inheritdoc/>
public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete) public IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete)
{ {
var (animators, subscriptions) = InterpretKeyframes(control); var (animators, subscriptions) = InterpretKeyframes(control);
if (animators.Count == 1) if (animators.Count == 1)
{ {
subscriptions.Add(animators[0].Apply(this, control, clock, match, onComplete)); var subscription = animators[0].Apply(this, control, clock, match, onComplete);
if (subscription is not null)
{
subscriptions.Add(subscription);
}
} }
else else
{ {
var completionTasks = onComplete != null ? new List<Task>() : null; var completionTasks = onComplete != null ? new List<Task>() : null;
foreach (IAnimator animator in animators) foreach (IAnimator animator in animators)
{ {
Action animatorOnComplete = null; Action? animatorOnComplete = null;
if (onComplete != null) if (onComplete != null)
{ {
var tcs = new TaskCompletionSource<object>(); var tcs = new TaskCompletionSource<object?>();
animatorOnComplete = () => tcs.SetResult(null); animatorOnComplete = () => tcs.SetResult(null);
completionTasks.Add(tcs.Task); completionTasks!.Add(tcs.Task);
}
var subscription = animator.Apply(this, control, clock, match, animatorOnComplete);
if (subscription is not null)
{
subscriptions.Add(subscription);
} }
subscriptions.Add(animator.Apply(this, control, clock, match, animatorOnComplete));
} }
if (onComplete != null) if (onComplete != null)
{ {
Task.WhenAll(completionTasks).ContinueWith( Task.WhenAll(completionTasks!).ContinueWith(
(_, state) => ((Action)state).Invoke(), (_, state) => ((Action)state!).Invoke(),
onComplete); onComplete);
} }
} }
@ -354,25 +370,25 @@ namespace Avalonia.Animation
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null) public Task RunAsync(Animatable control, IClock? clock = null)
{ {
return RunAsync(control, clock, default); return RunAsync(control, clock, default);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default) public Task RunAsync(Animatable control, IClock? clock = null, CancellationToken cancellationToken = default)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
var run = new TaskCompletionSource<object>(); var run = new TaskCompletionSource<object?>();
if (this.IterationCount == IterationCount.Infinite) if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method.")); run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null, cancellation = null; IDisposable? subscriptions = null, cancellation = null;
subscriptions = this.Apply(control, clock, Observable.Return(true), () => subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{ {
run.TrySetResult(null); run.TrySetResult(null);

33
src/Avalonia.Animation/AnimationInstance`1.cs

@ -31,15 +31,15 @@ namespace Avalonia.Animation
private TimeSpan _initialDelay; private TimeSpan _initialDelay;
private TimeSpan _iterationDelay; private TimeSpan _iterationDelay;
private TimeSpan _duration; private TimeSpan _duration;
private Easings.Easing _easeFunc; private Easings.Easing? _easeFunc;
private Action _onCompleteAction; private Action? _onCompleteAction;
private Func<double, T, T> _interpolator; private Func<double, T, T> _interpolator;
private IDisposable _timerSub; private IDisposable? _timerSub;
private readonly IClock _baseClock; private readonly IClock _baseClock;
private IClock _clock; private IClock? _clock;
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedDelegate; private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChangedDelegate;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator) public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action? OnComplete, Func<double, T, T> Interpolator)
{ {
_animator = animator; _animator = animator;
_animation = animation; _animation = animation;
@ -47,6 +47,9 @@ namespace Avalonia.Animation
_onCompleteAction = OnComplete; _onCompleteAction = OnComplete;
_interpolator = Interpolator; _interpolator = Interpolator;
_baseClock = baseClock; _baseClock = baseClock;
_lastInterpValue = default!;
_firstKFValue = default!;
_neutralValue = default!;
FetchProperties(); FetchProperties();
} }
@ -82,7 +85,7 @@ namespace Avalonia.Animation
_targetControl.PropertyChanged -= _propertyChangedDelegate; _targetControl.PropertyChanged -= _propertyChangedDelegate;
_timerSub?.Dispose(); _timerSub?.Dispose();
_clock.PlayState = PlayState.Stop; _clock!.PlayState = PlayState.Stop;
} }
protected override void Subscribed() protected override void Subscribed()
@ -108,6 +111,8 @@ namespace Avalonia.Animation
private void ApplyFinalFill() private void ApplyFinalFill()
{ {
if (_animator.Property is null)
throw new InvalidOperationException("Animator has no property specified.");
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both) if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue); _targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
} }
@ -130,12 +135,12 @@ namespace Avalonia.Animation
private void DoPlayStates() private void DoPlayStates()
{ {
if (_clock.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop) if (_clock!.PlayState == PlayState.Stop || _baseClock.PlayState == PlayState.Stop)
DoComplete(); DoComplete();
if (!_gotFirstKFValue) if (!_gotFirstKFValue)
{ {
_firstKFValue = (T)_animator.First().Value; _firstKFValue = (T)_animator.First().Value!;
_gotFirstKFValue = true; _gotFirstKFValue = true;
} }
} }
@ -169,7 +174,7 @@ namespace Avalonia.Animation
// and snap the last iteration value to exact values. // and snap the last iteration value to exact values.
if ((_currentIteration + 1) > _iterationCount) if ((_currentIteration + 1) > _iterationCount)
{ {
var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0); var easedTime = _easeFunc!.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue); _lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete(); DoComplete();
} }
@ -203,7 +208,7 @@ namespace Avalonia.Animation
normalizedTime = 1 - normalizedTime; normalizedTime = 1 - normalizedTime;
// Ease and interpolate // Ease and interpolate
var easedTime = _easeFunc.Ease(normalizedTime); var easedTime = _easeFunc!.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue); _lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue); PublishNext(_lastInterpValue);
@ -223,14 +228,14 @@ namespace Avalonia.Animation
private void UpdateNeutralValue() private void UpdateNeutralValue()
{ {
var property = _animator.Property; var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified.");
var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue); var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
_neutralValue = baseValue != AvaloniaProperty.UnsetValue ? _neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
(T)baseValue : (T)_targetControl.GetValue(property); (T)baseValue! : (T)_targetControl.GetValue(property)!;
} }
private void ControlPropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e) private void ControlPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation) if (e.Property == _animator.Property && e.Priority > BindingPriority.Animation)
{ {

20
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -12,22 +12,22 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class AnimatorKeyFrame : AvaloniaObject public class AnimatorKeyFrame : AvaloniaObject
{ {
public static readonly DirectProperty<AnimatorKeyFrame, object> ValueProperty = public static readonly DirectProperty<AnimatorKeyFrame, object?> ValueProperty =
AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object>(nameof(Value), k => k.Value, (k, v) => k.Value = v); AvaloniaProperty.RegisterDirect<AnimatorKeyFrame, object?>(nameof(Value), k => k.Value, (k, v) => k.Value = v);
public AnimatorKeyFrame() public AnimatorKeyFrame()
{ {
} }
public AnimatorKeyFrame(Type animatorType, Cue cue) public AnimatorKeyFrame(Type? animatorType, Cue cue)
{ {
AnimatorType = animatorType; AnimatorType = animatorType;
Cue = cue; Cue = cue;
KeySpline = null; KeySpline = null;
} }
public AnimatorKeyFrame(Type animatorType, Cue cue, KeySpline keySpline) public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline)
{ {
AnimatorType = animatorType; AnimatorType = animatorType;
Cue = cue; Cue = cue;
@ -35,14 +35,14 @@ namespace Avalonia.Animation
} }
internal bool isNeutral; internal bool isNeutral;
public Type AnimatorType { get; } public Type? AnimatorType { get; }
public Cue Cue { get; } public Cue Cue { get; }
public KeySpline KeySpline { get; } public KeySpline? KeySpline { get; }
public AvaloniaProperty Property { get; private set; } public AvaloniaProperty? Property { get; private set; }
private object _value; private object? _value;
public object Value public object? Value
{ {
get => _value; get => _value;
set => SetAndRaise(ValueProperty, ref _value, value); set => SetAndRaise(ValueProperty, ref _value, value);
@ -80,7 +80,7 @@ namespace Avalonia.Animation
throw new InvalidCastException($"KeyFrame value doesnt match property type."); throw new InvalidCastException($"KeyFrame value doesnt match property type.");
} }
return (T)typeConv.ConvertTo(Value, typeof(T)); return (T)typeConv.ConvertTo(Value, typeof(T))!;
} }
} }
} }

9
src/Avalonia.Animation/Animators/Animator`1.cs

@ -24,7 +24,7 @@ namespace Avalonia.Animation.Animators
/// <summary> /// <summary>
/// Gets or sets the target property for the keyframe. /// Gets or sets the target property for the keyframe.
/// </summary> /// </summary>
public AvaloniaProperty Property { get; set; } public AvaloniaProperty? Property { get; set; }
public Animator() public Animator()
{ {
@ -33,7 +33,7 @@ namespace Avalonia.Animation.Animators
} }
/// <inheritdoc/> /// <inheritdoc/>
public virtual IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete) public virtual IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete)
{ {
if (!_isVerifiedAndConverted) if (!_isVerifiedAndConverted)
VerifyConvertKeyFrames(); VerifyConvertKeyFrames();
@ -106,13 +106,16 @@ namespace Avalonia.Animation.Animators
public virtual IDisposable BindAnimation(Animatable control, IObservable<T> instance) public virtual IDisposable BindAnimation(Animatable control, IObservable<T> instance)
{ {
if (Property is null)
throw new InvalidOperationException("Animator has no property specified.");
return control.Bind((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation); return control.Bind((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
} }
/// <summary> /// <summary>
/// Runs the KeyFrames Animation. /// Runs the KeyFrames Animation.
/// </summary> /// </summary>
internal IDisposable Run(Animation animation, Animatable control, IClock clock, Action onComplete) internal IDisposable Run(Animation animation, Animatable control, IClock? clock, Action? onComplete)
{ {
var instance = new AnimationInstance<T>( var instance = new AnimationInstance<T>(
animation, animation,

4
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -2,9 +2,13 @@
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> <TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Include="..\Avalonia.Base\Metadata\NullableAttributes.cs" Link="NullableAttributes.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" /> <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\Rx.props" /> <Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ApiDiff.props" /> <Import Project="..\..\build\ApiDiff.props" />
<Import Project="..\..\build\NullableEnable.props" />
</Project> </Project>

2
src/Avalonia.Animation/Clock.cs

@ -4,7 +4,7 @@ namespace Avalonia.Animation
{ {
public class Clock : ClockBase public class Clock : ClockBase
{ {
public static IClock GlobalClock => AvaloniaLocator.Current.GetService<IGlobalClock>(); public static IClock GlobalClock => AvaloniaLocator.Current.GetRequiredService<IGlobalClock>();
private readonly IDisposable _parentSubscription; private readonly IDisposable _parentSubscription;

8
src/Avalonia.Animation/Cue.cs

@ -30,7 +30,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Parses a string to a <see cref="Cue"/> object. /// Parses a string to a <see cref="Cue"/> object.
/// </summary> /// </summary>
public static Cue Parse(string value, CultureInfo culture) public static Cue Parse(string value, CultureInfo? culture)
{ {
string v = value; string v = value;
@ -72,14 +72,14 @@ namespace Avalonia.Animation
public class CueTypeConverter : TypeConverter public class CueTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); return sourceType == typeof(string);
} }
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{ {
return Cue.Parse((string)value, culture); return Cue.Parse((string)value, culture);
} }
} }
} }

8
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -8,15 +8,15 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable internal class DisposeAnimationInstanceSubject<T> : IObserver<bool>, IDisposable
{ {
private IDisposable _lastInstance; private IDisposable? _lastInstance;
private bool _lastMatch; private bool _lastMatch;
private Animator<T> _animator; private Animator<T> _animator;
private Animation _animation; private Animation _animation;
private Animatable _control; private Animatable _control;
private Action _onComplete; private Action? _onComplete;
private IClock _clock; private IClock? _clock;
public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock clock, Action onComplete) public DisposeAnimationInstanceSubject(Animator<T> animator, Animation animation, Animatable control, IClock? clock, Action? onComplete)
{ {
this._animator = animator; this._animator = animator;
this._animation = animation; this._animation = animation;

4
src/Avalonia.Animation/Easing/Easing.cs

@ -15,7 +15,7 @@ namespace Avalonia.Animation.Easings
/// <inheritdoc/> /// <inheritdoc/>
public abstract double Ease(double progress); public abstract double Ease(double progress);
static Dictionary<string, Type> _easingTypes; static Dictionary<string, Type>? _easingTypes;
static readonly Type s_thisType = typeof(Easing); static readonly Type s_thisType = typeof(Easing);
@ -48,7 +48,7 @@ namespace Avalonia.Animation.Easings
if (_easingTypes.ContainsKey(e)) if (_easingTypes.ContainsKey(e))
{ {
var type = _easingTypes[e]; var type = _easingTypes[e];
return (Easing)Activator.CreateInstance(type); return (Easing)Activator.CreateInstance(type)!;
} }
else else
{ {

4
src/Avalonia.Animation/Easing/EasingTypeConverter.cs

@ -6,12 +6,12 @@ namespace Avalonia.Animation.Easings
{ {
public class EasingTypeConverter : TypeConverter public class EasingTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); return sourceType == typeof(string);
} }
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{ {
return Easing.Parse((string)value); return Easing.Parse((string)value);
} }

2
src/Avalonia.Animation/IAnimation.cs

@ -12,7 +12,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Apply the animation to the specified control and run it when <paramref name="match" /> produces <c>true</c>. /// Apply the animation to the specified control and run it when <paramref name="match" /> produces <c>true</c>.
/// </summary> /// </summary>
IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete = null); IDisposable Apply(Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete = null);
/// <summary> /// <summary>
/// Run the animation on the specified control. /// Run the animation on the specified control.

4
src/Avalonia.Animation/IAnimationSetter.cs

@ -2,7 +2,7 @@ namespace Avalonia.Animation
{ {
public interface IAnimationSetter public interface IAnimationSetter
{ {
AvaloniaProperty Property { get; set; } AvaloniaProperty? Property { get; set; }
object Value { get; set; } object? Value { get; set; }
} }
} }

4
src/Avalonia.Animation/IAnimator.cs

@ -11,11 +11,11 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// The target property. /// The target property.
/// </summary> /// </summary>
AvaloniaProperty Property {get; set;} AvaloniaProperty? Property {get; set;}
/// <summary> /// <summary>
/// Applies the current KeyFrame group to the specified control. /// Applies the current KeyFrame group to the specified control.
/// </summary> /// </summary>
IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete); IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable<bool> match, Action? onComplete);
} }
} }

2
src/Avalonia.Animation/ITransition.cs

@ -10,7 +10,7 @@ namespace Avalonia.Animation
/// <summary> /// <summary>
/// Applies the transition to the specified <see cref="Animatable"/>. /// Applies the transition to the specified <see cref="Animatable"/>.
/// </summary> /// </summary>
IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue); IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue);
/// <summary> /// <summary>
/// Gets the property to be animated. /// Gets the property to be animated.

2
src/Avalonia.Animation/IterationCount.cs

@ -97,7 +97,7 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
/// <param name="o">The object with which to test equality.</param> /// <param name="o">The object with which to test equality.</param>
/// <returns>True if the objects are equal, otherwise false.</returns> /// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object o) public override bool Equals(object? o)
{ {
if (o == null) if (o == null)
{ {

4
src/Avalonia.Animation/IterationCountTypeConverter.cs

@ -6,12 +6,12 @@ namespace Avalonia.Animation
{ {
public class IterationCountTypeConverter : TypeConverter public class IterationCountTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); return sourceType == typeof(string);
} }
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{ {
return IterationCount.Parse((string)value); return IterationCount.Parse((string)value);
} }

4
src/Avalonia.Animation/KeyFrame.cs

@ -19,7 +19,7 @@ namespace Avalonia.Animation
{ {
private TimeSpan _ktimeSpan; private TimeSpan _ktimeSpan;
private Cue _kCue; private Cue _kCue;
private KeySpline _kKeySpline; private KeySpline? _kKeySpline;
public KeyFrame() public KeyFrame()
{ {
@ -79,7 +79,7 @@ namespace Avalonia.Animation
/// Gets or sets the KeySpline of this <see cref="KeyFrame"/>. /// Gets or sets the KeySpline of this <see cref="KeyFrame"/>.
/// </summary> /// </summary>
/// <value>The key spline.</value> /// <value>The key spline.</value>
public KeySpline KeySpline public KeySpline? KeySpline
{ {
get get
{ {

4
src/Avalonia.Animation/KeySplineTypeConverter.cs

@ -12,12 +12,12 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public class KeySplineTypeConverter : TypeConverter public class KeySplineTypeConverter : TypeConverter
{ {
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
{ {
return sourceType == typeof(string); return sourceType == typeof(string);
} }
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{ {
return KeySpline.Parse((string)value, CultureInfo.InvariantCulture); return KeySpline.Parse((string)value, CultureInfo.InvariantCulture);
} }

21
src/Avalonia.Animation/Transition.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
namespace Avalonia.Animation namespace Avalonia.Animation
@ -8,7 +9,7 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
public abstract class Transition<T> : AvaloniaObject, ITransition public abstract class Transition<T> : AvaloniaObject, ITransition
{ {
private AvaloniaProperty _prop; private AvaloniaProperty? _prop;
/// <summary> /// <summary>
/// Gets or sets the duration of the transition. /// Gets or sets the duration of the transition.
@ -26,7 +27,8 @@ namespace Avalonia.Animation
public Easing Easing { get; set; } = new LinearEasing(); public Easing Easing { get; set; } = new LinearEasing();
/// <inheritdocs/> /// <inheritdocs/>
public AvaloniaProperty Property [DisallowNull]
public AvaloniaProperty? Property
{ {
get get
{ {
@ -42,16 +44,25 @@ namespace Avalonia.Animation
} }
} }
AvaloniaProperty ITransition.Property
{
get => Property ?? throw new InvalidOperationException("Transition has no property specified.");
set => Property = value;
}
/// <summary> /// <summary>
/// Apply interpolation to the property. /// Apply interpolation to the property.
/// </summary> /// </summary>
public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue); public abstract IObservable<T> DoTransition(IObservable<double> progress, T oldValue, T newValue);
/// <inheritdocs/> /// <inheritdocs/>
public virtual IDisposable Apply(Animatable control, IClock clock, object oldValue, object newValue) public virtual IDisposable Apply(Animatable control, IClock clock, object? oldValue, object? newValue)
{ {
var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue, (T)newValue); if (Property is null)
throw new InvalidOperationException("Transition has no property specified.");
var transition = DoTransition(new TransitionInstance(clock, Delay, Duration), (T)oldValue!, (T)newValue!);
return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation); return control.Bind<T>((AvaloniaProperty<T>)Property, transition, Data.BindingPriority.Animation);
} }
} }
} }

6
src/Avalonia.Animation/TransitionInstance.cs

@ -10,11 +10,11 @@ namespace Avalonia.Animation
/// </summary> /// </summary>
internal class TransitionInstance : SingleSubscriberObservableBase<double>, IObserver<TimeSpan> internal class TransitionInstance : SingleSubscriberObservableBase<double>, IObserver<TimeSpan>
{ {
private IDisposable _timerSubscription; private IDisposable? _timerSubscription;
private TimeSpan _delay; private TimeSpan _delay;
private TimeSpan _duration; private TimeSpan _duration;
private readonly IClock _baseClock; private readonly IClock _baseClock;
private TransitionClock _clock; private TransitionClock? _clock;
public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration) public TransitionInstance(IClock clock, TimeSpan delay, TimeSpan duration)
{ {
@ -67,7 +67,7 @@ namespace Avalonia.Animation
protected override void Unsubscribed() protected override void Unsubscribed()
{ {
_timerSubscription?.Dispose(); _timerSubscription?.Dispose();
_clock.PlayState = PlayState.Stop; _clock!.PlayState = PlayState.Stop;
} }
protected override void Subscribed() protected override void Subscribed()

2
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@ -30,7 +30,7 @@ namespace Avalonia.Threading
/// <inheritdoc/> /// <inheritdoc/>
public override void Post(SendOrPostCallback d, object? state) public override void Post(SendOrPostCallback d, object? state)
{ {
Dispatcher.UIThread.Post(() => d(state), DispatcherPriority.Send); Dispatcher.UIThread.Post(() => d(state), DispatcherPriority.Background);
} }
/// <inheritdoc/> /// <inheritdoc/>

1
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -5751,6 +5751,7 @@ namespace Avalonia.Controls
return true; return true;
} }
// Unselect everything except the row that was clicked on // Unselect everything except the row that was clicked on
_noSelectionChangeCount++;
try try
{ {
UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false); UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);

2
src/Avalonia.Controls.DataGrid/DataGridColumn.cs

@ -448,7 +448,7 @@ namespace Avalonia.Controls
internal set; internal set;
} }
public bool IsReadOnly public virtual bool IsReadOnly
{ {
get get
{ {

72
src/Avalonia.Controls.DataGrid/DataGridTemplateColumn.cs

@ -1,4 +1,4 @@
// (c) Copyright Microsoft Corporation. // (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL). // This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved. // All other rights reserved.
@ -15,7 +15,7 @@ namespace Avalonia.Controls
{ {
public class DataGridTemplateColumn : DataGridColumn public class DataGridTemplateColumn : DataGridColumn
{ {
IDataTemplate _cellTemplate; private IDataTemplate _cellTemplate;
public static readonly DirectProperty<DataGridTemplateColumn, IDataTemplate> CellTemplateProperty = public static readonly DirectProperty<DataGridTemplateColumn, IDataTemplate> CellTemplateProperty =
AvaloniaProperty.RegisterDirect<DataGridTemplateColumn, IDataTemplate>( AvaloniaProperty.RegisterDirect<DataGridTemplateColumn, IDataTemplate>(
@ -30,17 +30,38 @@ namespace Avalonia.Controls
set { SetAndRaise(CellTemplateProperty, ref _cellTemplate, value); } set { SetAndRaise(CellTemplateProperty, ref _cellTemplate, value); }
} }
private IDataTemplate _cellEditingCellTemplate;
/// <summary>
/// Defines the <see cref="CellEditingTemplate"/> property.
/// </summary>
public static readonly DirectProperty<DataGridTemplateColumn, IDataTemplate> CellEditingTemplateProperty =
AvaloniaProperty.RegisterDirect<DataGridTemplateColumn, IDataTemplate>(
nameof(CellEditingTemplate),
o => o.CellEditingTemplate,
(o, v) => o.CellEditingTemplate = v);
/// <summary>
/// Gets or sets the <see cref="IDataTemplate"/> which is used for the editing mode of the current <see cref="DataGridCell"/>
/// </summary>
/// <value>
/// An <see cref="IDataTemplate"/> for the editing mode of the current <see cref="DataGridCell"/>
/// </value>
/// <remarks>
/// If this property is <see langword="null"/> the column is read-only.
/// </remarks>
public IDataTemplate CellEditingTemplate
{
get => _cellEditingCellTemplate;
set => SetAndRaise(CellEditingTemplateProperty, ref _cellEditingCellTemplate, value);
}
private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e) private void OnCellTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{ {
var oldValue = (IDataTemplate)e.OldValue; var oldValue = (IDataTemplate)e.OldValue;
var value = (IDataTemplate)e.NewValue; var value = (IDataTemplate)e.NewValue;
} }
public DataGridTemplateColumn()
{
IsReadOnly = true;
}
protected override IControl GenerateElement(DataGridCell cell, object dataItem) protected override IControl GenerateElement(DataGridCell cell, object dataItem)
{ {
if(CellTemplate != null) if(CellTemplate != null)
@ -60,7 +81,22 @@ namespace Avalonia.Controls
protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding) protected override IControl GenerateEditingElement(DataGridCell cell, object dataItem, out ICellEditBinding binding)
{ {
binding = null; binding = null;
return GenerateElement(cell, dataItem); if(CellEditingTemplate != null)
{
return CellEditingTemplate.Build(dataItem);
}
else if (CellTemplate != null)
{
return CellTemplate.Build(dataItem);
}
if (Design.IsDesignMode)
{
return null;
}
else
{
throw DataGridError.DataGridTemplateColumn.MissingTemplateForType(typeof(DataGridTemplateColumn));
}
} }
protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs)
@ -70,12 +106,30 @@ namespace Avalonia.Controls
protected internal override void RefreshCellContent(IControl element, string propertyName) protected internal override void RefreshCellContent(IControl element, string propertyName)
{ {
if(propertyName == nameof(CellTemplate) && element.Parent is DataGridCell cell) var cell = element.Parent as DataGridCell;
if(propertyName == nameof(CellTemplate) && cell is not null)
{ {
cell.Content = GenerateElement(cell, cell.DataContext); cell.Content = GenerateElement(cell, cell.DataContext);
} }
base.RefreshCellContent(element, propertyName); base.RefreshCellContent(element, propertyName);
} }
public override bool IsReadOnly
{
get
{
if (CellEditingTemplate is null)
{
return true;
}
return base.IsReadOnly;
}
set
{
base.IsReadOnly = value;
}
}
} }
} }

2
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -95,7 +95,7 @@
</StackPanel> </StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" > <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" >
<TextBlock Text="Chat Room | " /> <TextBlock Text="Chat Room | " />
<Button Classes="Hyperlink" CommandParameter="https://gitter.im/AvaloniaUI/Avalonia/" /> <Button Classes="Hyperlink" CommandParameter="https://t.me/Avalonia" />
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10"> <StackPanel VerticalAlignment="Bottom" Margin="10">

1
src/Avalonia.Dialogs/Avalonia.Dialogs.csproj

@ -5,6 +5,7 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="Assets\*" /> <AvaloniaResource Include="Assets\*" />
<AvaloniaResource Include="**/*.xaml" />
</ItemGroup> </ItemGroup>
<Import Project="..\..\build\BuildTargets.targets" /> <Import Project="..\..\build\BuildTargets.targets" />

6
src/Avalonia.Input/KeyBinding.cs

@ -35,9 +35,11 @@ namespace Avalonia.Input
{ {
if (Gesture?.Matches(args) == true) if (Gesture?.Matches(args) == true)
{ {
args.Handled = true; if (Command?.CanExecute(CommandParameter) == true)
if (Command?.CanExecute(CommandParameter) == true) {
args.Handled = true;
Command.Execute(CommandParameter); Command.Execute(CommandParameter);
}
} }
} }
} }

111
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -70,74 +70,76 @@ namespace Avalonia.Native
var result = new NativeMenu(); var result = new NativeMenu();
var aboutItem = new NativeMenuItem("About Avalonia"); var aboutItem = new NativeMenuItem("About Avalonia");
aboutItem.Click += async (sender, e) =>
aboutItem.Click += async (_, _) =>
{ {
var dialog = new AboutAvaloniaDialog(); var dialog = new AboutAvaloniaDialog();
var mainWindow = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow; if (Application.Current is
{ ApplicationLifetime: IClassicDesktopStyleApplicationLifetime { MainWindow: { } mainWindow } })
await dialog.ShowDialog(mainWindow); {
await dialog.ShowDialog(mainWindow);
}
}; };
result.Add(aboutItem); result.Add(aboutItem);
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions(); return result;
if (!macOpts.DisableDefaultApplicationMenuItems) }
{
result.Add(new NativeMenuItemSeparator());
var servicesMenu = new NativeMenuItem("Services"); private void PopulateStandardOSXMenuItems(NativeMenu appMenu)
servicesMenu.Menu = new NativeMenu {
{ appMenu.Add(new NativeMenuItemSeparator());
[MacOSNativeMenuCommands.IsServicesSubmenuProperty] = true
};
result.Add(servicesMenu);
result.Add(new NativeMenuItemSeparator()); var servicesMenu = new NativeMenuItem("Services");
servicesMenu.Menu = new NativeMenu { [MacOSNativeMenuCommands.IsServicesSubmenuProperty] = true };
var hideItem = new NativeMenuItem("Hide " + Application.Current.Name) appMenu.Add(servicesMenu);
{
Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
};
hideItem.Click += (sender, args) =>
{
_applicationCommands.HideApp();
};
result.Add(hideItem);
appMenu.Add(new NativeMenuItemSeparator());
var hideOthersItem = new NativeMenuItem("Hide Others") var hideItem = new NativeMenuItem("Hide " + (Application.Current?.Name ?? "Application"))
{ {
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta | KeyModifiers.Alt) Gesture = new KeyGesture(Key.H, KeyModifiers.Meta)
}; };
hideOthersItem.Click += (sender, args) =>
{
_applicationCommands.HideOthers();
};
result.Add(hideOthersItem);
hideItem.Click += (_, _) =>
{
_applicationCommands.HideApp();
};
var showAllItem = new NativeMenuItem("Show All"); appMenu.Add(hideItem);
showAllItem.Click += (sender, args) =>
{ var hideOthersItem = new NativeMenuItem("Hide Others")
_applicationCommands.ShowAll(); {
}; Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta | KeyModifiers.Alt)
result.Add(showAllItem); };
hideOthersItem.Click += (_, _) =>
{
_applicationCommands.HideOthers();
};
appMenu.Add(hideOthersItem);
result.Add(new NativeMenuItemSeparator()); var showAllItem = new NativeMenuItem("Show All");
showAllItem.Click += (_, _) =>
{
_applicationCommands.ShowAll();
};
var quitItem = new NativeMenuItem("Quit") appMenu.Add(showAllItem);
{
Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta)
};
quitItem.Click += (sender, args) =>
{
(Application.Current.ApplicationLifetime as IControlledApplicationLifetime)?.Shutdown();
};
result.Add(quitItem);
}
appMenu.Add(new NativeMenuItemSeparator());
return result; var quitItem = new NativeMenuItem("Quit") { Gesture = new KeyGesture(Key.Q, KeyModifiers.Meta) };
quitItem.Click += (_, _) =>
{
if (Application.Current is { ApplicationLifetime: IControlledApplicationLifetime lifetime })
{
lifetime.Shutdown();
}
};
appMenu.Add(quitItem);
} }
private void DoLayoutReset(bool forceUpdate = false) private void DoLayoutReset(bool forceUpdate = false)
@ -220,6 +222,13 @@ namespace Avalonia.Native
_nativeMenu.Initialize(this, appMenuHolder, ""); _nativeMenu.Initialize(this, appMenuHolder, "");
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>();
if (macOpts == null || !macOpts.DisableDefaultApplicationMenuItems)
{
PopulateStandardOSXMenuItems(menu);
}
setMenu = true; setMenu = true;
} }

2
src/Avalonia.Themes.Fluent/Controls/CalendarButton.xaml

@ -11,7 +11,7 @@
<Setter Property="MinWidth" Value="40"/> <Setter Property="MinWidth" Value="40"/>
<Setter Property="MinHeight" Value="40"/> <Setter Property="MinHeight" Value="40"/>
<Setter Property="Margin" Value="1"/> <Setter Property="Margin" Value="1"/>
<Setter Property="Padding" Value="0,0,0,4"/> <Setter Property="Padding" Value="0,0,0,0"/>
<!--These are actually set on the CalendarView in WinUI--> <!--These are actually set on the CalendarView in WinUI-->
<Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/>
<Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/> <Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/>

2
src/Avalonia.Themes.Fluent/Controls/CalendarDayButton.xaml

@ -11,7 +11,7 @@
<Setter Property="MinWidth" Value="40"/> <Setter Property="MinWidth" Value="40"/>
<Setter Property="MinHeight" Value="40"/> <Setter Property="MinHeight" Value="40"/>
<Setter Property="Margin" Value="1"/> <Setter Property="Margin" Value="1"/>
<Setter Property="Padding" Value="0,0,0,4"/> <Setter Property="Padding" Value="0,0,0,0"/>
<!--These are actually set on the CalendarView in WinUI--> <!--These are actually set on the CalendarView in WinUI-->
<Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/> <Setter Property="Foreground" Value="{DynamicResource CalendarViewCalendarItemForeground}"/>
<Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/> <Setter Property="Background" Value="{DynamicResource CalendarViewCalendarItemRevealBackground}"/>

1
src/Avalonia.Themes.Fluent/Controls/CalendarItem.xaml

@ -32,6 +32,7 @@
<Style Selector="Button.CalendarHeader"> <Style Selector="Button.CalendarHeader">
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" /> <Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="20" /> <Setter Property="FontSize" Value="20" />
<Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/> <Setter Property="Background" Value="{DynamicResource CalendarViewNavigationButtonBackground}"/>
<Setter Property="Template"> <Setter Property="Template">

1
src/Avalonia.Themes.Fluent/Controls/ListBox.xaml

@ -20,6 +20,7 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"
ClipToBounds="{TemplateBinding ClipToBounds}"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"

7
src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs

@ -38,8 +38,8 @@ namespace Avalonia.Animation.Animators
} }
/// <inheritdoc/> /// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock,
IObservable<bool> match, Action onComplete) IObservable<bool> match, Action? onComplete)
{ {
if (TryCreateCustomRegisteredAnimator(out var animator) if (TryCreateCustomRegisteredAnimator(out var animator)
|| TryCreateGradientAnimator(out animator) || TryCreateGradientAnimator(out animator)
@ -135,9 +135,8 @@ namespace Avalonia.Animation.Animators
private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator) private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator)
{ {
if (_brushAnimators.Count > 0) if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType)
{ {
var firstKeyType = this[0].Value.GetType();
foreach (var (match, animatorType) in _brushAnimators) foreach (var (match, animatorType) in _brushAnimators)
{ {
if (!match(firstKeyType)) if (!match(firstKeyType))

5
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@ -58,6 +58,11 @@ namespace Avalonia.Animation.Animators
public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance) public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance)
{ {
if (Property is null)
{
throw new InvalidOperationException("Animator has no property specified.");
}
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation); return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
} }

5
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -24,6 +24,11 @@ namespace Avalonia.Animation.Animators
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance) public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)
{ {
if (Property is null)
{
throw new InvalidOperationException("Animator has no property specified.");
}
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation); return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
} }
} }

7
src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@ -14,10 +14,15 @@ namespace Avalonia.Animation.Animators
DoubleAnimator? _doubleAnimator; DoubleAnimator? _doubleAnimator;
/// <inheritdoc/> /// <inheritdoc/>
public override IDisposable? Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> obsMatch, Action onComplete) public override IDisposable? Apply(Animation animation, Animatable control, IClock? clock, IObservable<bool> obsMatch, Action? onComplete)
{ {
var ctrl = (Visual)control; var ctrl = (Visual)control;
if (Property is null)
{
throw new InvalidOperationException("Animator has no property specified.");
}
// Check if the Target Property is Transform derived. // Check if the Target Property is Transform derived.
if (typeof(Transform).IsAssignableFrom(Property.OwnerType)) if (typeof(Transform).IsAssignableFrom(Property.OwnerType))
{ {

5
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -7,6 +7,9 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Task
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.PageSlide.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun..ctor()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender<T>(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
@ -83,4 +86,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avaloni
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract.
Total Issues: 84 Total Issues: 87

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

@ -7,7 +7,7 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Describes how a stroke is drawn. /// Describes how a stroke is drawn.
/// </summary> /// </summary>
public class Pen : AvaloniaObject, IPen public sealed class Pen : AvaloniaObject, IPen, IWeakEventSubscriber<EventArgs>
{ {
/// <summary> /// <summary>
/// Defines the <see cref="Brush"/> property. /// Defines the <see cref="Brush"/> property.
@ -45,6 +45,10 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> MiterLimitProperty = public static readonly StyledProperty<double> MiterLimitProperty =
AvaloniaProperty.Register<Pen, double>(nameof(MiterLimit), 10.0); AvaloniaProperty.Register<Pen, double>(nameof(MiterLimit), 10.0);
private EventHandler? _invalidated;
private IAffectsRender? _subscribedToBrush;
private IAffectsRender? _subscribedToDashes;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Pen"/> class. /// Initializes a new instance of the <see cref="Pen"/> class.
/// </summary> /// </summary>
@ -96,17 +100,6 @@ namespace Avalonia.Media
DashStyle = dashStyle; DashStyle = dashStyle;
} }
static Pen()
{
AffectsRender<Pen>(
BrushProperty,
ThicknessProperty,
DashStyleProperty,
LineCapProperty,
LineJoinProperty,
MiterLimitProperty);
}
/// <summary> /// <summary>
/// Gets or sets the brush used to draw the stroke. /// Gets or sets the brush used to draw the stroke.
/// </summary> /// </summary>
@ -116,6 +109,11 @@ namespace Avalonia.Media
set => SetValue(BrushProperty, value); set => SetValue(BrushProperty, value);
} }
private static readonly WeakEvent<IAffectsRender, EventArgs> InvalidatedWeakEvent =
WeakEvent.Register<IAffectsRender>(
(s, h) => s.Invalidated += h,
(s, h) => s.Invalidated -= h);
/// <summary> /// <summary>
/// Gets or sets the stroke thickness. /// Gets or sets the stroke thickness.
/// </summary> /// </summary>
@ -165,7 +163,19 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Raised when the pen changes. /// Raised when the pen changes.
/// </summary> /// </summary>
public event EventHandler? Invalidated; public event EventHandler? Invalidated
{
add
{
_invalidated += value;
UpdateSubscriptions();
}
remove
{
_invalidated -= value;
UpdateSubscriptions();
}
}
/// <summary> /// <summary>
/// Creates an immutable clone of the brush. /// Creates an immutable clone of the brush.
@ -182,68 +192,42 @@ namespace Avalonia.Media
MiterLimit); MiterLimit);
} }
/// <summary> protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
/// Marks a property as affecting the pen's visual representation.
/// </summary>
/// <param name="properties">The properties.</param>
/// <remarks>
/// After a call to this method in a pen's static constructor, any change to the
/// property will cause the <see cref="Invalidated"/> event to be raised on the pen.
/// </remarks>
protected static void AffectsRender<T>(params AvaloniaProperty[] properties)
where T : Pen
{ {
static void Invalidate(AvaloniaPropertyChangedEventArgs e) _invalidated?.Invoke(this, EventArgs.Empty);
{ if(change.Property == BrushProperty)
if (e.Sender is T sender) UpdateSubscription(ref _subscribedToBrush, Brush);
{ if(change.Property == DashStyleProperty)
sender.RaiseInvalidated(EventArgs.Empty); UpdateSubscription(ref _subscribedToDashes, DashStyle);
} base.OnPropertyChanged(change);
} }
static void InvalidateAndSubscribe(AvaloniaPropertyChangedEventArgs e)
void UpdateSubscription(ref IAffectsRender? field, object? value)
{
if ((_invalidated == null || field != value) && field != null)
{ {
if (e.Sender is T sender) InvalidatedWeakEvent.Unsubscribe(field, this);
{ field = null;
if (e.OldValue is IAffectsRender oldValue)
{
WeakEventHandlerManager.Unsubscribe<EventArgs, T>(
oldValue,
nameof(oldValue.Invalidated),
sender.AffectsRenderInvalidated);
}
if (e.NewValue is IAffectsRender newValue)
{
WeakEventHandlerManager.Subscribe<IAffectsRender, EventArgs, T>(
newValue,
nameof(newValue.Invalidated),
sender.AffectsRenderInvalidated);
}
sender.RaiseInvalidated(EventArgs.Empty);
}
} }
foreach (var property in properties) if (_invalidated != null && field != value && value is IAffectsRender affectsRender)
{ {
if (property.CanValueAffectRender()) InvalidatedWeakEvent.Subscribe(affectsRender, this);
{ field = affectsRender;
property.Changed.Subscribe(e => InvalidateAndSubscribe(e));
}
else
{
property.Changed.Subscribe(e => Invalidate(e));
}
} }
} }
/// <summary> void UpdateSubscriptions()
/// Raises the <see cref="Invalidated"/> event. {
/// </summary> UpdateSubscription(ref _subscribedToBrush, Brush);
/// <param name="e">The event args.</param> UpdateSubscription(ref _subscribedToDashes, DashStyle);
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e); }
private void AffectsRenderInvalidated(object? sender, EventArgs e) => RaiseInvalidated(EventArgs.Empty); void IWeakEventSubscriber<EventArgs>.OnEvent(object? sender, WeakEvent ev, EventArgs e)
{
if (ev == InvalidatedWeakEvent)
_invalidated?.Invoke(this, EventArgs.Empty);
}
} }
} }

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

@ -291,7 +291,7 @@ namespace Avalonia.Skia
{ {
AvaloniaFormattedTextLine line = _skiaLines[c]; AvaloniaFormattedTextLine line = _skiaLines[c];
float x = TransformX(origin.X, 0, paint.TextAlign); float x = TransformX(origin.X, line.Width, paint.TextAlign);
if (!hasCusomFGBrushes) if (!hasCusomFGBrushes)
{ {

25
tests/Avalonia.Animation.UnitTests/AnimatableTests.cs

@ -36,7 +36,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_Is_Not_Applied_To_Initial_Style() public void Transition_Is_Not_Applied_To_Initial_Style()
{ {
using (UnitTestApplication.Start(TestServices.RealStyler)) using (Start())
{ {
var target = CreateTarget(); var target = CreateTarget();
var control = new Control var control = new Control
@ -74,6 +74,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_Is_Applied_When_Local_Value_Changes() public void Transition_Is_Applied_When_Local_Value_Changes()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
@ -170,6 +171,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present() public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
@ -195,6 +197,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_Is_Disposed_When_Local_Value_Changes() public void Transition_Is_Disposed_When_Local_Value_Changes()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
var sub = new Mock<IDisposable>(); var sub = new Mock<IDisposable>();
@ -211,6 +214,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void New_Transition_Is_Applied_When_Local_Value_Changes() public void New_Transition_Is_Applied_When_Local_Value_Changes()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
@ -239,6 +243,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree() public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
@ -266,6 +271,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Animation_Is_Cancelled_When_Transition_Removed() public void Animation_Is_Cancelled_When_Transition_Removed()
{ {
using var app = Start();
var target = CreateTarget(); var target = CreateTarget();
var control = CreateControl(target.Object); var control = CreateControl(target.Object);
var sub = new Mock<IDisposable>(); var sub = new Mock<IDisposable>();
@ -285,7 +291,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Animation_Is_Cancelled_When_New_Style_Activates() public void Animation_Is_Cancelled_When_New_Style_Activates()
{ {
using (UnitTestApplication.Start(TestServices.RealStyler)) using (Start())
{ {
var target = CreateTarget(); var target = CreateTarget();
var control = CreateStyledControl(target.Object); var control = CreateStyledControl(target.Object);
@ -301,7 +307,7 @@ namespace Avalonia.Animation.UnitTests
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<Clock>(), It.IsAny<IClock>(),
1.0, 1.0,
0.5), 0.5),
Times.Once); Times.Once);
@ -315,7 +321,7 @@ namespace Avalonia.Animation.UnitTests
[Fact] [Fact]
public void Transition_From_Style_Trigger_Is_Applied() public void Transition_From_Style_Trigger_Is_Applied()
{ {
using (UnitTestApplication.Start(TestServices.RealStyler)) using (Start())
{ {
var target = CreateTransition(Control.WidthProperty); var target = CreateTransition(Control.WidthProperty);
var control = CreateStyledControl(transition2: target.Object); var control = CreateStyledControl(transition2: target.Object);
@ -326,7 +332,7 @@ namespace Avalonia.Animation.UnitTests
target.Verify(x => x.Apply( target.Verify(x => x.Apply(
control, control,
It.IsAny<Clock>(), It.IsAny<IClock>(),
double.NaN, double.NaN,
100.0), 100.0),
Times.Once); Times.Once);
@ -337,7 +343,7 @@ namespace Avalonia.Animation.UnitTests
public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound() public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound()
{ {
// Issue #4059 // Issue #4059
using (UnitTestApplication.Start(TestServices.RealStyler)) using (Start())
{ {
Border target; Border target;
var clock = new TestClock(); var clock = new TestClock();
@ -428,6 +434,13 @@ namespace Avalonia.Animation.UnitTests
control.EndBatchUpdate(); control.EndBatchUpdate();
} }
private static IDisposable Start()
{
var clock = new MockGlobalClock();
var services = TestServices.RealStyler.With(globalClock: clock);
return UnitTestApplication.Start(services);
}
private static Mock<ITransition> CreateTarget() private static Mock<ITransition> CreateTarget()
{ {
return CreateTransition(Visual.OpacityProperty); return CreateTransition(Visual.OpacityProperty);

Loading…
Cancel
Save