Browse Source

Merge branch 'linq-expression-expressionobserver' into selector-parse-no-sprache

pull/1668/head
Jeremy Koritzinsky 8 years ago
parent
commit
de7e8d5567
  1. 156
      .editorconfig
  2. 8
      .gitignore
  3. 5
      .ncrunch/Avalonia.Designer.HostApp.NetFX.v3.ncrunchproject
  4. 5
      .ncrunch/BindingDemo.net461.v3.ncrunchproject
  5. 5
      .ncrunch/BindingDemo.netcoreapp2.0.v3.ncrunchproject
  6. 5
      .ncrunch/Previewer.v3.ncrunchproject
  7. 5
      .ncrunch/RemoteDemo.v3.ncrunchproject
  8. 5
      .ncrunch/RenderDemo.net461.v3.ncrunchproject
  9. 5
      .ncrunch/RenderDemo.netcoreapp2.0.v3.ncrunchproject
  10. 5
      .ncrunch/VirtualizationDemo.net461.v3.ncrunchproject
  11. 5
      .ncrunch/VirtualizationDemo.netcoreapp2.0.v3.ncrunchproject
  12. 54
      Avalonia.sln
  13. 369
      build.cake
  14. 4
      build/SkiaSharp.props
  15. 17
      build/XUnit.props
  16. 15
      cake.config
  17. 92
      packages.cake
  18. 14
      parameters.cake
  19. 0
      samples/BindingDemo/App.config
  20. 0
      samples/BindingDemo/App.xaml
  21. 2
      samples/BindingDemo/App.xaml.cs
  22. 0
      samples/BindingDemo/BindingDemo.csproj
  23. 4
      samples/BindingDemo/MainWindow.xaml
  24. 4
      samples/BindingDemo/MainWindow.xaml.cs
  25. 0
      samples/BindingDemo/TestItemView.xaml
  26. 2
      samples/BindingDemo/TestItemView.xaml.cs
  27. 2
      samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
  28. 2
      samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs
  29. 2
      samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
  30. 2
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  31. 2
      samples/BindingDemo/ViewModels/NestedCommandViewModel.cs
  32. 2
      samples/BindingDemo/ViewModels/TestItem.cs
  33. 9
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  34. 4
      samples/ControlCatalog.NetCore/Program.cs
  35. 4
      samples/ControlCatalog/SideBar.xaml
  36. 2
      samples/RemoteDemo/Program.cs
  37. 0
      samples/RemoteDemo/RemoteDemo.csproj
  38. 0
      samples/RenderDemo/App.config
  39. 2
      samples/RenderDemo/App.xaml
  40. 2
      samples/RenderDemo/App.xaml.cs
  41. 2
      samples/RenderDemo/MainWindow.xaml
  42. 4
      samples/RenderDemo/MainWindow.xaml.cs
  43. 0
      samples/RenderDemo/Pages/AnimationsPage.xaml
  44. 4
      samples/RenderDemo/Pages/AnimationsPage.xaml.cs
  45. 0
      samples/RenderDemo/Pages/ClippingPage.xaml
  46. 2
      samples/RenderDemo/Pages/ClippingPage.xaml.cs
  47. 0
      samples/RenderDemo/Pages/DrawingPage.xaml
  48. 2
      samples/RenderDemo/Pages/DrawingPage.xaml.cs
  49. 0
      samples/RenderDemo/RenderDemo.csproj
  50. 4
      samples/RenderDemo/SideBar.xaml
  51. 2
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  52. 2
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  53. 0
      samples/VirtualizationDemo/App.config
  54. 0
      samples/VirtualizationDemo/App.xaml
  55. 2
      samples/VirtualizationDemo/App.xaml.cs
  56. 0
      samples/VirtualizationDemo/MainWindow.xaml
  57. 4
      samples/VirtualizationDemo/MainWindow.xaml.cs
  58. 2
      samples/VirtualizationDemo/Program.cs
  59. 2
      samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
  60. 2
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  61. 0
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  62. 1
      samples/interop/Direct3DInteropSample/MainWindow.cs
  63. 5
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  64. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  65. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  66. 9
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  67. 2
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  68. 192
      src/Avalonia.Base/AvaloniaObject.cs
  69. 81
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  70. 59
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  71. 25
      src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs
  72. 45
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  73. 5
      src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs
  74. 124
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  75. 174
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  76. 15
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  77. 44
      src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs
  78. 21
      src/Avalonia.Base/Data/Core/IndexerNodeBase.cs
  79. 11
      src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs
  80. 37
      src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs
  81. 10
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  82. 10
      src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
  83. 5
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  84. 14
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  85. 19
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  86. 16
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  87. 8
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  88. 68
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  89. 11
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  90. 38
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  91. 38
      src/Avalonia.Base/Data/Core/SettableNode.cs
  92. 5
      src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs
  93. 17
      src/Avalonia.Base/Data/Core/StreamNode.cs
  94. 9
      src/Avalonia.Base/IPriorityValueOwner.cs
  95. 4
      src/Avalonia.Base/PriorityValue.cs
  96. 42
      src/Avalonia.Base/Reactive/AvaloniaObservable.cs
  97. 46
      src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs
  98. 52
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  99. 202
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  100. 76
      src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs

156
.editorconfig

@ -1,11 +1,159 @@
; This file is for unifying the coding style for different editors and IDEs.
; More information at http://EditorConfig.org
# editorconfig.org
# top-most EditorConfig file
root = true
# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
[*]
end_of_line = CRLF
insert_final_newline = true
indent_style = space
indent_size = 4
# C# files
[*.cs]
indent_style = space
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# prefer var
csharp_style_var_for_built_in_types = true
csharp_style_var_when_type_is_apparent = true
csharp_style_var_elsewhere = true:suggestion
# use language keywords instead of BCL types
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# use accessibility modifiers
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
# Code style defaults
dotnet_sort_system_directives_first = true
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = false
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = false:none
csharp_style_expression_bodied_constructors = false:none
csharp_style_expression_bodied_operators = false:none
csharp_style_expression_bodied_properties = true:none
csharp_style_expression_bodied_indexers = true:none
csharp_style_expression_bodied_accessors = true:none
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Xaml files
[*.xaml]
indent_size = 4
# Xml project files
[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
# Xml build files
[*.builds]
indent_size = 2
# Xml files
[*.{xml,stylecop,resx,ruleset}]
indent_size = 2
# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf

8
.gitignore

@ -176,5 +176,9 @@ nuget
Avalonia.XBuild.sln
project.lock.json
.idea/*
**/obj-Skia/*
**/obj-Direct2D1/*
##################
## BenchmarkDotNet
##################
BenchmarkDotNet.Artifacts/

5
.ncrunch/Avalonia.Designer.HostApp.NetFX.v3.ncrunchproject

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

5
.ncrunch/BindingDemo.net461.v3.ncrunchproject

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

5
.ncrunch/BindingDemo.netcoreapp2.0.v3.ncrunchproject

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

5
.ncrunch/Previewer.v3.ncrunchproject

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

5
.ncrunch/RemoteDemo.v3.ncrunchproject

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

5
.ncrunch/RenderDemo.net461.v3.ncrunchproject

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

5
.ncrunch/RenderDemo.netcoreapp2.0.v3.ncrunchproject

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

5
.ncrunch/VirtualizationDemo.net461.v3.ncrunchproject

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

5
.ncrunch/VirtualizationDemo.netcoreapp2.0.v3.ncrunchproject

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

54
Avalonia.sln

@ -58,6 +58,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9B9E
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs
EndProjectSection
EndProject
@ -71,7 +72,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingDemo", "samples\BindingDemo\BindingDemo.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
EndProject
@ -109,7 +110,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationDemo", "samples\VirtualizationDemo\VirtualizationDemo.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}"
EndProject
@ -117,7 +118,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderDemo", "samples\RenderDemo\RenderDemo.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
EndProject
@ -170,7 +171,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteTest", "samples\RemoteTest\RemoteTest.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RemoteDemo", "samples\RemoteDemo\RemoteDemo.csproj", "{E2999E4A-9086-401F-898C-AEB0AD38E676}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}"
EndProject
@ -184,6 +185,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer.HostApp.NetFX", "src\tools\Avalonia.Designer.HostApp.NetFX\Avalonia.Designer.HostApp.NetFX.csproj", "{4ADA61C8-D191-428D-9066-EF4F0D86520F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.UnitTests", "tests\Avalonia.Skia.UnitTests\Avalonia.Skia.UnitTests.csproj", "{E1240B49-7B4B-4371-A00E-068778C5CF0B}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -396,6 +399,7 @@ Global
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|x86.Build.0 = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -405,6 +409,7 @@ Global
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.Build.0 = Release|Any CPU
{62024B2D-53EB-4638-B26B-85EEAA54866E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@ -2469,6 +2474,46 @@ Global
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.ActiveCfg = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|NetCoreOnly.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|NetCoreOnly.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhone.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|NetCoreOnly.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|NetCoreOnly.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|x86.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.AppStore|x86.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhone.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|x86.ActiveCfg = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Debug|x86.Build.0 = Debug|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|Any CPU.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhone.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.ActiveCfg = Release|Any CPU
{E1240B49-7B4B-4371-A00E-068778C5CF0B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2521,6 +2566,7 @@ Global
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

369
build.cake

@ -6,11 +6,13 @@
#addin "nuget:?package=NuGet.Core&version=2.14.0"
#tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
///////////////////////////////////////////////////////////////////////////////
// TOOLS
///////////////////////////////////////////////////////////////////////////////
#tool "nuget:?package=xunit.runner.console&version=2.3.0-beta5-build3769"
#tool "nuget:?package=xunit.runner.console&version=2.3.1"
#tool "nuget:?package=JetBrains.dotMemoryUnit&version=3.0.20171219.105559"
///////////////////////////////////////////////////////////////////////////////
// USINGS
@ -34,20 +36,31 @@ using NuGet;
// PARAMETERS
//////////////////////////////////////////////////////////////////////
Parameters parameters = new Parameters(Context);
Packages packages = new Packages(Context, parameters);
class AvaloniaBuildData
{
public AvaloniaBuildData(Parameters parameters, Packages packages)
{
Parameters = parameters;
Packages = packages;
}
public Parameters Parameters { get; }
public Packages Packages { get; }
}
///////////////////////////////////////////////////////////////////////////////
// SETUP
///////////////////////////////////////////////////////////////////////////////
Setup(context =>
Setup<AvaloniaBuildData>(context =>
{
Information("Building version {0} of Avalonia ({1}, {2}, {3}) using version {4} of Cake.",
var parameters = new Parameters(context);
var buildContext = new AvaloniaBuildData(parameters, new Packages(context, parameters));
Information("Building version {0} of Avalonia ({1}, {2}) using version {3} of Cake.",
parameters.Version,
parameters.Platform,
parameters.Configuration,
parameters.Target,
typeof(ICakeContext).Assembly.GetName().Version.ToString());
if (parameters.IsRunningOnAppVeyor)
@ -55,8 +68,7 @@ Setup(context =>
Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name);
Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
}
Information("Target: " + parameters.Target);
Information("Target:" + context.TargetTask.Name);
Information("Platform: " + parameters.Platform);
Information("Configuration: " + parameters.Configuration);
Information("IsLocalBuild: " + parameters.IsLocalBuild);
@ -70,13 +82,15 @@ Setup(context =>
Information("IsReleasable: " + parameters.IsReleasable);
Information("IsMyGetRelease: " + parameters.IsMyGetRelease);
Information("IsNuGetRelease: " + parameters.IsNuGetRelease);
return buildContext;
});
///////////////////////////////////////////////////////////////////////////////
// TEARDOWN
///////////////////////////////////////////////////////////////////////////////
Teardown(context =>
Teardown<AvaloniaBuildData>((context, buildContext) =>
{
Information("Finished running tasks.");
});
@ -85,20 +99,20 @@ Teardown(context =>
// TASKS
///////////////////////////////////////////////////////////////////////////////
Task("Clean")
.Does(() =>
Task("Clean-Impl")
.Does<AvaloniaBuildData>(data =>
{
CleanDirectories(parameters.BuildDirs);
CleanDirectory(parameters.ArtifactsDir);
CleanDirectory(parameters.NugetRoot);
CleanDirectory(parameters.ZipRoot);
CleanDirectory(parameters.BinRoot);
CleanDirectories(data.Parameters.BuildDirs);
CleanDirectory(data.Parameters.ArtifactsDir);
CleanDirectory(data.Parameters.NugetRoot);
CleanDirectory(data.Parameters.ZipRoot);
CleanDirectory(data.Parameters.BinRoot);
});
Task("Restore-NuGet-Packages")
.IsDependentOn("Clean")
.WithCriteria(parameters.IsRunningOnWindows)
.Does(() =>
Task("Restore-NuGet-Packages-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does<AvaloniaBuildData>(data =>
{
var maxRetryCount = 5;
var toolTimeout = 2d;
@ -115,13 +129,13 @@ Task("Restore-NuGet-Packages")
toolTimeout+=0.5;
}})
.Execute(()=> {
NuGetRestore(parameters.MSBuildSolution, new NuGetRestoreSettings {
NuGetRestore(data.Parameters.MSBuildSolution, new NuGetRestoreSettings {
ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
});
});
});
void DotNetCoreBuild()
void DotNetCoreBuild(Parameters parameters)
{
var settings = new DotNetCoreBuildSettings
{
@ -135,29 +149,28 @@ void DotNetCoreBuild()
DotNetCoreBuild(parameters.MSBuildSolution, settings);
}
Task("Build")
.IsDependentOn("Restore-NuGet-Packages")
.Does(() =>
Task("Build-Impl")
.Does<AvaloniaBuildData>(data =>
{
if(parameters.IsRunningOnWindows)
if(data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
{
MSBuild(parameters.MSBuildSolution, settings => {
settings.SetConfiguration(parameters.Configuration);
MSBuild(data.Parameters.MSBuildSolution, settings => {
settings.SetConfiguration(data.Parameters.Configuration);
settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
settings.WithProperty("Platform", "\"" + data.Parameters.Platform + "\"");
settings.WithProperty("UseRoslynPathHack", "true");
settings.UseToolVersion(MSBuildToolVersion.VS2017);
settings.WithProperty("Windows", "True");
settings.SetNodeReuse(false);
settings.SetMaxCpuCount(0);
});
}
else
{
DotNetCoreBuild();
DotNetCoreBuild(data.Parameters);
}
});
void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
{
if(!project.EndsWith(".csproj"))
@ -180,83 +193,106 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
}
}
Task("Run-Unit-Tests")
.IsDependentOn("Build")
.IsDependentOn("Run-Designer-Tests")
.IsDependentOn("Run-Render-Tests")
.WithCriteria(() => !parameters.SkipTests)
.Does(() => {
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false);
if (parameters.IsRunningOnWindows)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true);
}
});
Task("Run-Unit-Tests-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.Does<AvaloniaBuildData>(data =>
{
RunCoreTest("./tests/Avalonia.Base.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
if (data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
{
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, true);
}
});
Task("Run-Designer-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests)
.Does(() => {
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", parameters, false);
});
Task("Run-Designer-Tests-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.Does<AvaloniaBuildData>(data =>
{
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data.Parameters, false);
});
Task("Run-Render-Tests")
.IsDependentOn("Build")
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
.Does(() => {
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
});
Task("Run-Render-Tests-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does<AvaloniaBuildData>(data =>
{
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", data.Parameters, true);
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", data.Parameters, true);
});
Task("Copy-Files")
.IsDependentOn("Run-Unit-Tests")
Task("Run-Leak-Tests-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does(() =>
{
CopyFiles(packages.BinFiles, parameters.BinRoot);
var dotMemoryUnit = Context.Tools.Resolve("dotMemoryUnit.exe");
var leakTestsExitCode = StartProcess(dotMemoryUnit, new ProcessSettings
{
Arguments = new ProcessArgumentBuilder()
.Append(Context.Tools.Resolve("xunit.console.x86.exe").FullPath)
.Append("--propagate-exit-code")
.Append("--")
.Append("tests\\Avalonia.LeakTests\\bin\\Release\\net461\\Avalonia.LeakTests.dll"),
Timeout = 120000
});
if (leakTestsExitCode != 0)
{
throw new Exception("Leak Tests failed");
}
});
Task("Zip-Files")
.IsDependentOn("Copy-Files")
.Does(() =>
Task("Copy-Files-Impl")
.Does<AvaloniaBuildData>(data =>
{
Zip(parameters.BinRoot, parameters.ZipCoreArtifacts);
Zip(parameters.ZipSourceControlCatalogDesktopDirs,
parameters.ZipTargetControlCatalogDesktopDirs,
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") +
GetFiles(parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
CopyFiles(data.Packages.BinFiles, data.Parameters.BinRoot);
});
Task("Create-NuGet-Packages")
.IsDependentOn("Run-Unit-Tests")
.IsDependentOn("Inspect")
.Does(() =>
Task("Zip-Files-Impl")
.Does<AvaloniaBuildData>(data =>
{
Zip(data.Parameters.BinRoot, data.Parameters.ZipCoreArtifacts);
Zip(data.Parameters.NugetRoot, data.Parameters.ZipNuGetArtifacts);
if (!data.Parameters.IsPlatformNetCoreOnly) {
Zip(data.Parameters.ZipSourceControlCatalogDesktopDirs,
data.Parameters.ZipTargetControlCatalogDesktopDirs,
GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dll") +
GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.config") +
GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.so") +
GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.dylib") +
GetFiles(data.Parameters.ZipSourceControlCatalogDesktopDirs.FullPath + "/*.exe"));
}
});
Task("Create-NuGet-Packages-Impl")
.Does<AvaloniaBuildData>(data =>
{
foreach(var nuspec in packages.NuspecNuGetSettings)
foreach(var nuspec in data.Packages.NuspecNuGetSettings)
{
NuGetPack(nuspec);
}
});
Task("Publish-MyGet")
.IsDependentOn("Create-NuGet-Packages")
.WithCriteria(() => !parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsPullRequest)
.WithCriteria(() => parameters.IsMainRepo)
.WithCriteria(() => parameters.IsMasterBranch)
.WithCriteria(() => parameters.IsMyGetRelease)
.Does(() =>
Task("Publish-MyGet-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMasterBranch)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMyGetRelease)
.Does<AvaloniaBuildData>(data =>
{
var apiKey = EnvironmentVariable("MYGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
@ -270,7 +306,7 @@ Task("Publish-MyGet")
throw new InvalidOperationException("Could not resolve MyGet API url.");
}
foreach(var nupkg in packages.NugetPackages)
foreach(var nupkg in data.Packages.NugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
Source = apiUrl,
@ -283,13 +319,12 @@ Task("Publish-MyGet")
Information("Publish-MyGet Task failed, but continuing with next Task...");
});
Task("Publish-NuGet")
.IsDependentOn("Create-NuGet-Packages")
.WithCriteria(() => !parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsPullRequest)
.WithCriteria(() => parameters.IsMainRepo)
.WithCriteria(() => parameters.IsNuGetRelease)
.Does(() =>
Task("Publish-NuGet-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsNuGetRelease)
.Does<AvaloniaBuildData>(data =>
{
var apiKey = EnvironmentVariable("NUGET_API_KEY");
if(string.IsNullOrEmpty(apiKey))
@ -303,7 +338,7 @@ Task("Publish-NuGet")
throw new InvalidOperationException("Could not resolve NuGet API url.");
}
foreach(var nupkg in packages.NugetPackages)
foreach(var nupkg in data.Packages.NugetPackages)
{
NuGetPush(nupkg, new NuGetPushSettings {
ApiKey = apiKey,
@ -316,102 +351,80 @@ Task("Publish-NuGet")
Information("Publish-NuGet Task failed, but continuing with next Task...");
});
Task("Run-Leak-Tests")
.WithCriteria(parameters.IsRunningOnWindows)
.IsDependentOn("Build")
Task("Inspect-Impl")
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does(() =>
{
DotNetCoreRestore("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj");
DotNetBuild("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj", settings => settings.SetConfiguration("Release"));
var report = "tests\\Avalonia.LeakTests\\bin\\Release\\report.xml";
if(System.IO.File.Exists(report))
System.IO.File.Delete(report);
var toolXunitConsoleX86 = Context.Tools.Resolve("xunit.console.x86.exe").FullPath;
var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
Information("Running code inspections");
var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"),
new ProcessSettings
{
FileName="tests\\Avalonia.LeakTests\\toolproject\\bin\\dotMemoryUnit.exe",
Arguments="-targetExecutable=\"" + toolXunitConsoleX86 + "\" -returnTargetExitCode -- tests\\Avalonia.LeakTests\\bin\\Release\\Avalonia.LeakTests.dll -xml tests\\Avalonia.LeakTests\\bin\\Release\\report.xml ",
UseShellExecute = false,
Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln",
RedirectStandardOutput = true
});
var st = System.Diagnostics.Stopwatch.StartNew();
while(!proc.HasExited && !System.IO.File.Exists(report))
{
if(st.Elapsed.TotalSeconds>60)
{
Error("Timed out, probably a bug in dotMemoryUnit");
proc.Kill();
throw new Exception("dotMemory issue");
}
proc.WaitForExit(100);
}
try{
proc.Kill();
}catch{}
var doc = System.Xml.Linq.XDocument.Load(report);
if(doc.Root.Descendants("assembly").Any(x=>x.Attribute("failed").Value.ToString() != "0"))
{
throw new Exception("Tests failed");
}
});
Task("Inspect")
.WithCriteria(parameters.IsRunningOnWindows)
.IsDependentOn("Restore-NuGet-Packages")
.Does(() =>
Information("Analyzing report");
var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
var failBuild = false;
foreach(var xml in doc.Descendants("Issue"))
{
var badIssues = new []{"PossibleNullReferenceException"};
var whitelist = new []{"tests", "src\\android", "src\\ios",
"src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
Information("Running code inspections");
StartProcess(Context.Tools.Resolve("inspectcode.exe"),
new ProcessSettings{ Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln" });
Information("Analyzing report");
var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
var failBuild = false;
foreach(var xml in doc.Descendants("Issue"))
var typeId = xml.Attribute("TypeId").Value.ToString();
if(badIssues.Contains(typeId))
{
var typeId = xml.Attribute("TypeId").Value.ToString();
if(badIssues.Contains(typeId))
{
var file = xml.Attribute("File").Value.ToString().ToLower();
if(whitelist.Any(wh => file.StartsWith(wh)))
continue;
var line = xml.Attribute("Line").Value.ToString();
Error(typeId + " - " + file + " on line " + line);
failBuild = true;
}
var file = xml.Attribute("File").Value.ToString().ToLower();
if(whitelist.Any(wh => file.StartsWith(wh)))
continue;
var line = xml.Attribute("Line").Value.ToString();
Error(typeId + " - " + file + " on line " + line);
failBuild = true;
}
if(failBuild)
throw new Exception("Issues found");
});
}
if(failBuild)
throw new Exception("Issues found");
});
///////////////////////////////////////////////////////////////////////////////
// TARGETS
///////////////////////////////////////////////////////////////////////////////
Task("Run-Tests")
.IsDependentOn("Clean-Impl")
.IsDependentOn("Restore-NuGet-Packages-Impl")
.IsDependentOn("Build-Impl")
.IsDependentOn("Run-Unit-Tests-Impl")
.IsDependentOn("Run-Render-Tests-Impl")
.IsDependentOn("Run-Designer-Tests-Impl")
.IsDependentOn("Run-Leak-Tests-Impl");
Task("Package")
.IsDependentOn("Create-NuGet-Packages");
.IsDependentOn("Run-Tests")
.IsDependentOn("Inspect-Impl")
.IsDependentOn("Create-NuGet-Packages-Impl");
Task("Default").Does(() =>
{
if(parameters.IsRunningOnWindows)
RunTarget("Package");
else
RunTarget("Run-Unit-Tests");
});
Task("AppVeyor")
.IsDependentOn("Zip-Files")
.IsDependentOn("Publish-MyGet")
.IsDependentOn("Publish-NuGet");
.IsDependentOn("Package")
.IsDependentOn("Copy-Files-Impl")
.IsDependentOn("Zip-Files-Impl")
.IsDependentOn("Publish-MyGet-Impl")
.IsDependentOn("Publish-NuGet-Impl");
Task("Travis")
.IsDependentOn("Run-Unit-Tests");
.IsDependentOn("Run-Tests");
///////////////////////////////////////////////////////////////////////////////
// EXECUTE
///////////////////////////////////////////////////////////////////////////////
RunTarget(parameters.Target);
var target = Context.Argument("target", "Default");
if (target == "Default")
{
target = Context.IsRunningOnWindows() ? "Package" : "Run-Tests";
}
RunTarget(target);

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.57.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" />
<PackageReference Include="SkiaSharp" Version="1.60.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" />
</ItemGroup>
</Project>

17
build/XUnit.props

@ -9,21 +9,6 @@
<PackageReference Include="xunit.runner.console" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
<PackageReference Include="Xunit.SkippableFact" Version="1.3.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
</ItemGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<Target Name="ForceGenerationOfBindingRedirects"
AfterTargets="ResolveAssemblyReferences"
BeforeTargets="GenerateBindingRedirects"
Condition="'$(AutoGenerateBindingRedirects)' == 'true'">
<PropertyGroup>
<!-- Needs to be set in a target because it has to be set after the initial evaluation in the common targets -->
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
</PropertyGroup>
</Target>
<Import Project="$(MSBuildThisFileDirectory)\NetFX.props" />
</Project>

15
cake.config

@ -0,0 +1,15 @@
; This is the default configuration file for Cake.
; This file was downloaded from https://github.com/cake-build/resources
[Nuget]
Source=https://api.nuget.org/v3/index.json
UseInProcessClient=true
LoadDependencies=false
[Paths]
Tools=./tools
Addins=./tools/Addins
Modules=./tools/Modules
[Settings]
SkipVerification=false

92
packages.cake

@ -1,3 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Linq;
public class Packages
@ -9,12 +12,11 @@ public class Packages
public string SkiaSharpVersion {get; private set; }
public string SkiaSharpLinuxVersion {get; private set; }
public Dictionary<string, IList<Tuple<string,string>>> PackageVersions{get; private set;}
class DependencyBuilder : List<NuSpecDependency>
{
Packages _parent;
public DependencyBuilder(Packages parent)
{
_parent = parent;
@ -24,8 +26,7 @@ public class Packages
{
return _parent.PackageVersions[name].First().Item1;
}
public DependencyBuilder Dep(string name, params string[] fws)
{
if(fws.Length == 0)
@ -212,17 +213,33 @@ public class Packages
};
});
var toolsContent = new[] {
new NuSpecContent{
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath,
Target = "tools/netcoreapp2.0/previewer"
},
new NuSpecContent{
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath,
Target = "tools/net461/previewer"
}
var toolHostApp = new NuSpecContent{
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath,
Target = "tools/netcoreapp2.0/previewer"
};
var toolHostAppNetFx = new NuSpecContent{
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath,
Target = "tools/net461/previewer"
};
IList<NuSpecContent> coreFiles;
if (!parameters.IsPlatformNetCoreOnly) {
var toolsContent = new[] { toolHostApp, toolHostAppNetFx };
coreFiles = coreLibrariesNuSpecContent
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
.Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
.Concat(toolsContent)
.ToList();
} else {
var toolsContent = new[] { toolHostApp };
coreFiles = coreLibrariesNuSpecContent
.Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
.Concat(toolsContent)
.ToList();
}
var nuspecNuGetSettingsCore = new []
{
///////////////////////////////////////////////////////////////////////////////
@ -253,13 +270,9 @@ public class Packages
}
.Deps(new string[]{null, "netcoreapp2.0"},
"System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives",
"System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter")
"System.Runtime.Serialization.Primitives", "System.Xml.XmlDocument", "System.Xml.ReaderWriter", "System.Memory")
.ToArray(),
Files = coreLibrariesNuSpecContent
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
.Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
.Concat(toolsContent)
.ToList(),
Files = coreFiles,
BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot
},
@ -451,22 +464,6 @@ public class Packages
BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot
},
new NuGetPackSettings()
{
Id = "Avalonia.Win32.Interoperability",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.LinuxFramebuffer
///////////////////////////////////////////////////////////////////////////////
@ -487,11 +484,32 @@ public class Packages
}
};
var nuspecNuGetSettingInterop = new NuGetPackSettings()
{
Id = "Avalonia.Win32.Interoperability",
Dependencies = new []
{
new NuSpecDependency() { Id = "Avalonia.Win32", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Direct2D1", Version = parameters.Version },
new NuSpecDependency() { Id = "SharpDX.Direct3D9", Version = SharpDXDirect3D9Version },
},
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32.Interop/bin/" + parameters.DirSuffix + "/Avalonia.Win32.Interop.dll", Target = "lib/net45" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot
};
NuspecNuGetSettings = new List<NuGetPackSettings>();
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
if (!parameters.IsPlatformNetCoreOnly) {
NuspecNuGetSettings.Add(nuspecNuGetSettingInterop);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
}
NuspecNuGetSettings.ForEach((nuspec) => SetNuGetNuspecCommonProperties(nuspec));

14
parameters.cake

@ -1,6 +1,5 @@
public class Parameters
{
public string Target { get; private set; }
public string Platform { get; private set; }
public string Configuration { get; private set; }
public bool SkipTests { get; private set; }
@ -9,11 +8,11 @@ public class Parameters
public string AssemblyInfoPath { get; private set; }
public string ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; }
public string MSBuildSolution { get; private set; }
public string XBuildSolution { get; private set; }
public string MSBuildSolution { get; private set; }
public bool IsPlatformAnyCPU { get; private set; }
public bool IsPlatformX86 { get; private set; }
public bool IsPlatformX64 { get; private set; }
public bool IsPlatformNetCoreOnly { get; private set; }
public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; }
public bool IsRunningOnWindows { get; private set; }
@ -35,6 +34,7 @@ public class Parameters
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; }
@ -43,7 +43,6 @@ public class Parameters
var buildSystem = context.BuildSystem();
// ARGUMENTS
Target = context.Argument("target", "Default");
Platform = context.Argument("platform", "Any CPU");
Configuration = context.Argument("configuration", "Release");
SkipTests = context.HasArgument("skip-tests");
@ -55,12 +54,12 @@ public class Parameters
ReleasePlatform = "Any CPU";
ReleaseConfiguration = "Release";
MSBuildSolution = "./Avalonia.sln";
XBuildSolution = "./Avalonia.XBuild.sln";
// PARAMETERS
IsPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(Platform, "Any CPU");
IsPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x86");
IsPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x64");
IsPlatformNetCoreOnly = StringComparer.OrdinalIgnoreCase.Equals(Platform, "NetCoreOnly");
IsLocalBuild = buildSystem.IsLocalBuild;
IsRunningOnUnix = context.IsRunningOnUnix();
IsRunningOnWindows = context.IsRunningOnWindows();
@ -73,7 +72,6 @@ public class Parameters
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform)
&& StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable;
// VERSION
Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
@ -105,14 +103,12 @@ public class Parameters
NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
DirSuffix = Configuration;
DirSuffixIOS = "iPhone" + "/" + 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);
}

0
samples/BindingTest/App.config → samples/BindingDemo/App.config

0
samples/BindingTest/App.xaml → samples/BindingDemo/App.xaml

2
samples/BindingTest/App.xaml.cs → samples/BindingDemo/App.xaml.cs

@ -5,7 +5,7 @@ using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml;
using Serilog;
namespace BindingTest
namespace BindingDemo
{
public class App : Application
{

0
samples/BindingTest/BindingTest.csproj → samples/BindingDemo/BindingDemo.csproj

4
samples/BindingTest/MainWindow.xaml → samples/BindingDemo/MainWindow.xaml

@ -1,7 +1,7 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:vm="clr-namespace:BindingTest.ViewModels"
xmlns:local="clr-namespace:BindingTest"
xmlns:vm="clr-namespace:BindingDemo.ViewModels"
xmlns:local="clr-namespace:BindingDemo"
Title="AvaloniaUI Bindings Test"
Width="800"
Height="600">

4
samples/BindingTest/MainWindow.xaml.cs → samples/BindingDemo/MainWindow.xaml.cs

@ -1,9 +1,9 @@
using BindingTest.ViewModels;
using BindingDemo.ViewModels;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BindingTest
namespace BindingDemo
{
public class MainWindow : Window
{

0
samples/BindingTest/TestItemView.xaml → samples/BindingDemo/TestItemView.xaml

2
samples/BindingTest/TestItemView.xaml.cs → samples/BindingDemo/TestItemView.xaml.cs

@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace BindingTest
namespace BindingDemo
{
public class TestItemView : UserControl
{

2
samples/BindingTest/ViewModels/DataAnnotationsErrorViewModel.cs → samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs

@ -3,7 +3,7 @@
using System.ComponentModel.DataAnnotations;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class DataAnnotationsErrorViewModel
{

2
samples/BindingTest/ViewModels/ExceptionErrorViewModel.cs → samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs

@ -4,7 +4,7 @@
using ReactiveUI;
using System;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class ExceptionErrorViewModel : ReactiveObject
{

2
samples/BindingTest/ViewModels/IndeiErrorViewModel.cs → samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs

@ -6,7 +6,7 @@ using System;
using System.ComponentModel;
using System.Collections;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class IndeiErrorViewModel : ReactiveObject, INotifyDataErrorInfo
{

2
samples/BindingTest/ViewModels/MainWindowViewModel.cs → samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -6,7 +6,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class MainWindowViewModel : ReactiveObject
{

2
samples/BindingTest/ViewModels/NestedCommandViewModel.cs → samples/BindingDemo/ViewModels/NestedCommandViewModel.cs

@ -6,7 +6,7 @@ using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class NestedCommandViewModel : ReactiveObject
{

2
samples/BindingTest/ViewModels/TestItem.cs → samples/BindingDemo/ViewModels/TestItem.cs

@ -1,6 +1,6 @@
using ReactiveUI;
namespace BindingTest.ViewModels
namespace BindingDemo.ViewModels
{
public class TestItem : ReactiveObject
{

9
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@ -28,8 +28,6 @@ namespace ControlCatalog.Android
{
global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello;
global::Avalonia.Android.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name;
global::Splat.Resource.String.library_name = global::ControlCatalog.Android.Resource.String.library_name;
}
public partial class Attribute
@ -96,14 +94,11 @@ namespace ControlCatalog.Android
public partial class String
{
// aapt resource value: 0x7f040002
public const int ApplicationName = 2130968578;
// aapt resource value: 0x7f040001
public const int Hello = 2130968577;
public const int ApplicationName = 2130968577;
// aapt resource value: 0x7f040000
public const int library_name = 2130968576;
public const int Hello = 2130968576;
static String()
{

4
samples/ControlCatalog.NetCore/Program.cs

@ -1,9 +1,9 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia;
using Avalonia.Skia;
namespace ControlCatalog.NetCore
{
@ -37,7 +37,7 @@ namespace ControlCatalog.NetCore
/// This method is needed for IDE previewer infrastructure
/// </summary>
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>().UsePlatformDetect().UseReactiveUI();
=> AppBuilder.Configure<App>().UsePlatformDetect().UseSkia().UseReactiveUI();
static void ConsoleSilencer()
{

4
samples/ControlCatalog/SideBar.xaml

@ -8,7 +8,7 @@
<TabStrip Name="PART_TabStrip"
MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
<TabStrip.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
@ -20,7 +20,7 @@
Margin="8 0 0 0"
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
SelectedIndex="{TemplateBinding SelectedIndex}"
PageTransition="{TemplateBinding PageTransition}"
Grid.Row="1"/>
</DockPanel>

2
samples/RemoteTest/Program.cs → samples/RemoteDemo/Program.cs

@ -9,7 +9,7 @@ using Avalonia.Remote.Protocol;
using Avalonia.Threading;
using ControlCatalog;
namespace RemoteTest
namespace RemoteDemo
{
class Program
{

0
samples/RemoteTest/RemoteTest.csproj → samples/RemoteDemo/RemoteDemo.csproj

0
samples/RenderTest/App.config → samples/RenderDemo/App.config

2
samples/RenderTest/App.xaml → samples/RenderDemo/App.xaml

@ -2,6 +2,6 @@
<Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
<StyleInclude Source="resm:RenderTest.SideBar.xaml"/>
<StyleInclude Source="resm:RenderDemo.SideBar.xaml"/>
</Application.Styles>
</Application>

2
samples/RenderTest/App.xaml.cs → samples/RenderDemo/App.xaml.cs

@ -5,7 +5,7 @@ using Avalonia;
using Avalonia.Logging.Serilog;
using Avalonia.Markup.Xaml;
namespace RenderTest
namespace RenderDemo
{
public class App : Application
{

2
samples/RenderTest/MainWindow.xaml → samples/RenderDemo/MainWindow.xaml

@ -1,6 +1,6 @@
<Window xmlns="https://github.com/avaloniaui"
Title="AvaloniaUI Rendering Test"
xmlns:pages="clr-namespace:RenderTest.Pages"
xmlns:pages="clr-namespace:RenderDemo.Pages"
Width="800"
Height="600">
<DockPanel>

4
samples/RenderTest/MainWindow.xaml.cs → samples/RenderDemo/MainWindow.xaml.cs

@ -5,10 +5,10 @@ using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using RenderTest.ViewModels;
using RenderDemo.ViewModels;
using ReactiveUI;
namespace RenderTest
namespace RenderDemo
{
public class MainWindow : Window
{

0
samples/RenderTest/Pages/AnimationsPage.xaml → samples/RenderDemo/Pages/AnimationsPage.xaml

4
samples/RenderTest/Pages/AnimationsPage.xaml.cs → samples/RenderDemo/Pages/AnimationsPage.xaml.cs

@ -7,9 +7,9 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using RenderTest.ViewModels;
using RenderDemo.ViewModels;
namespace RenderTest.Pages
namespace RenderDemo.Pages
{
public class AnimationsPage : UserControl
{

0
samples/RenderTest/Pages/ClippingPage.xaml → samples/RenderDemo/Pages/ClippingPage.xaml

2
samples/RenderTest/Pages/ClippingPage.xaml.cs → samples/RenderDemo/Pages/ClippingPage.xaml.cs

@ -7,7 +7,7 @@ using Avalonia.Data;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
namespace RenderTest.Pages
namespace RenderDemo.Pages
{
public class ClippingPage : UserControl
{

0
samples/RenderTest/Pages/DrawingPage.xaml → samples/RenderDemo/Pages/DrawingPage.xaml

2
samples/RenderTest/Pages/DrawingPage.xaml.cs → samples/RenderDemo/Pages/DrawingPage.xaml.cs

@ -1,7 +1,7 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace RenderTest.Pages
namespace RenderDemo.Pages
{
public class DrawingPage : UserControl
{

0
samples/RenderTest/RenderTest.csproj → samples/RenderDemo/RenderDemo.csproj

4
samples/RenderTest/SideBar.xaml → samples/RenderDemo/SideBar.xaml

@ -9,7 +9,7 @@
<TabStrip Name="PART_TabStrip"
MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}">
SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
<TabStrip.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
@ -21,7 +21,7 @@
Margin="8 0 0 0"
MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}"
SelectedIndex="{TemplateBinding SelectedIndex}"
PageTransition="{TemplateBinding PageTransition}"
Grid.Row="1"/>
</DockPanel>

2
samples/RenderTest/ViewModels/AnimationsPageViewModel.cs → samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs

@ -2,7 +2,7 @@
using ReactiveUI;
using Avalonia.Animation;
namespace RenderTest.ViewModels
namespace RenderDemo.ViewModels
{
public class AnimationsPageViewModel : ReactiveObject
{

2
samples/RenderTest/ViewModels/MainWindowViewModel.cs → samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@ -1,7 +1,7 @@
using System;
using ReactiveUI;
namespace RenderTest.ViewModels
namespace RenderDemo.ViewModels
{
public class MainWindowViewModel : ReactiveObject
{

0
samples/VirtualizationTest/App.config → samples/VirtualizationDemo/App.config

0
samples/VirtualizationTest/App.xaml → samples/VirtualizationDemo/App.xaml

2
samples/VirtualizationTest/App.xaml.cs → samples/VirtualizationDemo/App.xaml.cs

@ -4,7 +4,7 @@
using Avalonia;
using Avalonia.Markup.Xaml;
namespace VirtualizationTest
namespace VirtualizationDemo
{
public class App : Application
{

0
samples/VirtualizationTest/MainWindow.xaml → samples/VirtualizationDemo/MainWindow.xaml

4
samples/VirtualizationTest/MainWindow.xaml.cs → samples/VirtualizationDemo/MainWindow.xaml.cs

@ -4,9 +4,9 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using VirtualizationTest.ViewModels;
using VirtualizationDemo.ViewModels;
namespace VirtualizationTest
namespace VirtualizationDemo
{
public class MainWindow : Window
{

2
samples/VirtualizationTest/Program.cs → samples/VirtualizationDemo/Program.cs

@ -7,7 +7,7 @@ using Avalonia.Controls;
using Avalonia.Logging.Serilog;
using Serilog;
namespace VirtualizationTest
namespace VirtualizationDemo
{
class Program
{

2
samples/VirtualizationTest/ViewModels/ItemViewModel.cs → samples/VirtualizationDemo/ViewModels/ItemViewModel.cs

@ -4,7 +4,7 @@
using System;
using ReactiveUI;
namespace VirtualizationTest.ViewModels
namespace VirtualizationDemo.ViewModels
{
internal class ItemViewModel : ReactiveObject
{

2
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs → samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -9,7 +9,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI;
namespace VirtualizationTest.ViewModels
namespace VirtualizationDemo.ViewModels
{
internal class MainWindowViewModel : ReactiveObject
{

0
samples/VirtualizationTest/VirtualizationTest.csproj → samples/VirtualizationDemo/VirtualizationDemo.csproj

1
samples/interop/Direct3DInteropSample/MainWindow.cs

@ -88,7 +88,6 @@ namespace Direct3DInteropSample
context.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(renderView, Color.White);
var time = 50;
// Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) *
Matrix.RotationZ((float) _model.RotationZ)

5
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -110,7 +110,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
//Not supported
}
public void SetTopmost(bool value)
{
//Not supported
}
}
}

2
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs

@ -126,8 +126,6 @@ namespace Avalonia.Android.Platform.Specific.Helpers
return e.Action != MotionEventActions.Up;
}
private Paint _paint;
public void Dispose()
{
HandleEvents = false;

7
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -40,14 +40,11 @@ namespace Avalonia.Android
public partial class String
{
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001
public static int Hello = 2130837505;
public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000
public static int library_name = 2130837504;
public static int Hello = 2130837504;
static String()
{

9
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@ -28,8 +28,6 @@ namespace Avalonia.AndroidTestApplication
{
global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello;
global::Avalonia.Android.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name;
global::Splat.Resource.String.library_name = global::Avalonia.AndroidTestApplication.Resource.String.library_name;
}
public partial class Attribute
@ -64,14 +62,11 @@ namespace Avalonia.AndroidTestApplication
public partial class String
{
// aapt resource value: 0x7f030002
public const int ApplicationName = 2130903042;
// aapt resource value: 0x7f030001
public const int Hello = 2130903041;
public const int ApplicationName = 2130903041;
// aapt resource value: 0x7f030000
public const int library_name = 2130903040;
public const int Hello = 2130903040;
static String()
{

2
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -9,7 +9,7 @@ namespace Avalonia.Animation
{
/// <summary>
/// Defines a KeyFrame that is used for
/// <see cref="Animators"/> objects.
/// <see cref="Animator{T}"/> objects.
/// </summary>
public class AnimatorKeyFrame
{

192
src/Avalonia.Base/AvaloniaObject.cs

@ -10,6 +10,7 @@ using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.Threading;
using Avalonia.Utilities;
@ -28,17 +29,11 @@ namespace Avalonia
/// </summary>
private IAvaloniaObject _inheritanceParent;
/// <summary>
/// The set values/bindings on this object.
/// </summary>
private readonly Dictionary<AvaloniaProperty, PriorityValue> _values =
new Dictionary<AvaloniaProperty, PriorityValue>();
/// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected.
/// </summary>
private List<IDisposable> _directBindings;
private List<DirectBindingSubscription> _directBindings;
/// <summary>
/// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
@ -51,6 +46,7 @@ namespace Avalonia
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
private ValueStore _values;
/// <summary>
/// Delayed setter helper for direct properties. Used to fix #855.
@ -227,9 +223,20 @@ namespace Avalonia
{
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
else if (_values != null)
{
var result = _values.GetValue(property);
if (result == AvaloniaProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
return result;
}
else
{
return GetValueInternal(property);
return GetDefaultValue(property);
}
}
@ -256,7 +263,7 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
return _values?.IsAnimating(property) ?? false;
}
/// <summary>
@ -273,14 +280,7 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess();
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
return value.Value != AvaloniaProperty.UnsetValue;
}
return false;
return _values?.IsSet(property) ?? false;
}
/// <summary>
@ -359,36 +359,15 @@ namespace Avalonia
property,
description);
IDisposable subscription = null;
if (_directBindings == null)
{
_directBindings = new List<IDisposable>();
_directBindings = new List<DirectBindingSubscription>();
}
subscription = source
.Select(x => CastOrDefault(x, property.PropertyType))
.Do(_ => { }, () => _directBindings.Remove(subscription))
.Subscribe(x => SetDirectValue(property, x));
_directBindings.Add(subscription);
return Disposable.Create(() =>
{
subscription.Dispose();
_directBindings.Remove(subscription);
});
return new DirectBindingSubscription(this, property, source);
}
else
{
PriorityValue v;
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
_values.Add(property, v);
}
Logger.Verbose(
LogArea.Property,
this,
@ -397,7 +376,12 @@ namespace Avalonia
description,
priority);
return v.Add(source, (int)priority);
if (_values == null)
{
_values = new ValueStore(this);
}
return _values.AddBinding(property, source, priority);
}
}
@ -428,20 +412,12 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property)
{
VerifyAccess();
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
value.Revalidate();
}
_values?.Revalidate(property);
}
/// <inheritdoc/>
void IPriorityValueOwner.Changed(PriorityValue sender, object oldValue, object newValue)
void IPriorityValueOwner.Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
{
var property = sender.Property;
var priority = (BindingPriority)sender.ValuePriority;
oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
GetDefaultValue(property) :
oldValue;
@ -451,7 +427,7 @@ namespace Avalonia
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(property, oldValue, newValue, priority);
RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
Logger.Verbose(
LogArea.Property,
@ -460,14 +436,14 @@ namespace Avalonia
property,
oldValue,
newValue,
priority);
(BindingPriority)priority);
}
}
/// <inheritdoc/>
void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification)
void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{
UpdateDataValidation(sender.Property, notification);
UpdateDataValidation(property, notification);
}
/// <inheritdoc/>
@ -480,10 +456,7 @@ namespace Avalonia
/// Gets all priority values set on the object.
/// </summary>
/// <returns>A collection of property/value tuples.</returns>
internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues()
{
return _values;
}
internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => _values?.GetSetValues();
/// <summary>
/// Forces revalidation of properties when a property value changes.
@ -672,68 +645,18 @@ namespace Avalonia
}
}
/// <summary>
/// Creates a <see cref="PriorityValue"/> for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The <see cref="PriorityValue"/>.</returns>
private PriorityValue CreatePriorityValue(AvaloniaProperty property)
{
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(GetType());
Func<object, object> validate2 = null;
if (validate != null)
{
validate2 = v => validate(this, v);
}
PriorityValue result = new PriorityValue(
this,
property,
property.PropertyType,
validate2);
return result;
}
/// <summary>
/// Gets the default value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property)
internal object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValueInternal(property);
return aobj.GetValue(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}
/// <summary>
/// Gets a <see cref="AvaloniaProperty"/> value
/// without check for registered as this can slow getting the value
/// this method is intended for internal usage in AvaloniaObject only
/// it's called only after check the property is registered
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The value.</returns>
private object GetValueInternal(AvaloniaProperty property)
{
object result = AvaloniaProperty.UnsetValue;
PriorityValue value;
if (_values.TryGetValue(property, out value))
{
result = value.Value;
}
if (result == AvaloniaProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
return result;
}
/// <summary>
/// Sets the value of a direct property.
/// </summary>
@ -814,21 +737,13 @@ namespace Avalonia
originalValue?.GetType().FullName ?? "(null)"));
}
PriorityValue v;
if (!_values.TryGetValue(property, out v))
if (_values == null)
{
if (value == AvaloniaProperty.UnsetValue)
{
return;
}
v = CreatePriorityValue(property);
_values.Add(property, v);
_values = new ValueStore(this);
}
LogPropertySet(property, value, priority);
v.SetValue(value, (int)priority);
_values.AddValue(property, value, (int)priority);
}
/// <summary>
@ -908,5 +823,38 @@ namespace Avalonia
value,
priority);
}
private class DirectBindingSubscription : IObserver<object>, IDisposable
{
readonly AvaloniaObject _owner;
readonly AvaloniaProperty _property;
IDisposable _subscription;
public DirectBindingSubscription(
AvaloniaObject owner,
AvaloniaProperty property,
IObservable<object> source)
{
_owner = owner;
_property = property;
_owner._directBindings.Add(this);
_subscription = source.Subscribe(this);
}
public void Dispose()
{
_subscription.Dispose();
_owner._directBindings.Remove(this);
}
public void OnCompleted() => Dispose();
public void OnError(Exception error) => Dispose();
public void OnNext(object value)
{
var castValue = CastOrDefault(value, _property.PropertyType);
_owner.SetDirectValue(_property, castValue);
}
}
}
}

81
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -36,32 +36,15 @@ namespace Avalonia
/// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes.
/// </returns>
/// <remarks>
/// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks>
public static IObservable<object> GetObservable(this IAvaloniaObject o, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return new AvaloniaObservable<object>(
observer =>
{
EventHandler<AvaloniaPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(e.NewValue);
}
};
observer.OnNext(o.GetValue(property));
o.PropertyChanged += handler;
return Disposable.Create(() =>
{
o.PropertyChanged -= handler;
});
},
GetDescription(o, property));
return new AvaloniaPropertyObservable<object>(o, property);
}
/// <summary>
@ -74,51 +57,36 @@ namespace Avalonia
/// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes.
/// </returns>
/// <remarks>
/// The subscription to <paramref name="o"/> is created using a weak reference.
/// </remarks>
public static IObservable<T> GetObservable<T>(this IAvaloniaObject o, AvaloniaProperty<T> property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return o.GetObservable((AvaloniaProperty)property).Cast<T>();
return new AvaloniaPropertyObservable<T>(o, property);
}
/// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>.
/// Gets an observable that listens for property changed events for an
/// <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <returns>
/// An observable which when subscribed pushes the old and new values of the property each
/// time it is changed. Note that the observable returned from this method does not fire
/// with the current value of the property immediately.
/// An observable which when subscribed pushes the property changed event args
/// each time a <see cref="IAvaloniaObject.PropertyChanged"/> event is raised
/// for the specified property.
/// </returns>
public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>(
public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable(
this IAvaloniaObject o,
AvaloniaProperty<T> property)
AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return new AvaloniaObservable<Tuple<T, T>>(
observer =>
{
EventHandler<AvaloniaPropertyChangedEventArgs> handler = (s, e) =>
{
if (e.Property == property)
{
observer.OnNext(Tuple.Create((T)e.OldValue, (T)e.NewValue));
}
};
o.PropertyChanged += handler;
return Disposable.Create(() =>
{
o.PropertyChanged -= handler;
});
},
GetDescription(o, property));
return new AvaloniaPropertyChangedObservable(o, property);
}
/// <summary>
@ -166,23 +134,6 @@ namespace Avalonia
o.GetObservable(property));
}
/// <summary>
/// Gets a weak observable for a <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>An observable.</returns>
public static IObservable<object> GetWeakObservable(this IAvaloniaObject o, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return new WeakPropertyChangedObservable(
new WeakReference<IAvaloniaObject>(o),
property,
GetDescription(o, property));
}
/// <summary>
/// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>.
/// </summary>

59
src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs

@ -2,13 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Collections
@ -43,9 +39,8 @@ namespace Avalonia.Collections
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(e => handler.Invoke(collection, e));
return collection.GetWeakCollectionChangedObservable()
.Subscribe(e => handler(collection, e));
}
/// <summary>
@ -63,18 +58,13 @@ namespace Avalonia.Collections
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(handler);
return collection.GetWeakCollectionChangedObservable().Subscribe(handler);
}
private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
private class WeakCollectionChangedObservable : LightweightObservableBase<NotifyCollectionChangedEventArgs>,
IWeakSubscriber<NotifyCollectionChangedEventArgs>
{
private WeakReference<INotifyCollectionChanged> _sourceReference;
private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
private int _count;
public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
{
@ -83,43 +73,28 @@ namespace Avalonia.Collections
public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
{
_changed.OnNext(e);
PublishNext(e);
}
protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
protected override void Initialize()
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
if (_count++ == 0)
{
WeakSubscriptionManager.Subscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
.Subscribe(observer);
}
else
{
_changed.OnCompleted();
observer.OnCompleted();
return Disposable.Empty;
WeakSubscriptionManager.Subscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
}
private void DecrementCount()
protected override void Deinitialize()
{
if (--_count == 0)
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
WeakSubscriptionManager.Unsubscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
WeakSubscriptionManager.Unsubscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
}
}

25
src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs

@ -2,11 +2,13 @@
using System.Collections.Generic;
using System.Reactive.Linq;
using System.Text;
using Avalonia.Reactive;
namespace Avalonia.Data.Core
{
public class AvaloniaPropertyAccessorNode : ExpressionNode, ISettableNode
public class AvaloniaPropertyAccessorNode : SettableNode
{
private IDisposable _subscription;
private readonly bool _enableValidation;
private readonly AvaloniaProperty _property;
@ -18,9 +20,9 @@ namespace Avalonia.Data.Core
public override string Description => PropertyName;
public string PropertyName { get; }
public Type PropertyType => _property.PropertyType;
public override Type PropertyType => _property.PropertyType;
public bool SetTargetValue(object value, BindingPriority priority)
protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
try
{
@ -37,9 +39,22 @@ namespace Avalonia.Data.Core
}
}
protected override IObservable<object> StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference reference)
{
return (reference.Target as IAvaloniaObject)?.GetWeakObservable(_property) ?? Observable.Empty<object>();
if (reference.Target is IAvaloniaObject obj)
{
_subscription = new AvaloniaPropertyObservable<object>(obj, _property).Subscribe(ValueChanged);
}
else
{
_subscription = null;
}
}
protected override void StopListeningCore()
{
_subscription?.Dispose();
_subscription = null;
}
}
}

45
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -7,21 +7,23 @@ using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data.Converters;
using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.Utilities;
namespace Avalonia.Data.Core
{
/// <summary>
/// Binds to an expression on an object using a type value converter to convert the values
/// that are send and received.
/// that are sent and received.
/// </summary>
public class BindingExpression : ISubject<object>, IDescription
public class BindingExpression : LightweightObservableBase<object>, ISubject<object>, IDescription
{
private readonly ExpressionObserver _inner;
private readonly Type _targetType;
private readonly object _fallbackValue;
private readonly BindingPriority _priority;
private readonly Subject<object> _errors = new Subject<object>();
InnerListener _innerListener;
WeakReference<object> _value;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -139,7 +141,7 @@ namespace Avalonia.Data.Core
"IValueConverter should not return non-errored BindingNotification.");
}
_errors.OnNext(notification);
PublishNext(notification);
if (_fallbackValue != AvaloniaProperty.UnsetValue)
{
@ -170,12 +172,18 @@ namespace Avalonia.Data.Core
}
}
/// <inheritdoc/>
public IDisposable Subscribe(IObserver<object> observer)
protected override void Initialize() => _innerListener = new InnerListener(this);
protected override void Deinitialize() => _innerListener.Dispose();
protected override void Subscribed(IObserver<object> observer, bool first)
{
return _inner.Select(ConvertValue).Merge(_errors).Subscribe(observer);
if (!first && _value != null && _value.TryGetTarget(out var val) == true)
{
observer.OnNext(val);
}
}
/// <inheritdoc/>
private object ConvertValue(object value)
{
var notification = value as BindingNotification;
@ -301,5 +309,28 @@ namespace Avalonia.Data.Core
return a;
}
public class InnerListener : IObserver<object>, IDisposable
{
private readonly BindingExpression _owner;
private readonly IDisposable _dispose;
public InnerListener(BindingExpression owner)
{
_owner = owner;
_dispose = owner._inner.Subscribe(this);
}
public void Dispose() => _dispose.Dispose();
public void OnCompleted() => _owner.PublishCompleted();
public void OnError(Exception error) => _owner.PublishError(error);
public void OnNext(object value)
{
var converted = _owner.ConvertValue(value);
_owner._value = new WeakReference<object>(converted);
_owner.PublishNext(converted);
}
}
}
}

5
src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs

@ -9,10 +9,5 @@ namespace Avalonia.Data.Core
public class EmptyExpressionNode : ExpressionNode
{
public override string Description => ".";
protected override IObservable<object> StartListeningCore(WeakReference reference)
{
return Observable.Return(reference.Target);
}
}
}

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

@ -2,21 +2,20 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Data.Core
{
public abstract class ExpressionNode : ISubject<object>
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue);
private WeakReference _target = UnsetReference;
private IDisposable _valueSubscription;
private IObserver<object> _observer;
private Action<object> _subscriber;
private bool _listening;
protected WeakReference LastValue { get; private set; }
public abstract string Description { get; }
public ExpressionNode Next { get; set; }
@ -30,119 +29,122 @@ namespace Avalonia.Data.Core
var oldTarget = _target?.Target;
var newTarget = value.Target;
var running = _valueSubscription != null;
if (!ReferenceEquals(oldTarget, newTarget))
{
_valueSubscription?.Dispose();
_valueSubscription = null;
if (_listening)
{
StopListening();
}
_target = value;
if (running)
if (_subscriber != null)
{
_valueSubscription = StartListening();
StartListening();
}
}
}
}
public IDisposable Subscribe(IObserver<object> observer)
public void Subscribe(Action<object> subscriber)
{
if (_observer != null)
if (_subscriber != null)
{
throw new AvaloniaInternalException("ExpressionNode can only be subscribed once.");
}
_observer = observer;
var nextSubscription = Next?.Subscribe(this);
_valueSubscription = StartListening();
return Disposable.Create(() =>
{
_valueSubscription?.Dispose();
_valueSubscription = null;
nextSubscription?.Dispose();
_observer = null;
});
_subscriber = subscriber;
Next?.Subscribe(NextValueChanged);
StartListening();
}
void IObserver<object>.OnCompleted()
public void Unsubscribe()
{
throw new AvaloniaInternalException("ExpressionNode.OnCompleted should not be called.");
}
Next?.Unsubscribe();
void IObserver<object>.OnError(Exception error)
{
throw new AvaloniaInternalException("ExpressionNode.OnError should not be called.");
if (_listening)
{
StopListening();
}
LastValue = null;
_subscriber = null;
}
void IObserver<object>.OnNext(object value)
protected virtual void StartListeningCore(WeakReference reference)
{
NextValueChanged(value);
ValueChanged(reference.Target);
}
protected virtual IObservable<object> StartListeningCore(WeakReference reference)
protected virtual void StopListeningCore()
{
return Observable.Return(reference.Target);
}
protected virtual void NextValueChanged(object value)
{
var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
bindingBroken?.AddNode(Description);
_observer.OnNext(value);
_subscriber(value);
}
private IDisposable StartListening()
{
var target = _target.Target;
IObservable<object> source;
if (target == null)
{
source = Observable.Return(TargetNullNotification());
}
else if (target == AvaloniaProperty.UnsetValue)
{
source = Observable.Empty<object>();
}
else
{
source = StartListeningCore(_target);
}
return source.Subscribe(ValueChanged);
}
private void ValueChanged(object value)
protected void ValueChanged(object value)
{
var notification = value as BindingNotification;
if (notification == null)
{
LastValue = new WeakReference(value);
if (Next != null)
{
Next.Target = new WeakReference(value);
}
else
{
_observer.OnNext(value);
_subscriber(value);
}
}
else
{
LastValue = new WeakReference(notification.Value);
if (Next != null)
{
Next.Target = new WeakReference(notification.Value);
}
if (Next == null || notification.Error != null)
{
_observer.OnNext(value);
_subscriber(value);
}
}
}
private void StartListening()
{
var target = _target.Target;
if (target == null)
{
ValueChanged(TargetNullNotification());
_listening = false;
}
else if (target != AvaloniaProperty.UnsetValue)
{
StartListeningCore(_target);
_listening = true;
}
else
{
_listening = false;
}
}
private void StopListening()
{
StopListeningCore();
}
private BindingNotification TargetNullNotification()
{
return new BindingNotification(

174
src/Avalonia.Base/Data/Core/ExpressionObserver.cs

@ -5,19 +5,18 @@ using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
using Avalonia.Data.Core.Parsers;
using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
namespace Avalonia.Data.Core
{
/// <summary>
/// Observes and sets the value of an expression on an object.
/// </summary>
public class ExpressionObserver : ObservableBase<object>, IDescription
public class ExpressionObserver : LightweightObservableBase<object>, IDescription
{
/// <summary>
/// An ordered collection of property accessor plugins that can be used to customize
@ -56,9 +55,9 @@ namespace Avalonia.Data.Core
private static readonly object UninitializedValue = new object();
private readonly ExpressionNode _node;
private readonly Subject<Unit> _finished;
private readonly object _root;
private IObservable<object> _result;
private object _root;
private IDisposable _rootSubscription;
private WeakReference<object> _value;
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -66,7 +65,7 @@ namespace Avalonia.Data.Core
/// <param name="root">The root object.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
object root,
@ -83,22 +82,13 @@ namespace Avalonia.Data.Core
_root = new WeakReference(root);
}
public static ExpressionObserver Create<T, U>(
T root,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
}
/// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="node">The expression.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
IObservable<object> rootObservable,
@ -110,20 +100,6 @@ namespace Avalonia.Data.Core
_node = node;
Description = description;
_root = rootObservable;
_finished = new Subject<Unit>();
}
public static ExpressionObserver Create<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
return new ExpressionObserver(
rootObservable.Select(o => (object)o),
Parse(expression, enableDataValidation),
description ?? expression.ToString());
}
/// <summary>
@ -133,7 +109,7 @@ namespace Avalonia.Data.Core
/// <param name="node">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="node"/> will be used.
/// A description of the expression.
/// </param>
public ExpressionObserver(
Func<object> rootGetter,
@ -143,14 +119,63 @@ namespace Avalonia.Data.Core
{
Contract.Requires<ArgumentNullException>(rootGetter != null);
Contract.Requires<ArgumentNullException>(update != null);
Description = description;
_node = node;
_finished = new Subject<Unit>();
_node.Target = new WeakReference(rootGetter());
_root = update.Select(x => rootGetter());
}
/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="root">The root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
T root,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
return new ExpressionObserver(root, Parse(expression, enableDataValidation), description ?? expression.ToString());
}
/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootObservable">An observable which provides the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
IObservable<T> rootObservable,
Expression<Func<T, U>> expression,
bool enableDataValidation = false,
string description = null)
{
Contract.Requires<ArgumentNullException>(rootObservable != null);
return new ExpressionObserver(
rootObservable.Select(o => (object)o),
Parse(expression, enableDataValidation),
description ?? expression.ToString());
}
/// <summary>
/// Creates a new instance of the <see cref="ExpressionObserver"/> class.
/// </summary>
/// <param name="rootGetter">A function which gets the root object.</param>
/// <param name="expression">The expression.</param>
/// <param name="update">An observable which triggers a re-read of the getter.</param>
/// <param name="enableDataValidation">Whether or not to track data validation</param>
/// <param name="description">
/// A description of the expression. If null, <paramref name="expression"/>'s string representation will be used.
/// </param>
public static ExpressionObserver Create<T, U>(
Func<T> rootGetter,
Expression<Func<T, U>> expression,
@ -180,7 +205,7 @@ namespace Avalonia.Data.Core
/// </returns>
public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
{
if (Leaf is ISettableNode settable)
if (Leaf is SettableNode settable)
{
var node = _node;
while (node != null)
@ -214,7 +239,7 @@ namespace Avalonia.Data.Core
/// Gets the type of the expression result or null if the expression could not be
/// evaluated.
/// </summary>
public Type ResultType => (Leaf as ISettableNode)?.PropertyType;
public Type ResultType => (Leaf as SettableNode)?.PropertyType;
/// <summary>
/// Gets the leaf node.
@ -229,71 +254,54 @@ namespace Avalonia.Data.Core
}
}
/// <inheritdoc/>
protected override IDisposable SubscribeCore(IObserver<object> observer)
protected override void Initialize()
{
if (_result == null)
{
var source = (IObservable<object>)_node;
if (_finished != null)
{
source = source.TakeUntil(_finished);
}
_result = Observable.Using(StartRoot, _ => source)
.Select(ToWeakReference)
.Publish(UninitializedValue)
.RefCount()
.Where(x => x != UninitializedValue)
.Select(Translate);
}
_value = null;
_node.Subscribe(ValueChanged);
StartRoot();
}
return _result.Subscribe(observer);
protected override void Deinitialize()
{
_rootSubscription?.Dispose();
_rootSubscription = null;
_node.Unsubscribe();
}
private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
protected override void Subscribed(IObserver<object> observer, bool first)
{
var parser = new ExpressionTreeParser(enableDataValidation);
return parser.Parse(expression);
if (!first && _value != null && _value.TryGetTarget(out var value))
{
observer.OnNext(value);
}
}
private static object ToWeakReference(object o)
private static ExpressionNode Parse(LambdaExpression expression, bool enableDataValidation)
{
return o is BindingNotification ? o : new WeakReference(o);
return ExpressionTreeParser.Parse(expression, enableDataValidation);
}
private object Translate(object o)
private void StartRoot()
{
if (o is WeakReference weak)
if (_root is IObservable<object> observable)
{
return weak.Target;
_rootSubscription = observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
x => PublishCompleted(),
() => PublishCompleted());
}
else if (BindingNotification.ExtractError(o) is MarkupBindingChainException broken)
else
{
broken.Commit(Description);
_node.Target = (WeakReference)_root;
}
return o;
}
private IDisposable StartRoot()
private void ValueChanged(object value)
{
switch (_root)
{
case IObservable<object> observable:
return observable.Subscribe(
x => _node.Target = new WeakReference(x != AvaloniaProperty.UnsetValue ? x : null),
_ => _finished.OnNext(Unit.Default),
() => _finished.OnNext(Unit.Default));
case WeakReference weak:
_node.Target = weak;
break;
default:
throw new AvaloniaInternalException("The ExpressionObserver._root member should only be either an observable or WeakReference.");
}
return Disposable.Empty;
var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
broken?.Commit(Description);
_value = new WeakReference<object>(value);
PublishNext(value);
}
}
}

15
src/Avalonia.Base/Data/Core/ISettableNode.cs

@ -1,15 +0,0 @@
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Data.Core
{
interface ISettableNode
{
bool SetTargetValue(object value, BindingPriority priority);
Type PropertyType { get; }
}
}

44
src/Avalonia.Base/Data/Core/IndexerExpressionNode.cs

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Avalonia.Data;
@ -9,35 +10,35 @@ namespace Avalonia.Data.Core
{
class IndexerExpressionNode : IndexerNodeBase
{
private readonly ParameterExpression parameter;
private readonly IndexExpression expression;
private readonly Delegate setDelegate;
private readonly Delegate getDelegate;
private readonly Delegate firstArgumentDelegate;
private readonly ParameterExpression _parameter;
private readonly IndexExpression _expression;
private readonly Delegate _setDelegate;
private readonly Delegate _getDelegate;
private readonly Delegate _firstArgumentDelegate;
public IndexerExpressionNode(IndexExpression expression)
{
parameter = Expression.Parameter(expression.Object.Type);
this.expression = expression.Update(parameter, expression.Arguments);
_parameter = Expression.Parameter(expression.Object.Type);
_expression = expression.Update(_parameter, expression.Arguments);
getDelegate = Expression.Lambda(this.expression, parameter).Compile();
_getDelegate = Expression.Lambda(_expression, _parameter).Compile();
var valueParameter = Expression.Parameter(expression.Type);
setDelegate = Expression.Lambda(Expression.Assign(this.expression, valueParameter), parameter, valueParameter).Compile();
_setDelegate = Expression.Lambda(Expression.Assign(_expression, valueParameter), _parameter, valueParameter).Compile();
firstArgumentDelegate = Expression.Lambda(this.expression.Arguments[0], parameter).Compile();
_firstArgumentDelegate = Expression.Lambda(_expression.Arguments[0], _parameter).Compile();
}
public override Type PropertyType => expression.Type;
public override Type PropertyType => _expression.Type;
public override string Description => expression.ToString();
public override string Description => _expression.ToString();
public override bool SetTargetValue(object value, BindingPriority priority)
protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
try
{
setDelegate.DynamicInvoke(Target.Target, value);
_setDelegate.DynamicInvoke(Target.Target, value);
return true;
}
catch (Exception)
@ -48,14 +49,23 @@ namespace Avalonia.Data.Core
protected override object GetValue(object target)
{
return getDelegate.DynamicInvoke(target);
try
{
return _getDelegate.DynamicInvoke(target);
}
catch (TargetInvocationException e) when (e.InnerException is ArgumentOutOfRangeException
|| e.InnerException is IndexOutOfRangeException
|| e.InnerException is KeyNotFoundException)
{
return AvaloniaProperty.UnsetValue;
}
}
protected override bool ShouldUpdate(object sender, PropertyChangedEventArgs e)
{
return expression.Indexer.Name == e.PropertyName;
return _expression.Indexer == null || _expression.Indexer.Name == e.PropertyName;
}
protected override int? TryGetFirstArgumentAsInt() => firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
protected override int? TryGetFirstArgumentAsInt() => _firstArgumentDelegate.DynamicInvoke(Target.Target) as int?;
}
}

21
src/Avalonia.Base/Data/Core/IndexerNodeBase.cs

@ -13,14 +13,18 @@ using Avalonia.Utilities;
namespace Avalonia.Data.Core
{
public abstract class IndexerNodeBase : ExpressionNode, ISettableNode
public abstract class IndexerNodeBase : SettableNode
{
protected override IObservable<object> StartListeningCore(WeakReference reference)
private IDisposable _subscription;
protected override void StartListeningCore(WeakReference reference)
{
var target = reference.Target;
var incc = target as INotifyCollectionChanged;
var inpc = target as INotifyPropertyChanged;
var inputs = new List<IObservable<object>>();
if (target is INotifyCollectionChanged incc)
if (incc != null)
{
inputs.Add(WeakObservable.FromEventPattern<INotifyCollectionChanged, NotifyCollectionChangedEventArgs>(
incc,
@ -29,7 +33,7 @@ namespace Avalonia.Data.Core
.Select(_ => GetValue(target)));
}
if (target is INotifyPropertyChanged inpc)
if (inpc != null)
{
inputs.Add(WeakObservable.FromEventPattern<INotifyPropertyChanged, PropertyChangedEventArgs>(
inpc,
@ -38,12 +42,13 @@ namespace Avalonia.Data.Core
.Select(_ => GetValue(target)));
}
return inputs.Merge().StartWith(GetValue(target));
_subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged);
}
public abstract bool SetTargetValue(object value, BindingPriority priority);
public abstract Type PropertyType { get; }
protected override void StopListeningCore()
{
_subscription.Dispose();
}
protected abstract object GetValue(object target);

11
src/Avalonia.Base/Data/Core/Parsers/ExpressionTreeParser.cs

@ -6,16 +6,9 @@ using System.Text;
namespace Avalonia.Data.Core.Parsers
{
class ExpressionTreeParser
static class ExpressionTreeParser
{
private readonly bool enableDataValidation;
public ExpressionTreeParser(bool enableDataValidation)
{
this.enableDataValidation = enableDataValidation;
}
public ExpressionNode Parse(Expression expr)
public static ExpressionNode Parse(Expression expr, bool enableDataValidation)
{
var visitor = new ExpressionVisitorNodeBuilder(enableDataValidation);

37
src/Avalonia.Base/Data/Core/Parsers/ExpressionVisitorNodeBuilder.cs

@ -10,10 +10,11 @@ namespace Avalonia.Data.Core.Parsers
{
class ExpressionVisitorNodeBuilder : ExpressionVisitor
{
private const string MultiDimensionalArrayGetterMethodName = "Get";
private static PropertyInfo AvaloniaObjectIndexer;
private static MethodInfo CreateDelegateMethod;
private readonly bool enableDataValidation;
private readonly bool _enableDataValidation;
static ExpressionVisitorNodeBuilder()
{
@ -25,7 +26,7 @@ namespace Avalonia.Data.Core.Parsers
public ExpressionVisitorNodeBuilder(bool enableDataValidation)
{
this.enableDataValidation = enableDataValidation;
_enableDataValidation = enableDataValidation;
Nodes = new List<ExpressionNode>();
}
@ -61,7 +62,7 @@ namespace Avalonia.Data.Core.Parsers
protected override Expression VisitMember(MemberExpression node)
{
var visited = base.VisitMember(node);
Nodes.Add(new PropertyAccessorNode(node.Member.Name, enableDataValidation));
Nodes.Add(new PropertyAccessorNode(node.Member.Name, _enableDataValidation));
return visited;
}
@ -72,7 +73,7 @@ namespace Avalonia.Data.Core.Parsers
if (node.Indexer == AvaloniaObjectIndexer)
{
var property = GetArgumentExpressionValue<AvaloniaProperty>(node.Arguments[0]);
Nodes.Add(new AvaloniaPropertyAccessorNode(property, enableDataValidation));
Nodes.Add(new AvaloniaPropertyAccessorNode(property, _enableDataValidation));
}
else
{
@ -98,7 +99,6 @@ namespace Avalonia.Data.Core.Parsers
{
if (node.NodeType == ExpressionType.ArrayIndex)
{
base.VisitBinary(node);
return Visit(Expression.MakeIndex(node.Left, null, new[] { node.Right }));
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
@ -161,21 +161,13 @@ namespace Avalonia.Data.Core.Parsers
protected override Expression VisitMethodCall(MethodCallExpression node)
{
var property = TryGetPropertyFromMethod(node.Method);
if (property != null)
{
return Visit(Expression.MakeIndex(node.Object, property, node.Arguments));
}
if (node.Method == CreateDelegateMethod)
{
var visited = Visit(node.Arguments[1]);
Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue<MethodInfo>(node.Object).Name, enableDataValidation));
return visited;
Nodes.Add(new PropertyAccessorNode(GetArgumentExpressionValue<MethodInfo>(node.Object).Name, _enableDataValidation));
return node;
}
if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`'))
else if (node.Method.Name == StreamBindingExtensions.StreamBindingName || node.Method.Name.StartsWith(StreamBindingExtensions.StreamBindingName + '`'))
{
if (node.Method.IsStatic)
{
@ -189,7 +181,18 @@ namespace Avalonia.Data.Core.Parsers
return node;
}
throw new ExpressionParseException(0, $"Invalid expression type in binding expression: {node.NodeType}.");
var property = TryGetPropertyFromMethod(node.Method);
if (property != null)
{
return Visit(Expression.MakeIndex(node.Object, property, node.Arguments));
}
else if (node.Object.Type.IsArray && node.Method.Name == MultiDimensionalArrayGetterMethodName)
{
return Visit(Expression.MakeIndex(node.Object, null, node.Arguments));
}
throw new ExpressionParseException(0, $"Invalid method call in binding expression: '{node.Method.DeclaringType.AssemblyQualifiedName}.{node.Method.Name}'.");
}
private PropertyInfo TryGetPropertyFromMethod(MethodInfo method)

10
src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs

@ -117,15 +117,15 @@ namespace Avalonia.Data.Core.Plugins
return false;
}
protected override void Dispose(bool disposing)
protected override void SubscribeCore()
{
_subscription?.Dispose();
_subscription = null;
_subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
}
protected override void SubscribeCore(IObserver<object> observer)
protected override void UnsubscribeCore()
{
_subscription = Instance?.GetWeakObservable(_property).Subscribe(observer);
_subscription?.Dispose();
_subscription = null;
}
}
}

10
src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs

@ -55,13 +55,13 @@ namespace Avalonia.Data.Core.Plugins
/// <param name="value">The value.</param>
void IObserver<object>.OnNext(object value) => InnerValueChanged(value);
/// <inheritdoc/>
protected override void Dispose(bool disposing) => _inner.Dispose();
/// <summary>
/// Begins listening to the inner <see cref="IPropertyAccessor"/>.
/// </summary>
protected override void SubscribeCore(IObserver<object> observer) => _inner.Subscribe(this);
protected override void SubscribeCore() => _inner.Subscribe(InnerValueChanged);
/// <inheritdoc/>
protected override void UnsubscribeCore() => _inner.Dispose();
/// <summary>
/// Called when the inner <see cref="IPropertyAccessor"/> notifies with a new value.
@ -74,7 +74,7 @@ namespace Avalonia.Data.Core.Plugins
protected virtual void InnerValueChanged(object value)
{
var notification = value as BindingNotification ?? new BindingNotification(value);
Observer.OnNext(notification);
PublishValue(notification);
}
}
}

5
src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs

@ -1,7 +1,6 @@
// 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 Avalonia.Data;
using System;
using System.Reflection;
@ -36,11 +35,11 @@ namespace Avalonia.Data.Core.Plugins
}
catch (TargetInvocationException ex)
{
Observer.OnNext(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError));
PublishValue(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError));
}
catch (Exception ex)
{
Observer.OnNext(new BindingNotification(ex, BindingErrorType.DataValidationError));
PublishValue(new BindingNotification(ex, BindingErrorType.DataValidationError));
}
return false;

14
src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
@ -10,7 +9,7 @@ namespace Avalonia.Data.Core.Plugins
/// Defines an accessor to a property on an object returned by a
/// <see cref="IPropertyAccessorPlugin"/>
/// </summary>
public interface IPropertyAccessor : IObservable<object>, IDisposable
public interface IPropertyAccessor : IDisposable
{
/// <summary>
/// Gets the type of the property.
@ -38,5 +37,16 @@ namespace Avalonia.Data.Core.Plugins
/// True if the property was set; false if the property could not be set.
/// </returns>
bool SetValue(object value, BindingPriority priority);
/// <summary>
/// Subscribes to the value of the member.
/// </summary>
/// <param name="listener">A method that receives the values.</param>
void Subscribe(Action<object> listener);
/// <summary>
/// Unsubscribes to the value of the member.
/// </summary>
void Unsubscribe();
}
}

19
src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.Data.Core.Plugins
@ -40,43 +39,43 @@ namespace Avalonia.Data.Core.Plugins
{
if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName))
{
Observer.OnNext(CreateBindingNotification(Value));
PublishValue(CreateBindingNotification(Value));
}
}
protected override void Dispose(bool disposing)
protected override void SubscribeCore()
{
base.Dispose(disposing);
var target = _reference.Target as INotifyDataErrorInfo;
if (target != null)
{
WeakSubscriptionManager.Unsubscribe(
WeakSubscriptionManager.Subscribe(
target,
nameof(target.ErrorsChanged),
this);
}
base.SubscribeCore();
}
protected override void SubscribeCore(IObserver<object> observer)
protected override void UnsubscribeCore()
{
var target = _reference.Target as INotifyDataErrorInfo;
if (target != null)
{
WeakSubscriptionManager.Subscribe(
WeakSubscriptionManager.Unsubscribe(
target,
nameof(target.ErrorsChanged),
this);
}
base.SubscribeCore(observer);
base.UnsubscribeCore();
}
protected override void InnerValueChanged(object value)
{
base.InnerValueChanged(CreateBindingNotification(value));
PublishValue(CreateBindingNotification(value));
}
private BindingNotification CreateBindingNotification(object value)

16
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -103,7 +103,13 @@ namespace Avalonia.Data.Core.Plugins
}
}
protected override void Dispose(bool disposing)
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
}
protected override void UnsubscribeCore()
{
var inpc = _reference.Target as INotifyPropertyChanged;
@ -116,18 +122,12 @@ namespace Avalonia.Data.Core.Plugins
}
}
protected override void SubscribeCore(IObserver<object> observer)
{
SendCurrentValue();
SubscribeToChanges();
}
private void SendCurrentValue()
{
try
{
var value = Value;
Observer.OnNext(value);
PublishValue(value);
}
catch { }
}

8
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -74,14 +74,18 @@ namespace Avalonia.Data.Core.Plugins
public override bool SetValue(object value, BindingPriority priority) => false;
protected override void SubscribeCore(IObserver<object> observer)
protected override void SubscribeCore()
{
try
{
Observer.OnNext(Value);
PublishValue(Value);
}
catch { }
}
protected override void UnsubscribeCore()
{
}
}
}
}

68
src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs

@ -2,67 +2,75 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
/// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
/// </summary>
/// <remarks>
/// <see cref="IPropertyAccessor"/> is an observable that will only be subscribed to one time.
/// In addition, the subscription can be disposed by calling <see cref="Dispose()"/> on the
/// property accessor itself - this prevents needing to hold two references for a subscription.
/// </remarks>
public abstract class PropertyAccessorBase : IPropertyAccessor
{
private Action<object> _listener;
/// <inheritdoc/>
public abstract Type PropertyType { get; }
/// <inheritdoc/>
public abstract object Value { get; }
/// <summary>
/// Stops the subscription.
/// </summary>
public void Dispose() => Dispose(true);
/// <inheritdoc/>
public void Dispose()
{
if (_listener != null)
{
Unsubscribe();
}
}
/// <inheritdoc/>
public abstract bool SetValue(object value, BindingPriority priority);
/// <summary>
/// The currently subscribed observer.
/// </summary>
protected IObserver<object> Observer { get; private set; }
/// <inheritdoc/>
public IDisposable Subscribe(IObserver<object> observer)
public void Subscribe(Action<object> listener)
{
Contract.Requires<ArgumentNullException>(observer != null);
Contract.Requires<ArgumentNullException>(listener != null);
if (Observer != null)
if (_listener != null)
{
throw new InvalidOperationException(
"A property accessor can be subscribed to only once.");
"A member accessor can be subscribed to only once.");
}
Observer = observer;
SubscribeCore(observer);
return this;
_listener = listener;
SubscribeCore();
}
public void Unsubscribe()
{
if (_listener == null)
{
throw new InvalidOperationException(
"The member accessor was not subscribed.");
}
UnsubscribeCore();
_listener = null;
}
/// <summary>
/// Publishes a value to the listener.
/// </summary>
/// <param name="value">The value.</param>
protected void PublishValue(object value) => _listener?.Invoke(value);
/// <summary>
/// Stops listening to the property.
/// When overridden in a derived class, begins listening to the member.
/// </summary>
/// <param name="disposing">
/// True if the <see cref="Dispose()"/> method was called, false if the object is being
/// finalized.
/// </param>
protected virtual void Dispose(bool disposing) => Observer = null;
protected abstract void SubscribeCore();
/// <summary>
/// When overridden in a derived class, begins listening to the property.
/// When overridden in a derived class, stops listening to the member.
/// </summary>
protected abstract void SubscribeCore(IObserver<object> observer);
protected abstract void UnsubscribeCore();
}
}

11
src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs

@ -1,6 +1,4 @@
using System;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins
{
@ -37,10 +35,13 @@ namespace Avalonia.Data.Core.Plugins
return false;
}
public IDisposable Subscribe(IObserver<object> observer)
public void Subscribe(Action<object> listener)
{
listener(_error);
}
public void Unsubscribe()
{
observer.OnNext(_error);
return Disposable.Empty;
}
}
}

38
src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs

@ -3,14 +3,12 @@
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core
{
public class PropertyAccessorNode : ExpressionNode, ISettableNode
public class PropertyAccessorNode : SettableNode
{
private readonly bool _enableValidation;
private IPropertyAccessor _accessor;
@ -23,19 +21,23 @@ namespace Avalonia.Data.Core
public override string Description => PropertyName;
public string PropertyName { get; }
public Type PropertyType => _accessor?.PropertyType;
public override Type PropertyType => _accessor?.PropertyType;
public bool SetTargetValue(object value, BindingPriority priority)
protected override bool SetTargetValueCore(object value, BindingPriority priority)
{
if (_accessor != null)
{
try { return _accessor.SetValue(value, priority); } catch { }
try
{
return _accessor.SetValue(value, priority);
}
catch { }
}
return false;
}
protected override IObservable<object> StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference reference)
{
var plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
var accessor = plugin?.Start(reference, PropertyName);
@ -51,14 +53,20 @@ namespace Avalonia.Data.Core
}
}
// Ensure that _accessor is set for the duration of the subscription.
return Observable.Using(
() =>
{
_accessor = accessor;
return Disposable.Create(() => _accessor = null);
},
_ => accessor);
if (accessor == null)
{
throw new NotSupportedException(
$"Could not find a matching property accessor for {PropertyName}.");
}
accessor.Subscribe(ValueChanged);
_accessor = accessor;
}
protected override void StopListeningCore()
{
_accessor.Dispose();
_accessor = null;
}
}
}

38
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -0,0 +1,38 @@
using Avalonia.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Data.Core
{
public abstract class SettableNode : ExpressionNode
{
public bool SetTargetValue(object value, BindingPriority priority)
{
if (ShouldNotSet(value))
{
return true;
}
return SetTargetValueCore(value, priority);
}
private bool ShouldNotSet(object value)
{
if (PropertyType == null)
{
return false;
}
if (PropertyType.IsValueType)
{
return LastValue?.Target != null && LastValue.Target.Equals(value);
}
return LastValue != null && Object.ReferenceEquals(LastValue?.Target, value);
}
protected abstract bool SetTargetValueCore(object value, BindingPriority priority);
public abstract Type PropertyType { get; }
}
}

5
src/Avalonia.Base/Data/Core/StreamBindingExtensions.cs

@ -14,6 +14,11 @@ namespace Avalonia
throw new InvalidOperationException("This should be used only in a binding expression");
}
public static object StreamBinding(this Task @this)
{
throw new InvalidOperationException("This should be used only in a binding expression");
}
public static T StreamBinding<T>(this IObservable<T> @this)
{
throw new InvalidOperationException("This should be used only in a binding expression");

17
src/Avalonia.Base/Data/Core/StreamNode.cs

@ -2,30 +2,37 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Globalization;
using Avalonia.Data;
using System.Reactive.Linq;
namespace Avalonia.Data.Core
{
public class StreamNode : ExpressionNode
{
private IDisposable _subscription;
public override string Description => "^";
protected override IObservable<object> StartListeningCore(WeakReference reference)
protected override void StartListeningCore(WeakReference reference)
{
foreach (var plugin in ExpressionObserver.StreamHandlers)
{
if (plugin.Match(reference))
{
return plugin.Start(reference);
_subscription = plugin.Start(reference).Subscribe(ValueChanged);
return;
}
}
// TODO: Improve error.
return Observable.Return(new BindingNotification(
ValueChanged(new BindingNotification(
new MarkupBindingChainException("Stream operator applied to unsupported type", Description),
BindingErrorType.Error));
}
protected override void StopListeningCore()
{
_subscription?.Dispose();
_subscription = null;
}
}
}

9
src/Avalonia.Base/IPriorityValueOwner.cs

@ -13,18 +13,19 @@ namespace Avalonia
/// <summary>
/// Called when a <see cref="PriorityValue"/>'s value changes.
/// </summary>
/// <param name="sender">The source of the change.</param>
/// <param name="property">The the property that has changed.</param>
/// <param name="priority">The priority of the value.</param>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
void Changed(PriorityValue sender, object oldValue, object newValue);
void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue);
/// <summary>
/// Called when a <see cref="BindingNotification"/> is received by a
/// <see cref="PriorityValue"/>.
/// </summary>
/// <param name="sender">The source of the change.</param>
/// <param name="property">The the property that has changed.</param>
/// <param name="notification">The notification.</param>
void BindingNotificationReceived(PriorityValue sender, BindingNotification notification);
void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
/// <summary>
/// Ensures that the current thread is the UI thread.

4
src/Avalonia.Base/PriorityValue.cs

@ -281,12 +281,12 @@ namespace Avalonia
if (notification == null || notification.HasValue)
{
notify(() => Owner?.Changed(this, old, Value));
notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
}
if (notification != null)
{
Owner?.BindingNotificationReceived(this, notification);
Owner?.BindingNotificationReceived(Property, notification);
}
}
else

42
src/Avalonia.Base/Reactive/AvaloniaObservable.cs

@ -1,42 +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.Reactive;
using System.Reactive.Disposables;
namespace Avalonia.Reactive
{
/// <summary>
/// An <see cref="IObservable{T}"/> with an additional description.
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence.</typeparam>
public class AvaloniaObservable<T> : ObservableBase<T>, IDescription
{
private readonly Func<IObserver<T>, IDisposable> _subscribe;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaObservable{T}"/> class.
/// </summary>
/// <param name="subscribe">The subscribe function.</param>
/// <param name="description">The description of the observable.</param>
public AvaloniaObservable(Func<IObserver<T>, IDisposable> subscribe, string description)
{
Contract.Requires<ArgumentNullException>(subscribe != null);
_subscribe = subscribe;
Description = description;
}
/// <summary>
/// Gets the description of the observable.
/// </summary>
public string Description { get; }
/// <inheritdoc/>
protected override IDisposable SubscribeCore(IObserver<T> observer)
{
return _subscribe(observer) ?? Disposable.Empty;
}
}
}

46
src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs

@ -0,0 +1,46 @@
using System;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyChangedObservable :
LightweightObservableBase<AvaloniaPropertyChangedEventArgs>,
IDescription
{
private readonly WeakReference<IAvaloniaObject> _target;
private readonly AvaloniaProperty _property;
public AvaloniaPropertyChangedObservable(
IAvaloniaObject target,
AvaloniaProperty property)
{
_target = new WeakReference<IAvaloniaObject>(target);
_property = property;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
protected override void Initialize()
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged += PropertyChanged;
}
}
protected override void Deinitialize()
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
}
}
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
PublishNext(e);
}
}
}
}

52
src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs

@ -0,0 +1,52 @@
using System;
namespace Avalonia.Reactive
{
internal class AvaloniaPropertyObservable<T> : LightweightObservableBase<T>, IDescription
{
private readonly WeakReference<IAvaloniaObject> _target;
private readonly AvaloniaProperty _property;
private T _value;
public AvaloniaPropertyObservable(
IAvaloniaObject target,
AvaloniaProperty property)
{
_target = new WeakReference<IAvaloniaObject>(target);
_property = property;
}
public string Description => $"{_target.GetType().Name}.{_property.Name}";
protected override void Initialize()
{
if (_target.TryGetTarget(out var target))
{
_value = (T)target.GetValue(_property);
target.PropertyChanged += PropertyChanged;
}
}
protected override void Deinitialize()
{
if (_target.TryGetTarget(out var target))
{
target.PropertyChanged -= PropertyChanged;
}
}
protected override void Subscribed(IObserver<T> observer, bool first)
{
observer.OnNext(_value);
}
private void PropertyChanged(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
_value = (T)e.NewValue;
PublishNext(_value);
}
}
}
}

202
src/Avalonia.Base/Reactive/LightweightObservableBase.cs

@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Reactive;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.Threading;
namespace Avalonia.Reactive
{
/// <summary>
/// Lightweight base class for observable implementations.
/// </summary>
/// <typeparam name="T">The observable type.</typeparam>
/// <remarks>
/// <see cref="ObservableBase{T}"/> is rather heavyweight in terms of allocations and memory
/// usage. This class provides a more lightweight base for some internal observable types
/// in the Avalonia framework.
/// </remarks>
public abstract class LightweightObservableBase<T> : IObservable<T>
{
private Exception _error;
private List<IObserver<T>> _observers = new List<IObserver<T>>();
public IDisposable Subscribe(IObserver<T> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
Dispatcher.UIThread.VerifyAccess();
var first = false;
for (; ; )
{
if (Volatile.Read(ref _observers) == null)
{
if (_error != null)
{
observer.OnError(_error);
}
else
{
observer.OnCompleted();
}
return Disposable.Empty;
}
lock (this)
{
if (_observers == null)
{
continue;
}
first = _observers.Count == 0;
_observers.Add(observer);
break;
}
}
if (first)
{
Initialize();
}
Subscribed(observer, first);
return new RemoveObserver(this, observer);
}
void Remove(IObserver<T> observer)
{
if (Volatile.Read(ref _observers) != null)
{
lock (this)
{
var observers = _observers;
if (observers != null)
{
observers.Remove(observer);
if (observers.Count == 0)
{
observers.TrimExcess();
}
else
{
return;
}
} else
{
return;
}
}
Deinitialize();
}
}
sealed class RemoveObserver : IDisposable
{
LightweightObservableBase<T> _parent;
IObserver<T> _observer;
public RemoveObserver(LightweightObservableBase<T> parent, IObserver<T> observer)
{
_parent = parent;
Volatile.Write(ref _observer, observer);
}
public void Dispose()
{
var observer = _observer;
Interlocked.Exchange(ref _parent, null)?.Remove(observer);
_observer = null;
}
}
protected abstract void Initialize();
protected abstract void Deinitialize();
protected void PublishNext(T value)
{
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[] observers;
lock (this)
{
if (_observers == null)
{
return;
}
observers = _observers.ToArray();
}
foreach (var observer in observers)
{
observer.OnNext(value);
}
}
}
protected void PublishCompleted()
{
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[] observers;
lock (this)
{
if (_observers == null)
{
return;
}
observers = _observers.ToArray();
Volatile.Write(ref _observers, null);
}
foreach (var observer in observers)
{
observer.OnCompleted();
}
Deinitialize();
}
}
protected void PublishError(Exception error)
{
if (Volatile.Read(ref _observers) != null)
{
IObserver<T>[] observers;
lock (this)
{
if (_observers == null)
{
return;
}
_error = error;
observers = _observers.ToArray();
Volatile.Write(ref _observers, null);
}
foreach (var observer in observers)
{
observer.OnError(error);
}
Deinitialize();
}
}
protected virtual void Subscribed(IObserver<T> observer, bool first)
{
}
}
}

76
src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs

@ -0,0 +1,76 @@
using System;
using Avalonia.Threading;
namespace Avalonia.Reactive
{
public abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable
{
private Exception _error;
private IObserver<T> _observer;
private bool _completed;
public IDisposable Subscribe(IObserver<T> observer)
{
Contract.Requires<ArgumentNullException>(observer != null);
Dispatcher.UIThread.VerifyAccess();
if (_observer != null)
{
throw new InvalidOperationException("The observable can only be subscribed once.");
}
if (_error != null)
{
observer.OnError(_error);
}
else if (_completed)
{
observer.OnCompleted();
}
else
{
_observer = observer;
Subscribed();
}
return this;
}
void IDisposable.Dispose()
{
Unsubscribed();
_observer = null;
}
protected abstract void Unsubscribed();
protected void PublishNext(T value)
{
_observer?.OnNext(value);
}
protected void PublishCompleted()
{
if (_observer != null)
{
_observer.OnCompleted();
_completed = true;
Unsubscribed();
_observer = null;
}
}
protected void PublishError(Exception error)
{
if (_observer != null)
{
_observer.OnError(error);
_error = error;
Unsubscribed();
_observer = null;
}
}
protected abstract void Subscribed();
}
}

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

Loading…
Cancel
Save