Browse Source

Merge branch 'master' into fixes/Warning_CS0169

pull/6097/head
Max Katz 5 years ago
committed by GitHub
parent
commit
5071126505
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      src/Avalonia.Animation/Animation.cs
  2. 6
      src/Avalonia.Animation/ApiCompatBaseline.txt
  3. 3
      src/Avalonia.Animation/IAnimation.cs
  4. 21
      src/Avalonia.Controls/Expander.cs
  5. 2
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  6. 22
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  7. 2
      src/Avalonia.Controls/TextBox.cs
  8. 13
      src/Avalonia.ReactiveUI/TransitioningContentControl.cs
  9. 6
      src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml
  10. 21
      src/Avalonia.Visuals/Animation/CompositePageTransition.cs
  11. 64
      src/Avalonia.Visuals/Animation/CrossFade.cs
  12. 6
      src/Avalonia.Visuals/Animation/IPageTransition.cs
  13. 30
      src/Avalonia.Visuals/Animation/PageSlide.cs
  14. 9
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  15. 5
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  16. 2
      src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs
  17. 88
      src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs
  18. 268
      tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

30
src/Avalonia.Animation/Animation.cs

@ -3,10 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Metadata;
@ -319,7 +320,7 @@ namespace Avalonia.Animation
return (newAnimatorInstances, subscriptions);
}
/// <inheritdocs/>
/// <inheritdoc/>
public IDisposable Apply(Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
var (animators, subscriptions) = InterpretKeyframes(control);
@ -344,25 +345,40 @@ namespace Avalonia.Animation
if (onComplete != null)
{
Task.WhenAll(completionTasks).ContinueWith(_ => onComplete());
Task.WhenAll(completionTasks).ContinueWith(
(_, state) => ((Action)state).Invoke(),
onComplete);
}
}
return new CompositeDisposable(subscriptions);
}
/// <inheritdocs/>
public Task RunAsync(Animatable control, IClock clock = null)
/// <inheritdoc/>
public Task RunAsync(Animatable control, IClock clock = null, CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.CompletedTask;
}
var run = new TaskCompletionSource<object>();
if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;
IDisposable subscriptions = null, cancellation = null;
subscriptions = this.Apply(control, clock, Observable.Return(true), () =>
{
run.SetResult(null);
run.TrySetResult(null);
subscriptions?.Dispose();
cancellation?.Dispose();
});
cancellation = cancellationToken.Register(() =>
{
run.TrySetResult(null);
subscriptions?.Dispose();
cancellation?.Dispose();
});
return run.Task;

6
src/Avalonia.Animation/ApiCompatBaseline.txt

@ -0,0 +1,6 @@
Compat issues with assembly Avalonia.Animation:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.Animation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IAnimation.RunAsync(Avalonia.Animation.Animatable, Avalonia.Animation.IClock, System.Threading.CancellationToken)' is present in the implementation but not in the contract.
Total Issues: 4

3
src/Avalonia.Animation/IAnimation.cs

@ -1,4 +1,5 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Animation
@ -16,6 +17,6 @@ namespace Avalonia.Animation
/// <summary>
/// Run the animation on the specified control.
/// </summary>
Task RunAsync(Animatable control, IClock clock);
Task RunAsync(Animatable control, IClock clock, CancellationToken cancellationToken = default);
}
}

21
src/Avalonia.Controls/Expander.cs

@ -1,7 +1,11 @@
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
#nullable enable
namespace Avalonia.Controls
{
/// <summary>
@ -36,8 +40,8 @@ namespace Avalonia.Controls
[PseudoClasses(":expanded", ":up", ":down", ":left", ":right")]
public class Expander : HeaderedContentControl
{
public static readonly StyledProperty<IPageTransition> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition>(nameof(ContentTransition));
public static readonly StyledProperty<IPageTransition?> ContentTransitionProperty =
AvaloniaProperty.Register<Expander, IPageTransition?>(nameof(ContentTransition));
public static readonly StyledProperty<ExpandDirection> ExpandDirectionProperty =
AvaloniaProperty.Register<Expander, ExpandDirection>(nameof(ExpandDirection), ExpandDirection.Down);
@ -50,6 +54,7 @@ namespace Avalonia.Controls
defaultBindingMode: Data.BindingMode.TwoWay);
private bool _isExpanded;
private CancellationTokenSource? _lastTransitionCts;
static Expander()
{
@ -61,7 +66,7 @@ namespace Avalonia.Controls
UpdatePseudoClasses(ExpandDirection);
}
public IPageTransition ContentTransition
public IPageTransition? ContentTransition
{
get => GetValue(ContentTransitionProperty);
set => SetValue(ContentTransitionProperty, value);
@ -83,19 +88,23 @@ namespace Avalonia.Controls
}
}
protected virtual void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
protected virtual async void OnIsExpandedChanged(AvaloniaPropertyChangedEventArgs e)
{
if (Content != null && ContentTransition != null && Presenter is Visual visualContent)
{
bool forward = ExpandDirection == ExpandDirection.Left ||
ExpandDirection == ExpandDirection.Up;
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (IsExpanded)
{
ContentTransition.Start(null, visualContent, forward);
await ContentTransition.Start(null, visualContent, forward, _lastTransitionCts.Token);
}
else
{
ContentTransition.Start(visualContent, null, !forward);
await ContentTransition.Start(visualContent, null, forward, _lastTransitionCts.Token);
}
}
}

2
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@ -186,7 +186,7 @@ namespace Avalonia.Controls.Presenters
if (PageTransition != null && (from != null || to != null))
{
await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex);
await PageTransition.Start((Visual)from, (Visual)to, fromIndex < toIndex, default);
}
else if (to != null)
{

22
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Specialized;
using System.Linq;
using System.Resources;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Utilities;
@ -45,10 +46,27 @@ namespace Avalonia.Controls.Primitives
?.AdornerLayer;
}
protected override Size ArrangeOverride(Size finalSize)
protected override Size MeasureOverride(Size availableSize)
{
var parent = Parent;
foreach (var child in Children)
{
var info = child.GetValue(s_adornedElementInfoProperty);
if (info != null && info.Bounds.HasValue)
{
child.Measure(info.Bounds.Value.Bounds.Size);
}
else
{
child.Measure(availableSize);
}
}
return default;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in Children)
{
var info = child.GetValue(s_adornedElementInfoProperty);

2
src/Avalonia.Controls/TextBox.cs

@ -1290,7 +1290,7 @@ namespace Avalonia.Controls
private void UpdatePseudoclasses()
{
PseudoClasses.Set(":empty", string.IsNullOrWhiteSpace(Text));
PseudoClasses.Set(":empty", string.IsNullOrEmpty(Text));
}
private bool IsPasswordBox => PasswordChar != default(char);

13
src/Avalonia.ReactiveUI/TransitioningContentControl.cs

@ -1,4 +1,6 @@
using System;
using System.Threading;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Styling;
@ -22,7 +24,9 @@ namespace Avalonia.ReactiveUI
/// </summary>
public static readonly StyledProperty<object?> DefaultContentProperty =
AvaloniaProperty.Register<TransitioningContentControl, object?>(nameof(DefaultContent));
private CancellationTokenSource? _lastTransitionCts;
/// <summary>
/// Gets or sets the animation played when content appears and disappears.
/// </summary>
@ -62,11 +66,14 @@ namespace Avalonia.ReactiveUI
/// <param name="content">New content to set.</param>
private async void UpdateContentWithTransition(object? content)
{
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
if (PageTransition != null)
await PageTransition.Start(this, null, true);
await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
base.Content = content;
if (PageTransition != null)
await PageTransition.Start(null, this, true);
await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
}
}
}

6
src/Avalonia.Themes.Fluent/Controls/ComboBox.xaml

@ -112,9 +112,7 @@
<Panel Height="12"
Width="12" />
<Path x:Name="DropDownGlyph"
Stretch="Uniform"
VerticalAlignment="Center"
Data="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
VerticalAlignment="Center" />
</Panel>
</Viewbox>
<Popup Name="PART_Popup"
@ -160,6 +158,8 @@
<Style Selector="ComboBox /template/ Path#DropDownGlyph">
<Setter Property="Fill" Value="{DynamicResource ComboBoxDropDownGlyphForeground}" />
<Setter Property="Data" Value="M1939 486L2029 576L1024 1581L19 576L109 486L1024 1401L1939 486Z" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<!-- PointerOver State -->

21
src/Avalonia.Visuals/Animation/CompositePageTransition.cs

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Metadata;
@ -35,25 +36,11 @@ namespace Avalonia.Animation
[Content]
public List<IPageTransition> PageTransitions { get; set; } = new List<IPageTransition>();
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// Defines the direction of the transition.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public Task Start(Visual from, Visual to, bool forward)
/// <inheritdoc />
public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
var transitionTasks = PageTransitions
.Select(transition => transition.Start(from, to, forward))
.Select(transition => transition.Start(from, to, forward, cancellationToken))
.ToList();
return Task.WhenAll(transitionTasks);
}

64
src/Avalonia.Visuals/Animation/CrossFade.cs

@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
@ -97,49 +99,39 @@ namespace Avalonia.Animation
set => _fadeOutAnimation.Easing = value;
}
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to)
/// <inheritdoc cref="Start(Visual, Visual, CancellationToken)" />
public async Task Start(Visual from, Visual to, CancellationToken cancellationToken)
{
var tasks = new List<Task>();
if (to != null)
{
to.Opacity = 0;
}
if (from != null)
if (cancellationToken.IsCancellationRequested)
{
tasks.Add(_fadeOutAnimation.RunAsync(from));
return;
}
if (to != null)
var tasks = new List<Task>();
using (var disposables = new CompositeDisposable())
{
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to));
if (to != null)
{
disposables.Add(to.SetValue(Visual.OpacityProperty, 0, Data.BindingPriority.Animation));
}
}
if (from != null)
{
tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken));
}
await Task.WhenAll(tasks);
if (to != null)
{
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken));
}
if (from != null)
{
from.IsVisible = false;
}
await Task.WhenAll(tasks);
if (to != null)
{
to.Opacity = 1;
if (from != null && !cancellationToken.IsCancellationRequested)
{
from.IsVisible = false;
}
}
}
@ -158,9 +150,9 @@ namespace Avalonia.Animation
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
Task IPageTransition.Start(Visual from, Visual to, bool forward)
Task IPageTransition.Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
return Start(from, to);
return Start(from, to, cancellationToken);
}
}
}

6
src/Avalonia.Visuals/Animation/IPageTransition.cs

@ -1,3 +1,4 @@
using System.Threading;
using System.Threading.Tasks;
namespace Avalonia.Animation
@ -19,9 +20,12 @@ namespace Avalonia.Animation
/// <param name="forward">
/// If the animation is bidirectional, controls the direction of the animation.
/// </param>
/// <param name="cancellationToken">
/// Animation cancellation.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
Task Start(Visual from, Visual to, bool forward);
Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken);
}
}

30
src/Avalonia.Visuals/Animation/PageSlide.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Animation.Easings;
using Avalonia.Media;
@ -60,23 +61,14 @@ namespace Avalonia.Animation
/// </summary>
public Easing SlideOutEasing { get; set; } = new LinearEasing();
/// <summary>
/// Starts the animation.
/// </summary>
/// <param name="from">
/// The control that is being transitioned away from. May be null.
/// </param>
/// <param name="to">
/// The control that is being transitioned to. May be null.
/// </param>
/// <param name="forward">
/// If true, the new page is slid in from the right, or if false from the left.
/// </param>
/// <returns>
/// A <see cref="Task"/> that tracks the progress of the animation.
/// </returns>
public async Task Start(Visual from, Visual to, bool forward)
/// <inheritdoc />
public async Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var tasks = new List<Task>();
var parent = GetVisualParent(from, to);
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
@ -109,7 +101,7 @@ namespace Avalonia.Animation
},
Duration = Duration
};
tasks.Add(animation.RunAsync(from));
tasks.Add(animation.RunAsync(from, null, cancellationToken));
}
if (to != null)
@ -140,12 +132,12 @@ namespace Avalonia.Animation
},
Duration = Duration
};
tasks.Add(animation.RunAsync(to));
tasks.Add(animation.RunAsync(to, null, cancellationToken));
}
await Task.WhenAll(tasks);
if (from != null)
if (from != null && !cancellationToken.IsCancellationRequested)
{
from.IsVisible = false;
}

9
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,4 +1,10 @@
Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.CompositePageTransition.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.CrossFade.Start(Avalonia.Visual, Avalonia.Visual)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Threading.Tasks.Task Avalonia.Animation.IPageTransition.Start(Avalonia.Visual, Avalonia.Visual, System.Boolean, System.Threading.CancellationToken)' is present in the implementation but not 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.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.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -63,9 +69,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalon
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public Avalonia.Platform.IGlyphRunImpl Avalonia.Platform.IPlatformRenderInterface.CreateGlyphRun(Avalonia.Media.GlyphRun, System.Double)' does not exist in the implementation but it does exist in the contract.
Total Issues: 64
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.IO.Stream)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 11
Total Issues: 74

5
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -132,7 +132,10 @@ public static class LinuxFramebufferPlatformExtensions
{
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev = null, double scaling = 1)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fbdev) {Scaling = scaling});
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: null) { Scaling = scaling });
public static int StartLinuxFbDev<T>(this T builder, string[] args, string fbdev, PixelFormat? format, double scaling)
where T : AppBuilderBase<T>, new() =>
StartLinuxDirect(builder, args, new FbdevOutput(fileName: fbdev, format: format) { Scaling = scaling });
public static int StartLinuxDrm<T>(this T builder, string[] args, string card = null, double scaling = 1)
where T : AppBuilderBase<T>, new() => StartLinuxDirect(builder, args, new DrmOutput(card) {Scaling = scaling});

2
src/Linux/Avalonia.LinuxFramebuffer/LockedFramebuffer.cs

@ -41,6 +41,6 @@ namespace Avalonia.LinuxFramebuffer
public PixelSize Size => new PixelSize((int)_varInfo.xres, (int) _varInfo.yres);
public int RowBytes => (int) _fixedInfo.line_length;
public Vector Dpi { get; }
public PixelFormat Format => _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
public PixelFormat Format => _varInfo.bits_per_pixel == 16 ? PixelFormat.Rgb565 : _varInfo.blue.offset == 16 ? PixelFormat.Rgba8888 : PixelFormat.Bgra8888;
}
}

88
src/Linux/Avalonia.LinuxFramebuffer/Output/FbdevOutput.cs

@ -16,16 +16,33 @@ namespace Avalonia.LinuxFramebuffer
private IntPtr _mappedAddress;
public double Scaling { get; set; }
public FbdevOutput(string fileName = null)
/// <summary>
/// Create a Linux frame buffer device output
/// </summary>
/// <param name="fileName">The frame buffer device name.
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
public FbdevOutput(string fileName = null) : this(fileName, null)
{
fileName = fileName ?? Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
}
/// <summary>
/// Create a Linux frame buffer device output
/// </summary>
/// <param name="fileName">The frame buffer device name.
/// Defaults to the value in environment variable FRAMEBUFFER or /dev/fb0 when FRAMEBUFFER is not set</param>
/// <param name="format">The required pixel format for the frame buffer.
/// A null value will leave the frame buffer in the current pixel format.
/// Otherwise sets the frame buffer to the required format</param>
public FbdevOutput(string fileName, PixelFormat? format)
{
fileName ??= Environment.GetEnvironmentVariable("FRAMEBUFFER") ?? "/dev/fb0";
_fd = NativeUnsafeMethods.open(fileName, 2, 0);
if (_fd <= 0)
throw new Exception("Error: " + Marshal.GetLastWin32Error());
try
{
Init();
Init(format);
}
catch
{
@ -34,25 +51,28 @@ namespace Avalonia.LinuxFramebuffer
}
}
void Init()
void Init(PixelFormat? format)
{
fixed (void* pnfo = &_varInfo)
{
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
SetBpp();
if (format.HasValue)
{
SetBpp(format.Value);
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
_varInfo.transp = new fb_bitfield();
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo))
_varInfo.transp = new fb_bitfield();
NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo);
NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOPUT_VSCREENINFO, pnfo);
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_VSCREENINFO, pnfo))
throw new Exception("FBIOGET_VSCREENINFO error: " + Marshal.GetLastWin32Error());
if (_varInfo.bits_per_pixel != 32)
throw new Exception("Unable to set 32-bit display mode");
if (_varInfo.bits_per_pixel != 32)
throw new Exception("Unable to set 32-bit display mode");
}
}
fixed(void*pnfo = &_fixedInfo)
if (-1 == NativeUnsafeMethods.ioctl(_fd, FbIoCtl.FBIOGET_FSCREENINFO, pnfo))
@ -70,17 +90,43 @@ namespace Avalonia.LinuxFramebuffer
}
}
void SetBpp()
void SetBpp(PixelFormat format)
{
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
switch (format)
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
case PixelFormat.Rgba8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.blue.offset = 16;
_varInfo.transp.offset = 24;
break;
case PixelFormat.Bgra8888:
_varInfo.bits_per_pixel = 32;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield
{
length = 8
};
_varInfo.green.offset = 8;
_varInfo.red.offset = 16;
_varInfo.transp.offset = 24;
break;
case PixelFormat.Rgb565:
_varInfo.bits_per_pixel = 16;
_varInfo.grayscale = 0;
_varInfo.red = _varInfo.blue = _varInfo.green = _varInfo.transp = new fb_bitfield();
_varInfo.red.length = 5;
_varInfo.green.offset = 5;
_varInfo.green.length = 6;
_varInfo.blue.offset = 11;
_varInfo.blue.length = 5;
break;
}
}
public string Id { get; private set; }

268
tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs

@ -9,6 +9,8 @@ using Avalonia.UnitTests;
using Avalonia.Data;
using Xunit;
using Avalonia.Animation.Easings;
using System.Threading;
using System.Reactive.Linq;
namespace Avalonia.Animation.UnitTests
{
@ -176,5 +178,271 @@ namespace Avalonia.Animation.UnitTests
clock.Step(TimeSpan.FromSeconds(0.100d));
Assert.Equal(border.Width, 300d);
}
[Fact(Skip = "See #6111")]
public void Dispose_Subscription_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
var animationCompletedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var disposable = animation.Apply(border, clock, Observable.Return(true), () => animationCompletedCount++);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.Equal(0, animationCompletedCount);
Assert.Equal(1, propertyChangedCount);
disposable.Dispose();
// Clock ticks should be ignored after Dispose
clock.Step(TimeSpan.FromSeconds(5));
clock.Step(TimeSpan.FromSeconds(6));
clock.Step(TimeSpan.FromSeconds(7));
// On animation disposing (cancellation) on completed is not invoked (is it expected?)
Assert.Equal(0, animationCompletedCount);
// Initial property changed before cancellation + animation value removal.
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Do_Not_Run_Cancelled_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 100d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.Cancel();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
clock.Step(TimeSpan.FromSeconds(10));
Assert.Equal(0, propertyChangedCount);
Assert.True(animationRun.IsCompleted);
}
[Fact(Skip = "See #6111")]
public void Cancellation_Should_Stop_Animation()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
cancellationTokenSource.Cancel();
clock.Step(TimeSpan.FromSeconds(1));
clock.Step(TimeSpan.FromSeconds(2));
clock.Step(TimeSpan.FromSeconds(3));
//Assert.Equal(2, propertyChangedCount);
animationRun.Wait();
clock.Step(TimeSpan.FromSeconds(6));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
}
[Fact]
public void Cancellation_Of_Completed_Animation_Does_Not_Fail()
{
var keyframe1 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 200d),
},
Cue = new Cue(1d)
};
var keyframe2 = new KeyFrame()
{
Setters =
{
new Setter(Border.WidthProperty, 100d),
},
Cue = new Cue(0d)
};
var animation = new Animation()
{
Duration = TimeSpan.FromSeconds(10),
Delay = TimeSpan.FromSeconds(0),
DelayBetweenIterations = TimeSpan.FromSeconds(0),
IterationCount = new IterationCount(1),
Children =
{
keyframe2,
keyframe1
}
};
var border = new Border()
{
Height = 100d,
Width = 50d
};
var propertyChangedCount = 0;
border.PropertyChanged += (sender, e) =>
{
if (e.Property == Control.WidthProperty)
{
propertyChangedCount++;
}
};
var clock = new TestClock();
var cancellationTokenSource = new CancellationTokenSource();
var animationRun = animation.RunAsync(border, clock, cancellationTokenSource.Token);
Assert.Equal(0, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(0));
Assert.False(animationRun.IsCompleted);
Assert.Equal(1, propertyChangedCount);
clock.Step(TimeSpan.FromSeconds(10));
Assert.True(animationRun.IsCompleted);
Assert.Equal(2, propertyChangedCount);
cancellationTokenSource.Cancel();
animationRun.Wait();
}
}
}

Loading…
Cancel
Save