Merge pull request #2546 from unoplatform/dev/dr/AnimsRollback
Fix animations are not reaching their final state on iOS
This commit is contained in:
Коммит
01ac836b10
|
@ -41,7 +41,7 @@ jobs:
|
|||
|
||||
- job: Android_Tests
|
||||
dependsOn: Android_Build_For_Tests
|
||||
|
||||
timeoutInMinutes: 90
|
||||
variables:
|
||||
CI_Build: true
|
||||
SourceLinkEnabled: false
|
||||
|
|
|
@ -55,7 +55,7 @@ jobs:
|
|||
ArtifactType: Container
|
||||
|
||||
- job: iOS_Tests
|
||||
|
||||
timeoutInMinutes: 90
|
||||
dependsOn: iOS_Build
|
||||
|
||||
pool:
|
||||
|
|
|
@ -40,7 +40,8 @@ else
|
|||
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.TextBlockTests' or \
|
||||
namespace = 'SamplesApp.UITests.Microsoft_UI_Xaml_Controls.NumberBoxTests' or \
|
||||
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.ImageTests' or \
|
||||
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.TextBoxTests'
|
||||
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Controls.TextBoxTests' or \
|
||||
namespace = 'SamplesApp.UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_Tests'
|
||||
"
|
||||
fi
|
||||
|
||||
|
|
|
@ -69,7 +69,7 @@ namespace Uno.Samples.UITest.Generator
|
|||
}
|
||||
|
||||
private object GetAttributePropertyValue(AttributeData attr, string name)
|
||||
=> attr.NamedArguments.FirstOrDefault(kvp => kvp.Key == name).Value;
|
||||
=> attr.NamedArguments.FirstOrDefault(kvp => kvp.Key == name).Value.Value;
|
||||
|
||||
private object GetConstructorParameterValue(AttributeData info, string name)
|
||||
=> info.ConstructorArguments.IsDefaultOrEmpty
|
||||
|
|
|
@ -212,7 +212,7 @@ namespace SamplesApp.UITests.TestFramework
|
|||
}
|
||||
}
|
||||
|
||||
public static void AssertDoesNotHaveColorAt(FileInfo screenshot, float x, float y, Color excludedColor, byte tolerance = 0)
|
||||
public static void DoesNotHaveColorAt(FileInfo screenshot, float x, float y, Color excludedColor, byte tolerance = 0)
|
||||
{
|
||||
using (var bitmap = new Bitmap(screenshot.FullName))
|
||||
{
|
||||
|
|
|
@ -119,7 +119,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Controls.PopupTests
|
|||
|
||||
var during = TakeScreenshot("During", ignoreInSnapshotCompare: AppInitializer.GetLocalPlatform() == Platform.Android /*Status bar appears with clock*/);
|
||||
|
||||
ImageAssert.AssertDoesNotHaveColorAt(during, rect.CenterX, rect.CenterY, Color.Blue);
|
||||
ImageAssert.DoesNotHaveColorAt(during, rect.CenterX, rect.CenterY, Color.Blue);
|
||||
|
||||
// Dismiss popup
|
||||
var screenRect = _app.Marked("sampleContent").FirstResult().Rect;
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using SamplesApp.UITests.TestFramework;
|
||||
using Uno.UITest.Helpers;
|
||||
using Uno.UITest.Helpers.Queries;
|
||||
|
||||
namespace SamplesApp.UITests.Windows_UI_Xaml_Media_Animation
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class DoubleAnimation_Tests : SampleControlUITestBase
|
||||
{
|
||||
private const string _finalStateOpacityTestControl = "UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState_Opacity";
|
||||
|
||||
[Test] [AutoRetry] public void When_Opacity_Completed_With_FillBehaviorStop_Then_Rollback() => TestOpacityFinalState();
|
||||
[Test] [AutoRetry] public void When_Opacity_Completed_With_FillBehaviorHold_Then_Hold() => TestOpacityFinalState();
|
||||
[Test] [AutoRetry] public void When_Opacity_Paused_With_FillBehaviorStop_Then_Hold() => TestOpacityFinalState();
|
||||
[Test] [AutoRetry] public void When_Opacity_Paused_With_FillBehaviorHold_Then_Hold() => TestOpacityFinalState();
|
||||
[Test] [AutoRetry] public void When_Opacity_Canceled_With_FillBehaviorStop_Then_Rollback() => TestOpacityFinalState();
|
||||
[Test] [AutoRetry] public void When_Opacity_Canceled_With_FillBehaviorHold_Then_Rollback() => TestOpacityFinalState();
|
||||
|
||||
private void TestOpacityFinalState([CallerMemberName] string testName = null)
|
||||
{
|
||||
var match = Regex.Match(testName, @"When_Opacity_(?<type>\w+)_With_FillBehavior(?<fill>\w+)_Then_(?<expected>\w+)");
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid test name.");
|
||||
}
|
||||
|
||||
var type = match.Groups["type"].Value;
|
||||
var fill = match.Groups["fill"].Value;
|
||||
var expected = match.Groups["expected"].Value;
|
||||
|
||||
bool isSame = false, isGray = false, isDifferent = false;
|
||||
switch (type)
|
||||
{
|
||||
case "Completed" when expected == "Hold":
|
||||
isGray = true;
|
||||
break;
|
||||
|
||||
case "Completed" when expected == "Rollback":
|
||||
isSame = true;
|
||||
break;
|
||||
|
||||
case "Paused":
|
||||
isDifferent = true;
|
||||
break;
|
||||
|
||||
case "Canceled":
|
||||
isSame = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid test name.");
|
||||
}
|
||||
|
||||
Run(_finalStateOpacityTestControl, skipInitialScreenshot: true);
|
||||
|
||||
var initial = TakeScreenshot("Initial", ignoreInSnapshotCompare: true);
|
||||
var element = _app.WaitForElement($"{type}AnimationHost_{fill}").Single().Rect;
|
||||
|
||||
_app.Marked("StartButton").Tap();
|
||||
_app.WaitForDependencyPropertyValue(_app.Marked("Status"), "Text", "Completed");
|
||||
|
||||
// Assert
|
||||
var final = TakeScreenshot("Final", ignoreInSnapshotCompare: true);
|
||||
|
||||
if (isSame)
|
||||
{
|
||||
ImageAssert.AreEqual(initial, final, element);
|
||||
}
|
||||
else if (isGray)
|
||||
{
|
||||
ImageAssert.HasColorAt(final, element.CenterX, element.CenterY, Color.LightGray);
|
||||
}
|
||||
else if (isDifferent)
|
||||
{
|
||||
ImageAssert.AreNotEqual(initial, final, element);
|
||||
ImageAssert.DoesNotHaveColorAt(final, element.CenterX, element.CenterY, Color.LightGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,18 +15,18 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Media_Animation
|
|||
[TestFixture]
|
||||
public partial class DoubleAnimation_Tests : SampleControlUITestBase
|
||||
{
|
||||
private const string _finalStateTestControl = "UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState";
|
||||
private const string _finalStateTransformsTestControl = "UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState_Transforms";
|
||||
|
||||
[Test] [AutoRetry] public void When_Completed_With_FillBehaviorStop_Then_Rollback() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Completed_With_FillBehaviorHold_Then_Hold() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Paused_With_FillBehaviorStop_Then_Hold() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Paused_With_FillBehaviorHold_Then_Hold() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Canceled_With_FillBehaviorStop_Then_Rollback() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Canceled_With_FillBehaviorHold_Then_Rollback() => TestFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Completed_With_FillBehaviorStop_Then_Rollback() => TestTransformsFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Completed_With_FillBehaviorHold_Then_Hold() => TestTransformsFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Paused_With_FillBehaviorStop_Then_Hold() => TestTransformsFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Paused_With_FillBehaviorHold_Then_Hold() => TestTransformsFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Canceled_With_FillBehaviorStop_Then_Rollback() => TestTransformsFinalState();
|
||||
[Test] [AutoRetry] public void When_Transforms_Canceled_With_FillBehaviorHold_Then_Rollback() => TestTransformsFinalState();
|
||||
|
||||
private void TestFinalState([CallerMemberName] string testName = null)
|
||||
private void TestTransformsFinalState([CallerMemberName] string testName = null)
|
||||
{
|
||||
var match = Regex.Match(testName, @"When_(?<type>\w+)_With_FillBehavior(?<fill>\w+)_Then_(?<expected>\w+)");
|
||||
var match = Regex.Match(testName, @"When_Transforms_(?<type>\w+)_With_FillBehavior(?<fill>\w+)_Then_(?<expected>\w+)");
|
||||
if (!match.Success)
|
||||
{
|
||||
throw new InvalidOperationException("Invalid test name.");
|
||||
|
@ -60,7 +60,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Media_Animation
|
|||
throw new InvalidOperationException("Invalid test name.");
|
||||
}
|
||||
|
||||
Run(_finalStateTestControl);
|
||||
Run(_finalStateTransformsTestControl, skipInitialScreenshot: true);
|
||||
|
||||
var initial = TakeScreenshot("Initial", ignoreInSnapshotCompare: true);
|
||||
var initialLocation = _app.WaitForElement($"{type}AnimationHost_{fill}").Single().Rect;
|
||||
|
@ -73,7 +73,7 @@ namespace SamplesApp.UITests.Windows_UI_Xaml_Media_Animation
|
|||
_app.WaitForDependencyPropertyValue(_app.Marked("Status"), "Text", "Completed");
|
||||
|
||||
// Assert
|
||||
var final = TakeScreenshot("Final", ignoreInSnapshotCompare: false);
|
||||
var final = TakeScreenshot("Final", ignoreInSnapshotCompare: true);
|
||||
var finalLocation = _app.WaitForElement($"{type}AnimationHost_{fill}").Single().Rect;
|
||||
var actualDelta = finalLocation.Y - initialLocation.Y;
|
||||
|
|
@ -2401,7 +2401,11 @@
|
|||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState.xaml">
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState_Opacity.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState_Transforms.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
|
@ -4110,8 +4114,11 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Input\RoutedEvents\RoutedEvent_TappedControl.xaml.cs">
|
||||
<DependentUpon>RoutedEvent_TappedControl.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState.xaml.cs">
|
||||
<DependentUpon>DoubleAnimation_FinalState.xaml</DependentUpon>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState_Opacity.xaml.cs">
|
||||
<DependentUpon>DoubleAnimation_FinalState_Opacity.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\DoubleAnimation_FinalState_Transforms.xaml.cs">
|
||||
<DependentUpon>DoubleAnimation_FinalState_Transforms.xaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Media_Animation\SequentialAnimationsPage.xaml.cs">
|
||||
<DependentUpon>SequentialAnimationsPage.xaml</DependentUpon>
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
<Page
|
||||
x:Class="UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState_Opacity"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UITests.Shared.Windows_UI_Xaml_Media_Animation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
|
||||
<Page.Resources>
|
||||
<Style TargetType="Border" x:Key="Marker">
|
||||
<Setter Property="Width" Value="50" />
|
||||
<Setter Property="Height" Value="50" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
</Style>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button
|
||||
x:Name="StartButton"
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Content="Start animations"
|
||||
Click="StartAnimations"/>
|
||||
|
||||
<CheckBox
|
||||
x:Name="UseFromValue"
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Content="Set 'From = .75' " />
|
||||
|
||||
<TextBlock
|
||||
x:Name="Status"
|
||||
Grid.Row="0"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="16"
|
||||
Text="Waiting" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Complete" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Paused" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Canceled" />
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
HorizontalAlignment="Center"
|
||||
Text="FillBehavior: HOLD END" />
|
||||
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="3" Grid.Column="0" />
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="3" Grid.Column="1" />
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="3" Grid.Column="2" />
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="5" Grid.Column="0" />
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="5" Grid.Column="1" />
|
||||
<Border Style="{StaticResource Marker}" Grid.Row="5" Grid.Column="2" />
|
||||
|
||||
<Border
|
||||
x:Name="CompletedAnimationHost_Hold"
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Background="#FF0000"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="PausedAnimationHost_Hold"
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
Background="#FF8000"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="CanceledAnimationHost_Hold"
|
||||
Grid.Row="3"
|
||||
Grid.Column="2"
|
||||
Background="#FFFF00"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
|
||||
<TextBlock
|
||||
Grid.Row="4"
|
||||
Grid.ColumnSpan="3"
|
||||
HorizontalAlignment="Center"
|
||||
Text="FillBehavior: STOP" />
|
||||
|
||||
<Border
|
||||
x:Name="CompletedAnimationHost_Stop"
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
Background="#008000"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="PausedAnimationHost_Stop"
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Background="#0000FF"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
x:Name="CanceledAnimationHost_Stop"
|
||||
Grid.Row="6"
|
||||
Grid.Column="2"
|
||||
Background="#A000C0"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Top"
|
||||
Width="50"
|
||||
Height="50">
|
||||
<Border.RenderTransform>
|
||||
<TranslateTransform />
|
||||
</Border.RenderTransform>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Page>
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Media.Animation;
|
||||
using Uno.Extensions;
|
||||
using Uno.UI.Samples.Controls;
|
||||
|
||||
namespace UITests.Windows_UI_Xaml_Media_Animation
|
||||
{
|
||||
[Sample("Animations", Name = "DoubleAnimation opacity final state", Description = _description)]
|
||||
public sealed partial class DoubleAnimation_FinalState_Opacity : Page
|
||||
{
|
||||
private const string _description = @"This (automated) test validate the final state of when animating opacity using a double animation.
|
||||
|
||||
Expected result:
|
||||
* Completed: stays at 0 if HOLD, goes back to 1 if STOP
|
||||
* Paused: stays half visible no matter the fill behavior
|
||||
* Canceled: goes back to 1 no matter the fill behavior
|
||||
|
||||
If 'Set From' was selected, then rollback means back to value before animation, not the 'From' value!";
|
||||
|
||||
private TimeSpan _duration;
|
||||
|
||||
public DoubleAnimation_FinalState_Opacity()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
#if DEBUG
|
||||
_duration = TimeSpan.FromSeconds(2);
|
||||
#else
|
||||
_duration = TimeSpan.FromMilliseconds(400);
|
||||
#endif
|
||||
}
|
||||
|
||||
private async void StartAnimations(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var toLetComplete = new[]
|
||||
{
|
||||
CreateAnimation(CompletedAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(CompletedAnimationHost_Stop, FillBehavior.Stop)
|
||||
};
|
||||
var toPause = new[]
|
||||
{
|
||||
CreateAnimation(PausedAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(PausedAnimationHost_Stop, FillBehavior.Stop)
|
||||
};
|
||||
var toCancel = new[]
|
||||
{
|
||||
CreateAnimation(CanceledAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(CanceledAnimationHost_Stop, FillBehavior.Stop)
|
||||
};
|
||||
|
||||
Status.Text = "Animating";
|
||||
await Dispatcher.RunAsync(
|
||||
CoreDispatcherPriority.Normal,
|
||||
async () =>
|
||||
{
|
||||
var halfAnimation = (int)_duration.TotalMilliseconds / 2;
|
||||
await Task.Delay(halfAnimation);
|
||||
toPause.ForEach(s => s.Pause());
|
||||
toCancel.ForEach(s => s.Stop());
|
||||
|
||||
await Task.Delay((int)(halfAnimation * 1.2));
|
||||
Status.Text = "Completed";
|
||||
});
|
||||
toLetComplete.Concat(toPause).Concat(toCancel).ForEach(s => s.Begin());
|
||||
}
|
||||
|
||||
private Storyboard CreateAnimation(UIElement target, FillBehavior fill)
|
||||
{
|
||||
var animation = new DoubleAnimation
|
||||
{
|
||||
To = 0,
|
||||
Duration = _duration,
|
||||
FillBehavior = fill
|
||||
};
|
||||
|
||||
if (UseFromValue.IsChecked.GetValueOrDefault())
|
||||
{
|
||||
animation.From = .75;
|
||||
}
|
||||
|
||||
Storyboard.SetTarget(animation, target);
|
||||
Storyboard.SetTargetProperty(animation, nameof(UIElement.Opacity));
|
||||
|
||||
return new Storyboard
|
||||
{
|
||||
Children = { animation }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<Page
|
||||
x:Class="UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState"
|
||||
x:Class="UITests.Windows_UI_Xaml_Media_Animation.DoubleAnimation_FinalState_Transforms"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:UITests.Shared.Windows_UI_Xaml_Media_Animation"
|
||||
|
@ -12,7 +12,7 @@
|
|||
<Style TargetType="Border" x:Key="Marker">
|
||||
<Setter Property="Width" Value="50" />
|
||||
<Setter Property="Height" Value="50" />
|
||||
<Setter Property="Background" Value="#33000000" />
|
||||
<Setter Property="Background" Value="LightGray" />
|
||||
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
</Style>
|
|
@ -11,12 +11,21 @@ using Uno.UI.Samples.Controls;
|
|||
|
||||
namespace UITests.Windows_UI_Xaml_Media_Animation
|
||||
{
|
||||
[Sample("Animations")]
|
||||
public sealed partial class DoubleAnimation_FinalState : Page
|
||||
[Sample("Animations", "Transform", Name="DoubleAnimation transforms final state", Description = _description)]
|
||||
public sealed partial class DoubleAnimation_FinalState_Transforms : Page
|
||||
{
|
||||
private const string _description = @"This (automated) test validate the final state of when animating transformation using a double animation.
|
||||
|
||||
Expected result:
|
||||
* Completed: stays at bottom if HOLD, goes back to top if STOP
|
||||
* Paused: stays in the middle no matter the fill behavior
|
||||
* Canceled: goes back to top no matter the fille behavior
|
||||
|
||||
If 'Set From' was selected, then rollback means back to value before animation, not the 'From' value!";
|
||||
|
||||
private TimeSpan _duration;
|
||||
|
||||
public DoubleAnimation_FinalState()
|
||||
public DoubleAnimation_FinalState_Transforms()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
|
||||
|
@ -34,12 +43,12 @@ namespace UITests.Windows_UI_Xaml_Media_Animation
|
|||
CreateAnimation(CompletedAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(CompletedAnimationHost_Stop, FillBehavior.Stop)
|
||||
};
|
||||
var toPause = new []
|
||||
var toPause = new[]
|
||||
{
|
||||
CreateAnimation(PausedAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(PausedAnimationHost_Stop, FillBehavior.Stop)
|
||||
};
|
||||
var toCancel = new []
|
||||
var toCancel = new[]
|
||||
{
|
||||
CreateAnimation(CanceledAnimationHost_Hold, FillBehavior.HoldEnd),
|
||||
CreateAnimation(CanceledAnimationHost_Stop, FillBehavior.Stop)
|
|
@ -103,6 +103,7 @@ namespace Windows.UI.Composition
|
|||
duration = _durationMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
if (_stop.value.HasValue)
|
||||
{
|
||||
from = _stop.value.Value;
|
||||
|
@ -133,8 +134,8 @@ namespace Windows.UI.Composition
|
|||
if (_isDiscrete)
|
||||
{
|
||||
var discreteAnim = CAKeyFrameAnimation.FromKeyPath(_property);
|
||||
discreteAnim.KeyTimes = new NSNumber[] { new NSNumber(0.0), new NSNumber(1.0) };
|
||||
discreteAnim.Values = new NSObject[] { _nsValueConversion(to) };
|
||||
discreteAnim.KeyTimes = new NSNumber[] {new NSNumber(0.0), new NSNumber(1.0)};
|
||||
discreteAnim.Values = new NSObject[] {_nsValueConversion(to)};
|
||||
discreteAnim.CalculationMode = CAKeyFrameAnimation.AnimationDescrete;
|
||||
|
||||
animation = discreteAnim;
|
||||
|
@ -148,17 +149,19 @@ namespace Windows.UI.Composition
|
|||
|
||||
animation = continuousAnim;
|
||||
}
|
||||
|
||||
if (delayMilliseconds > 0)
|
||||
{
|
||||
// Note: We must make sure to use the time relative to the 'layer', otherwise we might introduce a random delay and the animations
|
||||
// will run twice (once "managed" while updating the DP, and a second "native" using this animator)
|
||||
animation.BeginTime = layer.ConvertTimeFromLayer(CAAnimation.CurrentMediaTime() + delayMilliseconds / __millisecondsPerSecond, null);
|
||||
}
|
||||
|
||||
animation.Duration = durationMilliseconds / __millisecondsPerSecond;
|
||||
animation.FillMode = CAFillMode.Forwards;
|
||||
animation.RemovedOnCompletion = true;
|
||||
animation.AnimationStarted += OnAnimationStarted;
|
||||
animation.AnimationStopped += OnAnimationStopped;
|
||||
animation.AnimationStarted += OnAnimationStarted(animation);
|
||||
animation.AnimationStopped += OnAnimationStopped(animation);
|
||||
|
||||
// Start the animation
|
||||
_stop = default; // Cleanup stop reason
|
||||
|
@ -182,87 +185,108 @@ namespace Windows.UI.Composition
|
|||
layer.RemoveAnimation(_key); // This will effectively stop the animation (and invoke OnAnimationStopped)
|
||||
}
|
||||
|
||||
private void OnAnimationStarted(object sender, EventArgs _)
|
||||
private EventHandler OnAnimationStarted(CAAnimation animation)
|
||||
{
|
||||
if (sender is CAAnimation animation)
|
||||
{
|
||||
animation.AnimationStarted -= OnAnimationStarted; // Prevent leak
|
||||
}
|
||||
EventHandler handler = default;
|
||||
handler= Handler;
|
||||
|
||||
if (_current.animation != sender)
|
||||
{
|
||||
return; // We are no longer the current animation, do not interfere with the current
|
||||
}
|
||||
return handler;
|
||||
|
||||
if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
void Handler(object sender, EventArgs _)
|
||||
{
|
||||
this.Log().DebugFormat("CoreAnimation '{0}' has started.", _property);
|
||||
}
|
||||
// Note: The sender is usually not the same managed instance of the started animation
|
||||
// (even the sender.Handle is not the same). So we cannot rely on it to determine
|
||||
// if we are still the '_current'. Instead we have to create a new handler
|
||||
// for each started animation which captures its target 'animation' instance.
|
||||
|
||||
// This will disable the transform while the native animation handles it
|
||||
// It must be the first thing we do when the animation starts
|
||||
// (However we have to wait for the first frame in order to not remove the transform while waiting for the BeginTime)
|
||||
_prepare?.Invoke();
|
||||
animation.AnimationStarted -= handler; // Prevent leak
|
||||
|
||||
if (_current.animation != animation)
|
||||
{
|
||||
return; // We are no longer the current animation, do not interfere with the current
|
||||
}
|
||||
|
||||
if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
{
|
||||
this.Log().DebugFormat("CoreAnimation '{0}' has started.", _property);
|
||||
}
|
||||
|
||||
// This will disable the transform while the native animation handles it
|
||||
// It must be the first thing we do when the animation starts
|
||||
// (However we have to wait for the first frame in order to not remove the transform while waiting for the BeginTime)
|
||||
_prepare?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnimationStopped(object sender, CAAnimationStateEventArgs args)
|
||||
private EventHandler<CAAnimationStateEventArgs> OnAnimationStopped(CAAnimation animation)
|
||||
{
|
||||
// This callback will be invoked when the animation is stopped, no matter the reason (completed, paused or canceled)
|
||||
|
||||
if (sender is CAAnimation animation)
|
||||
{
|
||||
animation.AnimationStopped -= OnAnimationStopped; // Prevent leak
|
||||
}
|
||||
EventHandler<CAAnimationStateEventArgs> handler = default;
|
||||
handler = Handler;
|
||||
|
||||
var (currentAnim, from, to) = _current;
|
||||
if (currentAnim != sender)
|
||||
{
|
||||
return; // We are no longer the current animation, do not interfere with the current
|
||||
}
|
||||
_current = default;
|
||||
return handler;
|
||||
|
||||
if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
void Handler(object sender, CAAnimationStateEventArgs args)
|
||||
{
|
||||
this.Log().DebugFormat("CoreAnimation on property {0} has been {1}.", _property, _stop.reason);
|
||||
}
|
||||
// Note: The sender is usually not the same managed instance of the started animation
|
||||
// (even the sender.Handle is not the same). So we cannot rely on it to determine
|
||||
// if we are still the '_current'. Instead we have to create a new handler
|
||||
// for each started animation which captures its target 'animation' instance.
|
||||
|
||||
// First commit the expected final (end, current or initial) value.
|
||||
if (_layer.TryGetTarget(out var layer))
|
||||
{
|
||||
var keyPath = new NSString(_property);
|
||||
NSObject finalValue;
|
||||
switch (_stop.reason)
|
||||
animation.AnimationStopped -= handler; // Prevent leak
|
||||
|
||||
var (currentAnim, from, to) = _current;
|
||||
if (currentAnim != animation)
|
||||
{
|
||||
case StopReason.Paused:
|
||||
finalValue = _stop.value.HasValue
|
||||
? _nsValueConversion(_stop.value.Value)
|
||||
: layer.ValueForKeyPath(keyPath);
|
||||
break;
|
||||
|
||||
case StopReason.Canceled:
|
||||
finalValue = _nsValueConversion(from);
|
||||
break;
|
||||
|
||||
default:
|
||||
case StopReason.Completed:
|
||||
finalValue = _nsValueConversion(to);
|
||||
break;
|
||||
return; // We are no longer the current animation, do not interfere with the current
|
||||
}
|
||||
|
||||
CATransaction.Begin();
|
||||
CATransaction.DisableActions = true;
|
||||
layer.SetValueForKeyPath(finalValue, keyPath);
|
||||
CATransaction.Commit();
|
||||
}
|
||||
_current = default;
|
||||
|
||||
// Then reactivate the managed code handling of transforms that was disabled by the _prepare.
|
||||
_cleanup?.Invoke();
|
||||
if (this.Log().IsEnabled(Microsoft.Extensions.Logging.LogLevel.Debug))
|
||||
{
|
||||
this.Log().DebugFormat("CoreAnimation on property {0} has been {1}.", _property, _stop.reason);
|
||||
}
|
||||
|
||||
// Finally raise callbacks
|
||||
if (_stop.reason == StopReason.Completed)
|
||||
{
|
||||
Debug.Assert(args.Finished);
|
||||
_onCompleted();
|
||||
// First commit the expected final (end, current or initial) value.
|
||||
if (_layer.TryGetTarget(out var layer))
|
||||
{
|
||||
var keyPath = new NSString(_property);
|
||||
NSObject finalValue;
|
||||
switch (_stop.reason)
|
||||
{
|
||||
case StopReason.Paused:
|
||||
finalValue = _stop.value.HasValue
|
||||
? _nsValueConversion(_stop.value.Value)
|
||||
: layer.ValueForKeyPath(keyPath);
|
||||
break;
|
||||
|
||||
case StopReason.Canceled:
|
||||
finalValue = _nsValueConversion(from);
|
||||
break;
|
||||
|
||||
default:
|
||||
case StopReason.Completed:
|
||||
finalValue = _nsValueConversion(to);
|
||||
break;
|
||||
}
|
||||
|
||||
CATransaction.Begin();
|
||||
CATransaction.DisableActions = true;
|
||||
layer.SetValueForKeyPath(finalValue, keyPath);
|
||||
CATransaction.Commit();
|
||||
}
|
||||
|
||||
// Then reactivate the managed code handling of transforms that was disabled by the _prepare.
|
||||
_cleanup?.Invoke();
|
||||
|
||||
// Finally raise callbacks
|
||||
if (_stop.reason == StopReason.Completed)
|
||||
{
|
||||
Debug.Assert(args.Finished);
|
||||
_onCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче