Browse Source

Merge branch 'master' into fixes/border-corner-radius

pull/9543/head
Max Katz 3 years ago
committed by GitHub
parent
commit
372fbf055f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      .gitignore
  2. 0
      .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject
  3. 0
      .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject
  4. 0
      .ncrunch/Avalonia.Browser.v3.ncrunchproject
  5. 0
      .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject
  6. 5
      .ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject
  7. 5
      .ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject
  8. 5
      .ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject
  9. 5
      .ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject
  10. 5
      .ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject
  11. 5
      .ncrunch/ControlCatalog.Browser.v3.ncrunchproject
  12. 5
      .ncrunch/MobileSandbox.v3.ncrunchproject
  13. 54
      Avalonia.sln
  14. 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  15. 0
      samples/ControlCatalog.Browser.Blazor/App.razor
  16. 4
      samples/ControlCatalog.Browser.Blazor/App.razor.cs
  17. 6
      samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj
  18. 2
      samples/ControlCatalog.Browser.Blazor/Pages/Index.razor
  19. 2
      samples/ControlCatalog.Browser.Blazor/Program.cs
  20. 0
      samples/ControlCatalog.Browser.Blazor/Properties/launchSettings.json
  21. 0
      samples/ControlCatalog.Browser.Blazor/Shared/MainLayout.razor
  22. 2
      samples/ControlCatalog.Browser.Blazor/_Imports.razor
  23. 0
      samples/ControlCatalog.Browser.Blazor/wwwroot/css/app.css
  24. 0
      samples/ControlCatalog.Browser.Blazor/wwwroot/favicon.ico
  25. 0
      samples/ControlCatalog.Browser.Blazor/wwwroot/index.html
  26. 6
      samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj
  27. 4
      samples/ControlCatalog.Browser/EmbedSample.Browser.cs
  28. 0
      samples/ControlCatalog.Browser/Logo.svg
  29. 4
      samples/ControlCatalog.Browser/Program.cs
  30. 0
      samples/ControlCatalog.Browser/Roots.xml
  31. 0
      samples/ControlCatalog.Browser/app.css
  32. 0
      samples/ControlCatalog.Browser/embed.js
  33. 0
      samples/ControlCatalog.Browser/favicon.ico
  34. 0
      samples/ControlCatalog.Browser/index.html
  35. 0
      samples/ControlCatalog.Browser/main.js
  36. 0
      samples/ControlCatalog.Browser/runtimeconfig.template.json
  37. 9
      samples/ControlCatalog/App.xaml
  38. 158
      samples/ControlCatalog/App.xaml.cs
  39. 39
      samples/ControlCatalog/MainView.xaml.cs
  40. 51
      samples/ControlCatalog/Pages/DataGridPage.xaml
  41. 1
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  42. 8
      samples/ControlCatalog/Pages/TabControlPage.xaml
  43. 36
      samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs
  44. 4
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  45. 25
      src/Android/Avalonia.Android/AvaloniaMainActivity.cs
  46. 22
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  47. 2
      src/Avalonia.Base/Animation/Animatable.cs
  48. 2
      src/Avalonia.Base/Animation/AnimationInstance`1.cs
  49. 3
      src/Avalonia.Base/Avalonia.Base.csproj
  50. 542
      src/Avalonia.Base/AvaloniaObject.cs
  51. 36
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  52. 17
      src/Avalonia.Base/AvaloniaProperty.cs
  53. 24
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs
  54. 28
      src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs
  55. 32
      src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs
  56. 20
      src/Avalonia.Base/Data/BindingNotification.cs
  57. 27
      src/Avalonia.Base/Data/BindingPriority.cs
  58. 130
      src/Avalonia.Base/Data/BindingValue.cs
  59. 9
      src/Avalonia.Base/Data/Core/ExpressionObserver.cs
  60. 50
      src/Avalonia.Base/DirectPropertyBase.cs
  61. 7
      src/Avalonia.Base/IStyledPropertyAccessor.cs
  62. 11
      src/Avalonia.Base/Layout/Layoutable.cs
  63. 4
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  64. 25
      src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs
  65. 154
      src/Avalonia.Base/PropertyStore/BindingEntry.cs
  66. 148
      src/Avalonia.Base/PropertyStore/BindingEntryBase.cs
  67. 82
      src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs
  68. 76
      src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs
  69. 55
      src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs
  70. 168
      src/Avalonia.Base/PropertyStore/EffectiveValue.cs
  71. 270
      src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs
  72. 36
      src/Avalonia.Base/PropertyStore/FramePriority.cs
  73. 8
      src/Avalonia.Base/PropertyStore/IBatchUpdate.cs
  74. 18
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  75. 28
      src/Avalonia.Base/PropertyStore/IValue.cs
  76. 30
      src/Avalonia.Base/PropertyStore/IValueEntry.cs
  77. 16
      src/Avalonia.Base/PropertyStore/IValueEntry`1.cs
  78. 31
      src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs
  79. 63
      src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs
  80. 59
      src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs
  81. 41
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  82. 62
      src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs
  83. 82
      src/Avalonia.Base/PropertyStore/LoggingUtils.cs
  84. 326
      src/Avalonia.Base/PropertyStore/PriorityValue.cs
  85. 35
      src/Avalonia.Base/PropertyStore/PropertyNotifying.cs
  86. 35
      src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs
  87. 52
      src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs
  88. 33
      src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs
  89. 55
      src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs
  90. 115
      src/Avalonia.Base/PropertyStore/ValueFrame.cs
  91. 45
      src/Avalonia.Base/PropertyStore/ValueOwner.cs
  92. 1010
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  93. 59
      src/Avalonia.Base/Reactive/BindingValueAdapter.cs
  94. 33
      src/Avalonia.Base/Reactive/BindingValueExtensions.cs
  95. 6
      src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
  96. 62
      src/Avalonia.Base/Reactive/TypedBindingAdapter.cs
  97. 55
      src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs
  98. 5
      src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs
  99. 20
      src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
  100. 3
      src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

10
.gitignore

@ -210,9 +210,9 @@ coc-settings.json
.ccls-cache
.ccls
*.map
src/Web/Avalonia.Web.Blazor/wwwroot/*.js
src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
src/Browser/Avalonia.Browser.Blazor/wwwroot/*.js
src/Browser/Avalonia.Browser.Blazor/Interop/Typescript/*.js
node_modules
src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
src/Web/Avalonia.Web.Blazor/wwwroot
src/Web/Avalonia.Web/wwwroot
src/Browser/Avalonia.Browser.Blazor/webapp/package-lock.json
src/Browser/Avalonia.Browser.Blazor/wwwroot
src/Browser/Avalonia.Browser/wwwroot

0
.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject → .ncrunch/Avalonia.Benchmarks.v3.ncrunchproject

0
.ncrunch/Avalonia.Web.v3.ncrunchproject → .ncrunch/Avalonia.Browser.Blazor.v3.ncrunchproject

0
.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject → .ncrunch/Avalonia.Browser.v3.ncrunchproject

0
.ncrunch/ControlCatalog.Web.v3.ncrunchproject → .ncrunch/Avalonia.Designer.HostApp.v3.ncrunchproject

5
.ncrunch/Avalonia.Themes.Fluent.net6.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<InstrumentOutputAssembly>False</InstrumentOutputAssembly>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Themes.Fluent.netstandard2.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<InstrumentOutputAssembly>False</InstrumentOutputAssembly>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Themes.Simple.net6.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<InstrumentOutputAssembly>False</InstrumentOutputAssembly>
</Settings>
</ProjectConfiguration>

5
.ncrunch/Avalonia.Themes.Simple.netstandard2.0.v3.ncrunchproject

@ -0,0 +1,5 @@
<ProjectConfiguration>
<Settings>
<InstrumentOutputAssembly>False</InstrumentOutputAssembly>
</Settings>
</ProjectConfiguration>

5
.ncrunch/ControlCatalog.Browser.Blazor.v3.ncrunchproject

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

5
.ncrunch/ControlCatalog.Browser.v3.ncrunchproject

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

5
.ncrunch/MobileSandbox.v3.ncrunchproject

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

54
Avalonia.sln

@ -198,9 +198,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.IntegrationTests.Appium", "tests\Avalonia.IntegrationTests.Appium\Avalonia.IntegrationTests.Appium.csproj", "{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Web", "Web", "{86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web.Blazor", "src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj", "{25831348-EB2A-483E-9576-E8F6528674A5}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Browser", "Browser", "{86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "samples\interop\WindowsInteropTest\WindowsInteropTest.csproj", "{26A98DA1-D89D-4A95-8152-349F404DA2E2}"
EndProject
@ -216,8 +214,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesignerSupport.Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DevGenerators", "src\tools\DevGenerators\DevGenerators.csproj", "{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Web", "src\Web\Avalonia.Web\Avalonia.Web.csproj", "{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox", "samples\MobileSandbox\MobileSandbox.csproj", "{3B8519C1-2F51-4F12-A348-120AB91D4532}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Android", "samples\MobileSandbox.Android\MobileSandbox.Android.csproj", "{C90FE60B-B01E-4F35-91D6-379D6966030F}"
@ -226,9 +222,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.iOS", "sample
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MobileSandbox.Desktop", "samples\MobileSandbox.Desktop\MobileSandbox.Desktop.csproj", "{62D392C9-81CF-487F-92E8-598B2AF3FDCE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Blazor.Web", "samples\ControlCatalog.Blazor.Web\ControlCatalog.Blazor.Web.csproj", "{6A710364-AE6D-40BD-968B-024311527AC2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser", "src\Browser\Avalonia.Browser\Avalonia.Browser.csproj", "{4A39637C-9338-4925-A4DB-D072E292EC78}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Browser.Blazor", "src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj", "{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser", "samples\ControlCatalog.Browser\ControlCatalog.Browser.csproj", "{15B93A4C-1B46-43F6-B534-7B25B6E99932}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Web", "samples\ControlCatalog.Web\ControlCatalog.Web.csproj", "{8B3E8405-DE18-4048-A459-9CA4AC3319A2}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Browser.Blazor", "samples\ControlCatalog.Browser.Blazor\ControlCatalog.Browser.Blazor.csproj", "{90B08091-9BBD-4362-B712-E9F2CC62B218}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -480,10 +480,6 @@ Global
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A}.Release|Any CPU.Build.0 = Release|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{25831348-EB2A-483E-9576-E8F6528674A5}.Release|Any CPU.Build.0 = Release|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{26A98DA1-D89D-4A95-8152-349F404DA2E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -512,10 +508,6 @@ Global
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB}.Release|Any CPU.Build.0 = Release|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E}.Release|Any CPU.Build.0 = Release|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -533,14 +525,22 @@ Global
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62D392C9-81CF-487F-92E8-598B2AF3FDCE}.Release|Any CPU.Build.0 = Release|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6A710364-AE6D-40BD-968B-024311527AC2}.Release|Any CPU.Build.0 = Release|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8B3E8405-DE18-4048-A459-9CA4AC3319A2}.Release|Any CPU.Build.0 = Release|Any CPU
{4A39637C-9338-4925-A4DB-D072E292EC78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A39637C-9338-4925-A4DB-D072E292EC78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A39637C-9338-4925-A4DB-D072E292EC78}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A39637C-9338-4925-A4DB-D072E292EC78}.Release|Any CPU.Build.0 = Release|Any CPU
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D}.Release|Any CPU.Build.0 = Release|Any CPU
{15B93A4C-1B46-43F6-B534-7B25B6E99932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{15B93A4C-1B46-43F6-B534-7B25B6E99932}.Debug|Any CPU.Build.0 = Debug|Any CPU
{15B93A4C-1B46-43F6-B534-7B25B6E99932}.Release|Any CPU.ActiveCfg = Release|Any CPU
{15B93A4C-1B46-43F6-B534-7B25B6E99932}.Release|Any CPU.Build.0 = Release|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.ActiveCfg = Release|Any CPU
{90B08091-9BBD-4362-B712-E9F2CC62B218}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -591,20 +591,20 @@ Global
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{676D6BFD-029D-4E43-BFC7-3892265CE251} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{F2CE566B-E7F6-447A-AB1A-3F574A6FE43A} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{2B390431-288C-435C-BB6B-A374033BD8D1} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{EABE2161-989B-42BF-BD8D-1E34B20C21F1} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{1BBFAD42-B99E-47E0-B00A-A4BC6B6BB4BB} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{76D39FF6-6B4F-46C4-93CD-E6FC4665739E} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{3B8519C1-2F51-4F12-A348-120AB91D4532} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{C90FE60B-B01E-4F35-91D6-379D6966030F} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{62D392C9-81CF-487F-92E8-598B2AF3FDCE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{6A710364-AE6D-40BD-968B-024311527AC2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{8B3E8405-DE18-4048-A459-9CA4AC3319A2} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{4A39637C-9338-4925-A4DB-D072E292EC78} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{47F8530C-F19B-4B1A-B4D6-EB231522AE5D} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
packages/Avalonia/AvaloniaBuildTasks.targets

@ -99,6 +99,7 @@
AssemblyFile="@(IntermediateAssembly)"
ReferencesFilePath="$(AvaloniaXamlReferencesTemporaryFilePath)"
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
RefAssemblyFile="@(IntermediateRefAssembly)"
ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
ReportImportance="$(AvaloniaXamlReportImportance)"

0
samples/ControlCatalog.Blazor.Web/App.razor → samples/ControlCatalog.Browser.Blazor/App.razor

4
samples/ControlCatalog.Blazor.Web/App.razor.cs → samples/ControlCatalog.Browser.Blazor/App.razor.cs

@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Web.Blazor;
using Avalonia.Browser.Blazor;
namespace ControlCatalog.Blazor.Web;
namespace ControlCatalog.Browser.Blazor;
public partial class App
{

6
samples/ControlCatalog.Blazor.Web/ControlCatalog.Blazor.Web.csproj → samples/ControlCatalog.Browser.Blazor/ControlCatalog.Browser.Blazor.csproj

@ -15,15 +15,15 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web.Blazor\Avalonia.Web.Blazor.csproj" />
<ProjectReference Include="..\..\src\Browser\Avalonia.Browser.Blazor\Avalonia.Browser.Blazor.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
<Import Project="..\..\build\ReferenceCoreLibraries.props" />
<Import Project="..\..\build\BuildTargets.targets" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.props" />
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.targets" />
</Project>

2
samples/ControlCatalog.Blazor.Web/Pages/Index.razor → samples/ControlCatalog.Browser.Blazor/Pages/Index.razor

@ -1,5 +1,5 @@
@page "/"
@using Avalonia.Web.Blazor
@using Avalonia.Browser.Blazor
<AvaloniaView />

2
samples/ControlCatalog.Blazor.Web/Program.cs → samples/ControlCatalog.Browser.Blazor/Program.cs

@ -3,7 +3,7 @@ using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using ControlCatalog.Blazor.Web;
using ControlCatalog.Browser.Blazor;
public class Program
{

0
samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json → samples/ControlCatalog.Browser.Blazor/Properties/launchSettings.json

0
samples/ControlCatalog.Blazor.Web/Shared/MainLayout.razor → samples/ControlCatalog.Browser.Blazor/Shared/MainLayout.razor

2
samples/ControlCatalog.Blazor.Web/_Imports.razor → samples/ControlCatalog.Browser.Blazor/_Imports.razor

@ -6,5 +6,5 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop
@using ControlCatalog.Blazor.Web.Shared
@using ControlCatalog.Browser.Blazor.Shared
@using SkiaSharp

0
samples/ControlCatalog.Blazor.Web/wwwroot/css/app.css → samples/ControlCatalog.Browser.Blazor/wwwroot/css/app.css

0
samples/ControlCatalog.Blazor.Web/wwwroot/favicon.ico → samples/ControlCatalog.Browser.Blazor/wwwroot/favicon.ico

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

0
samples/ControlCatalog.Blazor.Web/wwwroot/index.html → samples/ControlCatalog.Browser.Blazor/wwwroot/index.html

6
samples/ControlCatalog.Web/ControlCatalog.Web.csproj → samples/ControlCatalog.Browser/ControlCatalog.Browser.csproj

@ -26,7 +26,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\..\src\Web\Avalonia.Web\Avalonia.Web.csproj" />
<ProjectReference Include="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
@ -39,6 +39,6 @@
<WasmExtraFilesToDeploy Include="app.css" />
</ItemGroup>
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.props" />
<Import Project="..\..\src\Web\Avalonia.Web\Avalonia.Web.targets" />
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.props" />
<Import Project="..\..\src\Browser\Avalonia.Browser\Avalonia.Browser.targets" />
</Project>

4
samples/ControlCatalog.Web/EmbedSample.Browser.cs → samples/ControlCatalog.Browser/EmbedSample.Browser.cs

@ -1,11 +1,11 @@
using System;
using System.Runtime.InteropServices.JavaScript;
using Avalonia.Platform;
using Avalonia.Web;
using Avalonia.Browser;
using ControlCatalog.Pages;
namespace ControlCatalog.Web;
namespace ControlCatalog.Browser;
public class EmbedSampleWeb : INativeDemoControl
{

0
samples/ControlCatalog.Web/Logo.svg → samples/ControlCatalog.Browser/Logo.svg

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

4
samples/ControlCatalog.Web/Program.cs → samples/ControlCatalog.Browser/Program.cs

@ -1,8 +1,8 @@
using System.Runtime.Versioning;
using Avalonia;
using Avalonia.Web;
using Avalonia.Browser;
using ControlCatalog;
using ControlCatalog.Web;
using ControlCatalog.Browser;
[assembly:SupportedOSPlatform("browser")]

0
samples/ControlCatalog.Web/Roots.xml → samples/ControlCatalog.Browser/Roots.xml

0
samples/ControlCatalog.Web/app.css → samples/ControlCatalog.Browser/app.css

0
samples/ControlCatalog.Web/embed.js → samples/ControlCatalog.Browser/embed.js

0
samples/ControlCatalog.Web/favicon.ico → samples/ControlCatalog.Browser/favicon.ico

Before

Width:  |  Height:  |  Size: 172 KiB

After

Width:  |  Height:  |  Size: 172 KiB

0
samples/ControlCatalog.Web/index.html → samples/ControlCatalog.Browser/index.html

0
samples/ControlCatalog.Web/main.js → samples/ControlCatalog.Browser/main.js

0
samples/ControlCatalog.Web/runtimeconfig.template.json → samples/ControlCatalog.Browser/runtimeconfig.template.json

9
samples/ControlCatalog/App.xaml

@ -9,6 +9,15 @@
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://ControlSamples/HamburgerMenu/HamburgerMenu.xaml" />
</ResourceDictionary.MergedDictionaries>
<StyleInclude x:Key="DataGridFluent" Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml" />
<StyleInclude x:Key="DataGridSimple" Source="avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml" />
<StyleInclude x:Key="ColorPickerFluent" Source="avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml" />
<StyleInclude x:Key="ColorPickerSimple" Source="avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml" />
<ResourceInclude x:Key="FluentAccentColors" Source="avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml" />
<ResourceInclude x:Key="FluentBaseLightColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml" />
<ResourceInclude x:Key="FluentBaseDarkColors" Source="avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml" />
<ResourceInclude x:Key="FluentBaseColors" Source="avares://Avalonia.Themes.Fluent/Accents/Base.xaml" />
</ResourceDictionary>
</Application.Resources>
<Application.Styles>

158
samples/ControlCatalog/App.xaml.cs

@ -3,85 +3,46 @@ using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using Avalonia.Themes.Simple;
using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
using ControlCatalog.ViewModels;
namespace ControlCatalog
{
public class App : Application
{
private readonly Styles _themeStylesContainer = new();
private FluentTheme? _fluentTheme;
private SimpleTheme? _simpleTheme;
private IResourceDictionary? _fluentBaseLightColors, _fluentBaseDarkColors;
private IStyle? _colorPickerFluent, _colorPickerSimple;
private IStyle? _dataGridFluent, _dataGridSimple;
public App()
{
DataContext = new ApplicationViewModel();
}
public static readonly StyleInclude ColorPickerFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml")
};
public static readonly StyleInclude ColorPickerSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml")
};
public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
};
public static readonly StyleInclude DataGridSimple = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Simple.xaml")
};
public static FluentTheme Fluent = new FluentTheme(new Uri("avares://ControlCatalog/Styles"));
public static SimpleTheme Simple = new SimpleTheme(new Uri("avares://ControlCatalog/Styles"));
public static Styles SimpleLight = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseLight.xaml")
},
Simple
};
public static Styles SimpleDark = new Styles
{
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/AccentColors.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/Base.xaml")
},
new StyleInclude(new Uri("resm:Styles?assembly=ControlCatalog"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/BaseDark.xaml")
},
Simple
};
public override void Initialize()
{
Styles.Insert(0, Fluent);
Styles.Insert(1, ColorPickerFluent);
Styles.Insert(2, DataGridFluent);
Styles.Add(_themeStylesContainer);
AvaloniaXamlLoader.Load(this);
_fluentTheme = new FluentTheme();
_simpleTheme = new SimpleTheme();
_simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentAccentColors"]!);
_simpleTheme.Resources.MergedDictionaries.Add((IResourceDictionary)Resources["FluentBaseColors"]!);
_colorPickerFluent = (IStyle)Resources["ColorPickerFluent"]!;
_colorPickerSimple = (IStyle)Resources["ColorPickerSimple"]!;
_dataGridFluent = (IStyle)Resources["DataGridFluent"]!;
_dataGridSimple = (IStyle)Resources["DataGridSimple"]!;
_fluentBaseLightColors = (IResourceDictionary)Resources["FluentBaseLightColors"]!;
_fluentBaseDarkColors = (IResourceDictionary)Resources["FluentBaseDarkColors"]!;
SetThemeVariant(CatalogTheme.FluentLight);
}
public override void OnFrameworkInitializationCompleted()
@ -97,5 +58,78 @@ namespace ControlCatalog
base.OnFrameworkInitializationCompleted();
}
private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
public static void SetThemeVariant(CatalogTheme theme)
{
var app = (App)Current!;
var prevTheme = app._prevTheme;
app._prevTheme = theme;
var shouldReopenWindow = theme switch
{
CatalogTheme.FluentLight => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
CatalogTheme.FluentDark => prevTheme is CatalogTheme.SimpleDark or CatalogTheme.SimpleLight,
CatalogTheme.SimpleLight => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
CatalogTheme.SimpleDark => prevTheme is CatalogTheme.FluentDark or CatalogTheme.FluentLight,
_ => throw new ArgumentOutOfRangeException(nameof(theme), theme, null)
};
if (app._themeStylesContainer.Count == 0)
{
app._themeStylesContainer.Add(new Style());
app._themeStylesContainer.Add(new Style());
app._themeStylesContainer.Add(new Style());
}
if (theme == CatalogTheme.FluentLight)
{
app._fluentTheme!.Mode = FluentThemeMode.Light;
app._themeStylesContainer[0] = app._fluentTheme;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
else if (theme == CatalogTheme.FluentDark)
{
app._fluentTheme!.Mode = FluentThemeMode.Dark;
app._themeStylesContainer[0] = app._fluentTheme;
app._themeStylesContainer[1] = app._colorPickerFluent!;
app._themeStylesContainer[2] = app._dataGridFluent!;
}
else if (theme == CatalogTheme.SimpleLight)
{
app._simpleTheme!.Mode = SimpleThemeMode.Light;
app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseDarkColors!);
app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseLightColors!);
app._themeStylesContainer[0] = app._simpleTheme;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}
else if (theme == CatalogTheme.SimpleDark)
{
app._simpleTheme!.Mode = SimpleThemeMode.Dark;
app._simpleTheme.Resources.MergedDictionaries.Remove(app._fluentBaseLightColors!);
app._simpleTheme.Resources.MergedDictionaries.Add(app._fluentBaseDarkColors!);
app._themeStylesContainer[0] = app._simpleTheme;
app._themeStylesContainer[1] = app._colorPickerSimple!;
app._themeStylesContainer[2] = app._dataGridSimple!;
}
if (shouldReopenWindow)
{
if (app.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
var oldWindow = desktopLifetime.MainWindow;
var newWindow = new MainWindow();
desktopLifetime.MainWindow = newWindow;
newWindow.Show();
oldWindow?.Close();
}
else if (app.ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
{
singleViewLifetime.MainView = new MainView();
}
}
}
}
}

39
samples/ControlCatalog/MainView.xaml.cs

@ -6,7 +6,6 @@ using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Themes.Fluent;
using ControlCatalog.Models;
using ControlCatalog.Pages;
@ -31,46 +30,12 @@ namespace ControlCatalog
}
var themes = this.Get<ComboBox>("Themes");
themes.SelectedItem = App.CurrentTheme;
themes.SelectionChanged += (sender, e) =>
{
if (themes.SelectedItem is CatalogTheme theme)
{
var themeStyle = Application.Current!.Styles[0];
if (theme == CatalogTheme.FluentLight)
{
if (App.Fluent.Mode != FluentThemeMode.Light)
{
App.Fluent.Mode = FluentThemeMode.Light;
}
Application.Current.Styles[0] = App.Fluent;
Application.Current.Styles[1] = App.ColorPickerFluent;
Application.Current.Styles[2] = App.DataGridFluent;
}
else if (theme == CatalogTheme.FluentDark)
{
if (App.Fluent.Mode != FluentThemeMode.Dark)
{
App.Fluent.Mode = FluentThemeMode.Dark;
}
Application.Current.Styles[0] = App.Fluent;
Application.Current.Styles[1] = App.ColorPickerFluent;
Application.Current.Styles[2] = App.DataGridFluent;
}
else if (theme == CatalogTheme.SimpleLight)
{
App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Light;
Application.Current.Styles[0] = App.SimpleLight;
Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridSimple;
}
else if (theme == CatalogTheme.SimpleDark)
{
App.Simple.Mode = Avalonia.Themes.Simple.SimpleThemeMode.Dark;
Application.Current.Styles[0] = App.SimpleDark;
Application.Current.Styles[1] = App.ColorPickerSimple;
Application.Current.Styles[2] = App.DataGridSimple;
}
App.SetThemeVariant(theme);
}
};

51
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -14,22 +14,6 @@
<Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" />
</ControlTheme>
</UserControl.Resources>
<UserControl.Styles>
<Style Selector="DataGridColumnHeader:nth-last-child(1)">
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="DataGridCell:nth-last-child(1)">
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="DataGrid#dataGridGrouping DataGridRow:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="DataGrid#dataGridGrouping DataGridRow:nth-last-child(5n+1)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,Auto,*">
<StackPanel Orientation="Vertical" Spacing="4" Grid.Row="0">
<TextBlock Classes="h2">A control for displaying and interacting with a data source.</TextBlock>
@ -45,8 +29,7 @@
<CheckBox x:Name="ShowGDP" IsChecked="True" Content="Toggle GDP Column Visibility"
DockPanel.Dock="Top"/>
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All"
RowBackground="#1000"
AlternatingRowBackground="#1fff">
RowBackground="#1000">
<DataGrid.Columns>
<!-- Using HeaderTemplate -->
<DataGridTextColumn Header="Country" HeaderTemplate="{StaticResource Demo.DataTemplates.CountryHeader}" Binding="{Binding Name}" Width="6*" x:DataType="local:Country" />
@ -59,6 +42,24 @@
IsVisible="{Binding #ShowGDP.IsChecked}"
x:DataType="local:Country" />
</DataGrid.Columns>
<DataGrid.CellTheme>
<ControlTheme TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<ControlTheme.Children>
<Style Selector="^:nth-child(1)">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</ControlTheme.Children>
</ControlTheme>
</DataGrid.CellTheme>
<DataGrid.ColumnHeaderTheme>
<ControlTheme TargetType="DataGridColumnHeader" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<ControlTheme.Children>
<Style Selector="^:nth-child(1)">
<Setter Property="FontWeight" Value="Bold" />
</Style>
</ControlTheme.Children>
</ControlTheme>
</DataGrid.ColumnHeaderTheme>
</DataGrid>
</DockPanel>
</TabItem>
@ -71,6 +72,20 @@
<DataGridTextColumn DisplayIndex="2" Header="Area" Binding="{Binding Area}" Width="3*" x:DataType="local:Country" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" x:DataType="local:Country" />
</DataGrid.Columns>
<DataGrid.RowTheme>
<ControlTheme TargetType="DataGridRow" BasedOn="{StaticResource {x:Type DataGridRow}}">
<ControlTheme.Children>
<Style Selector="^:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="^:nth-last-child(5n+1)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</ControlTheme.Children>
</ControlTheme>
</DataGrid.RowTheme>
</DataGrid>
</TabItem>
<TabItem x:Name="EditableTab" Header="Editable">

1
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -32,6 +32,7 @@
</StackPanel>
<ListBox Items="{Binding Items}"
Selection="{Binding Selection}"
DisplayMemberBinding="{Binding (viewModels:ItemModel).ID, StringFormat='{}Item {0:N0}'}"
AutoScrollToSelectedItem="{Binding AutoScrollToSelectedItem}"
SelectionMode="{Binding SelectionMode^}"
WrapSelection="{Binding WrapSelection}"/>

8
samples/ControlCatalog/Pages/TabControlPage.xaml

@ -53,14 +53,8 @@
<TabControl
Items="{Binding Tabs}"
Margin="0 16"
HeaderDisplayMemberBinding="{Binding Header, x:DataType=viewModels:TabControlPageViewModelItem}"
TabStripPlacement="{Binding TabPlacement}">
<TabControl.ItemTemplate>
<DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
<TextBlock
Text="{Binding Header}">
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:DataType="viewModels:TabControlPageViewModelItem">
<StackPanel Orientation="Vertical" Spacing="8">

36
samples/ControlCatalog/ViewModels/ListBoxPageViewModel.cs

@ -4,6 +4,7 @@ using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Controls.Selection;
using ControlCatalog.Pages;
using MiniMvvm;
namespace ControlCatalog.ViewModels
@ -20,9 +21,9 @@ namespace ControlCatalog.ViewModels
public ListBoxPageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
Items = new ObservableCollection<ItemModel>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
Selection = new SelectionModel<string>();
Selection = new SelectionModel<ItemModel>();
Selection.Select(1);
_selectionMode = this.WhenAnyValue(
@ -58,8 +59,8 @@ namespace ControlCatalog.ViewModels
});
}
public ObservableCollection<string> Items { get; }
public SelectionModel<string> Selection { get; }
public ObservableCollection<ItemModel> Items { get; }
public SelectionModel<ItemModel> Selection { get; }
public IObservable<SelectionMode> SelectionMode => _selectionMode;
public bool Multiple
@ -96,6 +97,31 @@ namespace ControlCatalog.ViewModels
public MiniCommand RemoveItemCommand { get; }
public MiniCommand SelectRandomItemCommand { get; }
private string GenerateItem() => $"Item {_counter++.ToString()}";
private ItemModel GenerateItem() => new ItemModel(_counter ++);
}
/// <summary>
/// An Item model for the <see cref="ListBoxPage"/>
/// </summary>
public class ItemModel
{
/// <summary>
/// Creates a new ItemModel with the given ID
/// </summary>
/// <param name="id">The ID to display</param>
public ItemModel(int id)
{
ID = id;
}
/// <summary>
/// The ID of this Item
/// </summary>
public int ID { get; }
public override string ToString()
{
return $"Item {ID}";
}
}
}

4
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -95,6 +95,10 @@ namespace Avalonia.Android
_imm.UpdateSelection(_host, surroundingText.AnchorOffset, surroundingText.CursorOffset, surroundingText.AnchorOffset, surroundingText.CursorOffset);
}
else
{
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.ImplicitOnly);
}
}
private void SurroundingTextChanged(object sender, EventArgs e)

25
src/Android/Avalonia.Android/AvaloniaMainActivity.cs

@ -4,9 +4,12 @@ using Android.Content;
using Android.Content.Res;
using Android.OS;
using Android.Runtime;
using Android.Views;
using AndroidX.AppCompat.App;
using AndroidX.Lifecycle;
using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android
{
public abstract class AvaloniaMainActivity : AppCompatActivity, IActivityResultHandler
@ -15,6 +18,7 @@ namespace Avalonia.Android
public Action<int, Result, Intent> ActivityResult { get; set; }
internal AvaloniaView View;
private GlobalLayoutListener _listener;
protected override void OnCreate(Bundle savedInstanceState)
{
@ -32,6 +36,10 @@ namespace Avalonia.Android
base.OnCreate(savedInstanceState);
SetContentView(View);
_listener = new GlobalLayoutListener(View);
View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
}
public object Content
@ -57,6 +65,8 @@ namespace Avalonia.Android
{
View.Content = null;
View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
base.OnDestroy();
}
@ -66,5 +76,20 @@ namespace Avalonia.Android
ActivityResult?.Invoke(requestCode, resultCode, data);
}
class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
{
private AvaloniaView _view;
public GlobalLayoutListener(AvaloniaView view)
{
_view = view;
}
public void OnGlobalLayout()
{
_view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
}
}
}
}

22
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -26,6 +26,7 @@ using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -63,7 +64,21 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IInputRoot InputRoot { get; private set; }
public virtual Size ClientSize => Size.ToSize(RenderScaling);
public virtual Size ClientSize
{
get
{
AndroidRect rect = new AndroidRect();
AndroidRect intersection = new AndroidRect();
_view.GetWindowVisibleDisplayFrame(intersection);
_view.GetGlobalVisibleRect(rect);
intersection.Intersect(rect);
return new PixelSize(intersection.Right - intersection.Left, intersection.Bottom - intersection.Top).ToSize(RenderScaling);
}
}
public Size? FrameSize => null;
@ -149,6 +164,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
Resized?.Invoke(size, PlatformResizeReason.Unspecified);
}
internal void Resize(Size size)
{
Resized?.Invoke(size, PlatformResizeReason.Layout);
}
class ViewImpl : InvalidationAwareSurfaceView, ISurfaceHolderCallback, IInitEditorInfo
{
private readonly TopLevelImpl _tl;

2
src/Avalonia.Base/Animation/Animatable.cs

@ -235,7 +235,7 @@ namespace Avalonia.Animation
private object? GetAnimationBaseValue(AvaloniaProperty property)
{
var value = this.GetBaseValue(property, BindingPriority.LocalValue);
var value = this.GetBaseValue(property);
if (value == AvaloniaProperty.UnsetValue)
{

2
src/Avalonia.Base/Animation/AnimationInstance`1.cs

@ -229,7 +229,7 @@ namespace Avalonia.Animation
private void UpdateNeutralValue()
{
var property = _animator.Property ?? throw new InvalidOperationException("Animator has no property specified.");
var baseValue = _targetControl.GetBaseValue(property, BindingPriority.LocalValue);
var baseValue = _targetControl.GetBaseValue(property);
_neutralValue = baseValue != AvaloniaProperty.UnsetValue ?
(T)baseValue! : (T)_targetControl.GetValue(property)!;

3
src/Avalonia.Base/Avalonia.Base.csproj

@ -28,6 +28,7 @@
<ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.Base.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.ColorPicker, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Controls.DataGrid, PublicKey=$(AvaloniaPublicKey)" />
@ -39,8 +40,8 @@
<InternalsVisibleTo Include="Avalonia.Skia.RenderTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Skia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.UnitTests, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Benchmarks, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Win32, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Web.Blazor, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Dialogs, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.Diagnostics, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7" />

542
src/Avalonia.Base/AvaloniaObject.cs

@ -1,11 +1,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Data;
using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.PropertyStore;
using Avalonia.Reactive;
using Avalonia.Threading;
namespace Avalonia
@ -18,13 +18,11 @@ namespace Avalonia
/// </remarks>
public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
{
private readonly ValueStore _values;
private AvaloniaObject? _inheritanceParent;
private List<IDisposable>? _directBindings;
private PropertyChangedEventHandler? _inpcChanged;
private EventHandler<AvaloniaPropertyChangedEventArgs>? _propertyChanged;
private List<AvaloniaObject>? _inheritanceChildren;
private ValueStore? _values;
private bool _batchUpdate;
/// <summary>
/// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
@ -32,6 +30,7 @@ namespace Avalonia
public AvaloniaObject()
{
VerifyAccess();
_values = new ValueStore(this);
}
/// <summary>
@ -59,7 +58,7 @@ namespace Avalonia
/// <value>
/// The inheritance parent.
/// </value>
protected AvaloniaObject? InheritanceParent
protected internal AvaloniaObject? InheritanceParent
{
get
{
@ -72,28 +71,10 @@ namespace Avalonia
if (_inheritanceParent != value)
{
var oldParent = _inheritanceParent;
var valuestore = _values;
_inheritanceParent?.RemoveInheritanceChild(this);
_inheritanceParent = value;
var properties = AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType());
var propertiesCount = properties.Count;
for (var i = 0; i < propertiesCount; i++)
{
var property = properties[i];
if (valuestore?.IsSet(property) == true)
{
// If local value set there can be no change.
continue;
}
property.RouteInheritanceParentChanged(this, oldParent);
}
_inheritanceParent?.AddInheritanceChild(this);
_values.SetInheritanceParent(value);
}
}
}
@ -118,24 +99,15 @@ namespace Avalonia
set { this.Bind(binding.Property!, value); }
}
private ValueStore Values
{
get
{
if (_values is null)
{
_values = new ValueStore(this);
if (_batchUpdate)
_values.BeginBatchUpdate();
}
return _values;
}
}
/// <summary>
/// Returns a value indicating whether the current thread is the UI thread.
/// </summary>
/// <returns>true if the current thread is the UI thread; otherwise false.</returns>
public bool CheckAccess() => Dispatcher.UIThread.CheckAccess();
/// <summary>
/// Checks that the current thread is the UI thread and throws if not.
/// </summary>
public void VerifyAccess() => Dispatcher.UIThread.VerifyAccess();
/// <summary>
@ -144,9 +116,9 @@ namespace Avalonia
/// <param name="property">The property.</param>
public void ClearValue(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
property.RouteClearValue(this);
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
_values.ClearLocalValue(property);
}
/// <summary>
@ -234,9 +206,12 @@ namespace Avalonia
/// <returns>The value.</returns>
public object? GetValue(AvaloniaProperty property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
_ = property ?? throw new ArgumentNullException(nameof(property));
return property.RouteGetValue(this);
if (property.IsDirect)
return property.RouteGetValue(this);
else
return _values.GetValue(property);
}
/// <summary>
@ -247,10 +222,9 @@ namespace Avalonia
/// <returns>The value.</returns>
public T GetValue<T>(StyledPropertyBase<T> property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
return GetValueOrInheritedOrDefault(property);
return _values.GetValue(property);
}
/// <summary>
@ -269,18 +243,11 @@ namespace Avalonia
}
/// <inheritdoc/>
public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property, BindingPriority maxPriority)
public Optional<T> GetBaseValue<T>(StyledPropertyBase<T> property)
{
property = property ?? throw new ArgumentNullException(nameof(property));
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
if (_values is object &&
_values.TryGetValue(property, maxPriority, out var value))
{
return value;
}
return default;
return _values.GetBaseValue(property);
}
/// <summary>
@ -346,26 +313,20 @@ namespace Avalonia
T value,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
_ = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
ValidatePriority(priority);
LogPropertySet(property, value, priority);
LogPropertySet(property, value, BindingPriority.LocalValue);
if (value is UnsetValueType)
{
if (priority == BindingPriority.LocalValue)
{
Values.ClearLocalValue(property);
}
else
{
throw new NotSupportedException(
"Cannot set property to Unset at non-local value priority.");
}
_values.ClearLocalValue(property);
}
else if (!(value is DoNothingType))
else if (value is not DoNothingType)
{
return Values.SetValue(property, value, priority);
return _values.SetValue(property, value, priority);
}
return null;
@ -382,6 +343,7 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
LogPropertySet(property, value, BindingPriority.LocalValue);
SetDirectValueUnchecked(property, value);
}
@ -398,12 +360,52 @@ namespace Avalonia
public IDisposable Bind(
AvaloniaProperty property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue) => property.RouteBind(this, source, priority);
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
IObservable<object?> source,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
VerifyAccess();
ValidatePriority(priority);
return _values.AddBinding(property, source, priority);
}
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <param name="priority">The priority of the binding.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
StyledPropertyBase<T> property,
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
VerifyAccess();
ValidatePriority(priority);
return property.RouteBind(this, source.ToBindingValue(), priority);
return _values.AddBinding(property, source, priority);
}
/// <summary>
@ -424,8 +426,9 @@ namespace Avalonia
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
VerifyAccess();
ValidatePriority(priority);
return Values.AddBinding(property, source, priority);
return _values.AddBinding(property, source, priority);
}
/// <summary>
@ -439,10 +442,9 @@ namespace Avalonia
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
IObservable<object?> source)
{
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
@ -452,48 +454,67 @@ namespace Avalonia
throw new ArgumentException($"The property {property.Name} is readonly.");
}
Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
this,
"Bound {Property} to {Binding} with priority LocalValue",
property,
GetDescription(source));
_directBindings ??= new List<IDisposable>();
return new DirectBindingSubscription<T>(this, property, source);
return _values.AddBinding(property, source);
}
/// <summary>
/// Coerces the specified <see cref="AvaloniaProperty"/>.
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property)
/// <param name="source">The observable.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<T> source)
{
_values?.CoerceValue(property);
}
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
public void BeginBatchUpdate()
{
if (_batchUpdate)
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
if (property.IsReadOnly)
{
throw new InvalidOperationException("Batch update already in progress.");
throw new ArgumentException($"The property {property.Name} is readonly.");
}
_batchUpdate = true;
_values?.BeginBatchUpdate();
return _values.AddBinding(property, source);
}
public void EndBatchUpdate()
/// <summary>
/// Binds a <see cref="AvaloniaProperty"/> to an observable.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="source">The observable.</param>
/// <returns>
/// A disposable which can be used to terminate the binding.
/// </returns>
public IDisposable Bind<T>(
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
{
if (!_batchUpdate)
property = property ?? throw new ArgumentNullException(nameof(property));
VerifyAccess();
property = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
if (property.IsReadOnly)
{
throw new InvalidOperationException("No batch update in progress.");
throw new ArgumentException($"The property {property.Name} is readonly.");
}
_batchUpdate = false;
_values?.EndBatchUpdate();
return _values.AddBinding(property, source);
}
/// <summary>
/// Coerces the specified <see cref="AvaloniaProperty"/>.
/// </summary>
/// <param name="property">The property.</param>
public void CoerceValue(AvaloniaProperty property) => _values.CoerceValue(property);
/// <inheritdoc/>
internal void AddInheritanceChild(AvaloniaObject child)
{
@ -507,98 +528,12 @@ namespace Avalonia
_inheritanceChildren?.Remove(child);
}
internal void InheritedPropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
Optional<T> newValue)
{
if (property.Inherits && (_values == null || !_values.IsSet(property)))
{
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
}
}
/// <inheritdoc/>
Delegate[]? IAvaloniaObjectDebug.GetPropertyChangedSubscribers()
{
return _propertyChanged?.GetInvocationList();
}
internal void ValueChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
var property = (StyledPropertyBase<T>)change.Property;
LogIfError(property, change.NewValue);
// If the change is to the effective value of the property and no old/new value is set
// then fill in the old/new value from property inheritance/default value. We don't do
// this for non-effective value changes because these are only needed for property
// transitions, where knowing e.g. that an inherited value is active at an arbitrary
// priority isn't of any use and would introduce overhead.
if (change.IsEffectiveValueChange && !change.OldValue.HasValue)
{
change.SetOldValue(GetInheritedOrDefault<T>(property));
}
if (change.IsEffectiveValueChange && !change.NewValue.HasValue)
{
change.SetNewValue(GetInheritedOrDefault(property));
}
if (!change.IsEffectiveValueChange ||
!EqualityComparer<T>.Default.Equals(change.OldValue.Value, change.NewValue.Value))
{
RaisePropertyChanged(change);
if (change.IsEffectiveValueChange)
{
Logger.TryGet(LogEventLevel.Verbose, LogArea.Property)?.Log(
this,
"{Property} changed from {$Old} to {$Value} with priority {Priority}",
property,
change.OldValue,
change.NewValue,
change.Priority);
}
}
}
internal void Completed<T>(
StyledPropertyBase<T> property,
IPriorityValueEntry entry,
Optional<T> oldValue)
{
var change = new AvaloniaPropertyChangedEventArgs<T>(
this,
property,
oldValue,
default,
BindingPriority.Unset);
ValueChanged(change);
}
/// <summary>
/// Called for each inherited property when the <see cref="InheritanceParent"/> changes.
/// </summary>
/// <typeparam name="T">The type of the property value.</typeparam>
/// <param name="property">The property.</param>
/// <param name="oldParent">The old inheritance parent.</param>
internal void InheritanceParentChanged<T>(
StyledPropertyBase<T> property,
AvaloniaObject? oldParent)
{
var oldValue = oldParent is not null ?
oldParent.GetValueOrInheritedOrDefault(property) :
property.GetDefaultValue(GetType());
var newValue = GetInheritedOrDefault(property);
if (!EqualityComparer<T>.Default.Equals(oldValue, newValue))
{
RaisePropertyChanged(property, oldValue, newValue);
}
}
internal AvaloniaPropertyValue GetDiagnosticInternal(AvaloniaProperty property)
{
if (property.IsDirect)
@ -626,19 +561,23 @@ namespace Avalonia
"Unset");
}
internal ValueStore GetValueStore() => _values;
internal IReadOnlyList<AvaloniaObject>? GetInheritanceChildren() => _inheritanceChildren;
/// <summary>
/// Logs a binding error for a property.
/// Gets a logger to which a binding warning may be written.
/// </summary>
/// <param name="property">The property that the error occurred on.</param>
/// <param name="e">The binding error.</param>
protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
/// <param name="e">The binding exception, if any.</param>
/// <remarks>
/// This is overridden in <see cref="Visual"/> to prevent logging binding errors when a
/// control is not attached to the visual tree.
/// </remarks>
internal virtual ParametrizedLogger? GetBindingWarningLogger(
AvaloniaProperty property,
Exception? e)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.Binding)?.Log(
this,
"Error in binding to {Target}.{Property}: {Message}",
this,
property,
e.Message);
return Logger.TryGet(LogEventLevel.Warning, LogArea.Binding);
}
/// <summary>
@ -675,6 +614,22 @@ namespace Avalonia
{
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event for a direct property.
/// </summary>
/// <param name="property">The property that has changed.</param>
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
private protected void RaisePropertyChanged<T>(
DirectPropertyBase<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority = BindingPriority.LocalValue)
{
RaisePropertyChanged(property, oldValue, newValue, priority, true);
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event.
/// </summary>
@ -682,18 +637,32 @@ namespace Avalonia
/// <param name="oldValue">The old property value.</param>
/// <param name="newValue">The new property value.</param>
/// <param name="priority">The priority of the binding that produced the value.</param>
protected internal void RaisePropertyChanged<T>(
/// <param name="isEffectiveValue">
/// Whether the notification represents a change to the effective value of the property.
/// </param>
internal void RaisePropertyChanged<T>(
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority = BindingPriority.LocalValue)
BindingPriority priority,
bool isEffectiveValue)
{
RaisePropertyChanged(new AvaloniaPropertyChangedEventArgs<T>(
var e = new AvaloniaPropertyChangedEventArgs<T>(
this,
property,
oldValue,
newValue,
priority));
priority,
isEffectiveValue);
OnPropertyChangedCore(e);
if (isEffectiveValue)
{
property.NotifyChanged(e);
_propertyChanged?.Invoke(this, e);
_inpcChanged?.Invoke(this, new PropertyChangedEventArgs(property.Name));
}
}
/// <summary>
@ -718,110 +687,24 @@ namespace Avalonia
var old = field;
field = value;
RaisePropertyChanged(property, old, value);
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue, true);
return true;
}
private T GetInheritedOrDefault<T>(StyledPropertyBase<T> property)
{
if (property.Inherits && InheritanceParent is AvaloniaObject o)
{
return o.GetValueOrInheritedOrDefault(property);
}
return property.GetDefaultValue(GetType());
}
private T GetValueOrInheritedOrDefault<T>(
StyledPropertyBase<T> property,
BindingPriority maxPriority = BindingPriority.Animation)
{
var o = this;
var inherits = property.Inherits;
var value = default(T);
while (o != null)
{
var values = o._values;
if (values != null
&& values.TryGetValue(property, maxPriority, out value) == true)
{
return value;
}
if (!inherits)
{
break;
}
o = o.InheritanceParent as AvaloniaObject;
}
return property.GetDefaultValue(GetType());
}
protected internal void RaisePropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
VerifyAccess();
if (change.IsEffectiveValueChange)
{
change.Property.Notifying?.Invoke(this, true);
}
try
{
OnPropertyChangedCore(change);
if (change.IsEffectiveValueChange)
{
change.Property.NotifyChanged(change);
_propertyChanged?.Invoke(this, change);
if (_inpcChanged != null)
{
var inpce = new PropertyChangedEventArgs(change.Property.Name);
_inpcChanged(this, inpce);
}
if (change.Property.Inherits && _inheritanceChildren != null)
{
foreach (var child in _inheritanceChildren)
{
child.InheritedPropertyChanged(
change.Property,
change.OldValue,
change.NewValue.ToOptional());
}
}
}
}
finally
{
if (change.IsEffectiveValueChange)
{
change.Property.Notifying?.Invoke(this, false);
}
}
}
/// <summary>
/// Sets the value of a direct property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, T value)
{
var p = AvaloniaPropertyRegistry.Instance.GetRegisteredDirect(this, property);
if (value is UnsetValueType)
{
p.InvokeSetter(this, p.GetUnsetValue(GetType()));
property.InvokeSetter(this, property.GetUnsetValue(GetType()));
}
else if (!(value is DoNothingType))
{
p.InvokeSetter(this, value);
property.InvokeSetter(this, value);
}
}
@ -830,16 +713,9 @@ namespace Avalonia
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value.</param>
private void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
internal void SetDirectValueUnchecked<T>(DirectPropertyBase<T> property, BindingValue<T> value)
{
var p = AvaloniaPropertyRegistry.Instance.FindRegisteredDirect(this, property);
if (p == null)
{
throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
}
LogIfError(property, value);
LoggingUtils.LogIfNecessary(this, property, value);
switch (value.Type)
{
@ -858,7 +734,7 @@ namespace Avalonia
break;
}
var metadata = p.GetMetadata(GetType());
var metadata = property.GetMetadata(GetType());
if (metadata.EnableDataValidation == true)
{
@ -877,29 +753,6 @@ namespace Avalonia
return description?.Description ?? o.ToString() ?? o.GetType().Name;
}
/// <summary>
/// Logs a message if the notification represents a binding error.
/// </summary>
/// <param name="property">The property being bound.</param>
/// <param name="value">The binding notification.</param>
private void LogIfError<T>(AvaloniaProperty property, BindingValue<T> value)
{
if (value.HasError)
{
if (value.Error is AggregateException aggregate)
{
foreach (var inner in aggregate.InnerExceptions)
{
LogBindingError(property, inner);
}
}
else
{
LogBindingError(property, value.Error!);
}
}
}
/// <summary>
/// Logs a property set message.
/// </summary>
@ -916,49 +769,16 @@ namespace Avalonia
priority);
}
private class DirectBindingSubscription<T> : IObserver<BindingValue<T>>, IDisposable
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidatePriority(BindingPriority priority)
{
private readonly AvaloniaObject _owner;
private readonly DirectPropertyBase<T> _property;
private readonly IDisposable _subscription;
public DirectBindingSubscription(
AvaloniaObject owner,
DirectPropertyBase<T> property,
IObservable<BindingValue<T>> source)
{
_owner = owner;
_property = property;
_owner._directBindings!.Add(this);
_subscription = source.Subscribe(this);
}
public void Dispose()
{
// _subscription can be null, if Subscribe failed with an exception.
_subscription?.Dispose();
_owner._directBindings!.Remove(this);
}
public void OnCompleted() => Dispose();
public void OnError(Exception error) => Dispose();
public void OnNext(BindingValue<T> value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.SetDirectValueUnchecked(_property, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner;
var property = _property;
var newValue = value;
if (priority < BindingPriority.Animation || priority >= BindingPriority.Inherited)
ThrowInvalidPriority(priority);
}
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
private static void ThrowInvalidPriority(BindingPriority priority)
{
throw new ArgumentException($"Invalid priority ${priority}", nameof(priority));
}
}
}

36
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -261,7 +261,6 @@ namespace Avalonia
}
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
}
/// <summary>
@ -280,14 +279,17 @@ namespace Avalonia
IObservable<T> source,
BindingPriority priority = BindingPriority.LocalValue)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
source = source ?? throw new ArgumentNullException(nameof(source));
if (target is AvaloniaObject ao)
{
return property switch
{
StyledPropertyBase<T> styled => ao.Bind(styled, source, priority),
DirectPropertyBase<T> direct => ao.Bind(direct, source),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type."),
};
}
return target.Bind(
property,
source.ToBindingValue(),
priority);
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
}
/// <summary>
@ -362,10 +364,8 @@ namespace Avalonia
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="maxPriority">The maximum priority for the value.</param>
/// <remarks>
/// For styled properties, gets the value of the property if set on the object with a
/// priority equal or lower to <paramref name="maxPriority"/>, otherwise
/// For styled properties, gets the value of the property excluding animated values, otherwise
/// <see cref="AvaloniaProperty.UnsetValue"/>. Note that this method does not return
/// property values that come from inherited or default values.
///
@ -373,14 +373,13 @@ namespace Avalonia
/// </remarks>
public static object? GetBaseValue(
this IAvaloniaObject target,
AvaloniaProperty property,
BindingPriority maxPriority)
AvaloniaProperty property)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
if (target is AvaloniaObject ao)
return property.RouteGetBaseValue(ao, maxPriority);
return property.RouteGetBaseValue(ao);
throw new NotSupportedException("Custom implementations of IAvaloniaObject not supported.");
}
@ -389,10 +388,8 @@ namespace Avalonia
/// </summary>
/// <param name="target">The object.</param>
/// <param name="property">The property.</param>
/// <param name="maxPriority">The maximum priority for the value.</param>
/// <remarks>
/// For styled properties, gets the value of the property if set on the object with a
/// priority equal or lower to <paramref name="maxPriority"/>, otherwise
/// For styled properties, gets the value of the property excluding animated values, otherwise
/// <see cref="Optional{T}.Empty"/>. Note that this method does not return property values
/// that come from inherited or default values.
///
@ -400,8 +397,7 @@ namespace Avalonia
/// </remarks>
public static Optional<T> GetBaseValue<T>(
this IAvaloniaObject target,
AvaloniaProperty<T> property,
BindingPriority maxPriority)
AvaloniaProperty<T> property)
{
target = target ?? throw new ArgumentNullException(nameof(target));
property = property ?? throw new ArgumentNullException(nameof(property));
@ -410,7 +406,7 @@ namespace Avalonia
{
return property switch
{
StyledPropertyBase<T> styled => ao.GetBaseValue(styled, maxPriority),
StyledPropertyBase<T> styled => ao.GetBaseValue(styled),
DirectPropertyBase<T> direct => ao.GetValue(direct),
_ => throw new NotSupportedException("Unsupported AvaloniaProperty type.")
};

17
src/Avalonia.Base/AvaloniaProperty.cs

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.PropertyStore;
using Avalonia.Styling;
using Avalonia.Utilities;
@ -176,7 +177,7 @@ namespace Avalonia
{
return new IndexerDescriptor
{
Priority = BindingPriority.TemplatedParent,
Priority = BindingPriority.Template,
Property = property,
};
}
@ -455,6 +456,12 @@ namespace Avalonia
return Name;
}
/// <summary>
/// Creates an effective value for the property.
/// </summary>
/// <param name="o">The effective value owner.</param>
internal abstract EffectiveValue CreateEffectiveValue(AvaloniaObject o);
/// <summary>
/// Routes an untyped ClearValue call to a typed call.
/// </summary>
@ -471,8 +478,7 @@ namespace Avalonia
/// Routes an untyped GetBaseValue call to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
/// <param name="maxPriority">The maximum priority for the value.</param>
internal abstract object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority);
internal abstract object? RouteGetBaseValue(AvaloniaObject o);
/// <summary>
/// Routes an untyped SetValue call to a typed call.
@ -496,12 +502,9 @@ namespace Avalonia
/// <param name="priority">The priority.</param>
internal abstract IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
IObservable<object?> source,
BindingPriority priority);
internal abstract void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent);
internal abstract ISetterInstance CreateSetterInstance(IStyleable target, object? value);
/// <summary>
/// Overrides the metadata for the property on the specified type.
/// </summary>

24
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs.cs

@ -17,6 +17,16 @@ namespace Avalonia
IsEffectiveValueChange = true;
}
internal AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender,
BindingPriority priority,
bool isEffectiveValueChange)
{
Sender = sender;
Priority = priority;
IsEffectiveValueChange = isEffectiveValueChange;
}
/// <summary>
/// Gets the <see cref="AvaloniaObject"/> that the property changed on.
/// </summary>
@ -49,20 +59,8 @@ namespace Avalonia
/// </value>
public BindingPriority Priority { get; private set; }
/// <summary>
/// Gets a value indicating whether the change represents a change to the effective value of
/// the property.
/// </summary>
/// <remarks>
/// This will usually be true, except in
/// <see cref="AvaloniaObject.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs)"/>
/// which receives notifications for all changes to property values, whether a value with a higher
/// priority is present or not. When this property is false, the change that is being signaled
/// has not resulted in a change to the property value on the object.
/// </remarks>
public bool IsEffectiveValueChange { get; private set; }
internal bool IsEffectiveValueChange { get; private set; }
internal void MarkNonEffectiveValue() => IsEffectiveValueChange = false;
protected abstract AvaloniaProperty GetProperty();
protected abstract object? GetOldValue();
protected abstract object? GetNewValue();

28
src/Avalonia.Base/AvaloniaPropertyChangedEventArgs`1.cs

@ -21,7 +21,18 @@ namespace Avalonia
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority)
: base(sender, priority)
: this(sender, property, oldValue, newValue, priority, true)
{
}
internal AvaloniaPropertyChangedEventArgs(
IAvaloniaObject sender,
AvaloniaProperty<T> property,
Optional<T> oldValue,
BindingValue<T> newValue,
BindingPriority priority,
bool isEffectiveValueChange)
: base(sender, priority, isEffectiveValueChange)
{
Property = property;
OldValue = oldValue;
@ -39,28 +50,13 @@ namespace Avalonia
/// <summary>
/// Gets the old value of the property.
/// </summary>
/// <remarks>
/// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
/// old value of the property on the object.
/// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false, returns
/// <see cref="Optional{T}.Empty"/>.
/// </remarks>
public new Optional<T> OldValue { get; private set; }
/// <summary>
/// Gets the new value of the property.
/// </summary>
/// <remarks>
/// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is true, returns the
/// value of the property on the object.
/// When <see cref="AvaloniaPropertyChangedEventArgs.IsEffectiveValueChange"/> is false returns the
/// changed value, or <see cref="Optional{T}.Empty"/> if the value was removed.
/// </remarks>
public new BindingValue<T> NewValue { get; private set; }
internal void SetOldValue(Optional<T> value) => OldValue = value;
internal void SetNewValue(BindingValue<T> value) => NewValue = value;
protected override AvaloniaProperty GetProperty() => Property;
protected override object? GetOldValue() => OldValue.GetValueOrDefault(AvaloniaProperty.UnsetValue);

32
src/Avalonia.Base/Compatibility/CollectionCompatibilityExtensions.cs

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace System;
#if !NET6_0_OR_GREATER
internal static class CollectionCompatibilityExtensions
{
public static bool Remove<TKey, TValue>(
this Dictionary<TKey, TValue> o,
TKey key,
[MaybeNullWhen(false)] out TValue value)
where TKey : notnull
{
if (o.TryGetValue(key, out value))
return o.Remove(key);
return false;
}
public static bool TryAdd<TKey, TValue>(this Dictionary<TKey, TValue> o, TKey key, TValue value)
where TKey : notnull
{
if (!o.ContainsKey(key))
{
o.Add(key, value);
return true;
}
return false;
}
}
#endif

20
src/Avalonia.Base/Data/BindingNotification.cs

@ -241,26 +241,6 @@ namespace Avalonia.Data
_value = value;
}
public BindingValue<object?> ToBindingValue()
{
if (ErrorType == BindingErrorType.None)
{
return HasValue ? new BindingValue<object?>(Value) : BindingValue<object?>.Unset;
}
else if (ErrorType == BindingErrorType.Error)
{
return BindingValue<object?>.BindingError(
Error!,
HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
}
else
{
return BindingValue<object?>.DataValidationError(
Error!,
HasValue ? new Optional<object?>(Value) : Optional<object?>.Empty);
}
}
/// <inheritdoc/>
public override string ToString()
{

27
src/Avalonia.Base/Data/BindingPriority.cs

@ -1,7 +1,9 @@
using System;
namespace Avalonia.Data
{
/// <summary>
/// The priority of a binding.
/// The priority of a value or binding.
/// </summary>
public enum BindingPriority
{
@ -16,29 +18,36 @@ namespace Avalonia.Data
LocalValue = 0,
/// <summary>
/// A triggered style binding.
/// A triggered style value.
/// </summary>
/// <remarks>
/// A style trigger is a selector such as .class which overrides a
/// <see cref="TemplatedParent"/> binding. In this way, a basic control can have
/// for example a Background from the templated parent which changes when the
/// control has the :pointerover class.
/// <see cref="Template"/> value. In this way, a control can have, e.g. a Background from
/// the template which changes when the control has the :pointerover class.
/// </remarks>
StyleTrigger,
/// <summary>
/// A binding to a property on the templated parent.
/// A value from the control's template.
/// </summary>
TemplatedParent,
Template,
/// <summary>
/// A style binding.
/// A style value.
/// </summary>
Style,
/// <summary>
/// The value is inherited from an ancestor element.
/// </summary>
Inherited,
/// <summary>
/// The binding is uninitialized.
/// The value is uninitialized.
/// </summary>
Unset = int.MaxValue,
[Obsolete("Use Template priority")]
TemplatedParent = Template,
}
}

130
src/Avalonia.Base/Data/BindingValue.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Utilities;
@ -230,19 +231,77 @@ namespace Avalonia.Data
/// <summary>
/// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
/// <see cref="AvaloniaProperty.UnsetValue"/> and <see cref="BindingOperations.DoNothing"/>.
/// <see cref="AvaloniaProperty.UnsetValue"/>, <see cref="BindingOperations.DoNothing"/> and
/// <see cref="BindingNotification"/>.
/// </summary>
/// <param name="value">The untyped value.</param>
/// <returns>The typed binding value.</returns>
public static BindingValue<T> FromUntyped(object? value)
{
return value switch
return FromUntyped(value, typeof(T));
}
/// <summary>
/// Creates a <see cref="BindingValue{T}"/> from an object, handling the special values
/// <see cref="AvaloniaProperty.UnsetValue"/>, <see cref="BindingOperations.DoNothing"/> and
/// <see cref="BindingNotification"/>.
/// </summary>
/// <param name="value">The untyped value.</param>
/// <param name="targetType">The runtime target type.</param>
/// <returns>The typed binding value.</returns>
public static BindingValue<T> FromUntyped(object? value, Type targetType)
{
if (value == AvaloniaProperty.UnsetValue)
return Unset;
else if (value == BindingOperations.DoNothing)
return DoNothing;
var type = BindingValueType.Value;
T? v = default;
Exception? error = null;
List<Exception>? errors = null;
if (value is BindingNotification n)
{
UnsetValueType _ => Unset,
DoNothingType _ => DoNothing,
BindingNotification n => n.ToBindingValue().Cast<T>(),
_ => new BindingValue<T>((T)value!)
};
error = n.Error;
type = n.ErrorType switch
{
BindingErrorType.Error => BindingValueType.BindingError,
BindingErrorType.DataValidationError => BindingValueType.DataValidationError,
_ => BindingValueType.Value,
};
if (n.HasValue)
type |= BindingValueType.HasValue;
value = n.Value;
}
if ((type & BindingValueType.HasValue) != 0)
{
if (TypeUtilities.TryConvertImplicit(targetType, value, out var typed))
v = (T)typed!;
else
{
var e = new InvalidCastException(
$"Unable to convert object '{value ?? "(null)"}' " +
$"of type '{value?.GetType()}' to type '{targetType}'.");
if (error is null)
error = e;
else
{
errors ??= new List<Exception>() { error };
errors.Add(e);
}
type = BindingValueType.BindingError;
}
}
if (errors is not null)
error = new AggregateException(errors);
return new BindingValue<T>(type, v, error);
}
/// <summary>
@ -372,61 +431,4 @@ namespace Avalonia.Data
}
}
}
public static class BindingValueExtensions
{
/// <summary>
/// Casts the type of a <see cref="BindingValue{T}"/> using only the C# cast operator.
/// </summary>
/// <typeparam name="T">The target type.</typeparam>
/// <param name="value">The binding value.</param>
/// <returns>The cast value.</returns>
public static BindingValue<T> Cast<T>(this BindingValue<object?> value)
{
return value.Type switch
{
BindingValueType.DoNothing => BindingValue<T>.DoNothing,
BindingValueType.UnsetValue => BindingValue<T>.Unset,
BindingValueType.Value => new BindingValue<T>((T)value.Value!),
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
value.Error!,
(T)value.Value!),
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
value.Error!,
(T)value.Value!),
_ => throw new NotSupportedException("Invalid BindingValue type."),
};
}
/// <summary>
/// Casts the type of a <see cref="BindingValue{T}"/> using the implicit conversions
/// allowed by the C# language.
/// </summary>
/// <typeparam name="T">The target type.</typeparam>
/// <param name="value">The binding value.</param>
/// <returns>The cast value.</returns>
/// <remarks>
/// Note that this method uses reflection and as such may be slow.
/// </remarks>
public static BindingValue<T> Convert<T>(this BindingValue<object?> value)
{
return value.Type switch
{
BindingValueType.DoNothing => BindingValue<T>.DoNothing,
BindingValueType.UnsetValue => BindingValue<T>.Unset,
BindingValueType.Value => new BindingValue<T>(TypeUtilities.ConvertImplicit<T>(value.Value!)),
BindingValueType.BindingError => BindingValue<T>.BindingError(value.Error!),
BindingValueType.BindingErrorWithFallback => BindingValue<T>.BindingError(
value.Error!,
TypeUtilities.ConvertImplicit<T>(value.Value!)),
BindingValueType.DataValidationError => BindingValue<T>.DataValidationError(value.Error!),
BindingValueType.DataValidationErrorWithFallback => BindingValue<T>.DataValidationError(
value.Error!,
TypeUtilities.ConvertImplicit<T>(value.Value!)),
_ => throw new NotSupportedException("Invalid BindingValue type."),
};
}
}
}

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

@ -52,6 +52,7 @@ namespace Avalonia.Data.Core
private static readonly object UninitializedValue = new object();
private readonly ExpressionNode _node;
private object? _root;
private Func<object?>? _rootGetter;
private IDisposable? _rootSubscription;
private WeakReference<object?>? _value;
private IReadOnlyList<ITransformNode>? _transformNodes;
@ -109,11 +110,9 @@ namespace Avalonia.Data.Core
IObservable<Unit> update,
string? description)
{
_ = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
Description = description;
_node = node ?? throw new ArgumentNullException(nameof(rootGetter));
_node.Target = new WeakReference<object?>(rootGetter());
_rootGetter = rootGetter ?? throw new ArgumentNullException(nameof(rootGetter));
_node = node ?? throw new ArgumentNullException(nameof(node));
_root = update.Select(x => rootGetter());
}
@ -263,6 +262,8 @@ namespace Avalonia.Data.Core
protected override void Initialize()
{
_value = null;
if (_rootGetter is not null)
_node.Target = new WeakReference<object?>(_rootGetter());
_node.Subscribe(ValueChanged);
StartRoot();
}

50
src/Avalonia.Base/DirectPropertyBase.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Reactive;
using Avalonia.Styling;
@ -105,6 +106,11 @@ namespace Avalonia
base.OverrideMetadata(type, metadata);
}
internal override EffectiveValue CreateEffectiveValue(AvaloniaObject o)
{
throw new InvalidOperationException("Cannot create EffectiveValue for direct property.");
}
/// <inheritdoc/>
internal override void RouteClearValue(AvaloniaObject o)
{
@ -117,7 +123,7 @@ namespace Avalonia
return o.GetValue<TValue>(this);
}
internal override object? RouteGetBaseValue(AvaloniaObject o, BindingPriority maxPriority)
internal override object? RouteGetBaseValue(AvaloniaObject o)
{
return o.GetValue<TValue>(this);
}
@ -146,44 +152,18 @@ namespace Avalonia
return null;
}
/// <inheritdoc/>
/// <summary>
/// Routes an untyped Bind call to a typed call.
/// </summary>
/// <param name="o">The object instance.</param>
/// <param name="source">The binding source.</param>
/// <param name="priority">The priority.</param>
internal override IDisposable RouteBind(
AvaloniaObject o,
IObservable<BindingValue<object?>> source,
IObservable<object?> source,
BindingPriority priority)
{
var adapter = TypedBindingAdapter<TValue>.Create(o, this, source);
return o.Bind<TValue>(this, adapter);
}
internal override void RouteInheritanceParentChanged(AvaloniaObject o, AvaloniaObject? oldParent)
{
throw new NotSupportedException("Direct properties do not support inheritance.");
}
internal override ISetterInstance CreateSetterInstance(IStyleable target, object? value)
{
if (value is IBinding binding)
{
return new PropertySetterBindingInstance<TValue>(
target,
this,
binding);
}
else if (value is ITemplate template && !typeof(ITemplate).IsAssignableFrom(PropertyType))
{
return new PropertySetterTemplateInstance<TValue>(
target,
this,
template);
}
else
{
return new PropertySetterInstance<TValue>(
target,
this,
(TValue)value!);
}
return o.Bind(this, source);
}
}
}

7
src/Avalonia.Base/IStyledPropertyAccessor.cs

@ -15,5 +15,12 @@ namespace Avalonia
/// The default value.
/// </returns>
object? GetDefaultValue(Type type);
/// <summary>
/// Validates the specified property value.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>True if the value is valid, otherwise false.</returns>
bool ValidateValue(object? value);
}
}

11
src/Avalonia.Base/Layout/Layoutable.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Logging;
using Avalonia.Styling;
using Avalonia.VisualTree;
#nullable enable
@ -719,9 +720,9 @@ namespace Avalonia.Layout
return finalSize;
}
protected sealed override void InvalidateStyles()
internal sealed override void InvalidateStyles(bool recurse)
{
base.InvalidateStyles();
base.InvalidateStyles(recurse);
InvalidateMeasure();
}
@ -795,6 +796,12 @@ namespace Avalonia.Layout
base.OnVisualParentChanged(oldParent, newParent);
}
private protected override void OnControlThemeChanged()
{
base.OnControlThemeChanged();
InvalidateMeasure();
}
/// <summary>
/// Called when the layout manager raises a LayoutUpdated event.
/// </summary>

4
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -29,6 +29,6 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.Skia.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Skia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Web.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Web, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Browser.Blazor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Browser, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

25
src/Avalonia.Base/PropertyStore/AvaloniaPropertyDictionaryPool.cs

@ -0,0 +1,25 @@
using System.Collections.Generic;
using Avalonia.Utilities;
namespace Avalonia.PropertyStore
{
internal static class AvaloniaPropertyDictionaryPool<TValue>
{
private const int MaxPoolSize = 4;
private static readonly Stack<AvaloniaPropertyDictionary<TValue>> _pool = new();
public static AvaloniaPropertyDictionary<TValue> Get()
{
return _pool.Count == 0 ? new() : _pool.Pop();
}
public static void Release(AvaloniaPropertyDictionary<TValue> dictionary)
{
if (_pool.Count < MaxPoolSize)
{
dictionary.Clear();
_pool.Push(dictionary);
}
}
}
}

154
src/Avalonia.Base/PropertyStore/BindingEntry.cs

@ -1,154 +0,0 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped interface to <see cref="BindingEntry{T}"/>.
/// </summary>
internal interface IBindingEntry : IBatchUpdate, IPriorityValueEntry, IDisposable
{
void Start(bool ignoreBatchUpdate);
}
/// <summary>
/// Stores a binding in a <see cref="ValueStore"/> or <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class BindingEntry<T> : IBindingEntry, IPriorityValueEntry<T>, IObserver<BindingValue<T>>
{
private readonly AvaloniaObject _owner;
private ValueOwner<T> _sink;
private IDisposable? _subscription;
private bool _isSubscribed;
private bool _batchUpdate;
private Optional<T> _value;
public BindingEntry(
AvaloniaObject owner,
StyledPropertyBase<T> property,
IObservable<BindingValue<T>> source,
BindingPriority priority,
ValueOwner<T> sink)
{
_owner = owner;
Property = property;
Source = source;
Priority = priority;
_sink = sink;
}
public StyledPropertyBase<T> Property { get; }
public BindingPriority Priority { get; private set; }
public IObservable<BindingValue<T>> Source { get; }
Optional<object?> IValue.GetValue() => _value.ToObject();
public void BeginBatchUpdate() => _batchUpdate = true;
public void EndBatchUpdate()
{
_batchUpdate = false;
if (_sink.IsValueStore)
Start();
}
public Optional<T> GetValue(BindingPriority maxPriority)
{
return Priority >= maxPriority ? _value : Optional<T>.Empty;
}
public void Dispose()
{
_subscription?.Dispose();
_subscription = null;
OnCompleted();
}
public void OnCompleted()
{
var oldValue = _value;
_value = default;
Priority = BindingPriority.Unset;
_isSubscribed = false;
_sink.Completed(Property, this, oldValue);
}
public void OnError(Exception error)
{
throw new NotImplementedException("BindingEntry.OnError is not implemented", error);
}
public void OnNext(BindingValue<T> value)
{
if (Dispatcher.UIThread.CheckAccess())
{
UpdateValue(value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.UpdateValue(newValue));
}
}
public void Start() => Start(false);
public void Start(bool ignoreBatchUpdate)
{
// We can't use _subscription to check whether we're subscribed because it won't be set
// until Subscribe has finished, which will be too late to prevent reentrancy. In addition
// don't re-subscribe to completed/disposed bindings (indicated by Unset priority).
if (!_isSubscribed &&
Priority != BindingPriority.Unset &&
(!_batchUpdate || ignoreBatchUpdate))
{
_isSubscribed = true;
_subscription = Source.Subscribe(this);
}
}
public void Reparent(PriorityValue<T> parent) => _sink = new(parent);
public void RaiseValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
newValue.Cast<T>(),
Priority));
}
private void UpdateValue(BindingValue<T> value)
{
if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false)
{
value = Property.GetDefaultValue(_owner.GetType());
}
if (value.Type == BindingValueType.DoNothing)
{
return;
}
var old = _value;
if (value.Type != BindingValueType.DataValidationError)
{
_value = value.ToOptional();
}
_sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(_owner, Property, old, value, Priority));
}
}
}

148
src/Avalonia.Base/PropertyStore/BindingEntryBase.cs

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal abstract class BindingEntryBase<TValue, TSource> : IValueEntry<TValue>,
IObserver<TSource>,
IObserver<BindingValue<TSource>>,
IDisposable
{
private static readonly IDisposable s_creating = Disposable.Empty;
private static readonly IDisposable s_creatingQuiet = Disposable.Create(() => { });
private IDisposable? _subscription;
private bool _hasValue;
private TValue? _value;
protected BindingEntryBase(
ValueFrame frame,
AvaloniaProperty property,
IObservable<BindingValue<TSource>> source)
{
Frame = frame;
Source = source;
Property = property;
}
protected BindingEntryBase(
ValueFrame frame,
AvaloniaProperty property,
IObservable<TSource> source)
{
Frame = frame;
Source = source;
Property = property;
}
public bool HasValue
{
get
{
Start(produceValue: false);
return _hasValue;
}
}
public bool IsSubscribed => _subscription is not null;
public AvaloniaProperty Property { get; }
AvaloniaProperty IValueEntry.Property => Property;
protected ValueFrame Frame { get; }
protected object Source { get; }
public void Dispose()
{
Unsubscribe();
BindingCompleted();
}
public TValue GetValue()
{
Start(produceValue: false);
if (!_hasValue)
throw new AvaloniaInternalException("The binding entry has no value.");
return _value!;
}
public void Start() => Start(true);
public void OnCompleted() => BindingCompleted();
public void OnError(Exception error) => BindingCompleted();
public void OnNext(TSource value) => SetValue(ConvertAndValidate(value));
public void OnNext(BindingValue<TSource> value) => SetValue(ConvertAndValidate(value));
public virtual void Unsubscribe()
{
_subscription?.Dispose();
_subscription = null;
}
object? IValueEntry.GetValue()
{
Start(produceValue: false);
if (!_hasValue)
throw new AvaloniaInternalException("The BindingEntry<T> has no value.");
return _value!;
}
protected abstract BindingValue<TValue> ConvertAndValidate(TSource value);
protected abstract BindingValue<TValue> ConvertAndValidate(BindingValue<TSource> value);
protected virtual void Start(bool produceValue)
{
if (_subscription is not null)
return;
_subscription = produceValue ? s_creating : s_creatingQuiet;
_subscription = Source switch
{
IObservable<BindingValue<TSource>> bv => bv.Subscribe(this),
IObservable<TSource> b => b.Subscribe(this),
_ => throw new AvaloniaInternalException("Unexpected binding source."),
};
}
private void ClearValue()
{
if (_hasValue)
{
_hasValue = false;
_value = default;
if (_subscription is not null)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void SetValue(BindingValue<TValue> value)
{
if (Frame.Owner is null)
return;
LoggingUtils.LogIfNecessary(Frame.Owner.Owner, Property, value);
if (value.HasValue)
{
if (!_hasValue || !EqualityComparer<TValue>.Default.Equals(_value, value.Value))
{
_value = value.Value;
_hasValue = true;
if (_subscription is not null && _subscription != s_creatingQuiet)
Frame.Owner?.OnBindingValueChanged(this, Frame.Priority);
}
}
else if (value.Type != BindingValueType.DoNothing)
{
ClearValue();
if (_subscription is not null && _subscription != s_creatingQuiet)
Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority);
}
}
private void BindingCompleted()
{
_subscription = null;
Frame.OnBindingCompleted(this);
}
}
}

82
src/Avalonia.Base/PropertyStore/ConstantValueEntry.cs

@ -1,82 +0,0 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped interface to <see cref="ConstantValueEntry{T}"/>.
/// </summary>
internal interface IConstantValueEntry : IPriorityValueEntry, IDisposable
{
}
/// <summary>
/// Stores a value with a priority in a <see cref="ValueStore"/> or
/// <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class ConstantValueEntry<T> : IPriorityValueEntry<T>, IConstantValueEntry
{
private ValueOwner<T> _sink;
private Optional<T> _value;
public ConstantValueEntry(
StyledPropertyBase<T> property,
T value,
BindingPriority priority,
ValueOwner<T> sink)
{
Property = property;
_value = value;
Priority = priority;
_sink = sink;
}
public ConstantValueEntry(
StyledPropertyBase<T> property,
Optional<T> value,
BindingPriority priority,
ValueOwner<T> sink)
{
Property = property;
_value = value;
Priority = priority;
_sink = sink;
}
public StyledPropertyBase<T> Property { get; }
public BindingPriority Priority { get; private set; }
Optional<object?> IValue.GetValue() => _value.ToObject();
public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation)
{
return Priority >= maxPriority ? _value : Optional<T>.Empty;
}
public void Dispose()
{
var oldValue = _value;
_value = default;
Priority = BindingPriority.Unset;
_sink.Completed(Property, this, oldValue);
}
public void Reparent(PriorityValue<T> sink) => _sink = new(sink);
public void Start() { }
public void RaiseValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
newValue.Cast<T>(),
Priority));
}
}
}

76
src/Avalonia.Base/PropertyStore/DirectBindingObserver.cs

@ -0,0 +1,76 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
internal class DirectBindingObserver<T> : IObserver<T>,
IObserver<BindingValue<T>>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public DirectBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public DirectPropertyBase<T> Property { get;}
public void Start(IObservable<T> source)
{
_subscription = source.Subscribe(this);
}
public void Start(IObservable<BindingValue<T>> source)
{
_subscription = source.Subscribe(this);
}
public void Dispose()
{
_subscription?.Dispose();
_subscription = null;
_owner.OnLocalValueBindingCompleted(Property, this);
}
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
public void OnError(Exception error) => OnCompleted();
public void OnNext(T value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
public void OnNext(BindingValue<T> value)
{
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, value);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, newValue));
}
}
}
}

55
src/Avalonia.Base/PropertyStore/DirectUntypedBindingObserver.cs

@ -0,0 +1,55 @@
using System;
using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia.PropertyStore
{
internal class DirectUntypedBindingObserver<T> : IObserver<object?>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public DirectUntypedBindingObserver(ValueStore owner, DirectPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public DirectPropertyBase<T> Property { get;}
public void Start(IObservable<object?> source)
{
_subscription = source.Subscribe(this);
}
public void Dispose()
{
_subscription?.Dispose();
_subscription = null;
_owner.OnLocalValueBindingCompleted(Property, this);
}
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
public void OnError(Exception error) => OnCompleted();
public void OnNext(object? value)
{
var typed = BindingValue<T>.FromUntyped(value);
if (Dispatcher.UIThread.CheckAccess())
{
_owner.Owner.SetDirectValueUnchecked<T>(Property, typed);
}
else
{
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = _owner.Owner;
var property = Property;
var newValue = value;
Dispatcher.UIThread.Post(() => instance.SetDirectValueUnchecked(property, typed));
}
}
}
}

168
src/Avalonia.Base/PropertyStore/EffectiveValue.cs

@ -0,0 +1,168 @@
using System.Diagnostics;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents the active value for a property in a <see cref="ValueStore"/>.
/// </summary>
/// <remarks>
/// This class is an abstract base for the generic <see cref="EffectiveValue{T}"/>.
/// </remarks>
internal abstract class EffectiveValue
{
private IValueEntry? _valueEntry;
private IValueEntry? _baseValueEntry;
/// <summary>
/// Gets the current effective value as a boxed value.
/// </summary>
public object? Value => GetBoxedValue();
/// <summary>
/// Gets the priority of the current effective value.
/// </summary>
public BindingPriority Priority { get; protected set; }
/// <summary>
/// Gets the priority of the current base value.
/// </summary>
public BindingPriority BasePriority { get; protected set; }
/// <summary>
/// Begins a reevaluation pass on the effective value.
/// </summary>
/// <param name="clearLocalValue">
/// Determines whether any current local value should be cleared.
/// </param>
/// <remarks>
/// This method resets the <see cref="Priority"/> and <see cref="BasePriority"/> properties
/// to Unset, pending reevaluation.
/// </remarks>
public void BeginReevaluation(bool clearLocalValue = false)
{
if (clearLocalValue || Priority != BindingPriority.LocalValue)
Priority = BindingPriority.Unset;
if (clearLocalValue || BasePriority != BindingPriority.LocalValue)
BasePriority = BindingPriority.Unset;
}
/// <summary>
/// Ends a reevaluation pass on the effective value.
/// </summary>
/// <remarks>
/// This method unsubscribes from any unused value entries.
/// </remarks>
public void EndReevaluation()
{
if (Priority == BindingPriority.Unset)
{
_valueEntry?.Unsubscribe();
_valueEntry = null;
}
if (BasePriority == BindingPriority.Unset)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = null;
}
}
/// <summary>
/// Sets the value and base value for a non-LocalValue priority, raising
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="value">The new value of the property.</param>
/// <param name="priority">The priority of the new value.</param>
public abstract void SetAndRaise(
ValueStore owner,
IValueEntry value,
BindingPriority priority);
/// <summary>
/// Raises <see cref="AvaloniaObject.PropertyChanged"/> in response to an inherited value
/// change.
/// </summary>
/// <param name="owner">The owner object.</param>
/// <param name="property">The property being changed.</param>
/// <param name="oldValue">The old value of the property.</param>
/// <param name="newValue">The new value of the property.</param>
public abstract void RaiseInheritedValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
EffectiveValue? oldValue,
EffectiveValue? newValue);
/// <summary>
/// Removes the current animation value and reverts to the base value, raising
/// <see cref="AvaloniaObject.PropertyChanged"/> where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property being changed.</param>
public abstract void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property);
/// <summary>
/// Coerces the property value.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property to coerce.</param>
public abstract void CoerceValue(ValueStore owner, AvaloniaProperty property);
/// <summary>
/// Disposes the effective value, raising <see cref="AvaloniaObject.PropertyChanged"/>
/// where necessary.
/// </summary>
/// <param name="owner">The associated value store.</param>
/// <param name="property">The property being cleared.</param>
public abstract void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property);
protected abstract object? GetBoxedValue();
protected void UpdateValueEntry(IValueEntry? entry, BindingPriority priority)
{
Debug.Assert(priority != BindingPriority.LocalValue);
if (priority <= BindingPriority.Animation)
{
// If we've received an animation value and the current value is a non-animation
// value, then the current entry becomes our base entry.
if (Priority > BindingPriority.LocalValue && Priority < BindingPriority.Inherited)
{
Debug.Assert(_valueEntry is not null);
_baseValueEntry = _valueEntry;
_valueEntry = null;
}
if (_valueEntry != entry)
{
_valueEntry?.Unsubscribe();
_valueEntry = entry;
}
}
else if (Priority <= BindingPriority.Animation)
{
// We've received a non-animation value and have an active animation value, so the
// new entry becomes our base entry.
if (_baseValueEntry != entry)
{
_baseValueEntry?.Unsubscribe();
_baseValueEntry = entry;
}
}
else if (_valueEntry != entry)
{
// Both the current value and the new value are non-animation values, so the new
// entry replaces the existing entry.
_valueEntry?.Unsubscribe();
_valueEntry = entry;
}
}
protected void UnsubscribeValueEntries()
{
_valueEntry?.Unsubscribe();
_baseValueEntry?.Unsubscribe();
}
}
}

270
src/Avalonia.Base/PropertyStore/EffectiveValue`1.cs

@ -0,0 +1,270 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents the active value for a property in a <see cref="ValueStore"/>.
/// </summary>
/// <remarks>
/// Stores the active value in an <see cref="AvaloniaObject"/>'s <see cref="ValueStore"/>
/// for a single property, when the value is not inherited or unset/default.
/// </remarks>
internal sealed class EffectiveValue<T> : EffectiveValue
{
private readonly StyledPropertyMetadata<T> _metadata;
private T? _baseValue;
private UncommonFields? _uncommon;
public EffectiveValue(AvaloniaObject owner, StyledPropertyBase<T> property)
{
Priority = BindingPriority.Unset;
BasePriority = BindingPriority.Unset;
_metadata = property.GetMetadata(owner.GetType());
var value = _metadata.DefaultValue;
if (property.HasCoercion && _metadata.CoerceValue is { } coerce)
{
_uncommon = new()
{
_coerce = coerce,
_uncoercedValue = value,
_uncoercedBaseValue = value,
};
Value = coerce(owner, value);
}
else
{
Value = value;
}
}
/// <summary>
/// Gets the current effective value.
/// </summary>
public new T Value { get; private set; }
public override void SetAndRaise(
ValueStore owner,
IValueEntry value,
BindingPriority priority)
{
Debug.Assert(priority != BindingPriority.LocalValue);
UpdateValueEntry(value, priority);
SetAndRaiseCore(owner, (StyledPropertyBase<T>)value.Property, GetValue(value), priority);
}
public void SetLocalValueAndRaise(
ValueStore owner,
StyledPropertyBase<T> property,
T value)
{
SetAndRaiseCore(owner, property, value, BindingPriority.LocalValue);
}
public bool TryGetBaseValue([MaybeNullWhen(false)] out T value)
{
value = _baseValue!;
return BasePriority != BindingPriority.Unset;
}
public override void RaiseInheritedValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
EffectiveValue? oldValue,
EffectiveValue? newValue)
{
Debug.Assert(oldValue is not null || newValue is not null);
var p = (StyledPropertyBase<T>)property;
var o = oldValue is not null ? ((EffectiveValue<T>)oldValue).Value : _metadata.DefaultValue;
var n = newValue is not null ? ((EffectiveValue<T>)newValue).Value : _metadata.DefaultValue;
var priority = newValue is not null ? BindingPriority.Inherited : BindingPriority.Unset;
if (!EqualityComparer<T>.Default.Equals(o, n))
{
owner.RaisePropertyChanged(p, o, n, priority, true);
}
}
public override void RemoveAnimationAndRaise(ValueStore owner, AvaloniaProperty property)
{
Debug.Assert(Priority != BindingPriority.Animation);
Debug.Assert(BasePriority != BindingPriority.Unset);
UpdateValueEntry(null, BindingPriority.Animation);
SetAndRaiseCore(owner, (StyledPropertyBase<T>)property, _baseValue!, BasePriority);
}
public override void CoerceValue(ValueStore owner, AvaloniaProperty property)
{
if (_uncommon is null)
return;
SetAndRaiseCore(
owner,
(StyledPropertyBase<T>)property,
_uncommon._uncoercedValue!,
Priority,
_uncommon._uncoercedBaseValue!,
BasePriority);
}
public override void DisposeAndRaiseUnset(ValueStore owner, AvaloniaProperty property)
{
UnsubscribeValueEntries();
DisposeAndRaiseUnset(owner, (StyledPropertyBase<T>)property);
}
public void DisposeAndRaiseUnset(ValueStore owner, StyledPropertyBase<T> property)
{
BindingPriority priority;
T oldValue;
if (property.Inherits && owner.TryGetInheritedValue(property, out var i))
{
oldValue = ((EffectiveValue<T>)i).Value;
priority = BindingPriority.Inherited;
}
else
{
oldValue = _metadata.DefaultValue;
priority = BindingPriority.Unset;
}
if (!EqualityComparer<T>.Default.Equals(oldValue, Value))
{
owner.Owner.RaisePropertyChanged(property, Value, oldValue, priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueDisposed(property, Value);
}
}
protected override object? GetBoxedValue() => Value;
private static T GetValue(IValueEntry entry)
{
if (entry is IValueEntry<T> typed)
return typed.GetValue();
else
return (T)entry.GetValue()!;
}
private void SetAndRaiseCore(
ValueStore owner,
StyledPropertyBase<T> property,
T value,
BindingPriority priority)
{
Debug.Assert(priority < BindingPriority.Inherited);
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
if (_uncommon?._coerce is { } coerce)
v = coerce(owner.Owner, value);
if (priority <= Priority)
{
valueChanged = !EqualityComparer<T>.Default.Equals(Value, v);
Value = v;
Priority = priority;
if (_uncommon is not null)
_uncommon._uncoercedValue = value;
}
if (priority <= BasePriority && priority >= BindingPriority.LocalValue)
{
baseValueChanged = !EqualityComparer<T>.Default.Equals(_baseValue, v);
_baseValue = v;
BasePriority = priority;
if (_uncommon is not null)
_uncommon._uncoercedBaseValue = value;
}
if (valueChanged)
{
using var notifying = PropertyNotifying.Start(owner.Owner, property);
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
}
else if (baseValueChanged)
{
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
}
}
private void SetAndRaiseCore(
ValueStore owner,
StyledPropertyBase<T> property,
T value,
BindingPriority priority,
T baseValue,
BindingPriority basePriority)
{
Debug.Assert(priority < BindingPriority.Inherited);
Debug.Assert(basePriority > BindingPriority.Animation);
Debug.Assert(priority <= basePriority);
var oldValue = Value;
var valueChanged = false;
var baseValueChanged = false;
var v = value;
var bv = baseValue;
if (_uncommon?._coerce is { } coerce)
{
v = coerce(owner.Owner, value);
bv = coerce(owner.Owner, baseValue);
}
if (priority != BindingPriority.Unset && !EqualityComparer<T>.Default.Equals(Value, v))
{
Value = v;
valueChanged = true;
if (_uncommon is not null)
_uncommon._uncoercedValue = value;
}
if (priority != BindingPriority.Unset &&
(BasePriority == BindingPriority.Unset ||
!EqualityComparer<T>.Default.Equals(_baseValue, bv)))
{
_baseValue = v;
baseValueChanged = true;
if (_uncommon is not null)
_uncommon._uncoercedValue = baseValue;
}
Priority = priority;
BasePriority = basePriority;
if (valueChanged)
{
using var notifying = PropertyNotifying.Start(owner.Owner, property);
owner.Owner.RaisePropertyChanged(property, oldValue, Value, Priority, true);
if (property.Inherits)
owner.OnInheritedEffectiveValueChanged(property, oldValue, this);
}
if (baseValueChanged)
{
owner.Owner.RaisePropertyChanged(property, default, _baseValue!, BasePriority, false);
}
}
private class UncommonFields
{
public Func<IAvaloniaObject, T, T>? _coerce;
public T? _uncoercedValue;
public T? _uncoercedBaseValue;
}
}
}

36
src/Avalonia.Base/PropertyStore/FramePriority.cs

@ -0,0 +1,36 @@
using System.Diagnostics;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal enum FramePriority : sbyte
{
Animation,
AnimationTemplatedParentTheme,
AnimationTheme,
StyleTrigger,
StyleTriggerTemplatedParentTheme,
StyleTriggerTheme,
Template,
TemplateTemplatedParentTheme,
TemplateTheme,
Style,
StyleTemplatedParentTheme,
StyleTheme,
}
internal static class FramePriorityExtensions
{
public static FramePriority ToFramePriority(this BindingPriority priority, FrameType type = FrameType.Style)
{
Debug.Assert(priority != BindingPriority.LocalValue);
var p = (int)(priority > 0 ? priority : priority + 1);
return (FramePriority)(p * 3 + (int)type);
}
public static bool IsType(this FramePriority priority, FrameType type)
{
return (FrameType)((int)priority % 3) == type;
}
}
}

8
src/Avalonia.Base/PropertyStore/IBatchUpdate.cs

@ -1,8 +0,0 @@
namespace Avalonia.PropertyStore
{
internal interface IBatchUpdate
{
void BeginBatchUpdate();
void EndBatchUpdate();
}
}

18
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@ -1,18 +0,0 @@
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped interface to <see cref="IPriorityValueEntry{T}"/>.
/// </summary>
internal interface IPriorityValueEntry : IValue
{
}
/// <summary>
/// Represents an object that can act as an entry in a <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal interface IPriorityValueEntry<T> : IPriorityValueEntry, IValue<T>
{
void Reparent(PriorityValue<T> parent);
}
}

28
src/Avalonia.Base/PropertyStore/IValue.cs

@ -1,28 +0,0 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped interface to <see cref="IValue{T}"/>.
/// </summary>
internal interface IValue
{
BindingPriority Priority { get; }
Optional<object?> GetValue();
void Start();
void RaiseValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue);
}
/// <summary>
/// Represents an object that can act as an entry in a <see cref="ValueStore"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal interface IValue<T> : IValue
{
Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation);
}
}

30
src/Avalonia.Base/PropertyStore/IValueEntry.cs

@ -0,0 +1,30 @@
using System;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped value entry in a <see cref="ValueFrame"/>.
/// </summary>
internal interface IValueEntry
{
bool HasValue { get; }
/// <summary>
/// Gets the property that this value applies to.
/// </summary>
AvaloniaProperty Property { get; }
/// <summary>
/// Gets the value associated with the entry.
/// </summary>
/// <exception cref="AvaloniaInternalException">
/// The entry has no value.
/// </exception>
object? GetValue();
/// <summary>
/// Called when the value entry is removed from the value store.
/// </summary>
void Unsubscribe();
}
}

16
src/Avalonia.Base/PropertyStore/IValueEntry`1.cs

@ -0,0 +1,16 @@
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents a typed value entry in a <see cref="ValueFrame"/>.
/// </summary>
internal interface IValueEntry<T> : IValueEntry
{
/// <summary>
/// Gets the value associated with the entry.
/// </summary>
/// <exception cref="AvaloniaInternalException">
/// The entry has no value.
/// </exception>
new T GetValue();
}
}

31
src/Avalonia.Base/PropertyStore/ImmediateValueEntry.cs

@ -0,0 +1,31 @@
using System;
namespace Avalonia.PropertyStore
{
internal class ImmediateValueEntry<T> : IValueEntry<T>, IDisposable
{
private readonly ImmediateValueFrame _owner;
private readonly T _value;
public ImmediateValueEntry(
ImmediateValueFrame owner,
StyledPropertyBase<T> property,
T value)
{
_owner = owner;
_value = value;
Property = property;
}
public StyledPropertyBase<T> Property { get; }
public bool HasValue => true;
AvaloniaProperty IValueEntry.Property => Property;
public void Unsubscribe() { }
public void Dispose() => _owner.OnEntryDisposed(this);
object? IValueEntry.GetValue() => _value;
T IValueEntry<T>.GetValue() => _value;
}
}

63
src/Avalonia.Base/PropertyStore/ImmediateValueFrame.cs

@ -0,0 +1,63 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Holds values in a <see cref="ValueStore"/> set by one of the SetValue or AddBinding
/// overloads with non-LocalValue priority.
/// </summary>
internal sealed class ImmediateValueFrame : ValueFrame
{
public ImmediateValueFrame(BindingPriority priority)
: base(priority, FrameType.Style)
{
}
public TypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
IObservable<BindingValue<T>> source)
{
var e = new TypedBindingEntry<T>(this, property, source);
Add(e);
return e;
}
public TypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
IObservable<T> source)
{
var e = new TypedBindingEntry<T>(this, property, source);
Add(e);
return e;
}
public SourceUntypedBindingEntry<T> AddBinding<T>(
StyledPropertyBase<T> property,
IObservable<object?> source)
{
var e = new SourceUntypedBindingEntry<T>(this, property, source);
Add(e);
return e;
}
public ImmediateValueEntry<T> AddValue<T>(StyledPropertyBase<T> property, T value)
{
var e = new ImmediateValueEntry<T>(this, property, value);
Add(e);
return e;
}
public void OnEntryDisposed(IValueEntry value)
{
Remove(value.Property);
Owner?.OnValueEntryRemoved(this, value.Property);
}
protected override bool GetIsActive(out bool hasChanged)
{
hasChanged = false;
return true;
}
}
}

59
src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs

@ -0,0 +1,59 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal class LocalValueBindingObserver<T> : IObserver<T>,
IObserver<BindingValue<T>>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public LocalValueBindingObserver(ValueStore owner, StyledPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public StyledPropertyBase<T> Property { get;}
public void Start(IObservable<T> source)
{
_subscription = source.Subscribe(this);
}
public void Start(IObservable<BindingValue<T>> source)
{
_subscription = source.Subscribe(this);
}
public void Dispose()
{
_subscription?.Dispose();
_subscription = null;
_owner.OnLocalValueBindingCompleted(Property, this);
}
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
public void OnError(Exception error) => OnCompleted();
public void OnNext(T value)
{
if (Property.ValidateValue?.Invoke(value) != false)
_owner.SetValue(Property, value, BindingPriority.LocalValue);
else
_owner.ClearLocalValue(Property);
}
public void OnNext(BindingValue<T> value)
{
LoggingUtils.LogIfNecessary(_owner.Owner, Property, value);
if (value.HasValue)
_owner.SetValue(Property, value.Value, BindingPriority.LocalValue);
else if (value.Type != BindingValueType.DataValidationError)
_owner.ClearLocalValue(Property);
}
}
}

41
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@ -1,41 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Stores a value with local value priority in a <see cref="ValueStore"/> or
/// <see cref="PriorityValue{T}"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
internal class LocalValueEntry<T> : IValue<T>
{
private T _value;
public LocalValueEntry(T value) => _value = value;
public BindingPriority Priority => BindingPriority.LocalValue;
Optional<object?> IValue.GetValue() => new Optional<object?>(_value);
public Optional<T> GetValue(BindingPriority maxPriority)
{
return BindingPriority.LocalValue >= maxPriority ? _value : Optional<T>.Empty;
}
public void SetValue(T value) => _value = value;
public void Start() { }
public void RaiseValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
newValue.Cast<T>(),
BindingPriority.LocalValue));
}
}
}

62
src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs

@ -0,0 +1,62 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal class LocalValueUntypedBindingObserver<T> : IObserver<object?>,
IDisposable
{
private readonly ValueStore _owner;
private IDisposable? _subscription;
public LocalValueUntypedBindingObserver(ValueStore owner, StyledPropertyBase<T> property)
{
_owner = owner;
Property = property;
}
public StyledPropertyBase<T> Property { get; }
public void Start(IObservable<object?> source)
{
_subscription = source.Subscribe(this);
}
public void Dispose()
{
_subscription?.Dispose();
_subscription = null;
_owner.OnLocalValueBindingCompleted(Property, this);
}
public void OnCompleted() => _owner.OnLocalValueBindingCompleted(Property, this);
public void OnError(Exception error) => OnCompleted();
public void OnNext(object? value)
{
if (value is BindingNotification n)
{
value = n.Value;
LoggingUtils.LogIfNecessary(_owner.Owner, Property, n);
}
if (value == AvaloniaProperty.UnsetValue)
{
_owner.ClearLocalValue(Property);
}
else if (value == BindingOperations.DoNothing)
{
// Do nothing!
}
else if (UntypedValueUtils.TryConvertAndValidate(Property, value, out var typedValue))
{
_owner.SetValue(Property, typedValue, BindingPriority.LocalValue);
}
else
{
_owner.ClearLocalValue(Property);
LoggingUtils.LogInvalidValue(_owner.Owner, Property, typeof(T), value);
}
}
}
}

82
src/Avalonia.Base/PropertyStore/LoggingUtils.cs

@ -0,0 +1,82 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
internal static class LoggingUtils
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogIfNecessary(
AvaloniaObject owner,
AvaloniaProperty property,
BindingNotification value)
{
if (value.ErrorType != BindingErrorType.None)
Log(owner, property, value.Error!);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogIfNecessary<T>(
AvaloniaObject owner,
AvaloniaProperty property,
BindingValue<T> value)
{
if (value.HasError)
Log(owner, property, value.Error!);
}
public static void LogInvalidValue(
AvaloniaObject owner,
AvaloniaProperty property,
Type expectedType,
object? value)
{
if (value is not null)
{
owner.GetBindingWarningLogger(property, null)?.Log(
owner,
"Error in binding to {Target}.{Property}: expected {ExpectedType}, got {Value} ({ValueType})",
owner,
property,
expectedType,
value,
value.GetType());
}
else
{
owner.GetBindingWarningLogger(property, null)?.Log(
owner,
"Error in binding to {Target}.{Property}: expected {ExpectedType}, got null",
owner,
property,
expectedType);
}
}
private static void Log(
AvaloniaObject owner,
AvaloniaProperty property,
Exception e)
{
if (e is TargetInvocationException t)
e = t.InnerException!;
if (e is AggregateException a)
{
foreach (var i in a.InnerExceptions)
Log(owner, property, i);
}
else
{
owner.GetBindingWarningLogger(property, e)?.Log(
owner,
"Error in binding to {Target}.{Property}: {Message}",
owner,
property,
e.Message);
}
}
}
}

326
src/Avalonia.Base/PropertyStore/PriorityValue.cs

@ -1,326 +0,0 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents an untyped interface to <see cref="PriorityValue{T}"/>.
/// </summary>
interface IPriorityValue : IValue
{
void UpdateEffectiveValue();
}
/// <summary>
/// Stores a set of prioritized values and bindings in a <see cref="ValueStore"/>.
/// </summary>
/// <typeparam name="T">The property type.</typeparam>
/// <remarks>
/// When more than a single value or binding is applied to a property in an
/// <see cref="AvaloniaObject"/>, the entry in the <see cref="ValueStore"/> is converted into
/// a <see cref="PriorityValue{T}"/>. This class holds any number of
/// <see cref="IPriorityValueEntry{T}"/> entries (sorted first by priority and then in the order
/// they were added) plus a local value.
/// </remarks>
internal class PriorityValue<T> : IPriorityValue, IValue<T>, IBatchUpdate
{
private readonly AvaloniaObject _owner;
private readonly ValueStore _store;
private readonly List<IPriorityValueEntry<T>> _entries = new List<IPriorityValueEntry<T>>();
private readonly Func<IAvaloniaObject, T, T>? _coerceValue;
private Optional<T> _localValue;
private Optional<T> _value;
private bool _isCalculatingValue;
private bool _batchUpdate;
public PriorityValue(
AvaloniaObject owner,
StyledPropertyBase<T> property,
ValueStore store)
{
_owner = owner;
Property = property;
_store = store;
if (property.HasCoercion)
{
var metadata = property.GetMetadata(owner.GetType());
_coerceValue = metadata.CoerceValue;
}
}
public PriorityValue(
AvaloniaObject owner,
StyledPropertyBase<T> property,
ValueStore store,
IPriorityValueEntry<T> existing)
: this(owner, property, store)
{
existing.Reparent(this);
_entries.Add(existing);
if (existing is IBindingEntry binding &&
existing.Priority == BindingPriority.LocalValue)
{
// Bit of a special case here: if we have a local value binding that is being
// promoted to a priority value we need to make sure the binding is subscribed
// even if we've got a batch operation in progress because otherwise we don't know
// whether the binding or a subsequent SetValue with local priority will win. A
// notification won't be sent during batch update anyway because it will be
// caught and stored for later by the ValueStore.
binding.Start(ignoreBatchUpdate: true);
}
var v = existing.GetValue();
if (v.HasValue)
{
_value = v;
Priority = existing.Priority;
}
}
public PriorityValue(
AvaloniaObject owner,
StyledPropertyBase<T> property,
ValueStore sink,
LocalValueEntry<T> existing)
: this(owner, property, sink)
{
_value = _localValue = existing.GetValue(BindingPriority.LocalValue);
Priority = BindingPriority.LocalValue;
}
public StyledPropertyBase<T> Property { get; }
public BindingPriority Priority { get; private set; } = BindingPriority.Unset;
public IReadOnlyList<IPriorityValueEntry<T>> Entries => _entries;
Optional<object?> IValue.GetValue() => _value.ToObject();
public void BeginBatchUpdate()
{
_batchUpdate = true;
foreach (var entry in _entries)
{
(entry as IBatchUpdate)?.BeginBatchUpdate();
}
}
public void EndBatchUpdate()
{
_batchUpdate = false;
foreach (var entry in _entries)
{
(entry as IBatchUpdate)?.EndBatchUpdate();
}
UpdateEffectiveValue(null);
}
public void ClearLocalValue()
{
_localValue = default;
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
default,
default,
BindingPriority.LocalValue));
}
public Optional<T> GetValue(BindingPriority maxPriority = BindingPriority.Animation)
{
if (Priority == BindingPriority.Unset)
{
return default;
}
if (Priority >= maxPriority)
{
return _value;
}
return CalculateValue(maxPriority).Item1;
}
public IDisposable? SetValue(T value, BindingPriority priority)
{
IDisposable? result = null;
if (priority == BindingPriority.LocalValue)
{
_localValue = value;
}
else
{
var insert = FindInsertPoint(priority);
var entry = new ConstantValueEntry<T>(Property, value, priority, new ValueOwner<T>(this));
_entries.Insert(insert, entry);
result = entry;
}
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
default,
value,
priority));
return result;
}
public BindingEntry<T> AddBinding(IObservable<BindingValue<T>> source, BindingPriority priority)
{
var binding = new BindingEntry<T>(_owner, Property, source, priority, new(this));
var insert = FindInsertPoint(binding.Priority);
_entries.Insert(insert, binding);
if (_batchUpdate)
{
binding.BeginBatchUpdate();
if (priority == BindingPriority.LocalValue)
{
binding.Start(ignoreBatchUpdate: true);
}
}
return binding;
}
public void UpdateEffectiveValue() => UpdateEffectiveValue(null);
public void Start() => UpdateEffectiveValue(null);
public void RaiseValueChanged(
AvaloniaObject owner,
AvaloniaProperty property,
Optional<object?> oldValue,
Optional<object?> newValue)
{
owner.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
owner,
(AvaloniaProperty<T>)property,
oldValue.Cast<T>(),
newValue.Cast<T>(),
Priority));
}
public void ValueChanged<TValue>(AvaloniaPropertyChangedEventArgs<TValue> change)
{
if (change.Priority == BindingPriority.LocalValue)
{
_localValue = default;
}
if (!_isCalculatingValue && change is AvaloniaPropertyChangedEventArgs<T> c)
{
UpdateEffectiveValue(c);
}
}
public void Completed(IPriorityValueEntry entry, Optional<T> oldValue)
{
_entries.Remove((IPriorityValueEntry<T>)entry);
UpdateEffectiveValue(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
oldValue,
default,
entry.Priority));
}
private int FindInsertPoint(BindingPriority priority)
{
var result = _entries.Count;
for (var i = 0; i < _entries.Count; ++i)
{
if (_entries[i].Priority < priority)
{
result = i;
break;
}
}
return result;
}
public (Optional<T>, BindingPriority) CalculateValue(BindingPriority maxPriority)
{
_isCalculatingValue = true;
try
{
for (var i = _entries.Count - 1; i >= 0; --i)
{
var entry = _entries[i];
if (entry.Priority < maxPriority)
{
continue;
}
entry.Start();
if (entry.Priority >= BindingPriority.LocalValue &&
maxPriority <= BindingPriority.LocalValue &&
_localValue.HasValue)
{
return (_localValue, BindingPriority.LocalValue);
}
var entryValue = entry.GetValue();
if (entryValue.HasValue)
{
return (entryValue, entry.Priority);
}
}
if (maxPriority <= BindingPriority.LocalValue && _localValue.HasValue)
{
return (_localValue, BindingPriority.LocalValue);
}
return (default, BindingPriority.Unset);
}
finally
{
_isCalculatingValue = false;
}
}
private void UpdateEffectiveValue(AvaloniaPropertyChangedEventArgs<T>? change)
{
var (value, priority) = CalculateValue(BindingPriority.Animation);
if (value.HasValue && _coerceValue != null)
{
value = _coerceValue(_owner, value.Value);
}
Priority = priority;
if (value != _value)
{
var old = _value;
_value = value;
_store.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
_owner,
Property,
old,
value,
Priority));
}
else if (change is object)
{
change.MarkNonEffectiveValue();
change.SetOldValue(default);
_store.ValueChanged(change);
}
}
}
}

35
src/Avalonia.Base/PropertyStore/PropertyNotifying.cs

@ -0,0 +1,35 @@
using System;
using System.Diagnostics;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Raises <see cref="AvaloniaProperty.Notifying"/> where necessary.
/// </summary>
/// <remarks>
/// Uses the disposable pattern to ensure that the closing Notifying call is made even in the
/// presence of exceptions.
/// </remarks>
internal readonly struct PropertyNotifying : IDisposable
{
private readonly AvaloniaObject _owner;
private readonly AvaloniaProperty _property;
private PropertyNotifying(AvaloniaObject owner, AvaloniaProperty property)
{
Debug.Assert(property.Notifying is not null);
_owner = owner;
_property = property;
_property.Notifying!(owner, true);
}
public void Dispose() => _property.Notifying!(_owner, false);
public static PropertyNotifying? Start(AvaloniaObject owner, AvaloniaProperty property)
{
if (property.Notifying is null)
return null;
return new PropertyNotifying(owner, property);
}
}
}

35
src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs

@ -0,0 +1,35 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// An <see cref="IValueEntry"/> that holds a binding whose source observable is untyped and
/// target property is typed.
/// </summary>
internal sealed class SourceUntypedBindingEntry<TTarget> : BindingEntryBase<TTarget, object?>
{
private readonly Func<TTarget, bool>? _validate;
public SourceUntypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<TTarget> property,
IObservable<object?> source)
: base(frame, property, source)
{
_validate = property.ValidateValue;
}
public new StyledPropertyBase<TTarget> Property => (StyledPropertyBase<TTarget>)base.Property;
protected override BindingValue<TTarget> ConvertAndValidate(object? value)
{
return UntypedValueUtils.ConvertAndValidate(value, Property.PropertyType, _validate);
}
protected override BindingValue<TTarget> ConvertAndValidate(BindingValue<object?> value)
{
throw new NotSupportedException();
}
}
}

52
src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs

@ -0,0 +1,52 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// An <see cref="IValueEntry"/> that holds a binding whose source observable and target
/// property are both typed.
/// </summary>
internal sealed class TypedBindingEntry<T> : BindingEntryBase<T, T>
{
public TypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<T> property,
IObservable<T> source)
: base(frame, property, source)
{
}
public TypedBindingEntry(
ValueFrame frame,
StyledPropertyBase<T> property,
IObservable<BindingValue<T>> source)
: base(frame, property, source)
{
}
public new StyledPropertyBase<T> Property => (StyledPropertyBase<T>)base.Property;
protected override BindingValue<T> ConvertAndValidate(T value)
{
if (Property.ValidateValue?.Invoke(value) == false)
{
return BindingValue<T>.BindingError(
new InvalidCastException($"'{value}' is not a valid value."));
}
return value;
}
protected override BindingValue<T> ConvertAndValidate(BindingValue<T> value)
{
if (value.HasValue && Property.ValidateValue?.Invoke(value.Value) == false)
{
return BindingValue<T>.BindingError(
new InvalidCastException($"'{value.Value}' is not a valid value."));
}
return value;
}
}
}

33
src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs

@ -0,0 +1,33 @@
using System;
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// An <see cref="IValueEntry"/> that holds a binding whose source observable and target
/// property are both untyped.
/// </summary>
internal class UntypedBindingEntry : BindingEntryBase<object?, object?>
{
private readonly Func<object?, bool>? _validate;
public UntypedBindingEntry(
ValueFrame frame,
AvaloniaProperty property,
IObservable<object?> source)
: base(frame, property, source)
{
_validate = ((IStyledPropertyAccessor)property).ValidateValue;
}
protected override BindingValue<object?> ConvertAndValidate(object? value)
{
return UntypedValueUtils.ConvertAndValidate(value, Property.PropertyType, _validate);
}
protected override BindingValue<object?> ConvertAndValidate(BindingValue<object?> value)
{
throw new NotSupportedException();
}
}
}

55
src/Avalonia.Base/PropertyStore/UntypedValueUtils.cs

@ -0,0 +1,55 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.PropertyStore
{
internal static class UntypedValueUtils
{
public static BindingValue<T> ConvertAndValidate<T>(
object? value,
Type targetType,
Func<T, bool>? validate)
{
var v = BindingValue<T>.FromUntyped(value, targetType);
if (v.HasValue && validate?.Invoke(v.Value) == false)
{
return BindingValue<T>.BindingError(
new InvalidCastException($"'{v.Value}' is not a valid value."));
}
return v;
}
public static bool TryConvertAndValidate(
AvaloniaProperty property,
object? value,
out object? result)
{
if (TypeUtilities.TryConvertImplicit(property.PropertyType, value, out result))
return ((IStyledPropertyAccessor)property).ValidateValue(result);
result = default;
return false;
}
public static bool TryConvertAndValidate<T>(
StyledPropertyBase<T> property,
object? value,
[MaybeNullWhen(false)] out T result)
{
if (TypeUtilities.TryConvertImplicit(typeof(T), value, out var v))
{
result = (T)v!;
if (property.ValidateValue?.Invoke(result) != false)
return true;
}
result = default;
return false;
}
}
}

115
src/Avalonia.Base/PropertyStore/ValueFrame.cs

@ -0,0 +1,115 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Data;
using Avalonia.Utilities;
namespace Avalonia.PropertyStore
{
internal enum FrameType
{
Style,
TemplatedParentTheme,
Theme,
}
internal abstract class ValueFrame
{
private List<IValueEntry>? _entries;
private AvaloniaPropertyDictionary<IValueEntry> _index;
private ValueStore? _owner;
private bool _isShared;
protected ValueFrame(BindingPriority priority, FrameType type)
{
Priority = priority;
FramePriority = priority.ToFramePriority(type);
}
public int EntryCount => _index.Count;
public bool IsActive => GetIsActive(out _);
public ValueStore? Owner => !_isShared ? _owner :
throw new AvaloniaInternalException("Cannot get owner for shared ValueFrame");
public BindingPriority Priority { get; }
public FramePriority FramePriority { get; }
public bool Contains(AvaloniaProperty property) => _index.ContainsKey(property);
public IValueEntry GetEntry(int index) => _entries?[index] ?? _index[0];
public void SetOwner(ValueStore? owner)
{
if (_owner is not null && owner is not null)
throw new AvaloniaInternalException("ValueFrame already has an owner.");
if (!_isShared)
_owner = owner;
}
public bool TryGetEntryIfActive(
AvaloniaProperty property,
[NotNullWhen(true)] out IValueEntry? entry,
out bool activeChanged)
{
if (_index.TryGetValue(property, out entry))
return GetIsActive(out activeChanged);
activeChanged = false;
return false;
}
public void OnBindingCompleted(IValueEntry binding)
{
var property = binding.Property;
Remove(property);
Owner?.OnValueEntryRemoved(this, property);
}
public virtual void Dispose()
{
for (var i = 0; i < _index.Count; ++i)
_index[i].Unsubscribe();
}
protected abstract bool GetIsActive(out bool hasChanged);
protected void MakeShared()
{
_isShared = true;
_owner = null;
}
protected void Add(IValueEntry value)
{
Debug.Assert(!value.Property.IsDirect);
if (_entries is null && _index.Count == 1)
{
_entries = new();
_entries.Add(_index[0]);
}
_index.Add(value.Property, value);
_entries?.Add(value);
}
protected void Remove(AvaloniaProperty property)
{
Debug.Assert(!property.IsDirect);
if (_entries is not null)
{
var count = _entries.Count;
for (var i = 0; i < count; ++i)
{
if (_entries[i].Property == property)
{
_entries.RemoveAt(i);
break;
}
}
}
_index.Remove(property);
}
}
}

45
src/Avalonia.Base/PropertyStore/ValueOwner.cs

@ -1,45 +0,0 @@
using Avalonia.Data;
namespace Avalonia.PropertyStore
{
/// <summary>
/// Represents a union type of <see cref="ValueStore"/> and <see cref="PriorityValue{T}"/>,
/// which are the valid owners of a value store <see cref="IValue"/>.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
internal readonly struct ValueOwner<T>
{
private readonly ValueStore? _store;
private readonly PriorityValue<T>? _priorityValue;
public ValueOwner(ValueStore o)
{
_store = o;
_priorityValue = null;
}
public ValueOwner(PriorityValue<T> v)
{
_store = null;
_priorityValue = v;
}
public bool IsValueStore => _store is not null;
public void Completed(StyledPropertyBase<T> property, IPriorityValueEntry entry, Optional<T> oldValue)
{
if (_store is not null)
_store?.Completed(property, entry, oldValue);
else
_priorityValue!.Completed(entry, oldValue);
}
public void ValueChanged(AvaloniaPropertyChangedEventArgs<T> e)
{
if (_store is not null)
_store?.ValueChanged(e);
else
_priorityValue!.ValueChanged(e);
}
}
}

1010
src/Avalonia.Base/PropertyStore/ValueStore.cs

File diff suppressed because it is too large

59
src/Avalonia.Base/Reactive/BindingValueAdapter.cs

@ -1,59 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class BindingValueAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
IObserver<T>
{
private readonly IObservable<T> _source;
private IDisposable? _subscription;
public BindingValueAdapter(IObservable<T> source) => _source = source;
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public void OnNext(T value) => PublishNext(BindingValue<T>.FromUntyped(value));
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
internal class BindingValueSubjectAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
ISubject<BindingValue<T>>
{
private readonly ISubject<T> _source;
private readonly Inner _inner;
private IDisposable? _subscription;
public BindingValueSubjectAdapter(ISubject<T> source)
{
_source = source;
_inner = new Inner(this);
}
public void OnCompleted() => _source.OnCompleted();
public void OnError(Exception error) => _source.OnError(error);
public void OnNext(BindingValue<T> value)
{
if (value.HasValue)
{
_source.OnNext(value.Value);
}
}
protected override void Subscribed() => _subscription = _source.Subscribe(_inner);
protected override void Unsubscribed() => _subscription?.Dispose();
private class Inner : IObserver<T>
{
private readonly BindingValueSubjectAdapter<T> _owner;
public Inner(BindingValueSubjectAdapter<T> owner) => _owner = owner;
public void OnCompleted() => _owner.PublishCompleted();
public void OnError(Exception error) => _owner.PublishError(error);
public void OnNext(T value) => _owner.PublishNext(BindingValue<T>.FromUntyped(value));
}
}
}

33
src/Avalonia.Base/Reactive/BindingValueExtensions.cs

@ -1,33 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
public static class BindingValueExtensions
{
public static IObservable<BindingValue<T>> ToBindingValue<T>(this IObservable<T> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new BindingValueAdapter<T>(source);
}
public static ISubject<BindingValue<T>> ToBindingValue<T>(this ISubject<T> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new BindingValueSubjectAdapter<T>(source);
}
public static IObservable<object?> ToUntyped<T>(this IObservable<BindingValue<T>> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingAdapter<T>(source);
}
public static ISubject<object?> ToUntyped<T>(this ISubject<BindingValue<T>> source)
{
source = source ?? throw new ArgumentNullException(nameof(source));
return new UntypedBindingSubjectAdapter<T>(source);
}
}
}

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

@ -51,10 +51,11 @@ namespace Avalonia.Reactive
protected void PublishCompleted()
{
_completed = true;
if (_observer != null)
{
_observer.OnCompleted();
_completed = true;
Unsubscribed();
_observer = null;
}
@ -62,10 +63,11 @@ namespace Avalonia.Reactive
protected void PublishError(Exception error)
{
_error = error;
if (_observer != null)
{
_observer.OnError(error);
_error = error;
Unsubscribed();
_observer = null;
}

62
src/Avalonia.Base/Reactive/TypedBindingAdapter.cs

@ -1,62 +0,0 @@
using System;
using Avalonia.Data;
using Avalonia.Logging;
namespace Avalonia.Reactive
{
internal class TypedBindingAdapter<T> : SingleSubscriberObservableBase<BindingValue<T>>,
IObserver<BindingValue<object?>>
{
private readonly IAvaloniaObject _target;
private readonly AvaloniaProperty<T> _property;
private readonly IObservable<BindingValue<object?>> _source;
private IDisposable? _subscription;
public TypedBindingAdapter(
IAvaloniaObject target,
AvaloniaProperty<T> property,
IObservable<BindingValue<object?>> source)
{
_target = target;
_property = property;
_source = source;
}
public void OnNext(BindingValue<object?> value)
{
try
{
PublishNext(value.Convert<T>());
}
catch (InvalidCastException e)
{
var unwrappedValue = value.HasValue ? value.Value : null;
Logger.TryGet(LogEventLevel.Error, LogArea.Binding)?.Log(
_target,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
_property.Name,
_property.PropertyType,
unwrappedValue,
unwrappedValue?.GetType());
PublishNext(BindingValue<T>.BindingError(e));
}
}
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public static IObservable<BindingValue<T>> Create(
IAvaloniaObject target,
AvaloniaProperty<T> property,
IObservable<BindingValue<object?>> source)
{
return source is IObservable<BindingValue<T>> result ?
result :
new TypedBindingAdapter<T>(target, property, source);
}
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
}

55
src/Avalonia.Base/Reactive/UntypedBindingAdapter.cs

@ -1,55 +0,0 @@
using System;
using System.Reactive.Subjects;
using Avalonia.Data;
namespace Avalonia.Reactive
{
internal class UntypedBindingAdapter<T> : SingleSubscriberObservableBase<object?>,
IObserver<BindingValue<T>>
{
private readonly IObservable<BindingValue<T>> _source;
private IDisposable? _subscription;
public UntypedBindingAdapter(IObservable<BindingValue<T>> source) => _source = source;
public void OnCompleted() => PublishCompleted();
public void OnError(Exception error) => PublishError(error);
public void OnNext(BindingValue<T> value) => value.ToUntyped();
protected override void Subscribed() => _subscription = _source.Subscribe(this);
protected override void Unsubscribed() => _subscription?.Dispose();
}
internal class UntypedBindingSubjectAdapter<T> : SingleSubscriberObservableBase<object?>,
ISubject<object?>
{
private readonly ISubject<BindingValue<T>> _source;
private readonly Inner _inner;
private IDisposable? _subscription;
public UntypedBindingSubjectAdapter(ISubject<BindingValue<T>> source)
{
_source = source;
_inner = new Inner(this);
}
public void OnCompleted() => _source.OnCompleted();
public void OnError(Exception error) => _source.OnError(error);
public void OnNext(object? value)
{
_source.OnNext(BindingValue<T>.FromUntyped(value));
}
protected override void Subscribed() => _subscription = _source.Subscribe(_inner);
protected override void Unsubscribed() => _subscription?.Dispose();
private class Inner : IObserver<BindingValue<T>>
{
private readonly UntypedBindingSubjectAdapter<T> _owner;
public Inner(UntypedBindingSubjectAdapter<T> owner) => _owner = owner;
public void OnCompleted() => _owner.PublishCompleted();
public void OnError(Exception error) => _owner.PublishError(error);
public void OnNext(BindingValue<T> value) => _owner.PublishNext(value.ToUntyped());
}
}
}

5
src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimationInstance.cs

@ -128,8 +128,11 @@ namespace Avalonia.Rendering.Composition.Animations
left = kf;
right = _keyFrames[c + 1];
break;
}
else if (c == 0)
return ExpressionVariant.Create(GetKeyFrame(ref ctx, kf));
else
break;
}
var keyProgress = Math.Max(0, Math.Min(1, (iterationProgress - left.Key) / (right.Key - left.Key)));

20
src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs

@ -146,8 +146,18 @@ public class CompositingRenderer : IRendererWithCompositor
return result == 0 ? lhs.index.CompareTo(rhs.index) : result;
});
}
if (compositionChildren.Count == visualChildren.Count)
var childVisual = v.ChildCompositionVisual;
// Check if the current visual somehow got migrated to another compositor
if (childVisual != null && childVisual.Compositor != v.CompositionVisual.Compositor)
childVisual = null;
var expectedCount = visualChildren.Count;
if (childVisual != null)
expectedCount++;
if (compositionChildren.Count == expectedCount)
{
bool mismatch = false;
if (sortedChildren != null)
@ -167,6 +177,9 @@ public class CompositingRenderer : IRendererWithCompositor
break;
}
if (childVisual != null &&
!ReferenceEquals(compositionChildren[compositionChildren.Count - 1], childVisual))
mismatch = true;
if (!mismatch)
{
@ -193,6 +206,9 @@ public class CompositingRenderer : IRendererWithCompositor
if (compositionChild != null)
compositionChildren.Add(compositionChild);
}
if (childVisual != null)
compositionChildren.Add(childVisual);
}
private void UpdateCore()

3
src/Avalonia.Base/Rendering/Composition/Compositor.Factories.cs

@ -29,4 +29,7 @@ public partial class Compositor
public ImplicitAnimationCollection CreateImplicitAnimationCollection() => new ImplicitAnimationCollection(this);
public CompositionAnimationGroup CreateAnimationGroup() => new CompositionAnimationGroup(this);
public CompositionSolidColorVisual CreateSolidColorVisual() =>
new(this, new ServerCompositionSolidColorVisual(Server));
}

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

Loading…
Cancel
Save