Browse Source

Merge branch 'master' into fixed/1664-menu-item-left-selected

pull/1739/head
danwalmsley 8 years ago
committed by GitHub
parent
commit
99dc2bc11c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 58
      Avalonia.sln
  13. 369
      build.cake
  14. 4
      build/SkiaSharp.props
  15. 5
      build/System.Memory.props
  16. 17
      build/XUnit.props
  17. 15
      cake.config
  18. 92
      packages.cake
  19. 14
      parameters.cake
  20. 0
      samples/BindingDemo/App.config
  21. 0
      samples/BindingDemo/App.xaml
  22. 2
      samples/BindingDemo/App.xaml.cs
  23. 0
      samples/BindingDemo/BindingDemo.csproj
  24. 4
      samples/BindingDemo/MainWindow.xaml
  25. 4
      samples/BindingDemo/MainWindow.xaml.cs
  26. 0
      samples/BindingDemo/TestItemView.xaml
  27. 2
      samples/BindingDemo/TestItemView.xaml.cs
  28. 2
      samples/BindingDemo/ViewModels/DataAnnotationsErrorViewModel.cs
  29. 2
      samples/BindingDemo/ViewModels/ExceptionErrorViewModel.cs
  30. 2
      samples/BindingDemo/ViewModels/IndeiErrorViewModel.cs
  31. 2
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  32. 2
      samples/BindingDemo/ViewModels/NestedCommandViewModel.cs
  33. 2
      samples/BindingDemo/ViewModels/TestItem.cs
  34. 9
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  35. 4
      samples/ControlCatalog.NetCore/Program.cs
  36. 4
      samples/ControlCatalog/SideBar.xaml
  37. 2
      samples/RemoteDemo/Program.cs
  38. 0
      samples/RemoteDemo/RemoteDemo.csproj
  39. 0
      samples/RenderDemo/App.config
  40. 2
      samples/RenderDemo/App.xaml
  41. 2
      samples/RenderDemo/App.xaml.cs
  42. 2
      samples/RenderDemo/MainWindow.xaml
  43. 4
      samples/RenderDemo/MainWindow.xaml.cs
  44. 0
      samples/RenderDemo/Pages/AnimationsPage.xaml
  45. 4
      samples/RenderDemo/Pages/AnimationsPage.xaml.cs
  46. 0
      samples/RenderDemo/Pages/ClippingPage.xaml
  47. 2
      samples/RenderDemo/Pages/ClippingPage.xaml.cs
  48. 0
      samples/RenderDemo/Pages/DrawingPage.xaml
  49. 2
      samples/RenderDemo/Pages/DrawingPage.xaml.cs
  50. 0
      samples/RenderDemo/RenderDemo.csproj
  51. 4
      samples/RenderDemo/SideBar.xaml
  52. 2
      samples/RenderDemo/ViewModels/AnimationsPageViewModel.cs
  53. 2
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  54. 0
      samples/VirtualizationDemo/App.config
  55. 0
      samples/VirtualizationDemo/App.xaml
  56. 2
      samples/VirtualizationDemo/App.xaml.cs
  57. 0
      samples/VirtualizationDemo/MainWindow.xaml
  58. 4
      samples/VirtualizationDemo/MainWindow.xaml.cs
  59. 2
      samples/VirtualizationDemo/Program.cs
  60. 2
      samples/VirtualizationDemo/ViewModels/ItemViewModel.cs
  61. 2
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  62. 0
      samples/VirtualizationDemo/VirtualizationDemo.csproj
  63. 1
      samples/interop/Direct3DInteropSample/MainWindow.cs
  64. 5
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  65. 2
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
  66. 7
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  67. 9
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  68. 2
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  69. 192
      src/Avalonia.Base/AvaloniaObject.cs
  70. 81
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  71. 59
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.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. 92
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  76. 15
      src/Avalonia.Base/Data/Core/ISettableNode.cs
  77. 17
      src/Avalonia.Base/Data/Core/IndexerNode.cs
  78. 10
      src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs
  79. 10
      src/Avalonia.Base/Data/Core/Plugins/DataValidatiorBase.cs
  80. 5
      src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs
  81. 14
      src/Avalonia.Base/Data/Core/Plugins/IPropertyAccessor.cs
  82. 19
      src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs
  83. 16
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  84. 8
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  85. 68
      src/Avalonia.Base/Data/Core/Plugins/PropertyAccessorBase.cs
  86. 11
      src/Avalonia.Base/Data/Core/Plugins/PropertyError.cs
  87. 38
      src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs
  88. 38
      src/Avalonia.Base/Data/Core/SettableNode.cs
  89. 17
      src/Avalonia.Base/Data/Core/StreamNode.cs
  90. 9
      src/Avalonia.Base/IPriorityValueOwner.cs
  91. 4
      src/Avalonia.Base/PriorityValue.cs
  92. 42
      src/Avalonia.Base/Reactive/AvaloniaObservable.cs
  93. 46
      src/Avalonia.Base/Reactive/AvaloniaPropertyChangedObservable.cs
  94. 52
      src/Avalonia.Base/Reactive/AvaloniaPropertyObservable.cs
  95. 202
      src/Avalonia.Base/Reactive/LightweightObservableBase.cs
  96. 76
      src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
  97. 85
      src/Avalonia.Base/Reactive/WeakPropertyChangedObservable.cs
  98. 172
      src/Avalonia.Base/ValueStore.cs
  99. 48
      src/Avalonia.Controls/AppBuilderBase.cs
  100. 112
      src/Avalonia.Controls/Application.cs

156
.editorconfig

@ -1,11 +1,159 @@
; This file is for unifying the coding style for different editors and IDEs. # editorconfig.org
; More information at http://EditorConfig.org
# top-most EditorConfig file
root = true 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] [*.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 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 Avalonia.XBuild.sln
project.lock.json project.lock.json
.idea/* .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>

58
Avalonia.sln

@ -58,6 +58,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{9B9E
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DEF5-D50F-4975-8B72-124C9EB54066}"
ProjectSection(SolutionItems) = preProject ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs src\Shared\SharedAssemblyInfo.cs = src\Shared\SharedAssemblyInfo.cs
EndProjectSection EndProjectSection
EndProject EndProject
@ -71,7 +72,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
EndProject 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 EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}" Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
EndProject EndProject
@ -105,11 +106,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Desktop", "s
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{57E0455D-D565-44BB-B069-EE1AA20F8337}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{52F55355-D120-42AC-8116-8410A7D602FA}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Tests", "tests\Avalonia.DesignerSupport.Tests\Avalonia.DesignerSupport.Tests.csproj", "{52F55355-D120-42AC-8116-8410A7D602FA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
EndProject 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 EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}"
EndProject EndProject
@ -117,7 +118,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
EndProject 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 EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
EndProject EndProject
@ -170,7 +171,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests",
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Remote.Protocol", "src\Avalonia.Remote.Protocol\Avalonia.Remote.Protocol.csproj", "{D78A720C-C0C6-478B-8564-F167F9BDD01B}"
EndProject 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 EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{4ED8B739-6F4E-4CD4-B993-545E6B5CE637}"
EndProject EndProject
@ -184,6 +185,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX
EndProject 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}" 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 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 Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 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.ActiveCfg = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|iPhoneSimulator.Build.0 = 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.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.ActiveCfg = Debug|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Debug|x86.Build.0 = 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 {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.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|iPhoneSimulator.Build.0 = 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.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.ActiveCfg = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Release|x86.Build.0 = 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 {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|NetCoreOnly.ActiveCfg = Release|Any CPU
{4ADA61C8-D191-428D-9066-EF4F0D86520F}.Release|x86.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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -2521,6 +2566,7 @@ Global
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098} {F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2} {CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} {4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{E1240B49-7B4B-4371-A00E-068778C5CF0B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

369
build.cake

@ -6,11 +6,13 @@
#addin "nuget:?package=NuGet.Core&version=2.14.0" #addin "nuget:?package=NuGet.Core&version=2.14.0"
#tool "nuget:?package=NuGet.CommandLine&version=4.3.0" #tool "nuget:?package=NuGet.CommandLine&version=4.3.0"
#tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720" #tool "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2017.1.20170613.162720"
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// TOOLS // 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 // USINGS
@ -34,20 +36,31 @@ using NuGet;
// PARAMETERS // PARAMETERS
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
Parameters parameters = new Parameters(Context); class AvaloniaBuildData
Packages packages = new Packages(Context, parameters); {
public AvaloniaBuildData(Parameters parameters, Packages packages)
{
Parameters = parameters;
Packages = packages;
}
public Parameters Parameters { get; }
public Packages Packages { get; }
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// SETUP // 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.Version,
parameters.Platform, parameters.Platform,
parameters.Configuration, parameters.Configuration,
parameters.Target,
typeof(ICakeContext).Assembly.GetName().Version.ToString()); typeof(ICakeContext).Assembly.GetName().Version.ToString());
if (parameters.IsRunningOnAppVeyor) if (parameters.IsRunningOnAppVeyor)
@ -55,8 +68,7 @@ Setup(context =>
Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name); Information("Repository Name: " + BuildSystem.AppVeyor.Environment.Repository.Name);
Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch); Information("Repository Branch: " + BuildSystem.AppVeyor.Environment.Repository.Branch);
} }
Information("Target:" + context.TargetTask.Name);
Information("Target: " + parameters.Target);
Information("Platform: " + parameters.Platform); Information("Platform: " + parameters.Platform);
Information("Configuration: " + parameters.Configuration); Information("Configuration: " + parameters.Configuration);
Information("IsLocalBuild: " + parameters.IsLocalBuild); Information("IsLocalBuild: " + parameters.IsLocalBuild);
@ -70,13 +82,15 @@ Setup(context =>
Information("IsReleasable: " + parameters.IsReleasable); Information("IsReleasable: " + parameters.IsReleasable);
Information("IsMyGetRelease: " + parameters.IsMyGetRelease); Information("IsMyGetRelease: " + parameters.IsMyGetRelease);
Information("IsNuGetRelease: " + parameters.IsNuGetRelease); Information("IsNuGetRelease: " + parameters.IsNuGetRelease);
return buildContext;
}); });
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// TEARDOWN // TEARDOWN
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
Teardown(context => Teardown<AvaloniaBuildData>((context, buildContext) =>
{ {
Information("Finished running tasks."); Information("Finished running tasks.");
}); });
@ -85,20 +99,20 @@ Teardown(context =>
// TASKS // TASKS
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
Task("Clean") Task("Clean-Impl")
.Does(() => .Does<AvaloniaBuildData>(data =>
{ {
CleanDirectories(parameters.BuildDirs); CleanDirectories(data.Parameters.BuildDirs);
CleanDirectory(parameters.ArtifactsDir); CleanDirectory(data.Parameters.ArtifactsDir);
CleanDirectory(parameters.NugetRoot); CleanDirectory(data.Parameters.NugetRoot);
CleanDirectory(parameters.ZipRoot); CleanDirectory(data.Parameters.ZipRoot);
CleanDirectory(parameters.BinRoot); CleanDirectory(data.Parameters.BinRoot);
}); });
Task("Restore-NuGet-Packages") Task("Restore-NuGet-Packages-Impl")
.IsDependentOn("Clean") .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria(parameters.IsRunningOnWindows) .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does(() => .Does<AvaloniaBuildData>(data =>
{ {
var maxRetryCount = 5; var maxRetryCount = 5;
var toolTimeout = 2d; var toolTimeout = 2d;
@ -115,13 +129,13 @@ Task("Restore-NuGet-Packages")
toolTimeout+=0.5; toolTimeout+=0.5;
}}) }})
.Execute(()=> { .Execute(()=> {
NuGetRestore(parameters.MSBuildSolution, new NuGetRestoreSettings { NuGetRestore(data.Parameters.MSBuildSolution, new NuGetRestoreSettings {
ToolTimeout = TimeSpan.FromMinutes(toolTimeout) ToolTimeout = TimeSpan.FromMinutes(toolTimeout)
}); });
}); });
}); });
void DotNetCoreBuild() void DotNetCoreBuild(Parameters parameters)
{ {
var settings = new DotNetCoreBuildSettings var settings = new DotNetCoreBuildSettings
{ {
@ -135,29 +149,28 @@ void DotNetCoreBuild()
DotNetCoreBuild(parameters.MSBuildSolution, settings); DotNetCoreBuild(parameters.MSBuildSolution, settings);
} }
Task("Build") Task("Build-Impl")
.IsDependentOn("Restore-NuGet-Packages") .Does<AvaloniaBuildData>(data =>
.Does(() =>
{ {
if(parameters.IsRunningOnWindows) if(data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
{ {
MSBuild(parameters.MSBuildSolution, settings => { MSBuild(data.Parameters.MSBuildSolution, settings => {
settings.SetConfiguration(parameters.Configuration); settings.SetConfiguration(data.Parameters.Configuration);
settings.SetVerbosity(Verbosity.Minimal); settings.SetVerbosity(Verbosity.Minimal);
settings.WithProperty("Platform", "\"" + parameters.Platform + "\""); settings.WithProperty("Platform", "\"" + data.Parameters.Platform + "\"");
settings.WithProperty("UseRoslynPathHack", "true"); settings.WithProperty("UseRoslynPathHack", "true");
settings.UseToolVersion(MSBuildToolVersion.VS2017); settings.UseToolVersion(MSBuildToolVersion.VS2017);
settings.WithProperty("Windows", "True"); settings.WithProperty("Windows", "True");
settings.SetNodeReuse(false); settings.SetNodeReuse(false);
settings.SetMaxCpuCount(0);
}); });
} }
else else
{ {
DotNetCoreBuild(); DotNetCoreBuild(data.Parameters);
} }
}); });
void RunCoreTest(string project, Parameters parameters, bool coreOnly = false) void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
{ {
if(!project.EndsWith(".csproj")) if(!project.EndsWith(".csproj"))
@ -180,83 +193,106 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
} }
} }
Task("Run-Unit-Tests") Task("Run-Unit-Tests-Impl")
.IsDependentOn("Build") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.IsDependentOn("Run-Designer-Tests") .Does<AvaloniaBuildData>(data =>
.IsDependentOn("Run-Render-Tests") {
.WithCriteria(() => !parameters.SkipTests) RunCoreTest("./tests/Avalonia.Base.UnitTests", data.Parameters, false);
.Does(() => { RunCoreTest("./tests/Avalonia.Controls.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Base.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Input.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Controls.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Input.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Layout.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Interactivity.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Markup.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Layout.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Markup.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Styling.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Markup.Xaml.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Visuals.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Styling.UnitTests", parameters, false); RunCoreTest("./tests/Avalonia.Skia.UnitTests", data.Parameters, false);
RunCoreTest("./tests/Avalonia.Visuals.UnitTests", parameters, false); if (data.Parameters.IsRunningOnWindows && !data.Parameters.IsPlatformNetCoreOnly)
if (parameters.IsRunningOnWindows) {
{ RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", data.Parameters, true);
RunCoreTest("./tests/Avalonia.Direct2D1.UnitTests", parameters, true); }
} });
});
Task("Run-Designer-Tests") Task("Run-Designer-Tests-Impl")
.IsDependentOn("Build") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.WithCriteria(() => !parameters.SkipTests) .Does<AvaloniaBuildData>(data =>
.Does(() => { {
RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", parameters, false); RunCoreTest("./tests/Avalonia.DesignerSupport.Tests", data.Parameters, false);
}); });
Task("Run-Render-Tests") Task("Run-Render-Tests-Impl")
.IsDependentOn("Build") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.Does(() => { .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true); .Does<AvaloniaBuildData>(data =>
RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true); {
}); 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") Task("Run-Leak-Tests-Impl")
.IsDependentOn("Run-Unit-Tests") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.SkipTests)
.WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does(() => .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") Task("Copy-Files-Impl")
.IsDependentOn("Copy-Files") .Does<AvaloniaBuildData>(data =>
.Does(() =>
{ {
Zip(parameters.BinRoot, parameters.ZipCoreArtifacts); CopyFiles(data.Packages.BinFiles, data.Parameters.BinRoot);
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"));
}); });
Task("Create-NuGet-Packages") Task("Zip-Files-Impl")
.IsDependentOn("Run-Unit-Tests") .Does<AvaloniaBuildData>(data =>
.IsDependentOn("Inspect") {
.Does(() => 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); NuGetPack(nuspec);
} }
}); });
Task("Publish-MyGet") Task("Publish-MyGet-Impl")
.IsDependentOn("Create-NuGet-Packages") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsLocalBuild) .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria(() => !parameters.IsPullRequest) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria(() => parameters.IsMainRepo) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMasterBranch)
.WithCriteria(() => parameters.IsMasterBranch) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMyGetRelease)
.WithCriteria(() => parameters.IsMyGetRelease) .Does<AvaloniaBuildData>(data =>
.Does(() =>
{ {
var apiKey = EnvironmentVariable("MYGET_API_KEY"); var apiKey = EnvironmentVariable("MYGET_API_KEY");
if(string.IsNullOrEmpty(apiKey)) if(string.IsNullOrEmpty(apiKey))
@ -270,7 +306,7 @@ Task("Publish-MyGet")
throw new InvalidOperationException("Could not resolve MyGet API url."); 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 { NuGetPush(nupkg, new NuGetPushSettings {
Source = apiUrl, Source = apiUrl,
@ -283,13 +319,12 @@ Task("Publish-MyGet")
Information("Publish-MyGet Task failed, but continuing with next Task..."); Information("Publish-MyGet Task failed, but continuing with next Task...");
}); });
Task("Publish-NuGet") Task("Publish-NuGet-Impl")
.IsDependentOn("Create-NuGet-Packages") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsLocalBuild)
.WithCriteria(() => !parameters.IsLocalBuild) .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPullRequest)
.WithCriteria(() => !parameters.IsPullRequest) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsMainRepo)
.WithCriteria(() => parameters.IsMainRepo) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsNuGetRelease)
.WithCriteria(() => parameters.IsNuGetRelease) .Does<AvaloniaBuildData>(data =>
.Does(() =>
{ {
var apiKey = EnvironmentVariable("NUGET_API_KEY"); var apiKey = EnvironmentVariable("NUGET_API_KEY");
if(string.IsNullOrEmpty(apiKey)) if(string.IsNullOrEmpty(apiKey))
@ -303,7 +338,7 @@ Task("Publish-NuGet")
throw new InvalidOperationException("Could not resolve NuGet API url."); 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 { NuGetPush(nupkg, new NuGetPushSettings {
ApiKey = apiKey, ApiKey = apiKey,
@ -316,102 +351,80 @@ Task("Publish-NuGet")
Information("Publish-NuGet Task failed, but continuing with next Task..."); Information("Publish-NuGet Task failed, but continuing with next Task...");
}); });
Task("Run-Leak-Tests") Task("Inspect-Impl")
.WithCriteria(parameters.IsRunningOnWindows) .WithCriteria<AvaloniaBuildData>((context, data) => data.Parameters.IsRunningOnWindows)
.IsDependentOn("Build") .WithCriteria<AvaloniaBuildData>((context, data) => !data.Parameters.IsPlatformNetCoreOnly)
.Does(() => .Does(() =>
{ {
DotNetCoreRestore("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj"); var badIssues = new []{"PossibleNullReferenceException"};
DotNetBuild("tests\\Avalonia.LeakTests\\toolproject\\tool.csproj", settings => settings.SetConfiguration("Release")); var whitelist = new []{"tests", "src\\android", "src\\ios",
var report = "tests\\Avalonia.LeakTests\\bin\\Release\\report.xml"; "src\\markup\\avalonia.markup.xaml\\portablexaml\\portable.xaml.github"};
if(System.IO.File.Exists(report)) Information("Running code inspections");
System.IO.File.Delete(report);
var exitCode = StartProcess(Context.Tools.Resolve("inspectcode.exe"),
var toolXunitConsoleX86 = Context.Tools.Resolve("xunit.console.x86.exe").FullPath; new ProcessSettings
var proc = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{ {
FileName="tests\\Avalonia.LeakTests\\toolproject\\bin\\dotMemoryUnit.exe", Arguments = "--output=artifacts\\inspectcode.xml --profile=Avalonia.sln.DotSettings Avalonia.sln",
Arguments="-targetExecutable=\"" + toolXunitConsoleX86 + "\" -returnTargetExitCode -- tests\\Avalonia.LeakTests\\bin\\Release\\Avalonia.LeakTests.dll -xml tests\\Avalonia.LeakTests\\bin\\Release\\report.xml ", RedirectStandardOutput = true
UseShellExecute = false,
}); });
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") Information("Analyzing report");
.WithCriteria(parameters.IsRunningOnWindows) var doc = XDocument.Parse(System.IO.File.ReadAllText("artifacts\\inspectcode.xml"));
.IsDependentOn("Restore-NuGet-Packages") var failBuild = false;
.Does(() => foreach(var xml in doc.Descendants("Issue"))
{ {
var badIssues = new []{"PossibleNullReferenceException"}; var typeId = xml.Attribute("TypeId").Value.ToString();
var whitelist = new []{"tests", "src\\android", "src\\ios", if(badIssues.Contains(typeId))
"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(); var file = xml.Attribute("File").Value.ToString().ToLower();
if(badIssues.Contains(typeId)) if(whitelist.Any(wh => file.StartsWith(wh)))
{ continue;
var file = xml.Attribute("File").Value.ToString().ToLower(); var line = xml.Attribute("Line").Value.ToString();
if(whitelist.Any(wh => file.StartsWith(wh))) Error(typeId + " - " + file + " on line " + line);
continue; failBuild = true;
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 // 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") 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") Task("AppVeyor")
.IsDependentOn("Zip-Files") .IsDependentOn("Package")
.IsDependentOn("Publish-MyGet") .IsDependentOn("Copy-Files-Impl")
.IsDependentOn("Publish-NuGet"); .IsDependentOn("Zip-Files-Impl")
.IsDependentOn("Publish-MyGet-Impl")
.IsDependentOn("Publish-NuGet-Impl");
Task("Travis") Task("Travis")
.IsDependentOn("Run-Unit-Tests"); .IsDependentOn("Run-Tests");
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// EXECUTE // 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"> <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup> <ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.57.1" /> <PackageReference Include="SkiaSharp" Version="1.60.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.57.1.3" /> <PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.60.0.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
build/System.Memory.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.0" />
</ItemGroup>
</Project>

17
build/XUnit.props

@ -9,21 +9,6 @@
<PackageReference Include="xunit.runner.console" Version="2.3.0" /> <PackageReference Include="xunit.runner.console" Version="2.3.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0" />
<PackageReference Include="Xunit.SkippableFact" Version="1.3.6" /> <PackageReference Include="Xunit.SkippableFact" Version="1.3.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
</ItemGroup> </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> </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; using System.Xml.Linq;
public class Packages public class Packages
@ -9,12 +12,11 @@ public class Packages
public string SkiaSharpVersion {get; private set; } public string SkiaSharpVersion {get; private set; }
public string SkiaSharpLinuxVersion {get; private set; } public string SkiaSharpLinuxVersion {get; private set; }
public Dictionary<string, IList<Tuple<string,string>>> PackageVersions{get; private set;} public Dictionary<string, IList<Tuple<string,string>>> PackageVersions{get; private set;}
class DependencyBuilder : List<NuSpecDependency> class DependencyBuilder : List<NuSpecDependency>
{ {
Packages _parent; Packages _parent;
public DependencyBuilder(Packages parent) public DependencyBuilder(Packages parent)
{ {
_parent = parent; _parent = parent;
@ -24,8 +26,7 @@ public class Packages
{ {
return _parent.PackageVersions[name].First().Item1; return _parent.PackageVersions[name].First().Item1;
} }
public DependencyBuilder Dep(string name, params string[] fws) public DependencyBuilder Dep(string name, params string[] fws)
{ {
if(fws.Length == 0) if(fws.Length == 0)
@ -212,17 +213,33 @@ public class Packages
}; };
}); });
var toolsContent = new[] { var toolHostApp = new NuSpecContent{
new NuSpecContent{ Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath,
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp/bin/" + parameters.DirSuffix + "/netcoreapp2.0/Avalonia.Designer.HostApp.dll")).FullPath, Target = "tools/netcoreapp2.0/previewer"
Target = "tools/netcoreapp2.0/previewer" };
},
new NuSpecContent{ var toolHostAppNetFx = new NuSpecContent{
Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath, Source = ((FilePath)context.File("./src/tools/Avalonia.Designer.HostApp.NetFx/bin/" + parameters.DirSuffix + "/Avalonia.Designer.HostApp.exe")).FullPath,
Target = "tools/net461/previewer" 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 [] var nuspecNuGetSettingsCore = new []
{ {
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -253,13 +270,9 @@ public class Packages
} }
.Deps(new string[]{null, "netcoreapp2.0"}, .Deps(new string[]{null, "netcoreapp2.0"},
"System.ValueTuple", "System.ComponentModel.TypeConverter", "System.ComponentModel.Primitives", "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(), .ToArray(),
Files = coreLibrariesNuSpecContent Files = coreFiles,
.Concat(win32CoreLibrariesNuSpecContent).Concat(net45RuntimePlatform)
.Concat(netcoreappCoreLibrariesNuSpecContent).Concat(netCoreRuntimePlatform)
.Concat(toolsContent)
.ToList(),
BasePath = context.Directory("./"), BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot OutputDirectory = parameters.NugetRoot
}, },
@ -451,22 +464,6 @@ public class Packages
BasePath = context.Directory("./"), BasePath = context.Directory("./"),
OutputDirectory = parameters.NugetRoot 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 // 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 = new List<NuGetPackSettings>();
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore); NuspecNuGetSettings.AddRange(nuspecNuGetSettingsCore);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop); NuspecNuGetSettings.AddRange(nuspecNuGetSettingsDesktop);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
if (!parameters.IsPlatformNetCoreOnly) {
NuspecNuGetSettings.Add(nuspecNuGetSettingInterop);
NuspecNuGetSettings.AddRange(nuspecNuGetSettingsMobile);
}
NuspecNuGetSettings.ForEach((nuspec) => SetNuGetNuspecCommonProperties(nuspec)); NuspecNuGetSettings.ForEach((nuspec) => SetNuGetNuspecCommonProperties(nuspec));

14
parameters.cake

@ -1,6 +1,5 @@
public class Parameters public class Parameters
{ {
public string Target { get; private set; }
public string Platform { get; private set; } public string Platform { get; private set; }
public string Configuration { get; private set; } public string Configuration { get; private set; }
public bool SkipTests { get; private set; } public bool SkipTests { get; private set; }
@ -9,11 +8,11 @@ public class Parameters
public string AssemblyInfoPath { get; private set; } public string AssemblyInfoPath { get; private set; }
public string ReleasePlatform { get; private set; } public string ReleasePlatform { get; private set; }
public string ReleaseConfiguration { get; private set; } public string ReleaseConfiguration { get; private set; }
public string MSBuildSolution { get; private set; } public string MSBuildSolution { get; private set; }
public string XBuildSolution { get; private set; }
public bool IsPlatformAnyCPU { get; private set; } public bool IsPlatformAnyCPU { get; private set; }
public bool IsPlatformX86 { get; private set; } public bool IsPlatformX86 { get; private set; }
public bool IsPlatformX64 { get; private set; } public bool IsPlatformX64 { get; private set; }
public bool IsPlatformNetCoreOnly { get; private set; }
public bool IsLocalBuild { get; private set; } public bool IsLocalBuild { get; private set; }
public bool IsRunningOnUnix { get; private set; } public bool IsRunningOnUnix { get; private set; }
public bool IsRunningOnWindows { get; private set; } public bool IsRunningOnWindows { get; private set; }
@ -35,6 +34,7 @@ public class Parameters
public DirectoryPathCollection BuildDirs { get; private set; } public DirectoryPathCollection BuildDirs { get; private set; }
public string FileZipSuffix { get; private set; } public string FileZipSuffix { get; private set; }
public FilePath ZipCoreArtifacts { get; private set; } public FilePath ZipCoreArtifacts { get; private set; }
public FilePath ZipNuGetArtifacts { get; private set; }
public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; } public DirectoryPath ZipSourceControlCatalogDesktopDirs { get; private set; }
public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; } public FilePath ZipTargetControlCatalogDesktopDirs { get; private set; }
@ -43,7 +43,6 @@ public class Parameters
var buildSystem = context.BuildSystem(); var buildSystem = context.BuildSystem();
// ARGUMENTS // ARGUMENTS
Target = context.Argument("target", "Default");
Platform = context.Argument("platform", "Any CPU"); Platform = context.Argument("platform", "Any CPU");
Configuration = context.Argument("configuration", "Release"); Configuration = context.Argument("configuration", "Release");
SkipTests = context.HasArgument("skip-tests"); SkipTests = context.HasArgument("skip-tests");
@ -55,12 +54,12 @@ public class Parameters
ReleasePlatform = "Any CPU"; ReleasePlatform = "Any CPU";
ReleaseConfiguration = "Release"; ReleaseConfiguration = "Release";
MSBuildSolution = "./Avalonia.sln"; MSBuildSolution = "./Avalonia.sln";
XBuildSolution = "./Avalonia.XBuild.sln";
// PARAMETERS // PARAMETERS
IsPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(Platform, "Any CPU"); IsPlatformAnyCPU = StringComparer.OrdinalIgnoreCase.Equals(Platform, "Any CPU");
IsPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x86"); IsPlatformX86 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x86");
IsPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x64"); IsPlatformX64 = StringComparer.OrdinalIgnoreCase.Equals(Platform, "x64");
IsPlatformNetCoreOnly = StringComparer.OrdinalIgnoreCase.Equals(Platform, "NetCoreOnly");
IsLocalBuild = buildSystem.IsLocalBuild; IsLocalBuild = buildSystem.IsLocalBuild;
IsRunningOnUnix = context.IsRunningOnUnix(); IsRunningOnUnix = context.IsRunningOnUnix();
IsRunningOnWindows = context.IsRunningOnWindows(); IsRunningOnWindows = context.IsRunningOnWindows();
@ -73,7 +72,6 @@ public class Parameters
IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform) IsReleasable = StringComparer.OrdinalIgnoreCase.Equals(ReleasePlatform, Platform)
&& StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration); && StringComparer.OrdinalIgnoreCase.Equals(ReleaseConfiguration, Configuration);
IsMyGetRelease = !IsTagged && IsReleasable; IsMyGetRelease = !IsTagged && IsReleasable;
// VERSION // VERSION
Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion); Version = context.Argument("force-nuget-version", context.ParseAssemblyInfo(AssemblyInfoPath).AssemblyVersion);
@ -105,14 +103,12 @@ public class Parameters
NugetRoot = ArtifactsDir.Combine("nuget"); NugetRoot = ArtifactsDir.Combine("nuget");
ZipRoot = ArtifactsDir.Combine("zip"); ZipRoot = ArtifactsDir.Combine("zip");
BinRoot = ArtifactsDir.Combine("bin"); BinRoot = ArtifactsDir.Combine("bin");
BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj"); BuildDirs = context.GetDirectories("**/bin") + context.GetDirectories("**/obj");
DirSuffix = Configuration; DirSuffix = Configuration;
DirSuffixIOS = "iPhone" + "/" + Configuration; DirSuffixIOS = "iPhone" + "/" + Configuration;
FileZipSuffix = Version + ".zip"; FileZipSuffix = Version + ".zip";
ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix); ZipCoreArtifacts = ZipRoot.CombineWithFilePath("Avalonia-" + FileZipSuffix);
ZipNuGetArtifacts = ZipRoot.CombineWithFilePath("Avalonia-NuGet-" + FileZipSuffix);
ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461"); ZipSourceControlCatalogDesktopDirs = (DirectoryPath)context.Directory("./samples/ControlCatalog.Desktop/bin/" + DirSuffix + "/net461");
ZipTargetControlCatalogDesktopDirs = ZipRoot.CombineWithFilePath("ControlCatalog.Desktop-" + FileZipSuffix); 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 Avalonia.Markup.Xaml;
using Serilog; using Serilog;
namespace BindingTest namespace BindingDemo
{ {
public class App : Application 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" <Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:vm="clr-namespace:BindingTest.ViewModels" xmlns:vm="clr-namespace:BindingDemo.ViewModels"
xmlns:local="clr-namespace:BindingTest" xmlns:local="clr-namespace:BindingDemo"
Title="AvaloniaUI Bindings Test" Title="AvaloniaUI Bindings Test"
Width="800" Width="800"
Height="600"> 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;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace BindingTest namespace BindingDemo
{ {
public class MainWindow : Window 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.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace BindingTest namespace BindingDemo
{ {
public class TestItemView : UserControl public class TestItemView : UserControl
{ {

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

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

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

@ -4,7 +4,7 @@
using ReactiveUI; using ReactiveUI;
using System; using System;
namespace BindingTest.ViewModels namespace BindingDemo.ViewModels
{ {
public class ExceptionErrorViewModel : ReactiveObject 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.ComponentModel;
using System.Collections; using System.Collections;
namespace BindingTest.ViewModels namespace BindingDemo.ViewModels
{ {
public class IndeiErrorViewModel : ReactiveObject, INotifyDataErrorInfo 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.Tasks;
using System.Threading; using System.Threading;
namespace BindingTest.ViewModels namespace BindingDemo.ViewModels
{ {
public class MainWindowViewModel : ReactiveObject 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.Threading.Tasks;
using System.Windows.Input; using System.Windows.Input;
namespace BindingTest.ViewModels namespace BindingDemo.ViewModels
{ {
public class NestedCommandViewModel : ReactiveObject public class NestedCommandViewModel : ReactiveObject
{ {

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

@ -1,6 +1,6 @@
using ReactiveUI; using ReactiveUI;
namespace BindingTest.ViewModels namespace BindingDemo.ViewModels
{ {
public class TestItem : ReactiveObject 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.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello; 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 public partial class Attribute
@ -96,14 +94,11 @@ namespace ControlCatalog.Android
public partial class String public partial class String
{ {
// aapt resource value: 0x7f040002
public const int ApplicationName = 2130968578;
// aapt resource value: 0x7f040001 // aapt resource value: 0x7f040001
public const int Hello = 2130968577; public const int ApplicationName = 2130968577;
// aapt resource value: 0x7f040000 // aapt resource value: 0x7f040000
public const int library_name = 2130968576; public const int Hello = 2130968576;
static String() static String()
{ {

4
samples/ControlCatalog.NetCore/Program.cs

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

4
samples/ControlCatalog/SideBar.xaml

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

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

@ -9,7 +9,7 @@ using Avalonia.Remote.Protocol;
using Avalonia.Threading; using Avalonia.Threading;
using ControlCatalog; using ControlCatalog;
namespace RemoteTest namespace RemoteDemo
{ {
class Program 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> <Application.Styles>
<StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/> <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: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.Styles>
</Application> </Application>

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

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

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

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

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

@ -5,10 +5,10 @@ using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using RenderTest.ViewModels; using RenderDemo.ViewModels;
using ReactiveUI; using ReactiveUI;
namespace RenderTest namespace RenderDemo
{ {
public class MainWindow : Window 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.Input;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
using RenderTest.ViewModels; using RenderDemo.ViewModels;
namespace RenderTest.Pages namespace RenderDemo.Pages
{ {
public class AnimationsPage : UserControl 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.Markup.Xaml;
using Avalonia.Media; using Avalonia.Media;
namespace RenderTest.Pages namespace RenderDemo.Pages
{ {
public class ClippingPage : UserControl 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.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace RenderTest.Pages namespace RenderDemo.Pages
{ {
public class DrawingPage : UserControl 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" <TabStrip Name="PART_TabStrip"
MemberSelector="{x:Static TabControl.HeaderSelector}" MemberSelector="{x:Static TabControl.HeaderSelector}"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex, Mode=TwoWay}"> SelectedIndex="{TemplateBinding SelectedIndex, Mode=TwoWay}">
<TabStrip.ItemsPanel> <TabStrip.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/> <StackPanel Orientation="Vertical"/>
@ -21,7 +21,7 @@
Margin="8 0 0 0" Margin="8 0 0 0"
MemberSelector="{x:Static TabControl.ContentSelector}" MemberSelector="{x:Static TabControl.ContentSelector}"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
SelectedIndex="{TemplateBinding Path=SelectedIndex}" SelectedIndex="{TemplateBinding SelectedIndex}"
PageTransition="{TemplateBinding PageTransition}" PageTransition="{TemplateBinding PageTransition}"
Grid.Row="1"/> Grid.Row="1"/>
</DockPanel> </DockPanel>

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

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

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

@ -1,7 +1,7 @@
using System; using System;
using ReactiveUI; using ReactiveUI;
namespace RenderTest.ViewModels namespace RenderDemo.ViewModels
{ {
public class MainWindowViewModel : ReactiveObject 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;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
namespace VirtualizationTest namespace VirtualizationDemo
{ {
public class App : Application 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;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using VirtualizationTest.ViewModels; using VirtualizationDemo.ViewModels;
namespace VirtualizationTest namespace VirtualizationDemo
{ {
public class MainWindow : Window 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 Avalonia.Logging.Serilog;
using Serilog; using Serilog;
namespace VirtualizationTest namespace VirtualizationDemo
{ {
class Program class Program
{ {

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

@ -4,7 +4,7 @@
using System; using System;
using ReactiveUI; using ReactiveUI;
namespace VirtualizationTest.ViewModels namespace VirtualizationDemo.ViewModels
{ {
internal class ItemViewModel : ReactiveObject 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 Avalonia.Controls.Primitives;
using ReactiveUI; using ReactiveUI;
namespace VirtualizationTest.ViewModels namespace VirtualizationDemo.ViewModels
{ {
internal class MainWindowViewModel : ReactiveObject 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.ClearDepthStencilView(depthView, DepthStencilClearFlags.Depth, 1.0f, 0);
context.ClearRenderTargetView(renderView, Color.White); context.ClearRenderTargetView(renderView, Color.White);
var time = 50;
// Update WorldViewProj Matrix // Update WorldViewProj Matrix
var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) * var worldViewProj = Matrix.RotationX((float) _model.RotationX) * Matrix.RotationY((float) _model.RotationY) *
Matrix.RotationZ((float) _model.RotationZ) 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 //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; return e.Action != MotionEventActions.Up;
} }
private Paint _paint;
public void Dispose() public void Dispose()
{ {
HandleEvents = false; HandleEvents = false;

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

@ -40,14 +40,11 @@ namespace Avalonia.Android
public partial class String public partial class String
{ {
// aapt resource value: 0x7f020002
public static int ApplicationName = 2130837506;
// aapt resource value: 0x7f020001 // aapt resource value: 0x7f020001
public static int Hello = 2130837505; public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000 // aapt resource value: 0x7f020000
public static int library_name = 2130837504; public static int Hello = 2130837504;
static String() 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.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello; 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 public partial class Attribute
@ -64,14 +62,11 @@ namespace Avalonia.AndroidTestApplication
public partial class String public partial class String
{ {
// aapt resource value: 0x7f030002
public const int ApplicationName = 2130903042;
// aapt resource value: 0x7f030001 // aapt resource value: 0x7f030001
public const int Hello = 2130903041; public const int ApplicationName = 2130903041;
// aapt resource value: 0x7f030000 // aapt resource value: 0x7f030000
public const int library_name = 2130903040; public const int Hello = 2130903040;
static String() static String()
{ {

2
src/Avalonia.Animation/AnimatorKeyFrame.cs

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

192
src/Avalonia.Base/AvaloniaObject.cs

@ -10,6 +10,7 @@ using System.Reactive.Linq;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.Utilities; using Avalonia.Utilities;
@ -28,17 +29,11 @@ namespace Avalonia
/// </summary> /// </summary>
private IAvaloniaObject _inheritanceParent; private IAvaloniaObject _inheritanceParent;
/// <summary>
/// The set values/bindings on this object.
/// </summary>
private readonly Dictionary<AvaloniaProperty, PriorityValue> _values =
new Dictionary<AvaloniaProperty, PriorityValue>();
/// <summary> /// <summary>
/// Maintains a list of direct property binding subscriptions so that the binding source /// Maintains a list of direct property binding subscriptions so that the binding source
/// doesn't get collected. /// doesn't get collected.
/// </summary> /// </summary>
private List<IDisposable> _directBindings; private List<DirectBindingSubscription> _directBindings;
/// <summary> /// <summary>
/// Event handler for <see cref="INotifyPropertyChanged"/> implementation. /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
@ -51,6 +46,7 @@ namespace Avalonia
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged; private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter; private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
private ValueStore _values;
/// <summary> /// <summary>
/// Delayed setter helper for direct properties. Used to fix #855. /// Delayed setter helper for direct properties. Used to fix #855.
@ -227,9 +223,20 @@ namespace Avalonia
{ {
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this); 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 else
{ {
return GetValueInternal(property); return GetDefaultValue(property);
} }
} }
@ -256,7 +263,7 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess(); VerifyAccess();
return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false; return _values?.IsAnimating(property) ?? false;
} }
/// <summary> /// <summary>
@ -273,14 +280,7 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
VerifyAccess(); VerifyAccess();
PriorityValue value; return _values?.IsSet(property) ?? false;
if (_values.TryGetValue(property, out value))
{
return value.Value != AvaloniaProperty.UnsetValue;
}
return false;
} }
/// <summary> /// <summary>
@ -359,36 +359,15 @@ namespace Avalonia
property, property,
description); description);
IDisposable subscription = null;
if (_directBindings == null) if (_directBindings == null)
{ {
_directBindings = new List<IDisposable>(); _directBindings = new List<DirectBindingSubscription>();
} }
subscription = source return new DirectBindingSubscription(this, property, 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);
});
} }
else else
{ {
PriorityValue v;
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
_values.Add(property, v);
}
Logger.Verbose( Logger.Verbose(
LogArea.Property, LogArea.Property,
this, this,
@ -397,7 +376,12 @@ namespace Avalonia
description, description,
priority); 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) public void Revalidate(AvaloniaProperty property)
{ {
VerifyAccess(); VerifyAccess();
PriorityValue value; _values?.Revalidate(property);
if (_values.TryGetValue(property, out value))
{
value.Revalidate();
}
} }
/// <inheritdoc/> /// <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) ? oldValue = (oldValue == AvaloniaProperty.UnsetValue) ?
GetDefaultValue(property) : GetDefaultValue(property) :
oldValue; oldValue;
@ -451,7 +427,7 @@ namespace Avalonia
if (!Equals(oldValue, newValue)) if (!Equals(oldValue, newValue))
{ {
RaisePropertyChanged(property, oldValue, newValue, priority); RaisePropertyChanged(property, oldValue, newValue, (BindingPriority)priority);
Logger.Verbose( Logger.Verbose(
LogArea.Property, LogArea.Property,
@ -460,14 +436,14 @@ namespace Avalonia
property, property,
oldValue, oldValue,
newValue, newValue,
priority); (BindingPriority)priority);
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
void IPriorityValueOwner.BindingNotificationReceived(PriorityValue sender, BindingNotification notification) void IPriorityValueOwner.BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{ {
UpdateDataValidation(sender.Property, notification); UpdateDataValidation(property, notification);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -480,10 +456,7 @@ namespace Avalonia
/// Gets all priority values set on the object. /// Gets all priority values set on the object.
/// </summary> /// </summary>
/// <returns>A collection of property/value tuples.</returns> /// <returns>A collection of property/value tuples.</returns>
internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() internal IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => _values?.GetSetValues();
{
return _values;
}
/// <summary> /// <summary>
/// Forces revalidation of properties when a property value changes. /// 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> /// <summary>
/// Gets the default value for a property. /// Gets the default value for a property.
/// </summary> /// </summary>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns>The default value.</returns> /// <returns>The default value.</returns>
private object GetDefaultValue(AvaloniaProperty property) internal object GetDefaultValue(AvaloniaProperty property)
{ {
if (property.Inherits && InheritanceParent is AvaloniaObject aobj) if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValueInternal(property); return aobj.GetValue(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType()); 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> /// <summary>
/// Sets the value of a direct property. /// Sets the value of a direct property.
/// </summary> /// </summary>
@ -814,21 +737,13 @@ namespace Avalonia
originalValue?.GetType().FullName ?? "(null)")); originalValue?.GetType().FullName ?? "(null)"));
} }
PriorityValue v; if (_values == null)
if (!_values.TryGetValue(property, out v))
{ {
if (value == AvaloniaProperty.UnsetValue) _values = new ValueStore(this);
{
return;
}
v = CreatePriorityValue(property);
_values.Add(property, v);
} }
LogPropertySet(property, value, priority); LogPropertySet(property, value, priority);
v.SetValue(value, (int)priority); _values.AddValue(property, value, (int)priority);
} }
/// <summary> /// <summary>
@ -908,5 +823,38 @@ namespace Avalonia
value, value,
priority); 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 /// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes. /// object and subsequently each time the property value changes.
/// </returns> /// </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) public static IObservable<object> GetObservable(this IAvaloniaObject o, AvaloniaProperty property)
{ {
Contract.Requires<ArgumentNullException>(o != null); Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
return new AvaloniaObservable<object>( return new AvaloniaPropertyObservable<object>(o, property);
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));
} }
/// <summary> /// <summary>
@ -74,51 +57,36 @@ namespace Avalonia
/// An observable which fires immediately with the current value of the property on the /// An observable which fires immediately with the current value of the property on the
/// object and subsequently each time the property value changes. /// object and subsequently each time the property value changes.
/// </returns> /// </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) public static IObservable<T> GetObservable<T>(this IAvaloniaObject o, AvaloniaProperty<T> property)
{ {
Contract.Requires<ArgumentNullException>(o != null); Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
return o.GetObservable((AvaloniaProperty)property).Cast<T>(); return new AvaloniaPropertyObservable<T>(o, property);
} }
/// <summary> /// <summary>
/// Gets an observable for a <see cref="AvaloniaProperty"/>. /// Gets an observable that listens for property changed events for an
/// <see cref="AvaloniaProperty"/>.
/// </summary> /// </summary>
/// <param name="o">The object.</param> /// <param name="o">The object.</param>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param> /// <param name="property">The property.</param>
/// <returns> /// <returns>
/// An observable which when subscribed pushes the old and new values of the property each /// An observable which when subscribed pushes the property changed event args
/// time it is changed. Note that the observable returned from this method does not fire /// each time a <see cref="IAvaloniaObject.PropertyChanged"/> event is raised
/// with the current value of the property immediately. /// for the specified property.
/// </returns> /// </returns>
public static IObservable<Tuple<T, T>> GetObservableWithHistory<T>( public static IObservable<AvaloniaPropertyChangedEventArgs> GetPropertyChangedObservable(
this IAvaloniaObject o, this IAvaloniaObject o,
AvaloniaProperty<T> property) AvaloniaProperty property)
{ {
Contract.Requires<ArgumentNullException>(o != null); Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null); Contract.Requires<ArgumentNullException>(property != null);
return new AvaloniaObservable<Tuple<T, T>>( return new AvaloniaPropertyChangedObservable(o, property);
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));
} }
/// <summary> /// <summary>
@ -166,23 +134,6 @@ namespace Avalonia
o.GetObservable(property)); 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> /// <summary>
/// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>. /// Binds a property on an <see cref="IAvaloniaObject"/> to an <see cref="IBinding"/>.
/// </summary> /// </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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using Avalonia.Reactive;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Collections namespace Avalonia.Collections
@ -43,9 +39,8 @@ namespace Avalonia.Collections
Contract.Requires<ArgumentNullException>(collection != null); Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null); Contract.Requires<ArgumentNullException>(handler != null);
return return collection.GetWeakCollectionChangedObservable()
collection.GetWeakCollectionChangedObservable() .Subscribe(e => handler(collection, e));
.Subscribe(e => handler.Invoke(collection, e));
} }
/// <summary> /// <summary>
@ -63,18 +58,13 @@ namespace Avalonia.Collections
Contract.Requires<ArgumentNullException>(collection != null); Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null); Contract.Requires<ArgumentNullException>(handler != null);
return return collection.GetWeakCollectionChangedObservable().Subscribe(handler);
collection.GetWeakCollectionChangedObservable()
.Subscribe(handler);
} }
private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>, private class WeakCollectionChangedObservable : LightweightObservableBase<NotifyCollectionChangedEventArgs>,
IWeakSubscriber<NotifyCollectionChangedEventArgs> IWeakSubscriber<NotifyCollectionChangedEventArgs>
{ {
private WeakReference<INotifyCollectionChanged> _sourceReference; private WeakReference<INotifyCollectionChanged> _sourceReference;
private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
private int _count;
public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source) public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
{ {
@ -83,43 +73,28 @@ namespace Avalonia.Collections
public void OnEvent(object sender, NotifyCollectionChangedEventArgs e) 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 (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{ {
if (_count++ == 0) WeakSubscriptionManager.Subscribe(
{ instance,
WeakSubscriptionManager.Subscribe( nameof(instance.CollectionChanged),
instance, this);
nameof(instance.CollectionChanged),
this);
}
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
.Subscribe(observer);
}
else
{
_changed.OnCompleted();
observer.OnCompleted();
return Disposable.Empty;
} }
} }
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,
WeakSubscriptionManager.Unsubscribe( nameof(instance.CollectionChanged),
instance, this);
nameof(instance.CollectionChanged),
this);
}
} }
} }
} }

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

@ -7,21 +7,23 @@ using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Reactive;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
/// <summary> /// <summary>
/// Binds to an expression on an object using a type value converter to convert the values /// 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> /// </summary>
public class BindingExpression : ISubject<object>, IDescription public class BindingExpression : LightweightObservableBase<object>, ISubject<object>, IDescription
{ {
private readonly ExpressionObserver _inner; private readonly ExpressionObserver _inner;
private readonly Type _targetType; private readonly Type _targetType;
private readonly object _fallbackValue; private readonly object _fallbackValue;
private readonly BindingPriority _priority; private readonly BindingPriority _priority;
private readonly Subject<object> _errors = new Subject<object>(); InnerListener _innerListener;
WeakReference<object> _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class. /// 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."); "IValueConverter should not return non-errored BindingNotification.");
} }
_errors.OnNext(notification); PublishNext(notification);
if (_fallbackValue != AvaloniaProperty.UnsetValue) if (_fallbackValue != AvaloniaProperty.UnsetValue)
{ {
@ -170,12 +172,18 @@ namespace Avalonia.Data.Core
} }
} }
/// <inheritdoc/> protected override void Initialize() => _innerListener = new InnerListener(this);
public IDisposable Subscribe(IObserver<object> observer) 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) private object ConvertValue(object value)
{ {
var notification = value as BindingNotification; var notification = value as BindingNotification;
@ -301,5 +309,28 @@ namespace Avalonia.Data.Core
return a; 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
internal class EmptyExpressionNode : ExpressionNode internal class EmptyExpressionNode : ExpressionNode
{ {
public override string Description => "."; 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal abstract class ExpressionNode : ISubject<object> internal abstract class ExpressionNode
{ {
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference UnsetReference = protected static readonly WeakReference UnsetReference =
new WeakReference(AvaloniaProperty.UnsetValue); new WeakReference(AvaloniaProperty.UnsetValue);
private WeakReference _target = UnsetReference; private WeakReference _target = UnsetReference;
private IDisposable _valueSubscription; private Action<object> _subscriber;
private IObserver<object> _observer; private bool _listening;
protected WeakReference LastValue { get; private set; }
public abstract string Description { get; } public abstract string Description { get; }
public ExpressionNode Next { get; set; } public ExpressionNode Next { get; set; }
@ -30,119 +29,122 @@ namespace Avalonia.Data.Core
var oldTarget = _target?.Target; var oldTarget = _target?.Target;
var newTarget = value.Target; var newTarget = value.Target;
var running = _valueSubscription != null;
if (!ReferenceEquals(oldTarget, newTarget)) if (!ReferenceEquals(oldTarget, newTarget))
{ {
_valueSubscription?.Dispose(); if (_listening)
_valueSubscription = null; {
StopListening();
}
_target = value; _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."); throw new AvaloniaInternalException("ExpressionNode can only be subscribed once.");
} }
_observer = observer; _subscriber = subscriber;
var nextSubscription = Next?.Subscribe(this); Next?.Subscribe(NextValueChanged);
_valueSubscription = StartListening(); StartListening();
return Disposable.Create(() =>
{
_valueSubscription?.Dispose();
_valueSubscription = null;
nextSubscription?.Dispose();
_observer = null;
});
} }
void IObserver<object>.OnCompleted() public void Unsubscribe()
{ {
throw new AvaloniaInternalException("ExpressionNode.OnCompleted should not be called."); Next?.Unsubscribe();
}
void IObserver<object>.OnError(Exception error) if (_listening)
{ {
throw new AvaloniaInternalException("ExpressionNode.OnError should not be called."); 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) protected virtual void NextValueChanged(object value)
{ {
var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException; var bindingBroken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
bindingBroken?.AddNode(Description); bindingBroken?.AddNode(Description);
_observer.OnNext(value); _subscriber(value);
} }
private IDisposable StartListening() protected void ValueChanged(object value)
{
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)
{ {
var notification = value as BindingNotification; var notification = value as BindingNotification;
if (notification == null) if (notification == null)
{ {
LastValue = new WeakReference(value);
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(value); Next.Target = new WeakReference(value);
} }
else else
{ {
_observer.OnNext(value); _subscriber(value);
} }
} }
else else
{ {
LastValue = new WeakReference(notification.Value);
if (Next != null) if (Next != null)
{ {
Next.Target = new WeakReference(notification.Value); Next.Target = new WeakReference(notification.Value);
} }
if (Next == null || notification.Error != null) 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() private BindingNotification TargetNullNotification()
{ {
return new BindingNotification( return new BindingNotification(

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

@ -4,18 +4,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
using Avalonia.Reactive;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
/// <summary> /// <summary>
/// Observes and sets the value of an expression on an object. /// Observes and sets the value of an expression on an object.
/// </summary> /// </summary>
public class ExpressionObserver : ObservableBase<object>, IDescription public class ExpressionObserver : LightweightObservableBase<object>, IDescription
{ {
/// <summary> /// <summary>
/// An ordered collection of property accessor plugins that can be used to customize /// An ordered collection of property accessor plugins that can be used to customize
@ -54,9 +53,9 @@ namespace Avalonia.Data.Core
private static readonly object UninitializedValue = new object(); private static readonly object UninitializedValue = new object();
private readonly ExpressionNode _node; private readonly ExpressionNode _node;
private readonly Subject<Unit> _finished; private object _root;
private readonly object _root; private IDisposable _rootSubscription;
private IObservable<object> _result; private WeakReference<object> _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ExpressionObserver"/> class. /// Initializes a new instance of the <see cref="ExpressionObserver"/> class.
@ -107,7 +106,6 @@ namespace Avalonia.Data.Core
Expression = expression; Expression = expression;
Description = description ?? expression; Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>();
_root = rootObservable; _root = rootObservable;
} }
@ -135,8 +133,6 @@ namespace Avalonia.Data.Core
Expression = expression; Expression = expression;
Description = description ?? expression; Description = description ?? expression;
_node = Parse(expression, enableDataValidation); _node = Parse(expression, enableDataValidation);
_finished = new Subject<Unit>();
_node.Target = new WeakReference(rootGetter()); _node.Target = new WeakReference(rootGetter());
_root = update.Select(x => rootGetter()); _root = update.Select(x => rootGetter());
} }
@ -154,7 +150,7 @@ namespace Avalonia.Data.Core
/// </returns> /// </returns>
public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue) public bool SetValue(object value, BindingPriority priority = BindingPriority.LocalValue)
{ {
if (Leaf is ISettableNode settable) if (Leaf is SettableNode settable)
{ {
var node = _node; var node = _node;
while (node != null) while (node != null)
@ -188,7 +184,7 @@ namespace Avalonia.Data.Core
/// Gets the type of the expression result or null if the expression could not be /// Gets the type of the expression result or null if the expression could not be
/// evaluated. /// evaluated.
/// </summary> /// </summary>
public Type ResultType => (Leaf as ISettableNode)?.PropertyType; public Type ResultType => (Leaf as SettableNode)?.PropertyType;
/// <summary> /// <summary>
/// Gets the leaf node. /// Gets the leaf node.
@ -203,27 +199,26 @@ namespace Avalonia.Data.Core
} }
} }
/// <inheritdoc/> protected override void Initialize()
protected override IDisposable SubscribeCore(IObserver<object> observer)
{ {
if (_result == null) _value = null;
{ _node.Subscribe(ValueChanged);
var source = (IObservable<object>)_node; StartRoot();
}
if (_finished != null) protected override void Deinitialize()
{ {
source = source.TakeUntil(_finished); _rootSubscription?.Dispose();
} _rootSubscription = null;
_node.Unsubscribe();
}
_result = Observable.Using(StartRoot, _ => source) protected override void Subscribed(IObserver<object> observer, bool first)
.Select(ToWeakReference) {
.Publish(UninitializedValue) if (!first && _value != null && _value.TryGetTarget(out var value))
.RefCount() {
.Where(x => x != UninitializedValue) observer.OnNext(value);
.Select(Translate);
} }
return _result.Subscribe(observer);
} }
private static ExpressionNode Parse(string expression, bool enableDataValidation) private static ExpressionNode Parse(string expression, bool enableDataValidation)
@ -238,42 +233,27 @@ namespace Avalonia.Data.Core
} }
} }
private static object ToWeakReference(object o) private void StartRoot()
{ {
return o is BindingNotification ? o : new WeakReference(o); if (_root is IObservable<object> observable)
}
private object Translate(object o)
{
if (o is WeakReference weak)
{ {
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) var broken = BindingNotification.ExtractError(value) as MarkupBindingChainException;
{ broken?.Commit(Description);
case IObservable<object> observable: _value = new WeakReference<object>(value);
return observable.Subscribe( PublishNext(value);
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;
} }
} }
} }

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; }
}
}

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

@ -15,8 +15,10 @@ using Avalonia.Data;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal class IndexerNode : ExpressionNode, ISettableNode internal class IndexerNode : SettableNode
{ {
private IDisposable _subscription;
public IndexerNode(IList<string> arguments) public IndexerNode(IList<string> arguments)
{ {
Arguments = arguments; Arguments = arguments;
@ -24,7 +26,7 @@ namespace Avalonia.Data.Core
public override string Description => "[" + string.Join(",", Arguments) + "]"; public override string Description => "[" + string.Join(",", Arguments) + "]";
protected override IObservable<object> StartListeningCore(WeakReference reference) protected override void StartListeningCore(WeakReference reference)
{ {
var target = reference.Target; var target = reference.Target;
var incc = target as INotifyCollectionChanged; var incc = target as INotifyCollectionChanged;
@ -49,10 +51,15 @@ namespace Avalonia.Data.Core
.Select(_ => GetValue(target))); .Select(_ => GetValue(target)));
} }
return Observable.Merge(inputs).StartWith(GetValue(target)); _subscription = Observable.Merge(inputs).StartWith(GetValue(target)).Subscribe(ValueChanged);
}
protected override void StopListeningCore()
{
_subscription.Dispose();
} }
public bool SetTargetValue(object value, BindingPriority priority) protected override bool SetTargetValueCore(object value, BindingPriority priority)
{ {
var typeInfo = Target.Target.GetType().GetTypeInfo(); var typeInfo = Target.Target.GetType().GetTypeInfo();
var list = Target.Target as IList; var list = Target.Target as IList;
@ -154,7 +161,7 @@ namespace Avalonia.Data.Core
public IList<string> Arguments { get; } public IList<string> Arguments { get; }
public Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType; public override Type PropertyType => GetIndexer(Target.Target.GetType().GetTypeInfo())?.PropertyType;
private object GetValue(object target) private object GetValue(object target)
{ {

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

@ -145,15 +145,15 @@ namespace Avalonia.Data.Core.Plugins
return false; return false;
} }
protected override void Dispose(bool disposing) protected override void SubscribeCore()
{ {
_subscription?.Dispose(); _subscription = Instance?.GetObservable(_property).Subscribe(PublishValue);
_subscription = null;
} }
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> /// <param name="value">The value.</param>
void IObserver<object>.OnNext(object value) => InnerValueChanged(value); void IObserver<object>.OnNext(object value) => InnerValueChanged(value);
/// <inheritdoc/>
protected override void Dispose(bool disposing) => _inner.Dispose();
/// <summary> /// <summary>
/// Begins listening to the inner <see cref="IPropertyAccessor"/>. /// Begins listening to the inner <see cref="IPropertyAccessor"/>.
/// </summary> /// </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> /// <summary>
/// Called when the inner <see cref="IPropertyAccessor"/> notifies with a new value. /// 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) protected virtual void InnerValueChanged(object value)
{ {
var notification = value as BindingNotification ?? new BindingNotification(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. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Data;
using System; using System;
using System.Reflection; using System.Reflection;
@ -36,11 +35,11 @@ namespace Avalonia.Data.Core.Plugins
} }
catch (TargetInvocationException ex) catch (TargetInvocationException ex)
{ {
Observer.OnNext(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError)); PublishValue(new BindingNotification(ex.InnerException, BindingErrorType.DataValidationError));
} }
catch (Exception ex) catch (Exception ex)
{ {
Observer.OnNext(new BindingNotification(ex, BindingErrorType.DataValidationError)); PublishValue(new BindingNotification(ex, BindingErrorType.DataValidationError));
} }
return false; 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins 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 /// Defines an accessor to a property on an object returned by a
/// <see cref="IPropertyAccessorPlugin"/> /// <see cref="IPropertyAccessorPlugin"/>
/// </summary> /// </summary>
public interface IPropertyAccessor : IObservable<object>, IDisposable public interface IPropertyAccessor : IDisposable
{ {
/// <summary> /// <summary>
/// Gets the type of the property. /// 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. /// True if the property was set; false if the property could not be set.
/// </returns> /// </returns>
bool SetValue(object value, BindingPriority priority); 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.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
@ -40,43 +39,43 @@ namespace Avalonia.Data.Core.Plugins
{ {
if (e.PropertyName == _name || string.IsNullOrEmpty(e.PropertyName)) 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; var target = _reference.Target as INotifyDataErrorInfo;
if (target != null) if (target != null)
{ {
WeakSubscriptionManager.Unsubscribe( WeakSubscriptionManager.Subscribe(
target, target,
nameof(target.ErrorsChanged), nameof(target.ErrorsChanged),
this); this);
} }
base.SubscribeCore();
} }
protected override void SubscribeCore(IObserver<object> observer) protected override void UnsubscribeCore()
{ {
var target = _reference.Target as INotifyDataErrorInfo; var target = _reference.Target as INotifyDataErrorInfo;
if (target != null) if (target != null)
{ {
WeakSubscriptionManager.Subscribe( WeakSubscriptionManager.Unsubscribe(
target, target,
nameof(target.ErrorsChanged), nameof(target.ErrorsChanged),
this); this);
} }
base.SubscribeCore(observer); base.UnsubscribeCore();
} }
protected override void InnerValueChanged(object value) protected override void InnerValueChanged(object value)
{ {
base.InnerValueChanged(CreateBindingNotification(value)); PublishValue(CreateBindingNotification(value));
} }
private BindingNotification CreateBindingNotification(object 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; 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() private void SendCurrentValue()
{ {
try try
{ {
var value = Value; var value = Value;
Observer.OnNext(value); PublishValue(value);
} }
catch { } 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; public override bool SetValue(object value, BindingPriority priority) => false;
protected override void SubscribeCore(IObserver<object> observer) protected override void SubscribeCore()
{ {
try try
{ {
Observer.OnNext(Value); PublishValue(Value);
} }
catch { } 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {
/// <summary> /// <summary>
/// Defines a default base implementation for a <see cref="IPropertyAccessor"/>. /// Defines a default base implementation for a <see cref="IPropertyAccessor"/>.
/// </summary> /// </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 public abstract class PropertyAccessorBase : IPropertyAccessor
{ {
private Action<object> _listener;
/// <inheritdoc/> /// <inheritdoc/>
public abstract Type PropertyType { get; } public abstract Type PropertyType { get; }
/// <inheritdoc/> /// <inheritdoc/>
public abstract object Value { get; } public abstract object Value { get; }
/// <summary> /// <inheritdoc/>
/// Stops the subscription. public void Dispose()
/// </summary> {
public void Dispose() => Dispose(true); if (_listener != null)
{
Unsubscribe();
}
}
/// <inheritdoc/> /// <inheritdoc/>
public abstract bool SetValue(object value, BindingPriority priority); public abstract bool SetValue(object value, BindingPriority priority);
/// <summary>
/// The currently subscribed observer.
/// </summary>
protected IObserver<object> Observer { get; private set; }
/// <inheritdoc/> /// <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( throw new InvalidOperationException(
"A property accessor can be subscribed to only once."); "A member accessor can be subscribed to only once.");
} }
Observer = observer; _listener = listener;
SubscribeCore(observer); SubscribeCore();
return this;
} }
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> /// <summary>
/// Stops listening to the property. /// When overridden in a derived class, begins listening to the member.
/// </summary> /// </summary>
/// <param name="disposing"> protected abstract void SubscribeCore();
/// 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;
/// <summary> /// <summary>
/// When overridden in a derived class, begins listening to the property. /// When overridden in a derived class, stops listening to the member.
/// </summary> /// </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;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.Data.Core.Plugins namespace Avalonia.Data.Core.Plugins
{ {
@ -37,10 +35,13 @@ namespace Avalonia.Data.Core.Plugins
return false; 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;
using System.Linq; using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins; using Avalonia.Data.Core.Plugins;
namespace Avalonia.Data.Core namespace Avalonia.Data.Core
{ {
internal class PropertyAccessorNode : ExpressionNode, ISettableNode internal class PropertyAccessorNode : SettableNode
{ {
private readonly bool _enableValidation; private readonly bool _enableValidation;
private IPropertyAccessor _accessor; private IPropertyAccessor _accessor;
@ -23,19 +21,23 @@ namespace Avalonia.Data.Core
public override string Description => PropertyName; public override string Description => PropertyName;
public string PropertyName { get; } 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) if (_accessor != null)
{ {
try { return _accessor.SetValue(value, priority); } catch { } try
{
return _accessor.SetValue(value, priority);
}
catch { }
} }
return false; 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 plugin = ExpressionObserver.PropertyAccessors.FirstOrDefault(x => x.Match(reference.Target, PropertyName));
var accessor = plugin?.Start(reference, 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. if (accessor == null)
return Observable.Using( {
() => throw new NotSupportedException(
{ $"Could not find a matching property accessor for {PropertyName}.");
_accessor = accessor; }
return Disposable.Create(() => _accessor = null);
}, accessor.Subscribe(ValueChanged);
_ => accessor); _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
{
internal 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; }
}
}

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

9
src/Avalonia.Base/IPriorityValueOwner.cs

@ -13,18 +13,19 @@ namespace Avalonia
/// <summary> /// <summary>
/// Called when a <see cref="PriorityValue"/>'s value changes. /// Called when a <see cref="PriorityValue"/>'s value changes.
/// </summary> /// </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="oldValue">The old value.</param>
/// <param name="newValue">The new 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> /// <summary>
/// Called when a <see cref="BindingNotification"/> is received by a /// Called when a <see cref="BindingNotification"/> is received by a
/// <see cref="PriorityValue"/>. /// <see cref="PriorityValue"/>.
/// </summary> /// </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> /// <param name="notification">The notification.</param>
void BindingNotificationReceived(PriorityValue sender, BindingNotification notification); void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
/// <summary> /// <summary>
/// Ensures that the current thread is the UI thread. /// 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) if (notification == null || notification.HasValue)
{ {
notify(() => Owner?.Changed(this, old, Value)); notify(() => Owner?.Changed(Property, ValuePriority, old, Value));
} }
if (notification != null) if (notification != null)
{ {
Owner?.BindingNotificationReceived(this, notification); Owner?.BindingNotificationReceived(Property, notification);
} }
} }
else 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();
}
}

85
src/Avalonia.Base/Reactive/WeakPropertyChangedObservable.cs

@ -1,85 +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;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Utilities;
namespace Avalonia.Reactive
{
internal class WeakPropertyChangedObservable : ObservableBase<object>,
IWeakSubscriber<AvaloniaPropertyChangedEventArgs>, IDescription
{
private WeakReference<IAvaloniaObject> _sourceReference;
private readonly AvaloniaProperty _property;
private readonly Subject<object> _changed = new Subject<object>();
private int _count;
public WeakPropertyChangedObservable(
WeakReference<IAvaloniaObject> source,
AvaloniaProperty property,
string description)
{
_sourceReference = source;
_property = property;
Description = description;
}
public string Description { get; }
public void OnEvent(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.Property == _property)
{
_changed.OnNext(e.NewValue);
}
}
protected override IDisposable SubscribeCore(IObserver<object> observer)
{
IAvaloniaObject instance;
if (_sourceReference.TryGetTarget(out instance))
{
if (_count++ == 0)
{
WeakSubscriptionManager.Subscribe(
instance,
nameof(instance.PropertyChanged),
this);
}
observer.OnNext(instance.GetValue(_property));
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
.Subscribe(observer);
}
else
{
_changed.OnCompleted();
observer.OnCompleted();
return Disposable.Empty;
}
}
private void DecrementCount()
{
if (--_count == 0)
{
IAvaloniaObject instance;
if (_sourceReference.TryGetTarget(out instance))
{
WeakSubscriptionManager.Unsubscribe(
instance,
nameof(instance.PropertyChanged),
this);
}
}
}
}
}

172
src/Avalonia.Base/ValueStore.cs

@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
private readonly AvaloniaObject _owner;
private readonly Dictionary<AvaloniaProperty, object> _values =
new Dictionary<AvaloniaProperty, object>();
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
}
public IDisposable AddBinding(
AvaloniaProperty property,
IObservable<object> source,
BindingPriority priority)
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
if (priorityValue == null)
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
}
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
}
return priorityValue.Add(source, (int)priority);
}
public void AddValue(AvaloniaProperty property, object value, int priority)
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
if (priorityValue == null)
{
if (priority == (int)BindingPriority.LocalValue)
{
_values[property] = Validate(property, value);
Changed(property, priority, v, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
}
}
}
else
{
if (value == AvaloniaProperty.UnsetValue)
{
return;
}
if (priority == (int)BindingPriority.LocalValue)
{
_values.Add(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
}
}
priorityValue.SetValue(value, priority);
}
public void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
{
((IPriorityValueOwner)_owner).BindingNotificationReceived(property, notification);
}
public void Changed(AvaloniaProperty property, int priority, object oldValue, object newValue)
{
((IPriorityValueOwner)_owner).Changed(property, priority, oldValue, newValue);
}
public IDictionary<AvaloniaProperty, PriorityValue> GetSetValues() => throw new NotImplementedException();
public object GetValue(AvaloniaProperty property)
{
var result = AvaloniaProperty.UnsetValue;
if (_values.TryGetValue(property, out var value))
{
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
}
return result;
}
public bool IsAnimating(AvaloniaProperty property)
{
return _values.TryGetValue(property, out var value) ? (value as PriorityValue)?.IsAnimating ?? false : false;
}
public bool IsSet(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
{
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
}
return false;
}
public void Revalidate(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
{
(value as PriorityValue)?.Revalidate();
}
}
public void VerifyAccess() => _owner.VerifyAccess();
private PriorityValue CreatePriorityValue(AvaloniaProperty property)
{
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
Func<object, object> validate2 = null;
if (validate != null)
{
validate2 = v => validate(_owner, v);
}
PriorityValue result = new PriorityValue(
this,
property,
property.PropertyType,
validate2);
return result;
}
private object Validate(AvaloniaProperty property, object value)
{
var validate = ((IStyledPropertyAccessor)property).GetValidationFunc(_owner.GetType());
if (validate != null && value != AvaloniaProperty.UnsetValue)
{
return validate(_owner, value);
}
return value;
}
}
}

48
src/Avalonia.Controls/AppBuilderBase.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new() public abstract class AppBuilderBase<TAppBuilder> where TAppBuilder : AppBuilderBase<TAppBuilder>, new()
{ {
private static bool s_setupWasAlreadyCalled; private static bool s_setupWasAlreadyCalled;
/// <summary> /// <summary>
/// Gets or sets the <see cref="IRuntimePlatform"/> instance. /// Gets or sets the <see cref="IRuntimePlatform"/> instance.
/// </summary> /// </summary>
@ -92,7 +92,7 @@ namespace Avalonia.Controls
}; };
} }
protected TAppBuilder Self => (TAppBuilder) this; protected TAppBuilder Self => (TAppBuilder)this;
/// <summary> /// <summary>
/// Registers a callback to call before Start is called on the <see cref="Application"/>. /// Registers a callback to call before Start is called on the <see cref="Application"/>.
@ -125,7 +125,6 @@ namespace Avalonia.Controls
var window = new TMainWindow(); var window = new TMainWindow();
if (dataContextProvider != null) if (dataContextProvider != null)
window.DataContext = dataContextProvider(); window.DataContext = dataContextProvider();
window.Show();
Instance.Run(window); Instance.Run(window);
} }
@ -143,7 +142,6 @@ namespace Avalonia.Controls
if (dataContextProvider != null) if (dataContextProvider != null)
mainWindow.DataContext = dataContextProvider(); mainWindow.DataContext = dataContextProvider();
mainWindow.Show();
Instance.Run(mainWindow); Instance.Run(mainWindow);
} }
@ -209,34 +207,36 @@ namespace Avalonia.Controls
public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules()); public TAppBuilder UseAvaloniaModules() => AfterSetup(builder => SetupAvaloniaModules());
private bool CheckSetup { get; set; } = true;
/// <summary> /// <summary>
/// Set this AppBuilder to ignore the setup check. Used for testing purposes. /// Sets the shutdown mode of the application.
/// </summary> /// </summary>
internal TAppBuilder IgnoreSetupCheck() /// <param name="exitMode">The shutdown mode.</param>
/// <returns></returns>
public TAppBuilder SetExitMode(ExitMode exitMode)
{ {
CheckSetup = false; Instance.ExitMode = exitMode;
return Self; return Self;
} }
protected virtual bool CheckSetup => true;
private void SetupAvaloniaModules() private void SetupAvaloniaModules()
{ {
var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies() var moduleInitializers = from assembly in AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetLoadedAssemblies()
from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>() from attribute in assembly.GetCustomAttributes<ExportAvaloniaModuleAttribute>()
where attribute.ForWindowingSubsystem == "" where attribute.ForWindowingSubsystem == ""
|| attribute.ForWindowingSubsystem == WindowingSubsystemName || attribute.ForWindowingSubsystem == WindowingSubsystemName
where attribute.ForRenderingSubsystem == "" where attribute.ForRenderingSubsystem == ""
|| attribute.ForRenderingSubsystem == RenderingSubsystemName || attribute.ForRenderingSubsystem == RenderingSubsystemName
group attribute by attribute.Name into exports group attribute by attribute.Name into exports
select (from export in exports select (from export in exports
orderby export.ForWindowingSubsystem.Length descending orderby export.ForWindowingSubsystem.Length descending
orderby export.ForRenderingSubsystem.Length descending orderby export.ForRenderingSubsystem.Length descending
select export).First().ModuleType into moduleType select export).First().ModuleType into moduleType
select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors select (from constructor in moduleType.GetTypeInfo().DeclaredConstructors
where constructor.GetParameters().Length == 0 && !constructor.IsStatic where constructor.GetParameters().Length == 0 && !constructor.IsStatic
select constructor).Single() into constructor select constructor).Single() into constructor
select (Action)(() => constructor.Invoke(new object[0])); select (Action)(() => constructor.Invoke(new object[0]));
Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke(); Delegate.Combine(moduleInitializers.ToArray()).DynamicInvoke();
} }

112
src/Avalonia.Controls/Application.cs

@ -43,11 +43,15 @@ namespace Avalonia
private Styles _styles; private Styles _styles;
private IResourceDictionary _resources; private IResourceDictionary _resources;
private CancellationTokenSource _mainLoopCancellationTokenSource;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Application"/> class. /// Initializes a new instance of the <see cref="Application"/> class.
/// </summary> /// </summary>
public Application() public Application()
{ {
Windows = new WindowCollection(this);
OnExit += OnExiting; OnExit += OnExiting;
} }
@ -158,6 +162,40 @@ namespace Avalonia
/// <inheritdoc/> /// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => null; IResourceNode IResourceNode.ResourceParent => null;
/// <summary>
/// Gets or sets the <see cref="ExitMode"/>. This property indicates whether the application exits explicitly or implicitly.
/// If <see cref="ExitMode"/> is set to OnExplicitExit the application is only closes if Exit is called.
/// The default is OnLastWindowClose
/// </summary>
/// <value>
/// The shutdown mode.
/// </value>
public ExitMode ExitMode { get; set; }
/// <summary>
/// Gets or sets the main window of the application.
/// </summary>
/// <value>
/// The main window.
/// </value>
public Window MainWindow { get; set; }
/// <summary>
/// Gets the open windows of the application.
/// </summary>
/// <value>
/// The windows.
/// </value>
public WindowCollection Windows { get; }
/// <summary>
/// Gets or sets a value indicating whether this instance is existing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is existing; otherwise, <c>false</c>.
/// </value>
internal bool IsExiting { get; set; }
/// <summary> /// <summary>
/// Initializes the application by loading XAML etc. /// Initializes the application by loading XAML etc.
/// </summary> /// </summary>
@ -171,19 +209,74 @@ namespace Avalonia
/// <param name="closable">The closable to track</param> /// <param name="closable">The closable to track</param>
public void Run(ICloseable closable) public void Run(ICloseable closable)
{ {
var source = new CancellationTokenSource(); if (_mainLoopCancellationTokenSource != null)
closable.Closed += OnExiting; {
closable.Closed += (s, e) => source.Cancel(); throw new Exception("Run should only called once");
Dispatcher.UIThread.MainLoop(source.Token); }
closable.Closed += (s, e) => Exit();
_mainLoopCancellationTokenSource = new CancellationTokenSource();
Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
}
/// <summary>
/// Runs the application's main loop until some condition occurs that is specified by ExitMode.
/// </summary>
/// <param name="mainWindow">The main window</param>
public void Run(Window mainWindow)
{
if (_mainLoopCancellationTokenSource != null)
{
throw new Exception("Run should only called once");
}
_mainLoopCancellationTokenSource = new CancellationTokenSource();
if (MainWindow == null)
{
if (mainWindow == null)
{
throw new ArgumentNullException(nameof(mainWindow));
}
if (!mainWindow.IsVisible)
{
mainWindow.Show();
}
MainWindow = mainWindow;
}
Dispatcher.UIThread.MainLoop(_mainLoopCancellationTokenSource.Token);
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
} }
/// <summary> /// <summary>
/// Runs the application's main loop until the <see cref="CancellationToken"/> is cancelled. /// Runs the application's main loop until the <see cref="CancellationToken"/> is canceled.
/// </summary> /// </summary>
/// <param name="token">The token to track</param> /// <param name="token">The token to track</param>
public void Run(CancellationToken token) public void Run(CancellationToken token)
{ {
Dispatcher.UIThread.MainLoop(token); Dispatcher.UIThread.MainLoop(token);
// Make sure we call OnExit in case an error happened and Exit() wasn't called explicitly
if (!IsExiting)
{
OnExit?.Invoke(this, EventArgs.Empty);
}
} }
/// <summary> /// <summary>
@ -191,7 +284,13 @@ namespace Avalonia
/// </summary> /// </summary>
public void Exit() public void Exit()
{ {
IsExiting = true;
Windows.Clear();
OnExit?.Invoke(this, EventArgs.Empty); OnExit?.Invoke(this, EventArgs.Empty);
_mainLoopCancellationTokenSource?.Cancel();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -233,7 +332,6 @@ namespace Avalonia
.Bind<IInputManager>().ToConstant(InputManager) .Bind<IInputManager>().ToConstant(InputManager)
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>() .Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IStyler>().ToConstant(_styler) .Bind<IStyler>().ToConstant(_styler)
.Bind<ILayoutManager>().ToSingleton<LayoutManager>()
.Bind<IApplicationLifecycle>().ToConstant(this) .Bind<IApplicationLifecycle>().ToConstant(this)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance) .Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance) .Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)

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

Loading…
Cancel
Save