Browse Source

Merge branch 'master' into fix-pointerleave-not-always-set

pull/2067/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
dee10d0482
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      .ncrunch/Avalonia.Desktop.v3.ncrunchproject
  2. 5
      .ncrunch/Avalonia.net461.v3.ncrunchproject
  3. 60
      Avalonia.sln
  4. 1
      Avalonia.v3.ncrunchsolution
  5. 11
      azure-pipelines.yml
  6. 2
      build/Magick.NET-Q16-AnyCPU.props
  7. 5
      build/NetFX.props
  8. 4
      build/SkiaSharp.props
  9. 8
      build/UnitTests.NetFX.props
  10. 1
      build/xunit.runner.mono.json
  11. 2
      global.json
  12. 21
      native/Avalonia.Native/src/OSX/window.mm
  13. 8
      nukebuild/.editorconfig
  14. 60
      nukebuild/Build.cs
  15. 12
      nukebuild/BuildParameters.cs
  16. 129
      parameters.cake
  17. 9
      samples/ControlCatalog/Pages/DropDownPage.xaml
  18. 3
      samples/ControlCatalog/Pages/DropDownPage.xaml.cs
  19. 35
      samples/RenderDemo/Pages/AnimationsPage.xaml
  20. 2
      samples/RenderDemo/Pages/ClippingPage.xaml
  21. 1
      src/Avalonia.Animation/Animatable.cs
  22. 199
      src/Avalonia.Animation/Animation.cs
  23. 207
      src/Avalonia.Animation/AnimationInstance`1.cs
  24. 1
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  25. 73
      src/Avalonia.Animation/Animators/Animator`1.cs
  26. 21
      src/Avalonia.Animation/Animators/BoolAnimator.cs
  27. 24
      src/Avalonia.Animation/Animators/ByteAnimator.cs
  28. 17
      src/Avalonia.Animation/Animators/DecimalAnimator.cs
  29. 17
      src/Avalonia.Animation/Animators/DoubleAnimator.cs
  30. 17
      src/Avalonia.Animation/Animators/FloatAnimator.cs
  31. 24
      src/Avalonia.Animation/Animators/Int16Animator.cs
  32. 24
      src/Avalonia.Animation/Animators/Int32Animator.cs
  33. 24
      src/Avalonia.Animation/Animators/Int64Animator.cs
  34. 24
      src/Avalonia.Animation/Animators/UInt16Animator.cs
  35. 24
      src/Avalonia.Animation/Animators/UInt32Animator.cs
  36. 24
      src/Avalonia.Animation/Animators/UInt64Animator.cs
  37. 3
      src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs
  38. 35
      src/Avalonia.Animation/DoubleAnimator.cs
  39. 0
      src/Avalonia.Animation/Easing/IEasing.cs
  40. 176
      src/Avalonia.Animation/IterationCount.cs
  41. 4
      src/Avalonia.Animation/IterationCountTypeConverter.cs
  42. 15
      src/Avalonia.Animation/KeyFrame.cs
  43. 33
      src/Avalonia.Animation/KeyFramePair`1.cs
  44. 33
      src/Avalonia.Animation/KeyFrames.cs
  45. 3
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  46. 199
      src/Avalonia.Animation/RepeatCount.cs
  47. 7
      src/Avalonia.Animation/Transitions/DoubleTransition.cs
  48. 0
      src/Avalonia.Animation/Transitions/FloatTransition.cs
  49. 0
      src/Avalonia.Animation/Transitions/IntegerTransition.cs
  50. 4
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  51. 2
      src/Avalonia.Base/Collections/AvaloniaList.cs
  52. 1
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  53. 1
      src/Avalonia.Base/Platform/IAssetLoader.cs
  54. 29
      src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs
  55. 2
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  56. 2
      src/Avalonia.Controls/Calendar/DatePicker.cs
  57. 4
      src/Avalonia.Controls/GridLength.cs
  58. 2
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  59. 1
      src/Avalonia.Controls/Panel.cs
  60. 11
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  61. 2
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  62. 2
      src/Avalonia.Controls/Viewbox.cs
  63. 2
      src/Avalonia.Input/Cursors.cs
  64. 6
      src/Avalonia.Input/DataFormats.cs
  65. 10
      src/Avalonia.Input/DragDrop.cs
  66. 2
      src/Avalonia.Native/PlatformThreadingInterface.cs
  67. 6
      src/Avalonia.OpenGL/EglContext.cs
  68. 17
      src/Avalonia.OpenGL/EglDisplay.cs
  69. 21
      src/Avalonia.OpenGL/EglErrors.cs
  70. 7
      src/Avalonia.OpenGL/EglInterface.cs
  71. 3
      src/Avalonia.OpenGL/GlConsts.cs
  72. 15
      src/Avalonia.OpenGL/GlErrors.cs
  73. 5
      src/Avalonia.OpenGL/GlInterface.cs
  74. 30
      src/Avalonia.OpenGL/OpenGlException.cs
  75. 20
      src/Avalonia.Styling/Styling/ChildSelector.cs
  76. 16
      src/Avalonia.Styling/Styling/DescendentSelector.cs
  77. 7
      src/Avalonia.Styling/Styling/IStyle.cs
  78. 12
      src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs
  79. 39
      src/Avalonia.Styling/Styling/Selector.cs
  80. 86
      src/Avalonia.Styling/Styling/SelectorMatch.cs
  81. 2
      src/Avalonia.Styling/Styling/Setter.cs
  82. 24
      src/Avalonia.Styling/Styling/Style.cs
  83. 45
      src/Avalonia.Styling/Styling/Styles.cs
  84. 5
      src/Avalonia.Styling/Styling/TemplateSelector.cs
  85. 19
      src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs
  86. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  87. 70
      src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs
  88. 27
      src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs
  89. 17
      src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs
  90. 23
      src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs
  91. 17
      src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs
  92. 74
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  93. 17
      src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs
  94. 37
      src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs
  95. 17
      src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs
  96. 44
      src/Avalonia.Visuals/Animation/CrossFade.cs
  97. 89
      src/Avalonia.Visuals/Animation/PageSlide.cs
  98. 36
      src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs
  99. 7
      src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs
  100. 25
      src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

5
.ncrunch/Avalonia.Desktop.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.net461.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
</Settings>
</ProjectConfiguration>

60
Avalonia.sln

@ -1,3 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
@ -130,6 +131,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
ProjectSection(SolutionItems) = preProject
build\Base.props = build\Base.props
build\Binding.props = build\Binding.props
build\BuildTargets.targets = build\BuildTargets.targets
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
@ -147,7 +149,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Splat.props = build\Splat.props
build\System.Memory.props = build\System.Memory.props
build\XUnit.props = build\XUnit.props
build\BuildTargets.targets = build\BuildTargets.targets
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
@ -189,10 +190,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalon
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Desktop", "src\Avalonia.Desktop\Avalonia.Desktop.csproj", "{3C471044-3640-45E3-B1B2-16D2FF8399EE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Build.Tasks", "src\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj", "{BF28998D-072C-439A-AFBB-2FE5021241E0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.csproj", "{3F00BC43-5095-477F-93D8-E65B08179A00}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -218,10 +221,6 @@ Global
Release|iPhoneSimulator = Release|iPhoneSimulator
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{B09B78D8-9B26-48B0-9149-D64A2F120F3F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -1720,6 +1719,54 @@ Global
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhone.Build.0 = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{BF28998D-072C-439A-AFBB-2FE5021241E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|Any CPU.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhone.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhone.Build.0 = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|Any CPU.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhone.Build.0 = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3F00BC43-5095-477F-93D8-E65B08179A00}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|Any CPU.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhone.Build.0 = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF227847-E65C-4BE9-BCE9-B551357788E0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1773,6 +1820,7 @@ Global
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{D49233F8-F29C-47DD-9975-C4C9E4502720} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{3C471044-3640-45E3-B1B2-16D2FF8399EE} = {E870DCD7-F46A-498D-83FC-D0FD13E0A11C}
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
Avalonia.v3.ncrunchsolution

@ -2,6 +2,7 @@
<Settings>
<AdditionalFilesToIncludeForSolution>
<Value>tests\TestFiles\**.*</Value>
<Value>src\Avalonia.Build.Tasks\bin\Debug\netstandard2.0\Avalonia.Build.Tasks.dll</Value>
</AdditionalFilesToIncludeForSolution>
<AllowParallelTestExecution>True</AllowParallelTestExecution>
<ProjectConfigStoragePathRelativeToSolutionDir>.ncrunch</ProjectConfigStoragePathRelativeToSolutionDir>

11
azure-pipelines.yml

@ -38,6 +38,13 @@ jobs:
inputs:
version: '2.1.403'
- task: CmdLine@2
displayName: 'Install Mono 5.18'
inputs:
script: |
curl -o ./mono.pkg https://download.mono-project.com/archive/5.18.0/macos-10-universal/MonoFramework-MDK-5.18.0.225.macos10.xamarin.universal.pkg
sudo installer -verbose -pkg ./mono.pkg -target /
- task: Xcode@5
inputs:
actions: 'build'
@ -51,7 +58,9 @@ jobs:
- task: CmdLine@2
displayName: 'Install CastXML'
inputs:
script: brew install castxml
script: |
brew update
brew install castxml
- task: CmdLine@2
displayName: 'Install Nuke'

2
build/Magick.NET-Q16-AnyCPU.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.0.6.102" />
<PackageReference Include="Magick.NET-Q16-AnyCPU" Version="7.9.0.2" />
</ItemGroup>
</Project>

5
build/NetFX.props

@ -3,4 +3,9 @@
<FrameworkPathOverride>/usr/lib/mono/4.6.1-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api</FrameworkPathOverride>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net47' and '$(OS)' == 'Unix' ">
<FrameworkPathOverride>/usr/lib/mono/4.7-api/</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api</FrameworkPathOverride>
</PropertyGroup>
</Project>

4
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.60.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" />
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
</ItemGroup>
</Project>

8
build/UnitTests.NetFX.props

@ -0,0 +1,8 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildThisFileDirectory)/NetFX.props" />
<ItemGroup Condition="$(TargetFramework.StartsWith('net4'))">
<Content Include="$(MSBuildThisFileDirectory)/xunit.runner.mono.json" Link="xunit.runner.json" >
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

1
build/xunit.runner.mono.json

@ -0,0 +1 @@
{ "appDomain": "denied" }

2
global.json

@ -1,7 +1,7 @@
{
"msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43",
"MSBuild.Sdk.Extras": "1.6.46",
"MSBuild.Sdk.Extras": "1.6.65",
"AggregatePackage.NuGet.Sdk" : "0.1.12"
}
}

21
native/Avalonia.Native/src/OSX/window.mm

@ -425,7 +425,7 @@ private:
[[Window parentWindow] removeChildWindow:Window];
WindowBaseImpl::Show();
return SetWindowState(_lastWindowState);
return SetWindowState(Normal);
}
}
@ -1184,6 +1184,25 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
- (void)windowDidMiniaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowDidDeminiaturize:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{

8
nukebuild/.editorconfig

@ -0,0 +1,8 @@
# editorconfig.org
# top-most EditorConfig file
root = false
# C# files
[*.cs]
dotnet_style_require_accessibility_modifiers = never

60
nukebuild/Build.cs

@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Xml.Linq;
using Nuke.Common;
using Nuke.Common.Git;
using Nuke.Common.ProjectModel;
@ -56,6 +57,17 @@ partial class Build : NukeBuild
Information("IsReleasable: " + Parameters.IsReleasable);
Information("IsMyGetRelease: " + Parameters.IsMyGetRelease);
Information("IsNuGetRelease: " + Parameters.IsNuGetRelease);
void ExecWait(string preamble, string command, string args)
{
Console.WriteLine(preamble);
Process.Start(new ProcessStartInfo(command, args) {UseShellExecute = false}).WaitForExit();
}
ExecWait("dotnet version:", "dotnet", "--version");
if (Parameters.IsRunningOnUnix)
ExecWait("Mono version:", "mono", "--version");
}
Target Clean => _ => _.Executes(() =>
@ -92,16 +104,24 @@ partial class Build : NukeBuild
);
});
void RunCoreTest(string project, bool coreOnly = false)
void RunCoreTest(string project)
{
if(!project.EndsWith(".csproj"))
project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
Information("Running tests from " + project);
var frameworks = new List<string>(){"netcoreapp2.0"};
XDocument xdoc;
using (var s = File.OpenRead(project))
xdoc = XDocument.Load(s);
List<string> frameworks = null;
var targets = xdoc.Root.Descendants("TargetFrameworks").FirstOrDefault();
if (targets != null)
frameworks = targets.Value.Split(';').Where(f => !string.IsNullOrWhiteSpace(f)).ToList();
else
frameworks = new List<string> {xdoc.Root.Descendants("TargetFramework").First().Value};
foreach(var fw in frameworks)
{
if(!fw.StartsWith("netcoreapp") && coreOnly)
continue;
Information("Running for " + fw);
DotNetTest(c =>
{
@ -124,26 +144,28 @@ partial class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Base.UnitTests", false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", false);
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests", false);
RunCoreTest("./tests/Avalonia.Animation.UnitTests");
RunCoreTest("./tests/Avalonia.Base.UnitTests");
RunCoreTest("./tests/Avalonia.Controls.UnitTests");
RunCoreTest("./tests/Avalonia.Input.UnitTests");
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests");
RunCoreTest("./tests/Avalonia.Layout.UnitTests");
RunCoreTest("./tests/Avalonia.Markup.UnitTests");
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests");
RunCoreTest("./tests/Avalonia.Styling.UnitTests");
RunCoreTest("./tests/Avalonia.Visuals.UnitTests");
RunCoreTest("./tests/Avalonia.Skia.UnitTests");
RunCoreTest("./tests/Avalonia.ReactiveUI.UnitTests");
});
Target RunRenderTests => _ => _
.OnlyWhen(() => !Parameters.SkipTests && Parameters.IsRunningOnWindows)
.OnlyWhen(() => !Parameters.SkipTests)
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", true);
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", true);
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj");
});
Target RunDesignerTests => _ => _
@ -151,7 +173,7 @@ partial class Build : NukeBuild
.DependsOn(Compile)
.Executes(() =>
{
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", false);
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests");
});
[PackageExecutable("JetBrains.dotMemoryUnit", "dotMemoryUnit.exe")] readonly Tool DotMemoryUnit;

12
nukebuild/BuildParameters.cs

@ -14,13 +14,13 @@ using static Nuke.Common.Tools.MSBuild.MSBuildTasks;
public partial class Build
{
[Parameter("configuration")]
public string NukeArgConfiguration { get; set; }
public string Configuration { get; set; }
[Parameter("skip-tests")]
public bool NukeArgSkipTests { get; set; }
public bool SkipTests { get; set; }
[Parameter("force-nuget-version")]
public string NukeArgForceNugetVersion { get; set; }
public string ForceNugetVersion { get; set; }
public class BuildParameters
{
@ -64,8 +64,8 @@ public partial class Build
public BuildParameters(Build b)
{
// ARGUMENTS
Configuration = b.NukeArgConfiguration ?? "Release";
SkipTests = b.NukeArgSkipTests;
Configuration = b.Configuration ?? "Release";
SkipTests = b.SkipTests;
// CONFIGURATION
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
@ -102,7 +102,7 @@ public partial class Build
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = b.NukeArgForceNugetVersion ?? GetVersion();
Version = b.ForceNugetVersion ?? GetVersion();
if (IsRunningOnAzure)
{

129
parameters.cake

@ -1,129 +0,0 @@
using System.Xml.Linq;
using System.Linq;
public class Parameters
{
public string Configuration { get; private set; }
public bool SkipTests { get; private set; }
public string MainRepo { get; private set; }
public string MasterBranch { get; private set; }
public string ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; }
public string ReleaseBranchPrefix { get; private set; }
public string MSBuildSolution { get; private set; }
public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; }
public bool IsRunningOnWindows { get; private set; }
public bool IsRunningOnAppVeyor { get; private set; }
public bool IsRunningOnAzure { get; private set; }
public bool IsPullRequest { get; private set; }
public bool IsMainRepo { get; private set; }
public bool IsMasterBranch { get; private set; }
public bool IsReleaseBranch { get; private set; }
public bool IsTagged { get; private set; }
public bool IsReleasable { get; private set; }
public bool IsMyGetRelease { get; private set; }
public bool IsNuGetRelease { get; private set; }
public bool PublishTestResults { get; private set; }
public string Version { get; private set; }
public DirectoryPath ArtifactsDir { get; private set; }
public DirectoryPath NugetRoot { get; private set; }
public DirectoryPath ZipRoot { get; private set; }
public DirectoryPath BinRoot { get; private set; }
public DirectoryPath TestResultsRoot { get; private set; }
public string DirSuffix { get; private set; }
public DirectoryPathCollection BuildDirs { get; private set; }
public string FileZipSuffix { get; private set; }
public FilePath ZipCoreArtifacts { get; private set; }
public FilePath ZipNuGetArtifacts { get; private set; }
public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; }
public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; }
public Parameters(ICakeContext context)
{
var buildSystem = context.BuildSystem();
// ARGUMENTS
Configuration = context.Argument("configuration", "Release");
SkipTests = context.HasArgument("skip-tests");
// CONFIGURATION
MainRepo = "https://github.com/AvaloniaUI/Avalonia";
MasterBranch = "master";
ReleaseBranchPrefix = "refs/heads/release/";
ReleaseConfiguration = "Release";
MSBuildSolution = "./dirs.proj";
// PARAMETERS
IsLocalBuild = buildSystem.IsLocalBuild;
IsRunningOnUnix = context.IsRunningOnUnix();
IsRunningOnWindows = context.IsRunningOnWindows();
IsRunningOnAppVeyor = buildSystem.AppVeyor.IsRunningOnAppVeyor;
IsRunningOnAzure = buildSystem.IsRunningOnVSTS || buildSystem.IsRunningOnTFS || context.EnvironmentVariable("LOGNAME") == "vsts";
IsPullRequest = buildSystem.AppVeyor.Environment.PullRequest.IsPullRequest;
IsMainRepo = StringComparer.OrdinalIgnoreCase.Equals(MainRepo, context.EnvironmentVariable("BUILD_REPOSITORY_URI"));
IsMasterBranch = StringComparer.OrdinalIgnoreCase.Equals(MasterBranch, context.EnvironmentVariable("BUILD_SOURCEBRANCHNAME"));
IsReleaseBranch = (context.EnvironmentVariable("BUILD_SOURCEBRANCH")??"").StartsWith(ReleaseBranchPrefix, StringComparison.OrdinalIgnoreCase);
IsTagged = buildSystem.AppVeyor.Environment.Repository.Tag.IsTag
&& !string.IsNullOrWhiteSpace(buildSystem.AppVeyor.Environment.Repository.Tag.Name);
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable;
IsNuGetRelease = IsMainRepo && IsReleasable && IsReleaseBranch;
// VERSION
Version = context.Argument("force-nuget-version", GetVersion());
if (IsRunningOnAppVeyor)
{
string tagVersion = null;
if (IsTagged)
{
var tag = buildSystem.AppVeyor.Environment.Repository.Tag.Name;
var nugetReleasePrefix = "nuget-release-";
IsNuGetRelease = IsTagged && IsReleasable && tag.StartsWith(nugetReleasePrefix);
if(IsNuGetRelease)
tagVersion = tag.Substring(nugetReleasePrefix.Length);
}
if(tagVersion != null)
{
Version = tagVersion;
}
else
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
}
}
else if (IsRunningOnAzure)
{
if(!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("BUILD_BUILDID") + "-beta";
}
PublishTestResults = true;
}
// DIRECTORIES
ArtifactsDir = (DirectoryPath)context.Directory("./artifacts");
NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin");
TestResultsRoot = ArtifactsDir.Combine("test-results");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
DirSuffix = Configuration;
FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix);
}
private static string GetVersion()
{
var xdoc = XDocument.Load("./build/SharedVersion.props");
return xdoc.Descendants().First(x => x.Name.LocalName == "Version").Value;
}
}

9
samples/ControlCatalog/Pages/DropDownPage.xaml

@ -27,6 +27,15 @@
<TextBox Text="TextBox"/>
</DropDownItem>
</DropDown>
<DropDown x:Name="fontDropDown" SelectedIndex="0">
<DropDown.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" FontFamily="{Binding}" />
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
</StackPanel>
</StackPanel>

3
samples/ControlCatalog/Pages/DropDownPage.xaml.cs

@ -13,6 +13,9 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
var fontDropDown = this.Find<DropDown>("fontDropDown");
fontDropDown.Items = Avalonia.Media.FontFamily.SystemFontFamilies;
fontDropDown.SelectedIndex = 0;
}
}
}

35
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -43,7 +43,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="Border.Rect1:pointerover">
<Style.Animations>
<Animation Duration="0:0:2.5"
RepeatCount="4"
IterationCount="4"
FillMode="None"
PlaybackDirection="AlternateReverse"
Easing="SineEaseInOut">
@ -73,7 +73,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style.Animations>
<Animation Duration="0:0:0.5"
Easing="QuadraticEaseInOut"
RepeatCount="Loop">
IterationCount="Infinite">
<KeyFrame Cue="50%">
<Setter Property="ScaleTransform.ScaleX" Value="0.8"/>
<Setter Property="ScaleTransform.ScaleY" Value="0.8"/>
@ -87,6 +87,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Animation Duration="0:0:3" Easing="BounceEaseInOut">
<KeyFrame Cue="48%">
<Setter Property="TranslateTransform.Y" Value="-100"/>
<Setter Property="Background" Value="Magenta"/>
</KeyFrame>
</Animation>
</Style.Animations>
@ -103,6 +104,35 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect6">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red"/>
</KeyFrame>
<KeyFrame Cue="15%">
<Setter Property="Background" Value="Yellow"/>
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background" Value="Green"/>
</KeyFrame>
<KeyFrame Cue="45%">
<Setter Property="Background" Value="Cyan"/>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue"/>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background" Value="Indigo"/>
</KeyFrame>
<KeyFrame Cue="90%">
<Setter Property="Background" Value="Violet"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
@ -120,6 +150,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Classes="Test Rect3"/>
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Red"/>
</WrapPanel>
</StackPanel>
</Grid>

2
samples/RenderDemo/Pages/ClippingPage.xaml

@ -8,7 +8,7 @@ xmlns="https://github.com/avaloniaui">
</Style>
<Style Selector="Border#clipChild">
<Style.Animations>
<Animation Duration="0:0:2" RepeatCount="Loop">
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360"/>
</KeyFrame>

1
src/Avalonia.Animation/Animatable.cs

@ -7,6 +7,7 @@ using System.Linq;
using System.Reactive.Linq;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{

199
src/Avalonia.Animation/Animation.cs

@ -7,68 +7,209 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings;
using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
/// <summary>
/// Tracks the progress of an animation.
/// </summary>
public class Animation : AvaloniaList<KeyFrame>, IAnimation
public class Animation : AvaloniaObject, IAnimation
{
/// <summary>
/// Defines the <see cref="Duration"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DurationProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_duration),
o => o._duration,
(o, v) => o._duration = v);
/// <summary>
/// Defines the <see cref="IterationCount"/> property.
/// </summary>
public static readonly DirectProperty<Animation, IterationCount> IterationCountProperty =
AvaloniaProperty.RegisterDirect<Animation, IterationCount>(
nameof(_iterationCount),
o => o._iterationCount,
(o, v) => o._iterationCount = v);
/// <summary>
/// Defines the <see cref="PlaybackDirection"/> property.
/// </summary>
public static readonly DirectProperty<Animation, PlaybackDirection> PlaybackDirectionProperty =
AvaloniaProperty.RegisterDirect<Animation, PlaybackDirection>(
nameof(_playbackDirection),
o => o._playbackDirection,
(o, v) => o._playbackDirection = v);
/// <summary>
/// Defines the <see cref="FillMode"/> property.
/// </summary>
public static readonly DirectProperty<Animation, FillMode> FillModeProperty =
AvaloniaProperty.RegisterDirect<Animation, FillMode>(
nameof(_fillMode),
o => o._fillMode,
(o, v) => o._fillMode = v);
/// <summary>
/// Defines the <see cref="Easing"/> property.
/// </summary>
public static readonly DirectProperty<Animation, Easing> EasingProperty =
AvaloniaProperty.RegisterDirect<Animation, Easing>(
nameof(_easing),
o => o._easing,
(o, v) => o._easing = v);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delay),
o => o._delay,
(o, v) => o._delay = v);
/// <summary>
/// Defines the <see cref="DelayBetweenIterations"/> property.
/// </summary>
public static readonly DirectProperty<Animation, TimeSpan> DelayBetweenIterationsProperty =
AvaloniaProperty.RegisterDirect<Animation, TimeSpan>(
nameof(_delayBetweenIterations),
o => o._delayBetweenIterations,
(o, v) => o._delayBetweenIterations = v);
/// <summary>
/// Defines the <see cref="SpeedRatio"/> property.
/// </summary>
public static readonly DirectProperty<Animation, double> SpeedRatioProperty =
AvaloniaProperty.RegisterDirect<Animation, double>(
nameof(_speedRatio),
o => o._speedRatio,
(o, v) => o._speedRatio = v,
defaultBindingMode: BindingMode.TwoWay);
private TimeSpan _duration;
private IterationCount _iterationCount = new IterationCount(1);
private PlaybackDirection _playbackDirection;
private FillMode _fillMode;
private Easing _easing = new LinearEasing();
private TimeSpan _delay = TimeSpan.Zero;
private TimeSpan _delayBetweenIterations = TimeSpan.Zero;
private double _speedRatio = 1d;
/// <summary>
/// Gets or sets the active time of this animation.
/// </summary>
public TimeSpan Duration { get; set; }
public TimeSpan Duration
{
get { return _duration; }
set { SetAndRaise(DurationProperty, ref _duration, value); }
}
/// <summary>
/// Gets or sets the repeat count for this animation.
/// </summary>
public RepeatCount RepeatCount { get; set; }
public IterationCount IterationCount
{
get { return _iterationCount; }
set { SetAndRaise(IterationCountProperty, ref _iterationCount, value); }
}
/// <summary>
/// Gets or sets the playback direction for this animation.
/// </summary>
public PlaybackDirection PlaybackDirection { get; set; }
public PlaybackDirection PlaybackDirection
{
get { return _playbackDirection; }
set { SetAndRaise(PlaybackDirectionProperty, ref _playbackDirection, value); }
}
/// <summary>
/// Gets or sets the value fill mode for this animation.
/// </summary>
public FillMode FillMode { get; set; }
/// </summary>
public FillMode FillMode
{
get { return _fillMode; }
set { SetAndRaise(FillModeProperty, ref _fillMode, value); }
}
/// <summary>
/// Gets or sets the easing function to be used for this animation.
/// </summary>
public Easing Easing { get; set; } = new LinearEasing();
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary>
public double SpeedRatio { get; set; } = 1d;
public Easing Easing
{
get { return _easing; }
set { SetAndRaise(EasingProperty, ref _easing, value); }
}
/// <summary>
/// Gets or sets the delay time for this animation.
/// Gets or sets the initial delay time for this animation.
/// </summary>
/// <remarks>
/// Describes a delay to be added before the animation starts, and optionally between
/// repeats of the animation if <see cref="DelayBetweenIterations"/> is set.
/// </remarks>
public TimeSpan Delay { get; set; }
public TimeSpan Delay
{
get { return _delay; }
set { SetAndRaise(DelayProperty, ref _delay, value); }
}
/// <summary>
/// Gets or sets a value indicating whether <see cref="Delay"/> will be applied between
/// iterations of the animation.
/// Gets or sets the delay time in between iterations.
/// </summary>
public TimeSpan DelayBetweenIterations
{
get { return _delayBetweenIterations; }
set { SetAndRaise(DelayBetweenIterationsProperty, ref _delayBetweenIterations, value); }
}
/// <summary>
/// Gets or sets the speed multiple for this animation.
/// </summary>
/// <remarks>
/// If this property is not set, then <see cref="Delay"/> will only be applied to the first
/// iteration of the animation.
/// </remarks>
public bool DelayBetweenIterations { get; set; }
public double SpeedRatio
{
get { return _speedRatio; }
set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
}
/// <summary>
/// Obsolete: Do not use this property, use <see cref="IterationCount"/> instead.
/// </summary>
/// <value></value>
[Obsolete("This property has been superceded by IterationCount.")]
public string RepeatCount
{
get { return IterationCount.ToString(); }
set
{
var val = value.ToUpper();
val = val.Replace("LOOP", "INFINITE");
val = val.Replace("NONE", "1");
IterationCount = IterationCount.Parse(val);
}
}
/// <summary>
/// Gets the children of the <see cref="Animation"/>.
/// </summary>
[Content]
public KeyFrames Children { get; } = new KeyFrames();
private readonly static List<(Func<AvaloniaProperty, bool> Condition, Type Animator)> Animators = new List<(Func<AvaloniaProperty, bool>, Type)>
{
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) )
( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
};
public static void RegisterAnimator<TAnimator>(Func<AvaloniaProperty, bool> condition)
@ -95,9 +236,9 @@ namespace Avalonia.Animation
var animatorKeyFrames = new List<AnimatorKeyFrame>();
var subscriptions = new List<IDisposable>();
foreach (var keyframe in this)
foreach (var keyframe in Children)
{
foreach (var setter in keyframe)
foreach (var setter in keyframe.Setters)
{
var handler = GetAnimatorType(setter.Property);
@ -179,7 +320,7 @@ namespace Avalonia.Animation
{
var run = new TaskCompletionSource<object>();
if (this.RepeatCount == RepeatCount.Loop)
if (this.IterationCount == IterationCount.Infinite)
run.SetException(new InvalidOperationException("Looping animations must not use the Run method."));
IDisposable subscriptions = null;

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

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Data;
using Avalonia.Reactive;
@ -15,78 +16,78 @@ namespace Avalonia.Animation
{
private T _lastInterpValue;
private T _firstKFValue;
private long _repeatCount;
private double _currentIteration;
private bool _isLooping;
private ulong? _iterationCount;
private ulong _currentIteration;
private bool _gotFirstKFValue;
private bool _iterationDelay;
private bool _playbackReversed;
private FillMode _fillMode;
private PlaybackDirection _animationDirection;
private Animator<T> _parent;
private PlaybackDirection _playbackDirection;
private Animator<T> _animator;
private Animation _animation;
private Animatable _targetControl;
private T _neutralValue;
private double _speedRatio;
private TimeSpan _delay;
private double _speedRatioConv;
private TimeSpan _initialDelay;
private TimeSpan _iterationDelay;
private TimeSpan _duration;
private Easings.Easing _easeFunc;
private Action _onCompleteAction;
private Func<double, T, T> _interpolator;
private IDisposable _timerSubscription;
private IDisposable _timerSub;
private readonly IClock _baseClock;
private IClock _clock;
public AnimationInstance(Animation animation, Animatable control, Animator<T> animator, IClock baseClock, Action OnComplete, Func<double, T, T> Interpolator)
{
if (animation.SpeedRatio <= 0)
throw new InvalidOperationException("Speed ratio cannot be negative or zero.");
_animator = animator;
_animation = animation;
_targetControl = control;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
_neutralValue = (T)_targetControl.GetValue(_animator.Property);
if (animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration cannot be negative or zero.");
FetchProperties();
}
_parent = animator;
_easeFunc = animation.Easing;
_targetControl = control;
_neutralValue = (T)_targetControl.GetValue(_parent.Property);
private void FetchProperties()
{
if (_animation.SpeedRatio < 0d)
throw new ArgumentOutOfRangeException("SpeedRatio value should not be negative.");
_speedRatio = animation.SpeedRatio;
if (_animation.Duration.TotalSeconds <= 0)
throw new InvalidOperationException("Duration value cannot be negative or zero.");
_delay = animation.Delay;
_duration = animation.Duration;
_iterationDelay = animation.DelayBetweenIterations;
_easeFunc = _animation.Easing;
switch (animation.RepeatCount.RepeatType)
{
case RepeatType.None:
_repeatCount = 1;
break;
case RepeatType.Loop:
_isLooping = true;
break;
case RepeatType.Repeat:
_repeatCount = (long)animation.RepeatCount.Value;
break;
}
_speedRatioConv = 1d / _animation.SpeedRatio;
_animationDirection = animation.PlaybackDirection;
_fillMode = animation.FillMode;
_onCompleteAction = OnComplete;
_interpolator = Interpolator;
_baseClock = baseClock;
}
_initialDelay = _animation.Delay;
_duration = _animation.Duration;
_iterationDelay = _animation.DelayBetweenIterations;
if (_animation.IterationCount.RepeatType == IterationType.Many)
_iterationCount = _animation.IterationCount.Value;
else
_iterationCount = null;
_playbackDirection = _animation.PlaybackDirection;
_fillMode = _animation.FillMode;
}
protected override void Unsubscribed()
{
//Animation may have been stopped before it has finished
// Animation may have been stopped before it has finished.
ApplyFinalFill();
_timerSubscription?.Dispose();
_timerSub?.Dispose();
_clock.PlayState = PlayState.Stop;
}
protected override void Subscribed()
{
_clock = new Clock(_baseClock);
_timerSubscription = _clock.Subscribe(Step);
_timerSub = _clock.Subscribe(Step);
}
public void Step(TimeSpan frameTick)
@ -104,7 +105,7 @@ namespace Avalonia.Animation
private void ApplyFinalFill()
{
if (_fillMode == FillMode.Forward || _fillMode == FillMode.Both)
_targetControl.SetValue(_parent.Property, _lastInterpValue, BindingPriority.LocalValue);
_targetControl.SetValue(_animator.Property, _lastInterpValue, BindingPriority.LocalValue);
}
private void DoComplete()
@ -130,7 +131,7 @@ namespace Avalonia.Animation
if (!_gotFirstKFValue)
{
_firstKFValue = (T)_parent.First().Value;
_firstKFValue = (T)_animator.First().Value;
_gotFirstKFValue = true;
}
}
@ -138,75 +139,81 @@ namespace Avalonia.Animation
private void InternalStep(TimeSpan time)
{
DoPlayStates();
var delayEndpoint = _delay;
var iterationEndpoint = delayEndpoint + _duration;
var iterationTime = time;
//determine if time is currently in the first iteration.
if (time >= TimeSpan.Zero & time <= iterationEndpoint)
{
_currentIteration = 1;
}
else if (time > iterationEndpoint)
{
//Subtract first iteration to properly get the subsequent iteration time
iterationTime -= iterationEndpoint;
FetchProperties();
if (!_iterationDelay & delayEndpoint > TimeSpan.Zero)
{
delayEndpoint = TimeSpan.Zero;
iterationEndpoint = _duration;
}
// Scale timebases according to speedratio.
var indexTime = time.Ticks;
var iterDuration = _duration.Ticks * _speedRatioConv;
var iterDelay = _iterationDelay.Ticks * _speedRatioConv;
var initDelay = _initialDelay.Ticks * _speedRatioConv;
//Calculate the current iteration number
_currentIteration = Math.Min(_repeatCount,(int)Math.Floor((double)((double)iterationTime.Ticks / iterationEndpoint.Ticks)) + 2);
if (indexTime > 0 & indexTime <= initDelay)
{
DoDelay();
}
else
{
return;
}
// Calculate timebases.
var iterationTime = iterDuration + iterDelay;
var opsTime = indexTime - initDelay;
var playbackTime = opsTime % iterationTime;
// Determine if the current iteration should have its normalized time inverted.
bool isCurIterReverse = _animationDirection == PlaybackDirection.Normal ? false :
_animationDirection == PlaybackDirection.Alternate ? (_currentIteration % 2 == 0) ? false : true :
_animationDirection == PlaybackDirection.AlternateReverse ? (_currentIteration % 2 == 0) ? true : false :
_animationDirection == PlaybackDirection.Reverse ? true : false;
if (!_isLooping)
{
var totalTime = _iterationDelay ? _repeatCount * ( _duration.Ticks + _delay.Ticks) : _repeatCount * _duration.Ticks + _delay.Ticks;
if (time.Ticks >= totalTime)
_currentIteration = (ulong)(opsTime / iterationTime);
// Stop animation when the current iteration is beyond the iteration count
// and snap the last iteration value to exact values.
if ((_currentIteration + 1) > _iterationCount)
{
var easedTime = _easeFunc.Ease(isCurIterReverse ? 0.0 : 1.0);
var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
DoComplete();
return;
}
}
iterationTime = TimeSpan.FromTicks((long)(iterationTime.Ticks % iterationEndpoint.Ticks));
if (delayEndpoint > TimeSpan.Zero & iterationTime < delayEndpoint)
{
DoDelay();
}
else
{
// Offset the delay time
iterationTime -= delayEndpoint;
iterationEndpoint -= delayEndpoint;
// Normalize time
var interpVal = (double)iterationTime.Ticks / iterationEndpoint.Ticks;
if (isCurIterReverse)
interpVal = 1 - interpVal;
// Ease and interpolate
var easedTime = _easeFunc.Ease(interpVal);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
if (playbackTime <= iterDuration)
{
// Normalize time for interpolation.
var normalizedTime = playbackTime / iterDuration;
// Check if normalized time needs to be reversed according to PlaybackDirection
switch (_playbackDirection)
{
case PlaybackDirection.Normal:
_playbackReversed = false;
break;
case PlaybackDirection.Reverse:
_playbackReversed = true;
break;
case PlaybackDirection.Alternate:
_playbackReversed = (_currentIteration % 2 == 0) ? false : true;
break;
case PlaybackDirection.AlternateReverse:
_playbackReversed = (_currentIteration % 2 == 0) ? true : false;
break;
default:
throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}");
}
if (_playbackReversed)
normalizedTime = 1 - normalizedTime;
// Ease and interpolate
var easedTime = _easeFunc.Ease(normalizedTime);
_lastInterpValue = _interpolator(easedTime, _neutralValue);
PublishNext(_lastInterpValue);
PublishNext(_lastInterpValue);
}
else if (playbackTime > iterDuration &
playbackTime <= iterationTime &
iterDelay > 0)
{
// The last iteration's trailing delay should be skipped.
if ((_currentIteration + 1) < _iterationCount)
DoDelay();
else
DoComplete();
}
}
}
}

1
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using Avalonia.Animation.Animators;
using Avalonia.Data;
using Avalonia.Reactive;

73
src/Avalonia.Animation/Animator`1.cs → src/Avalonia.Animation/Animators/Animator`1.cs

@ -10,10 +10,10 @@ using Avalonia.Collections;
using Avalonia.Data;
using Avalonia.Reactive;
namespace Avalonia.Animation
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Base class for KeyFrames objects
/// Base class for <see cref="Animator{T}"/> objects
/// </summary>
public abstract class Animator<T> : AvaloniaList<AnimatorKeyFrame>, IAnimator
{
@ -45,17 +45,10 @@ namespace Avalonia.Animation
return match.Subscribe(subject);
}
/// <summary>
/// Get the nearest pair of cue-time ordered keyframes
/// according to the given time parameter that is relative to the
/// total animation time and the normalized intra-keyframe pair time
/// (i.e., the normalized time between the selected keyframes, relative to the
/// time parameter).
/// </summary>
/// <param name="animationTime">The time parameter, relative to the total animation time</param>
protected (double IntraKFTime, KeyFramePair<T> KFPair) GetKFPairAndIntraKFTime(double animationTime)
protected T InterpolationHandler(double animationTime, T neutralValue)
{
AnimatorKeyFrame firstKeyframe, lastKeyframe;
int kvCount = _convertedKeyframes.Count;
if (kvCount > 2)
{
@ -84,38 +77,31 @@ namespace Avalonia.Animation
double t0 = firstKeyframe.Cue.CueValue;
double t1 = lastKeyframe.Cue.CueValue;
var intraframeTime = (animationTime - t0) / (t1 - t0);
var firstFrameData = (firstKeyframe.GetTypedValue<T>(), firstKeyframe.isNeutral);
var lastFrameData = (lastKeyframe.GetTypedValue<T>(), lastKeyframe.isNeutral);
return (intraframeTime, new KeyFramePair<T>(firstFrameData, lastFrameData));
double progress = (animationTime - t0) / (t1 - t0);
T oldValue, newValue;
if (firstKeyframe.isNeutral)
oldValue = neutralValue;
else
oldValue = (T)firstKeyframe.Value;
if (lastKeyframe.isNeutral)
newValue = neutralValue;
else
newValue = (T)lastKeyframe.Value;
return Interpolate(progress, oldValue, newValue);
}
private int FindClosestBeforeKeyFrame(double time)
{
int FindClosestBeforeKeyFrame(int startIndex, int length)
{
if (length == 0 || length == 1)
{
return startIndex;
}
for (int i = 0; i < _convertedKeyframes.Count; i++)
if (_convertedKeyframes[i].Cue.CueValue > time)
return i - 1;
int middle = startIndex + (length / 2);
if (_convertedKeyframes[middle].Cue.CueValue < time)
{
return FindClosestBeforeKeyFrame(middle, length - middle);
}
else if (_convertedKeyframes[middle].Cue.CueValue > time)
{
return FindClosestBeforeKeyFrame(startIndex, middle - startIndex);
}
else
{
return middle;
}
}
return FindClosestBeforeKeyFrame(0, _convertedKeyframes.Count);
throw new Exception("Index time is out of keyframe time range.");
}
/// <summary>
@ -129,18 +115,15 @@ namespace Avalonia.Animation
this,
clock ?? control.Clock ?? Clock.GlobalClock,
onComplete,
DoInterpolation);
InterpolationHandler);
return control.Bind<T>((AvaloniaProperty<T>)Property, instance, BindingPriority.Animation);
}
/// <summary>
/// Interpolates a value given the desired time.
/// Interpolates in-between two key values given the desired progress time.
/// </summary>
protected abstract T DoInterpolation(double time, T neutralValue);
public abstract T Interpolate(double progress, T oldValue, T newValue);
/// <summary>
/// Verifies, converts and sorts keyframe values according to this class's target type.
/// </summary>
private void VerifyConvertKeyFrames()
{
foreach (AnimatorKeyFrame keyframe in this)
@ -188,4 +171,4 @@ namespace Avalonia.Animation
}
}
}
}
}

21
src/Avalonia.Animation/Animators/BoolAnimator.cs

@ -0,0 +1,21 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="bool"/> properties.
/// </summary>
public class BoolAnimator : Animator<bool>
{
/// <inheritdocs/>
public override bool Interpolate(double progress, bool oldValue, bool newValue)
{
if(progress >= 1d)
return newValue;
if(progress >= 0)
return oldValue;
return oldValue;
}
}
}

24
src/Avalonia.Animation/Animators/ByteAnimator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="byte"/> properties.
/// </summary>
public class ByteAnimator : Animator<byte>
{
const double maxVal = (double)byte.MaxValue;
/// <inheritdocs/>
public override byte Interpolate(double progress, byte oldValue, byte newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (byte)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

17
src/Avalonia.Animation/Animators/DecimalAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="decimal"/> properties.
/// </summary>
public class DecimalAnimator : Animator<decimal>
{
/// <inheritdocs/>
public override decimal Interpolate(double progress, decimal oldValue, decimal newValue)
{
return ((newValue - oldValue) * (decimal)progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/DoubleAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.
/// </summary>
public class DoubleAnimator : Animator<double>
{
/// <inheritdocs/>
public override double Interpolate(double progress, double oldValue, double newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

17
src/Avalonia.Animation/Animators/FloatAnimator.cs

@ -0,0 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="float"/> properties.
/// </summary>
public class FloatAnimator : Animator<float>
{
/// <inheritdocs/>
public override float Interpolate(double progress, float oldValue, float newValue)
{
return (float)(((newValue - oldValue) * progress) + oldValue);
}
}
}

24
src/Avalonia.Animation/Animators/Int16Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int16"/> properties.
/// </summary>
public class Int16Animator : Animator<Int16>
{
const double maxVal = (double)Int16.MaxValue;
/// <inheritdocs/>
public override Int16 Interpolate(double progress, Int16 oldValue, Int16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int32Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int32"/> properties.
/// </summary>
public class Int32Animator : Animator<Int32>
{
const double maxVal = (double)Int32.MaxValue;
/// <inheritdocs/>
public override Int32 Interpolate(double progress, Int32 oldValue, Int32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/Int64Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Int64"/> properties.
/// </summary>
public class Int64Animator : Animator<Int64>
{
const double maxVal = (double)Int64.MaxValue;
/// <inheritdocs/>
public override Int64 Interpolate(double progress, Int64 oldValue, Int64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (Int64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt16Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt16"/> properties.
/// </summary>
public class UInt16Animator : Animator<UInt16>
{
const double maxVal = (double)UInt16.MaxValue;
/// <inheritdocs/>
public override UInt16 Interpolate(double progress, UInt16 oldValue, UInt16 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt16)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt32Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt32"/> properties.
/// </summary>
public class UInt32Animator : Animator<UInt32>
{
const double maxVal = (double)UInt32.MaxValue;
/// <inheritdocs/>
public override UInt32 Interpolate(double progress, UInt32 oldValue, UInt32 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt32)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

24
src/Avalonia.Animation/Animators/UInt64Animator.cs

@ -0,0 +1,24 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="UInt64"/> properties.
/// </summary>
public class UInt64Animator : Animator<UInt64>
{
const double maxVal = (double)UInt64.MaxValue;
/// <inheritdocs/>
public override UInt64 Interpolate(double progress, UInt64 oldValue, UInt64 newValue)
{
var normOV = oldValue / maxVal;
var normNV = newValue / maxVal;
var deltaV = normNV - normOV;
return (UInt64)Math.Round(maxVal * ((deltaV * progress) + normOV));
}
}
}

3
src/Avalonia.Animation/DisposeAnimationInstanceSubject.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Animation.Animators;
using Avalonia.Animation.Utils;
using Avalonia.Collections;
using Avalonia.Data;
@ -33,8 +34,6 @@ namespace Avalonia.Animation
this._onComplete = onComplete;
this._clock = clock;
}
public void Dispose()
{
_lastInstance?.Dispose();

35
src/Avalonia.Animation/DoubleAnimator.cs

@ -1,35 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Animator that handles <see cref="double"/> properties.
/// </summary>
public class DoubleAnimator : Animator<double>
{
/// <inheritdocs/>
protected override double DoInterpolation(double t, double neutralValue)
{
var pair = GetKFPairAndIntraKFTime(t);
double y0, y1;
var firstKF = pair.KFPair.FirstKeyFrame;
var secondKF = pair.KFPair.SecondKeyFrame;
if (firstKF.isNeutral)
y0 = neutralValue;
else
y0 = firstKF.TargetValue;
if (secondKF.isNeutral)
y1 = neutralValue;
else
y1 = secondKF.TargetValue;
// Do linear parametric interpolation
return y0 + (pair.IntraKFTime) * (y1 - y0);
}
}
}

0
src/Avalonia.Animation/IEasing.cs → src/Avalonia.Animation/Easing/IEasing.cs

176
src/Avalonia.Animation/IterationCount.cs

@ -0,0 +1,176 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Animation
{
/// <summary>
/// Defines the valid modes for a <see cref="IterationCount"/>.
/// </summary>
public enum IterationType
{
Many,
Infinite
}
/// <summary>
/// Determines the number of iterations of an animation.
/// Also defines its repeat behavior.
/// </summary>
[TypeConverter(typeof(IterationCountTypeConverter))]
public struct IterationCount : IEquatable<IterationCount>
{
private readonly IterationType _type;
private readonly ulong _value;
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The number of iterations of an animation.</param>
public IterationCount(ulong value)
: this(value, IterationType.Many)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="IterationCount"/> struct.
/// </summary>
/// <param name="value">The size of the IterationCount.</param>
/// <param name="type">The unit of the IterationCount.</param>
public IterationCount(ulong value, IterationType type)
{
if (type > IterationType.Infinite)
{
throw new ArgumentException("Invalid value", nameof(type));
}
_type = type;
_value = value;
}
/// <summary>
/// Gets an instance of <see cref="IterationCount"/> that indicates that an animation
/// should repeat forever.
/// </summary>
public static IterationCount Infinite => new IterationCount(0, IterationType.Infinite);
/// <summary>
/// Gets the unit of the <see cref="IterationCount"/>.
/// </summary>
public IterationType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
/// </summary>
public bool IsInfinite => _type == IterationType.Infinite;
/// <summary>
/// Gets the number of repeat iterations.
/// </summary>
public ulong Value => _value;
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="a">The first IterationCount.</param>
/// <param name="b">The second IterationCount.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public static bool operator ==(IterationCount a, IterationCount b)
{
return (a.IsInfinite && b.IsInfinite)
|| (a._value == b._value && a._type == b._type);
}
/// <summary>
/// Compares two IterationCount structures for inequality.
/// </summary>
/// <param name="rc1">The first IterationCount.</param>
/// <param name="rc2">The first IterationCount.</param>
/// <returns>True if the structures are unequal, otherwise false.</returns>
public static bool operator !=(IterationCount rc1, IterationCount rc2)
{
return !(rc1 == rc2);
}
/// <summary>
/// Determines whether the <see cref="IterationCount"/> is equal to the specified object.
/// </summary>
/// <param name="o">The object with which to test equality.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object o)
{
if (o == null)
{
return false;
}
if (!(o is IterationCount))
{
return false;
}
return this == (IterationCount)o;
}
/// <summary>
/// Compares two IterationCount structures for equality.
/// </summary>
/// <param name="IterationCount">The structure with which to test equality.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public bool Equals(IterationCount IterationCount)
{
return this == IterationCount;
}
/// <summary>
/// Gets a hash code for the IterationCount.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
/// <summary>
/// Gets a string representation of the <see cref="IterationCount"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
if (IsInfinite)
{
return "Infinite";
}
string s = _value.ToString();
return s;
}
/// <summary>
/// Parses a string to return a <see cref="IterationCount"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="IterationCount"/>.</returns>
public static IterationCount Parse(string s)
{
s = s.ToUpperInvariant().Trim();
if (s.EndsWith("INFINITE"))
{
return Infinite;
}
else
{
if (s.StartsWith("-"))
throw new InvalidCastException("IterationCount can't be a negative number.");
var value = ulong.Parse(s, CultureInfo.InvariantCulture);
return new IterationCount(value);
}
}
}
}

4
src/Avalonia.Animation/RepeatCountTypeConverter.cs → src/Avalonia.Animation/IterationCountTypeConverter.cs

@ -7,7 +7,7 @@ using System.Globalization;
namespace Avalonia.Animation
{
public class RepeatCountTypeConverter : TypeConverter
public class IterationCountTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
@ -16,7 +16,7 @@ namespace Avalonia.Animation
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return RepeatCount.Parse((string)value);
return IterationCount.Parse((string)value);
}
}
}

15
src/Avalonia.Animation/KeyFrame.cs

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Metadata;
namespace Avalonia.Animation
{
@ -17,7 +18,7 @@ namespace Avalonia.Animation
/// Stores data regarding a specific key
/// point and value in an animation.
/// </summary>
public class KeyFrame : AvaloniaList<IAnimationSetter>
public class KeyFrame : AvaloniaObject
{
private TimeSpan _ktimeSpan;
private Cue _kCue;
@ -26,13 +27,11 @@ namespace Avalonia.Animation
{
}
public KeyFrame(IEnumerable<IAnimationSetter> items) : base(items)
{
}
public KeyFrame(params IAnimationSetter[] items) : base(items)
{
}
/// <summary>
/// Gets the setters of <see cref="KeyFrame"/>.
/// </summary>
[Content]
public AvaloniaList<IAnimationSetter> Setters { get; } = new AvaloniaList<IAnimationSetter>();
internal KeyFrameTimingMode TimingMode { get; private set; }

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

@ -1,33 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia.Animation
{
/// <summary>
/// Represents a pair of keyframe, usually the
/// Start and End keyframes of a <see cref="Animator{T}"/> object.
/// </summary>
public struct KeyFramePair<T>
{
/// <summary>
/// Initializes this <see cref="KeyFramePair{T}"/>
/// </summary>
/// <param name="FirstKeyFrame"></param>
/// <param name="LastKeyFrame"></param>
public KeyFramePair((T TargetValue, bool isNeutral) FirstKeyFrame, (T TargetValue, bool isNeutral) LastKeyFrame) : this()
{
this.FirstKeyFrame = FirstKeyFrame;
this.SecondKeyFrame = LastKeyFrame;
}
/// <summary>
/// First <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) FirstKeyFrame { get; }
/// <summary>
/// Second <see cref="KeyFrame"/> object.
/// </summary>
public (T TargetValue, bool isNeutral) SecondKeyFrame { get; }
}
}

33
src/Avalonia.Animation/KeyFrames.cs

@ -0,0 +1,33 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using Avalonia.Collections;
namespace Avalonia.Animation
{
/// <summary>
/// A collection of <see cref="KeyFrame"/>s.
/// </summary>
public class KeyFrames : AvaloniaList<KeyFrame>
{
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
public KeyFrames()
{
ResetBehavior = ResetBehavior.Remove;
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyFrames"/> class.
/// </summary>
/// <param name="items">The initial items in the collection.</param>
public KeyFrames(IEnumerable<KeyFrame> items)
: base(items)
{
ResetBehavior = ResetBehavior.Remove;
}
}
}

3
src/Avalonia.Animation/Properties/AssemblyInfo.cs

@ -5,4 +5,5 @@ using Avalonia.Metadata;
using System.Reflection;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Easings")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Animation.Animators")]

199
src/Avalonia.Animation/RepeatCount.cs

@ -1,199 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.ComponentModel;
using System.Globalization;
namespace Avalonia.Animation
{
/// <summary>
/// Defines the valid modes for a <see cref="RepeatCount"/>.
/// </summary>
public enum RepeatType
{
None,
Repeat,
Loop
}
/// <summary>
/// Determines the number of iterations of an animation.
/// Also defines its repeat behavior.
/// </summary>
[TypeConverter(typeof(RepeatCountTypeConverter))]
public struct RepeatCount : IEquatable<RepeatCount>
{
private readonly RepeatType _type;
private readonly ulong _value;
/// <summary>
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
/// </summary>
/// <param name="value">The number of iterations of an animation.</param>
public RepeatCount(ulong value)
: this(value, RepeatType.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RepeatCount"/> struct.
/// </summary>
/// <param name="value">The size of the RepeatCount.</param>
/// <param name="type">The unit of the RepeatCount.</param>
public RepeatCount(ulong value, RepeatType type)
{
if (type < RepeatType.None || type > RepeatType.Loop)
{
throw new ArgumentException("Invalid value", "type");
}
_type = type;
_value = value;
}
/// <summary>
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
/// should repeat forever.
/// </summary>
public static RepeatCount Loop => new RepeatCount(0, RepeatType.Loop);
/// <summary>
/// Gets an instance of <see cref="RepeatCount"/> that indicates that an animation
/// should not repeat.
/// </summary>
public static RepeatCount None => new RepeatCount(0, RepeatType.None);
/// <summary>
/// Gets the unit of the <see cref="RepeatCount"/>.
/// </summary>
public RepeatType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to loop.
/// </summary>
public bool IsLoop => _type == RepeatType.Loop;
/// <summary>
/// Gets a value that indicates whether the <see cref="RepeatCount"/> is set to not repeat.
/// </summary>
public bool IsNone => _type == RepeatType.None;
/// <summary>
/// Gets the number of repeat iterations.
/// </summary>
public ulong Value => _value;
/// <summary>
/// Compares two RepeatCount structures for equality.
/// </summary>
/// <param name="a">The first RepeatCount.</param>
/// <param name="b">The second RepeatCount.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public static bool operator ==(RepeatCount a, RepeatCount b)
{
return (a.IsNone && b.IsNone) && (a.IsLoop && b.IsLoop)
|| (a._value == b._value && a._type == b._type);
}
/// <summary>
/// Compares two RepeatCount structures for inequality.
/// </summary>
/// <param name="rc1">The first RepeatCount.</param>
/// <param name="rc2">The first RepeatCount.</param>
/// <returns>True if the structures are unequal, otherwise false.</returns>
public static bool operator !=(RepeatCount rc1, RepeatCount rc2)
{
return !(rc1 == rc2);
}
/// <summary>
/// Determines whether the <see cref="RepeatCount"/> is equal to the specified object.
/// </summary>
/// <param name="o">The object with which to test equality.</param>
/// <returns>True if the objects are equal, otherwise false.</returns>
public override bool Equals(object o)
{
if (o == null)
{
return false;
}
if (!(o is RepeatCount))
{
return false;
}
return this == (RepeatCount)o;
}
/// <summary>
/// Compares two RepeatCount structures for equality.
/// </summary>
/// <param name="RepeatCount">The structure with which to test equality.</param>
/// <returns>True if the structures are equal, otherwise false.</returns>
public bool Equals(RepeatCount RepeatCount)
{
return this == RepeatCount;
}
/// <summary>
/// Gets a hash code for the RepeatCount.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
/// <summary>
/// Gets a string representation of the <see cref="RepeatCount"/>.
/// </summary>
/// <returns>The string representation.</returns>
public override string ToString()
{
if (IsLoop)
{
return "Auto";
}
else if (IsNone)
{
return "None";
}
string s = _value.ToString();
return s;
}
/// <summary>
/// Parses a string to return a <see cref="RepeatCount"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="RepeatCount"/>.</returns>
public static RepeatCount Parse(string s)
{
s = s.ToUpperInvariant().Trim();
if (s == "NONE")
{
return None;
}
else if (s.EndsWith("LOOP"))
{
return Loop;
}
else
{
if(s.StartsWith("-"))
throw new InvalidCastException("RepeatCount can't be a negative number.");
var value = ulong.Parse(s, CultureInfo.InvariantCulture);
if (value == 1)
return None;
return new RepeatCount(value);
}
}
}
}

7
src/Avalonia.Animation/DoubleTransition.cs → src/Avalonia.Animation/Transitions/DoubleTransition.cs

@ -14,9 +14,12 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public override IObservable<double> DoTransition(IObservable<double> progress, double oldValue, double newValue)
{
var delta = newValue - oldValue;
return progress
.Select(p => Easing.Ease(p) * delta + oldValue);
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

0
src/Avalonia.Animation/FloatTransition.cs → src/Avalonia.Animation/Transitions/FloatTransition.cs

0
src/Avalonia.Animation/IntegerTransition.cs → src/Avalonia.Animation/Transitions/IntegerTransition.cs

4
src/Avalonia.Base/Collections/AvaloniaDictionary.cs

@ -148,7 +148,7 @@ namespace Avalonia.Collections
{
if (_inner.TryGetValue(key, out TValue value))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));
if (CollectionChanged != null)
@ -209,7 +209,7 @@ namespace Avalonia.Collections
private void NotifyAdd(TKey key, TValue value)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]"));

2
src/Avalonia.Base/Collections/AvaloniaList.cs

@ -511,7 +511,7 @@ namespace Avalonia.Collections
/// </summary>
private void NotifyCountChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count)));
}
/// <summary>

1
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -147,6 +147,7 @@ namespace Avalonia.Data.Core
private void StopListening()
{
StopListeningCore();
_listening = false;
}
private BindingNotification TargetNullNotification()

1
src/Avalonia.Base/Platform/IAssetLoader.cs

@ -65,6 +65,7 @@ namespace Avalonia.Platform
/// Gets all assets of a folder and subfolders that match specified uri.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">The base URI.</param>
/// <returns>All matching assets as a tuple of the absolute path to the asset and the assembly containing the asset</returns>
IEnumerable<Uri> GetAssets(Uri uri, Uri baseUri);
}

29
src/Avalonia.Base/Threading/ThreadSafeObjectPool.cs

@ -0,0 +1,29 @@
using System.Collections.Generic;
namespace Avalonia.Threading
{
public class ThreadSafeObjectPool<T> where T : class, new()
{
private Stack<T> _stack = new Stack<T>();
private object _lock = new object();
public static ThreadSafeObjectPool<T> Default { get; } = new ThreadSafeObjectPool<T>();
public T Get()
{
lock (_lock)
{
if(_stack.Count == 0)
return new T();
return _stack.Pop();
}
}
public void Return(T obj)
{
lock (_stack)
{
_stack.Push(obj);
}
}
}
}

2
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -52,7 +52,7 @@ namespace Avalonia.Controls.Primitives
internal Calendar Owner { get; set; }
internal CalendarDayButton CurrentButton { get; set; }
public static StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
public static readonly StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
public IBrush HeaderBackground
{
get { return GetValue(HeaderBackgroundProperty); }

2
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -954,7 +954,7 @@ namespace Avalonia.Controls
}
else
{
var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", "SelectedDate value is not valid."), text);
var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException(nameof(text), "SelectedDate value is not valid."), text);
OnDateValidationError(dateValidationError);
if (dateValidationError.ThrowException)

4
src/Avalonia.Controls/GridLength.cs

@ -56,12 +56,12 @@ namespace Avalonia.Controls
{
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
{
throw new ArgumentException("Invalid value", "value");
throw new ArgumentException("Invalid value", nameof(value));
}
if (type < GridUnitType.Auto || type > GridUnitType.Star)
{
throw new ArgumentException("Invalid value", "type");
throw new ArgumentException("Invalid value", nameof(type));
}
_type = type;

2
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -487,7 +487,7 @@ namespace Avalonia.Controls
{
if (e == null)
{
throw new ArgumentNullException("e");
throw new ArgumentNullException(nameof(e));
}
var handler = Spinned;

1
src/Avalonia.Controls/Panel.cs

@ -31,7 +31,6 @@ namespace Avalonia.Controls
static Panel()
{
AffectsRender<Panel>(BackgroundProperty);
ClipToBoundsProperty.OverrideDefaultValue<Panel>(true);
}
/// <summary>

11
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -517,7 +517,7 @@ namespace Avalonia.Controls.Presenters
if (index >= 0 && index < ItemCount)
{
if (index < FirstIndex)
if (index <= FirstIndex)
{
newOffset = index;
}
@ -525,10 +525,6 @@ namespace Avalonia.Controls.Presenters
{
newOffset = index - Math.Ceiling(ViewportValue - 1);
}
else if (OffsetValue + ViewportValue >= ItemCount)
{
newOffset = OffsetValue - 1;
}
if (newOffset != -1)
{
@ -546,6 +542,11 @@ namespace Avalonia.Controls.Presenters
{
layoutManager.ExecuteLayoutPass();
if (newOffset != -1 && newOffset != OffsetValue)
{
OffsetValue = newOffset;
}
if (panel.ScrollDirection == Orientation.Vertical)
{
if (container.Bounds.Y < panel.Bounds.Y || container.Bounds.Bottom > panel.Bounds.Bottom)

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

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives
// TODO: Need to track position of adorned elements and move the adorner if they move.
public class AdornerLayer : Panel, ICustomSimpleHitTest
{
public static AttachedProperty<Visual> AdornedElementProperty =
public static readonly AttachedProperty<Visual> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual>("AdornedElement");
private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =

2
src/Avalonia.Controls/Viewbox.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls
/// <summary>
/// The stretch property
/// </summary>
public static AvaloniaProperty<Stretch> StretchProperty =
public static readonly AvaloniaProperty<Stretch> StretchProperty =
AvaloniaProperty.RegisterDirect<Viewbox, Stretch>(nameof(Stretch),
v => v.Stretch, (c, v) => c.Stretch = v, Stretch.Uniform);

2
src/Avalonia.Input/Cursors.cs

@ -47,7 +47,7 @@ namespace Avalonia.Input
public class Cursor
{
public static Cursor Default = new Cursor(StandardCursorType.Arrow);
public static readonly Cursor Default = new Cursor(StandardCursorType.Arrow);
internal Cursor(IPlatformHandle platformCursor)
{

6
src/Avalonia.Input/DataFormats.cs

@ -5,11 +5,11 @@
/// <summary>
/// Dataformat for plaintext
/// </summary>
public static string Text = nameof(Text);
public static readonly string Text = nameof(Text);
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
public static string FileNames = nameof(FileNames);
public static readonly string FileNames = nameof(FileNames);
}
}
}

10
src/Avalonia.Input/DragDrop.cs

@ -9,21 +9,21 @@ namespace Avalonia.Input
/// <summary>
/// Event which is raised, when a drag-and-drop operation enters the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation should complete over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static readonly RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
public static readonly AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
/// <summary>
/// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation.

2
src/Avalonia.Native/PlatformThreadingInterface.cs

@ -68,8 +68,6 @@ namespace Avalonia.Native
lock (l)
{
cancellation?.Cancel();
cancellation?.Dispose();
cancellation = null;
}
});
try

6
src/Avalonia.OpenGL/EglContext.cs

@ -31,14 +31,14 @@ namespace Avalonia.OpenGL
public void MakeCurrent()
{
if (!_egl.MakeCurrent(_disp.Handle, IntPtr.Zero, IntPtr.Zero, Context))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public void MakeCurrent(EglSurface surface)
{
var surf = ((EglSurface)surface)?.DangerousGetHandle() ?? OffscreenSurface;
var surf = surface?.DangerousGetHandle() ?? OffscreenSurface;
if (!_egl.MakeCurrent(_disp.Handle, surf, surf, Context))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
}
}

17
src/Avalonia.OpenGL/EglDisplay.cs

@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
using static Avalonia.OpenGL.EglConsts;
@ -34,11 +33,11 @@ namespace Avalonia.OpenGL
if (_display == IntPtr.Zero)
_display = _egl.GetDisplay(IntPtr.Zero);
if(_display == IntPtr.Zero)
throw new OpenGlException("eglGetDisplay failed");
if (_display == IntPtr.Zero)
throw OpenGlException.GetFormattedException("eglGetDisplay", _egl);
if (!_egl.Initialize(_display, out var major, out var minor))
throw new OpenGlException("eglInitialize failed");
throw OpenGlException.GetFormattedException("eglInitialize", _egl);
foreach (var cfg in new[]
{
@ -113,7 +112,7 @@ namespace Avalonia.OpenGL
var shareCtx = (EglContext)share;
var ctx = _egl.CreateContext(_display, _config, shareCtx?.Context ?? IntPtr.Zero, _contextAttributes);
if (ctx == IntPtr.Zero)
throw new OpenGlException("eglCreateContext failed");
throw OpenGlException.GetFormattedException("eglCreateContext", _egl);
var surf = _egl.CreatePBufferSurface(_display, _config, new[]
{
EGL_WIDTH, 1,
@ -121,7 +120,7 @@ namespace Avalonia.OpenGL
EGL_NONE
});
if (surf == IntPtr.Zero)
throw new OpenGlException("eglCreatePbufferSurface failed");
throw OpenGlException.GetFormattedException("eglCreatePBufferSurface", _egl);
var rv = new EglContext(this, _egl, ctx, surf);
rv.MakeCurrent(null);
return rv;
@ -130,14 +129,14 @@ namespace Avalonia.OpenGL
public void ClearContext()
{
if (!_egl.MakeCurrent(_display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero))
throw new OpenGlException("eglMakeCurrent failed");
throw OpenGlException.GetFormattedException("eglMakeCurrent", _egl);
}
public EglSurface CreateWindowSurface(IntPtr window)
{
var s = _egl.CreateWindowSurface(_display, _config, window, new[] {EGL_NONE, EGL_NONE});
if (s == IntPtr.Zero)
throw new OpenGlException("eglCreateWindowSurface failed");
throw OpenGlException.GetFormattedException("eglCreateWindowSurface", _egl);
return new EglSurface(this, _egl, s);
}

21
src/Avalonia.OpenGL/EglErrors.cs

@ -0,0 +1,21 @@
namespace Avalonia.OpenGL
{
public enum EglErrors
{
EGL_SUCCESS = EglConsts.EGL_SUCCESS,
EGL_NOT_INITIALIZED = EglConsts.EGL_NOT_INITIALIZED,
EGL_BAD_ACCESS = EglConsts.EGL_BAD_ACCESS,
EGL_BAD_ALLOC = EglConsts.EGL_BAD_ALLOC,
EGL_BAD_ATTRIBUTE = EglConsts.EGL_BAD_ATTRIBUTE,
EGL_BAD_CONTEXT = EglConsts.EGL_BAD_CONTEXT,
EGL_BAD_CONFIG = EglConsts.EGL_BAD_CONFIG,
EGL_BAD_CURRENT_SURFACE = EglConsts.EGL_BAD_CURRENT_SURFACE,
EGL_BAD_DISPLAY = EglConsts.EGL_BAD_DISPLAY,
EGL_BAD_SURFACE = EglConsts.EGL_BAD_SURFACE,
EGL_BAD_MATCH = EglConsts.EGL_BAD_MATCH,
EGL_BAD_PARAMETER = EglConsts.EGL_BAD_PARAMETER,
EGL_BAD_NATIVE_PIXMAP = EglConsts.EGL_BAD_NATIVE_PIXMAP,
EGL_BAD_NATIVE_WINDOW = EglConsts.EGL_BAD_NATIVE_WINDOW,
EGL_CONTEXT_LOST = EglConsts.EGL_CONTEXT_LOST
}
}

7
src/Avalonia.OpenGL/EglInterface.cs

@ -1,7 +1,6 @@
using System;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using static Avalonia.OpenGL.EglConsts;
namespace Avalonia.OpenGL
{
@ -32,8 +31,12 @@ namespace Avalonia.OpenGL
var lib = dyn.LoadLibrary(library);
return (s, o) => dyn.GetProcAddress(lib, s, o);
}
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int EglGetError();
[EntryPoint("eglGetError")]
public EglGetError GetError { get; }
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
[EntryPoint("eglGetDisplay")]
public EglGetDisplay GetDisplay { get; }

3
src/Avalonia.OpenGL/GlConsts.cs

@ -456,12 +456,15 @@ namespace Avalonia.OpenGL
public const int GL_RENDERER = 0x1F01;
public const int GL_VERSION = 0x1F02;
public const int GL_EXTENSIONS = 0x1F03;
public const int GL_NO_ERROR = 0;
public const int GL_INVALID_ENUM = 0x0500;
public const int GL_INVALID_VALUE = 0x0501;
public const int GL_INVALID_OPERATION = 0x0502;
public const int GL_STACK_OVERFLOW = 0x0503;
public const int GL_STACK_UNDERFLOW = 0x0504;
public const int GL_OUT_OF_MEMORY = 0x0505;
public const int GL_INVALID_FRAMEBUFFER_OPERATION = 0x0506;
public const int GL_CONTEXT_LOST = 0x0507;
public const int GL_CURRENT_BIT = 0x00000001;
public const int GL_POINT_BIT = 0x00000002;
public const int GL_LINE_BIT = 0x00000004;

15
src/Avalonia.OpenGL/GlErrors.cs

@ -0,0 +1,15 @@
namespace Avalonia.OpenGL
{
public enum GlErrors
{
GL_NO_ERROR = GlConsts.GL_NO_ERROR,
GL_INVALID_ENUM = GlConsts.GL_INVALID_ENUM,
GL_INVALID_VALUE = GlConsts.GL_INVALID_VALUE,
GL_INVALID_OPERATION = GlConsts.GL_INVALID_OPERATION,
GL_INVALID_FRAMEBUFFER_OPERATION = GlConsts.GL_INVALID_FRAMEBUFFER_OPERATION,
GL_STACK_OVERFLOW = GlConsts.GL_STACK_OVERFLOW,
GL_STACK_UNDERFLOW = GlConsts.GL_STACK_UNDERFLOW,
GL_OUT_OF_MEMORY = GlConsts.GL_OUT_OF_MEMORY,
GL_CONTEXT_LOST = GlConsts.GL_CONTEXT_LOST
}
}

5
src/Avalonia.OpenGL/GlInterface.cs

@ -19,7 +19,10 @@ namespace Avalonia.OpenGL
public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int GlGetError();
[EntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate void GlClearStencil(int s);
[EntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; }

30
src/Avalonia.OpenGL/OpenGlException.cs

@ -4,9 +4,39 @@ namespace Avalonia.OpenGL
{
public class OpenGlException : Exception
{
public int? ErrorCode { get; private set; }
public OpenGlException(string message) : base(message)
{
}
private OpenGlException(string message, int errorCode) : base(message)
{
ErrorCode = errorCode;
}
public static OpenGlException GetFormattedException(string funcName, EglInterface egl)
{
return GetFormattedException(typeof(EglErrors), funcName, egl.GetError());
}
public static OpenGlException GetFormattedException(string funcName, GlInterface gl)
{
return GetFormattedException(typeof(GlErrors), funcName, gl.GetError());
}
private static OpenGlException GetFormattedException(Type consts, string funcName, int errorCode)
{
try
{
string errorName = Enum.GetName(consts, errorCode);
return new OpenGlException(
$"{funcName} failed with error {errorName} (0x{errorCode.ToString("X")})", errorCode);
}
catch (ArgumentException)
{
return new OpenGlException($"{funcName} failed with error 0x{errorCode.ToString("X")}", errorCode);
}
}
}
}

20
src/Avalonia.Styling/Styling/ChildSelector.cs

@ -24,6 +24,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -43,11 +46,24 @@ namespace Avalonia.Styling
if (controlParent != null)
{
return _parent.Match((IStyleable)controlParent, subscribe);
var parentMatch = _parent.Match((IStyleable)controlParent, subscribe);
if (parentMatch.Result == SelectorMatchResult.Sometimes)
{
return parentMatch;
}
else if (parentMatch.IsMatch)
{
return SelectorMatch.AlwaysThisInstance;
}
else
{
return SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

16
src/Avalonia.Styling/Styling/DescendentSelector.cs

@ -22,6 +22,9 @@ namespace Avalonia.Styling
_parent = parent;
}
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override bool InTemplate => _parent.InTemplate;
@ -51,16 +54,13 @@ namespace Avalonia.Styling
{
var match = _parent.Match((IStyleable)c, subscribe);
if (match.ImmediateResult != null)
if (match.Result == SelectorMatchResult.Sometimes)
{
if (match.ImmediateResult == true)
{
return SelectorMatch.True;
}
descendantMatches.Add(match.Activator);
}
else
else if (match.IsMatch)
{
descendantMatches.Add(match.ObservableResult);
return SelectorMatch.AlwaysThisInstance;
}
}
}
@ -71,7 +71,7 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
}

7
src/Avalonia.Styling/Styling/IStyle.cs

@ -17,7 +17,12 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
void Attach(IStyleable control, IStyleHost container);
/// <returns>
/// True if the style can match a control of type <paramref name="control"/>
/// (even if it does not match this control specifically); false if the style
/// can never match.
/// </returns>
bool Attach(IStyleable control, IStyleHost container);
void Detach();
}

12
src/Avalonia.Styling/Styling/PropertyEqualsSelector.cs

@ -30,6 +30,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => _previous?.InTemplate ?? false;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Gets the name of the control to match.
/// </summary>
@ -72,17 +75,14 @@ namespace Avalonia.Styling
/// <inheritdoc/>
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(control, _property))
{
return SelectorMatch.False;
}
else if (subscribe)
if (subscribe)
{
return new SelectorMatch(control.GetObservable(_property).Select(v => Equals(v ?? string.Empty, _value)));
}
else
{
return new SelectorMatch((control.GetValue(_property) ?? string.Empty).Equals(_value));
var result = (control.GetValue(_property) ?? string.Empty).Equals(_value);
return result ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance;
}
}

39
src/Avalonia.Styling/Styling/Selector.cs

@ -17,6 +17,14 @@ namespace Avalonia.Styling
/// </summary>
public abstract bool InTemplate { get; }
/// <summary>
/// Gets a value indicating whether this selector is a combinator.
/// </summary>
/// <remarks>
/// A combinator is a selector such as Child or Descendent which links simple selectors.
/// </remarks>
public abstract bool IsCombinator { get; }
/// <summary>
/// Gets the target type of the selector, if available.
/// </summary>
@ -33,25 +41,32 @@ namespace Avalonia.Styling
/// <returns>A <see cref="SelectorMatch"/>.</returns>
public SelectorMatch Match(IStyleable control, bool subscribe = true)
{
List<IObservable<bool>> inputs = new List<IObservable<bool>>();
Selector selector = this;
var inputs = new List<IObservable<bool>>();
var selector = this;
var alwaysThisType = true;
var hitCombinator = false;
while (selector != null)
{
if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.False;
}
hitCombinator |= selector.IsCombinator;
var match = selector.Evaluate(control, subscribe);
if (match.ImmediateResult == false)
if (!match.IsMatch)
{
return hitCombinator ? SelectorMatch.NeverThisInstance : match;
}
else if (selector.InTemplate && control.TemplatedParent == null)
{
return SelectorMatch.NeverThisInstance;
}
else if (match.Result == SelectorMatchResult.AlwaysThisInstance)
{
return match;
alwaysThisType = false;
}
else if (match.ObservableResult != null)
else if (match.Result == SelectorMatchResult.Sometimes)
{
inputs.Add(match.ObservableResult);
inputs.Add(match.Activator);
}
selector = selector.MovePrevious();
@ -63,7 +78,9 @@ namespace Avalonia.Styling
}
else
{
return SelectorMatch.True;
return alwaysThisType && !hitCombinator ?
SelectorMatch.AlwaysThisType :
SelectorMatch.AlwaysThisInstance;
}
}

86
src/Avalonia.Styling/Styling/SelectorMatch.cs

@ -5,51 +5,95 @@ using System;
namespace Avalonia.Styling
{
/// <summary>
/// Describes how a <see cref="SelectorMatch"/> matches a control and its type.
/// </summary>
public enum SelectorMatchResult
{
/// <summary>
/// The selector never matches this type.
/// </summary>
NeverThisType,
/// <summary>
/// The selector never matches this instance, but can match this type.
/// </summary>
NeverThisInstance,
/// <summary>
/// The selector always matches this type.
/// </summary>
AlwaysThisType,
/// <summary>
/// The selector always matches this instance, but doesn't always match this type.
/// </summary>
AlwaysThisInstance,
/// <summary>
/// The selector matches this instance based on the <see cref="SelectorMatch.Activator"/>.
/// </summary>
Sometimes,
}
/// <summary>
/// Holds the result of a <see cref="Selector"/> match.
/// </summary>
/// <remarks>
/// There are two types of selectors - ones whose match can never change for a particular
/// control (such as <see cref="Selectors.OfType(Selector, Type)"/>) and ones whose result can
/// change over time (such as <see cref="Selectors.Class(Selector, string)"/>. For the first
/// category of selectors, the value of <see cref="ImmediateResult"/> will be set but for the
/// second, <see cref="ImmediateResult"/> will be null and <see cref="ObservableResult"/> will
/// hold an observable which tracks the match.
/// A selector match describes whether and how a <see cref="Selector"/> matches a control, and
/// in addition whether the selector can ever match a control of the same type.
/// </remarks>
public class SelectorMatch
{
public static readonly SelectorMatch False = new SelectorMatch(false);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisType"/>.
/// </summary>
public static readonly SelectorMatch NeverThisType = new SelectorMatch(SelectorMatchResult.NeverThisType);
public static readonly SelectorMatch True = new SelectorMatch(true);
/// <summary>
/// A selector match with the result of <see cref="SelectorMatchResult.NeverThisInstance"/>.
/// </summary>
public static readonly SelectorMatch NeverThisInstance = new SelectorMatch(SelectorMatchResult.NeverThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// A selector match with the result of <see cref="SelectorMatchResult.AlwaysThisType"/>.
/// </summary>
/// <param name="match">The immediate match value.</param>
public SelectorMatch(bool match)
{
ImmediateResult = match;
}
public static readonly SelectorMatch AlwaysThisType = new SelectorMatch(SelectorMatchResult.AlwaysThisType);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class.
/// Gets a selector match with the result of <see cref="SelectorMatchResult.AlwaysThisInstance"/>.
/// </summary>
/// <param name="match">The observable match value.</param>
public static readonly SelectorMatch AlwaysThisInstance = new SelectorMatch(SelectorMatchResult.AlwaysThisInstance);
/// <summary>
/// Initializes a new instance of the <see cref="SelectorMatch"/> class with a
/// <see cref="SelectorMatchResult.Sometimes"/> result.
/// </summary>
/// <param name="match">The match activator.</param>
public SelectorMatch(IObservable<bool> match)
{
ObservableResult = match;
Contract.Requires<ArgumentNullException>(match != null);
Result = SelectorMatchResult.Sometimes;
Activator = match;
}
private SelectorMatch(SelectorMatchResult result) => Result = result;
/// <summary>
/// Gets the immediate result of the selector match, in the case of selectors that cannot
/// change over time.
/// Gets a value indicating whether the match was positive.
/// </summary>
public bool IsMatch => Result >= SelectorMatchResult.AlwaysThisType;
/// <summary>
/// Gets the result of the match.
/// </summary>
public bool? ImmediateResult { get; }
public SelectorMatchResult Result { get; }
/// <summary>
/// Gets an observable which tracks the selector match, in the case of selectors that can
/// change over time.
/// </summary>
public IObservable<bool> ObservableResult { get; }
public IObservable<bool> Activator { get; }
}
}

2
src/Avalonia.Styling/Styling/Setter.cs

@ -69,7 +69,7 @@ namespace Avalonia.Styling
{
throw new ArgumentException(
"Cannot assign a control to Setter.Value. Wrap the control in a <Template>.",
"value");
nameof(value));
}
_value = value;

24
src/Avalonia.Styling/Styling/Style.cs

@ -106,20 +106,14 @@ namespace Avalonia.Styling
/// <inheritdoc/>
bool IResourceProvider.HasResources => _resources?.Count > 0;
/// <summary>
/// Attaches the style to a control if the style's selector matches.
/// </summary>
/// <param name="control">The control to attach to.</param>
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
/// <inheritdoc/>
public bool Attach(IStyleable control, IStyleHost container)
{
if (Selector != null)
{
var match = Selector.Match(control);
if (match.ImmediateResult != false)
if (match.IsMatch)
{
var controlSubscriptions = GetSubscriptions(control);
@ -129,9 +123,10 @@ namespace Avalonia.Styling
{
foreach (var animation in Animations)
{
IObservable<bool> obsMatch = match.ObservableResult;
var obsMatch = match.Activator;
if (match.ImmediateResult == true)
if (match.Result == SelectorMatchResult.AlwaysThisType ||
match.Result == SelectorMatchResult.AlwaysThisInstance)
{
obsMatch = Observable.Return(true);
}
@ -143,13 +138,15 @@ namespace Avalonia.Styling
foreach (var setter in Setters)
{
var sub = setter.Apply(this, control, match.ObservableResult);
var sub = setter.Apply(this, control, match.Activator);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
return match.Result != SelectorMatchResult.NeverThisType;
}
else if (control == container)
{
@ -165,7 +162,10 @@ namespace Avalonia.Styling
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
return true;
}
return false;
}
/// <inheritdoc/>

45
src/Avalonia.Styling/Styling/Styles.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls;
@ -15,6 +16,7 @@ namespace Avalonia.Styling
{
private IResourceNode _parent;
private IResourceDictionary _resources;
private Dictionary<Type, List<IStyle>> _cache;
public Styles()
{
@ -34,6 +36,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged += SubResourceChanged;
_cache = null;
},
x =>
{
@ -49,6 +52,7 @@ namespace Avalonia.Styling
}
x.ResourcesChanged -= SubResourceChanged;
_cache = null;
},
() => { });
}
@ -97,11 +101,46 @@ namespace Avalonia.Styling
/// <param name="container">
/// The control that contains this style. May be null.
/// </param>
public void Attach(IStyleable control, IStyleHost container)
public bool Attach(IStyleable control, IStyleHost container)
{
foreach (IStyle style in this)
if (_cache == null)
{
_cache = new Dictionary<Type, List<IStyle>>();
}
if (_cache.TryGetValue(control.StyleKey, out var cached))
{
if (cached != null)
{
foreach (var style in cached)
{
style.Attach(control, container);
}
return true;
}
return false;
}
else
{
style.Attach(control, container);
List<IStyle> result = null;
foreach (var style in this)
{
if (style.Attach(control, container))
{
if (result == null)
{
result = new List<IStyle>();
}
result.Add(style);
}
}
_cache.Add(control.StyleKey, result);
return result != null;
}
}

5
src/Avalonia.Styling/Styling/TemplateSelector.cs

@ -23,6 +23,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override bool InTemplate => true;
/// <inheritdoc/>
public override bool IsCombinator => true;
/// <inheritdoc/>
public override Type TargetType => null;
@ -46,7 +49,7 @@ namespace Avalonia.Styling
"Cannot call Template selector on control with null TemplatedParent.");
}
return _parent.Match(templatedParent, subscribe) ?? SelectorMatch.True;
return _parent.Match(templatedParent, subscribe);
}
protected override Selector MovePrevious() => null;

19
src/Avalonia.Styling/Styling/TypeNameAndClassSelector.cs

@ -68,6 +68,9 @@ namespace Avalonia.Styling
/// <inheritdoc/>
public override Type TargetType => _targetType ?? _previous?.TargetType;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <summary>
/// Whether the selector matches the concrete <see cref="TargetType"/> or any object which
/// implements <see cref="TargetType"/>.
@ -101,21 +104,21 @@ namespace Avalonia.Styling
{
if (controlType != TargetType)
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
else
{
if (!TargetType.GetTypeInfo().IsAssignableFrom(controlType.GetTypeInfo()))
{
return SelectorMatch.False;
return SelectorMatch.NeverThisType;
}
}
}
if (Name != null && control.Name != Name)
{
return SelectorMatch.False;
return SelectorMatch.NeverThisInstance;
}
if (_classes.IsValueCreated && _classes.Value.Count > 0)
@ -125,15 +128,13 @@ namespace Avalonia.Styling
var observable = new ClassObserver(control.Classes, _classes.Value);
return new SelectorMatch(observable);
}
else
else if (!Matches(control.Classes))
{
return new SelectorMatch(Matches(control.Classes));
return SelectorMatch.NeverThisInstance;
}
}
else
{
return SelectorMatch.True;
}
return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance;
}
protected override Selector MovePrevious() => _previous;

4
src/Avalonia.Themes.Default/ProgressBar.xaml

@ -33,7 +33,7 @@
<Style Selector="ProgressBar:horizontal:indeterminate /template/ Border#PART_Indicator">
<Style.Animations>
<Animation Duration="0:0:3"
RepeatCount="Loop"
IterationCount="Infinite"
Easing="LinearEasing">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X"
@ -49,7 +49,7 @@
<Style Selector="ProgressBar:vertical:indeterminate /template/ Border#PART_Indicator">
<Style.Animations>
<Animation Duration="0:0:3"
RepeatCount="Loop"
IterationCount="Infinite"
Easing="LinearEasing">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y"

70
src/Avalonia.Visuals/Animation/Animators/ColorAnimator.cs

@ -0,0 +1,70 @@
// Original color interpolation code was written by Romain Guy and Francois Blavoet
// and adopted from LottieSharp Project (https://github.com/ascora/LottieSharp).
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that interpolates <see cref="Color"/> through
/// gamma sRGB color space for better visual result.
/// </summary>
public class ColorAnimator : Animator<Color>
{
// Opto-electronic conversion function for the sRGB color space
// Takes a gamma-encoded sRGB value and converts it to a linear sRGB value
private static double OECF_sRGB(double linear)
{
// IEC 61966-2-1:1999
return linear <= 0.0031308d ? linear * 12.92d : (double)(Math.Pow(linear, 1.0d / 2.4d) * 1.055d - 0.055d);
}
// Electro-optical conversion function for the sRGB color space
// Takes a linear sRGB value and converts it to a gamma-encoded sRGB value
private static double EOCF_sRGB(double srgb)
{
// IEC 61966-2-1:1999
return srgb <= 0.04045d ? srgb / 12.92d : (double)Math.Pow((srgb + 0.055d) / 1.055d, 2.4d);
}
public override Color Interpolate(double progress, Color oldValue, Color newValue)
{
// normalize sRGB values.
var oldA = oldValue.A / 255d;
var oldR = oldValue.R / 255d;
var oldG = oldValue.G / 255d;
var oldB = oldValue.B / 255d;
var newA = newValue.A / 255d;
var newR = newValue.R / 255d;
var newG = newValue.G / 255d;
var newB = newValue.B / 255d;
// convert from sRGB to linear
oldR = EOCF_sRGB(oldR);
oldG = EOCF_sRGB(oldG);
oldB = EOCF_sRGB(oldB);
newR = EOCF_sRGB(newR);
newG = EOCF_sRGB(newG);
newB = EOCF_sRGB(newB);
// compute the interpolated color in linear space
var a = oldA + progress * (newA - oldA);
var r = oldR + progress * (newR - oldR);
var g = oldG + progress * (newG - oldG);
var b = oldB + progress * (newB - oldB);
// convert back to sRGB in the [0..255] range
a = a * 255d;
r = OECF_sRGB(r) * 255d;
g = OECF_sRGB(g) * 255d;
b = OECF_sRGB(b) * 255d;
return new Color((byte)Math.Round(a), (byte)Math.Round(r), (byte)Math.Round(g), (byte)Math.Round(b));
}
}
}

27
src/Avalonia.Visuals/Animation/Animators/CornerRadiusAnimator.cs

@ -0,0 +1,27 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="CornerRadius"/> properties.
/// </summary>
public class CornerRadiusAnimator : Animator<CornerRadius>
{
public override CornerRadius Interpolate(double progress, CornerRadius oldValue, CornerRadius newValue)
{
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = progress * deltaTL + oldValue.TopLeft;
var nTR = progress * deltaTR + oldValue.TopRight;
var nBR = progress * deltaBR + oldValue.BottomRight;
var nBL = progress * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/PointAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Point"/> properties.
/// </summary>
public class PointAnimator : Animator<Point>
{
public override Point Interpolate(double progress, Point oldValue, Point newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

23
src/Avalonia.Visuals/Animation/Animators/RectAnimator.cs

@ -0,0 +1,23 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Rect"/> properties.
/// </summary>
public class RectAnimator : Animator<Rect>
{
public override Rect Interpolate(double progress, Rect oldValue, Rect newValue)
{
var deltaPos = newValue.Position - oldValue.Position;
var deltaSize = newValue.Size - oldValue.Size;
var newPos = (deltaPos * progress) + oldValue.Position;
var newSize = (deltaSize * progress) + oldValue.Size;
return new Rect(newPos, newSize);
}
}
}

17
src/Avalonia.Visuals/Animation/Animators/SizeAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Size"/> properties.
/// </summary>
public class SizeAnimator : Animator<Size>
{
public override Size Interpolate(double progress, Size oldValue, Size newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

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

@ -0,0 +1,74 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Logging;
using Avalonia.Media;
using Avalonia.Media.Immutable;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/>.
/// </summary>
public class SolidColorBrushAnimator : Animator<SolidColorBrush>
{
ColorAnimator _colorAnimator;
void InitializeColorAnimator()
{
_colorAnimator = new ColorAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_colorAnimator.Add(keyframe);
}
_colorAnimator.Property = SolidColorBrush.ColorProperty;
}
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> match, Action onComplete)
{
foreach (var keyframe in this)
{
if (keyframe.Value as ISolidColorBrush == null)
return Disposable.Empty;
// Preprocess keyframe values to Color if the xaml parser converts them to ISCB.
if (keyframe.Value.GetType() == typeof(ImmutableSolidColorBrush))
{
keyframe.Value = ((ImmutableSolidColorBrush)keyframe.Value).Color;
}
}
// Add SCB if the target prop is empty.
if (control.GetValue(Property) == null)
control.SetValue(Property, new SolidColorBrush(Colors.Transparent));
var targetVal = control.GetValue(Property);
// Continue if target prop is not empty & is a SolidColorBrush derivative.
if (typeof(ISolidColorBrush).IsAssignableFrom(targetVal.GetType()))
{
if (_colorAnimator == null)
InitializeColorAnimator();
SolidColorBrush finalTarget;
// If it's ISCB, change it back to SCB.
if (targetVal.GetType() == typeof(ImmutableSolidColorBrush))
{
var col = (ImmutableSolidColorBrush)targetVal;
targetVal = new SolidColorBrush(col.Color);
control.SetValue(Property, targetVal);
}
finalTarget = targetVal as SolidColorBrush;
return _colorAnimator.Apply(animation, finalTarget, clock ?? control.Clock, match, onComplete);
}
return Disposable.Empty;
}
public override SolidColorBrush Interpolate(double p, SolidColorBrush o, SolidColorBrush n) => null;
}
}

17
src/Avalonia.Visuals/Animation/Animators/ThicknessAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Thickness"/> properties.
/// </summary>
public class ThicknessAnimator : Animator<Thickness>
{
public override Thickness Interpolate(double progress, Thickness oldValue, Thickness newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

37
src/Avalonia.Visuals/Animation/TransformAnimator.cs → src/Avalonia.Visuals/Animation/Animators/TransformAnimator.cs

@ -2,14 +2,14 @@
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Transform"/> properties.
/// </summary>
public class TransformAnimator : Animator<double>
{
DoubleAnimator childAnimator;
DoubleAnimator _doubleAnimator;
/// <inheritdoc/>
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, IObservable<bool> obsMatch, Action onComplete)
@ -27,7 +27,7 @@ namespace Avalonia.Animation
// default RenderTransform order.
normalTransform.Children.Add(new ScaleTransform());
normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new SkewTransform());
normalTransform.Children.Add(new RotateTransform());
normalTransform.Children.Add(new TranslateTransform());
@ -36,15 +36,22 @@ namespace Avalonia.Animation
var renderTransformType = ctrl.RenderTransform.GetType();
if (childAnimator == null)
if (_doubleAnimator == null)
{
InitializeChildAnimator();
_doubleAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
_doubleAnimator.Add(keyframe);
}
_doubleAnimator.Property = Property;
}
// It's a transform object so let's target that.
if (renderTransformType == Property.OwnerType)
{
return childAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
return _doubleAnimator.Apply(animation, ctrl.RenderTransform, clock ?? control.Clock, obsMatch, onComplete);
}
// It's a TransformGroup and try finding the target there.
else if (renderTransformType == typeof(TransformGroup))
@ -53,7 +60,7 @@ namespace Avalonia.Animation
{
if (transform.GetType() == Property.OwnerType)
{
return childAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete);
return _doubleAnimator.Apply(animation, transform, clock ?? control.Clock, obsMatch, onComplete);
}
}
}
@ -73,19 +80,7 @@ namespace Avalonia.Animation
return null;
}
void InitializeChildAnimator()
{
childAnimator = new DoubleAnimator();
foreach (AnimatorKeyFrame keyframe in this)
{
childAnimator.Add(keyframe);
}
childAnimator.Property = Property;
}
/// <inheritdocs/>
protected override double DoInterpolation(double time, double neutralValue) => 0;
/// <inheritdocs/>
public override double Interpolate(double p, double o, double n) => 0;
}
}

17
src/Avalonia.Visuals/Animation/Animators/VectorAnimator.cs

@ -0,0 +1,17 @@
using System;
using Avalonia.Logging;
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="Vector"/> properties.
/// </summary>
public class VectorAnimator : Animator<Vector>
{
public override Vector Interpolate(double progress, Vector oldValue, Vector newValue)
{
return ((newValue - oldValue) * progress) + oldValue;
}
}
}

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

@ -21,7 +21,7 @@ namespace Avalonia.Animation
/// Initializes a new instance of the <see cref="CrossFade"/> class.
/// </summary>
public CrossFade()
:this(TimeSpan.Zero)
: this(TimeSpan.Zero)
{
}
@ -33,30 +33,40 @@ namespace Avalonia.Animation
{
_fadeOutAnimation = new Animation
{
new KeyFrame
(
new Setter
Children =
{
new KeyFrame()
{
Property = Visual.OpacityProperty,
Value = 0d
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(1d)
}
)
{
Cue = new Cue(1d)
}
};
_fadeInAnimation = new Animation
{
new KeyFrame
(
new Setter
Children =
{
new KeyFrame()
{
Property = Visual.OpacityProperty,
Value = 0d
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(0d)
}
)
{
Cue = new Cue(0d)
}
};
_fadeOutAnimation.Duration = _fadeInAnimation.Duration = duration;

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

@ -74,34 +74,36 @@ namespace Avalonia.Animation
var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
// TODO: Implement relevant transition logic here (or discard this class)
// in favor of XAML based transition for pages
if (from != null)
{
var animation = new Animation
{
new KeyFrame
(
new Setter
{
Property = translateProperty,
Value = 0d
}
)
Children =
{
Cue = new Cue(0d)
},
new KeyFrame
(
new Setter
new KeyFrame
{
Property = translateProperty,
Value = forward ? -distance : distance
}
)
{
Cue = new Cue(1d)
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter
{
Property = translateProperty,
Value = forward ? -distance : distance
}
},
Cue = new Cue(1d)
}
}
};
animation.Duration = Duration;
@ -113,29 +115,34 @@ namespace Avalonia.Animation
to.IsVisible = true;
var animation = new Animation
{
Children =
{
new KeyFrame
(
new Setter
new KeyFrame
{
Property = translateProperty,
Value = forward ? distance : -distance
}
)
{
Cue = new Cue(0d)
},
new KeyFrame
(
new Setter
Setters =
{
new Setter
{
Property = translateProperty,
Value = forward ? distance : -distance
}
},
Cue = new Cue(0d)
},
new KeyFrame
{
Property = translateProperty,
Value = 0d
Setters =
{
new Setter
{
Property = translateProperty,
Value = 0d
}
},
Cue = new Cue(1d)
}
)
{
Cue = new Cue(1d)
},
}
};
animation.Duration = Duration;
tasks.Add(animation.RunAsync(to));

36
src/Avalonia.Visuals/Animation/Transitions/CornerRadiusTransition.cs

@ -0,0 +1,36 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="CornerRadius"/> type.
/// </summary>
public class CornerRadiusTransition : Transition<CornerRadius>
{
/// <inheritdocs/>
public override IObservable<CornerRadius> DoTransition(IObservable<double> progress, CornerRadius oldValue, CornerRadius newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var deltaTL = newValue.TopLeft - oldValue.TopLeft;
var deltaTR = newValue.TopRight - oldValue.TopRight;
var deltaBR = newValue.BottomRight - oldValue.BottomRight;
var deltaBL = newValue.BottomLeft - oldValue.BottomLeft;
var nTL = f * deltaTL + oldValue.TopLeft;
var nTR = f * deltaTR + oldValue.TopRight;
var nBR = f * deltaBR + oldValue.BottomRight;
var nBL = f * deltaBL + oldValue.BottomLeft;
return new CornerRadius(nTL, nTR, nBR, nBL);
});
}
}
}

7
src/Avalonia.Visuals/Animation/PointTransition.cs → src/Avalonia.Visuals/Animation/Transitions/PointTransition.cs

@ -14,16 +14,11 @@ namespace Avalonia.Animation
/// <inheritdocs/>
public override IObservable<Point> DoTransition(IObservable<double> progress, Point oldValue, Point newValue)
{
var deltaX = newValue.X - oldValue.Y;
var deltaY = newValue.X - oldValue.Y;
return progress
.Select(p =>
{
var f = Easing.Ease(p);
var nX = f * deltaX + oldValue.X;
var nY = f * deltaY + oldValue.Y;
return new Point(nX, nY);
return ((newValue - oldValue) * f) + oldValue;
});
}
}

25
src/Avalonia.Visuals/Animation/Transitions/SizeTransition.cs

@ -0,0 +1,25 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Linq;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="Size"/> type.
/// </summary>
public class SizeTransition : Transition<Size>
{
/// <inheritdocs/>
public override IObservable<Size> DoTransition(IObservable<double> progress, Size oldValue, Size newValue)
{
return progress
.Select(p =>
{
var f = Easing.Ease(p);
return ((newValue - oldValue) * f) + oldValue;
});
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save