Browse Source

Merge branch 'master' into readonly-struct

pull/1308/head
Steven Kirk 8 years ago
committed by GitHub
parent
commit
c4e5805da4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      .github/PULL_REQUEST_TEMPLATE.md
  2. 65
      Avalonia.sln
  3. 2
      build/Base.props
  4. 11
      build/EmbedXaml.props
  5. 1
      build/Rx.props
  6. 13
      build/SampleApp.props
  7. 13
      build/SharedVersion.props
  8. 5
      build/System.Drawing.Common.props
  9. 7
      packages.cake
  10. 2
      parameters.cake
  11. 38
      readme.md
  12. 174
      samples/BindingTest/BindingTest.csproj
  13. 15
      samples/BindingTest/MainWindow.xaml
  14. 36
      samples/BindingTest/Properties/AssemblyInfo.cs
  15. 143
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  16. 1
      samples/ControlCatalog.Desktop/Program.cs
  17. 36
      samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs
  18. 2
      samples/ControlCatalog.NetCore/Program.cs
  19. 5
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  20. 194
      samples/ControlCatalog/ControlCatalog.csproj
  21. 4
      samples/ControlCatalog/DecoratedWindow.xaml
  22. 10
      samples/ControlCatalog/MainView.xaml
  23. 2
      samples/ControlCatalog/MainWindow.xaml
  24. 59
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  25. 143
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  26. 24
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  27. 54
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  28. 19
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  29. 71
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  30. 80
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  31. 94
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  32. 36
      samples/ControlCatalog/Properties/AssemblyInfo.cs
  33. 3
      samples/Directory.Build.props
  34. 6
      samples/RenderTest/MainWindow.xaml
  35. 36
      samples/RenderTest/Properties/AssemblyInfo.cs
  36. 205
      samples/RenderTest/RenderTest.csproj
  37. 4
      samples/VirtualizationTest/MainWindow.xaml
  38. 36
      samples/VirtualizationTest/Properties/AssemblyInfo.cs
  39. 170
      samples/VirtualizationTest/VirtualizationTest.csproj
  40. 4
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  41. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  42. 28
      src/Avalonia.Animation/Avalonia.Animation.csproj
  43. 6
      src/Avalonia.Animation/Properties/AssemblyInfo.cs
  44. 9
      src/Avalonia.Base/AttachedProperty.cs
  45. 29
      src/Avalonia.Base/Avalonia.Base.csproj
  46. 63
      src/Avalonia.Base/AvaloniaObject.cs
  47. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  48. 283
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  49. 4
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  50. 26
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  51. 127
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  52. 3
      src/Avalonia.Base/DirectProperty.cs
  53. 7
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  54. 16
      src/Avalonia.Base/Platform/IAssetLoader.cs
  55. 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  56. 2
      src/Avalonia.Base/Threading/Dispatcher.cs
  57. 219
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  58. 9
      src/Avalonia.Controls/Application.cs
  59. 2730
      src/Avalonia.Controls/AutoCompleteBox.cs
  60. 28
      src/Avalonia.Controls/Avalonia.Controls.csproj
  61. 51
      src/Avalonia.Controls/Border.cs
  62. 2
      src/Avalonia.Controls/Button.cs
  63. 263
      src/Avalonia.Controls/ButtonSpinner.cs
  64. 2
      src/Avalonia.Controls/ColumnDefinitions.cs
  65. 7
      src/Avalonia.Controls/ContextMenu.cs
  66. 1
      src/Avalonia.Controls/DropDown.cs
  67. 822
      src/Avalonia.Controls/Grid.cs
  68. 19
      src/Avalonia.Controls/GridLength.cs
  69. 12
      src/Avalonia.Controls/ItemsControl.cs
  70. 6
      src/Avalonia.Controls/LayoutTransformControl.cs
  71. 35
      src/Avalonia.Controls/MenuItem.cs
  72. 998
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  73. 16
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  74. 10
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  75. 11
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  76. 210
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  77. 153
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  78. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  79. 66
      src/Avalonia.Controls/Primitives/Popup.cs
  80. 26
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  81. 8
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  82. 26
      src/Avalonia.Controls/ProgressBar.cs
  83. 1
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  84. 4
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  85. 29
      src/Avalonia.Controls/RepeatButton.cs
  86. 2
      src/Avalonia.Controls/RowDefinitions.cs
  87. 2
      src/Avalonia.Controls/Screens.cs
  88. 174
      src/Avalonia.Controls/Spinner.cs
  89. 4
      src/Avalonia.Controls/TextBlock.cs
  90. 37
      src/Avalonia.Controls/TextBox.cs
  91. 4
      src/Avalonia.Controls/UserControl.cs
  92. 279
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  93. 700
      src/Avalonia.Controls/Utils/GridLayout.cs
  94. 64
      src/Avalonia.Controls/Utils/ISelectionAdapter.cs
  95. 342
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  96. 8
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  97. 101
      src/Avalonia.Controls/Window.cs
  98. 24
      src/Avalonia.Controls/WindowBase.cs
  99. 23
      src/Avalonia.Controls/WindowStartupLocation.cs
  100. 8
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs

17
.github/PULL_REQUEST_TEMPLATE.md

@ -0,0 +1,17 @@
This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
- What does the pull request do?
- What is the current behavior?
- What is the updated/expected behavior with this PR?
- How was the solution implemented (if it's not obvious)?
Checklist:
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
If the pull request fixes issue(s) list them like this:
Fixes #123
Fixes #456

65
Avalonia.sln

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2024
VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Layout", "src\Aval
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Windows", "Windows", "{B39A8919-9F95-48FE-AD7B-76E08B509888}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32", "src\Windows\Avalonia.Win32\Avalonia.Win32.csproj", "{811A76CF-1CF6-440F-963B-BBE31BD72A82}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
EndProject
@ -76,7 +76,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup", "src\Mark
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.UnitTests", "tests\Avalonia.Markup.UnitTests\Avalonia.Markup.UnitTests.csproj", "{8EF392D5-1416-45AA-9956-7CBBC3229E8A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BindingTest", "samples\BindingTest\BindingTest.csproj", "{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "RenderHelpers", "src\Shared\RenderHelpers\RenderHelpers.shproj", "{3C4C0CB4-0C0F-4450-A37B-148C84FF905F}"
EndProject
@ -114,7 +114,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Interop", "Interop", "{A0CC0258-D18C-4AB3-854F-7101680FC3F9}"
EndProject
@ -122,14 +122,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.Android", "samples\ControlCatalog.Android\ControlCatalog.Android.csproj", "{29132311-1848-4FD6-AE0C-4FF841151BD3}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.Win32.Shared", "src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.shproj", "{9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Win32.NetStandard", "src\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj", "{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetCoreRuntime", "src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj", "{7863EA94-F0FB-4386-BF8C-E5BFA761560A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia", "src\Skia\Avalonia.Skia\Avalonia.Skia.csproj", "{7D2D3083-71DD-4CC9-8907-39A0D86FB322}"
@ -150,11 +146,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
build\NetCore.props = build\NetCore.props
build\NetFX.props = build\NetFX.props
build\ReactiveUI.props = build\ReactiveUI.props
build\Rx.props = build\Rx.props
build\SampleApp.props = build\SampleApp.props
build\Serilog.props = build\Serilog.props
build\SharpDX.props = build\SharpDX.props
build\SkiaSharp.Desktop.props = build\SkiaSharp.Desktop.props
build\SkiaSharp.props = build\SkiaSharp.props
build\Splat.props = build\Splat.props
build\Sprache.props = build\Sprache.props
@ -196,14 +193,11 @@ Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 4
src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{40759a76-d0f2-464e-8000-6ff0f5c4bd7c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7863ea94-f0fb-4386-bf8c-e5bfa761560a}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 4
src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Windows\Avalonia.Win32\Avalonia.Win32.Shared.projitems*{9defc6b7-845b-4d8f-afc0-d32bf0032b8c}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
EndGlobalSection
@ -369,6 +363,7 @@ Global
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.ActiveCfg = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Debug|x86.Build.0 = Debug|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -378,6 +373,7 @@ Global
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.ActiveCfg = Release|Any CPU
{811A76CF-1CF6-440F-963B-BBE31BD72A82}.Release|x86.Build.0 = Release|Any CPU
{3E908F67-5543-4879-A1DC-08EACE79B3CD}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
@ -1994,46 +1990,6 @@ Global
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.ActiveCfg = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Build.0 = Release|Any CPU
{29132311-1848-4FD6-AE0C-4FF841151BD3}.Release|x86.Deploy.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|NetCoreOnly.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|Any CPU.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhone.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|NetCoreOnly.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.AppStore|x86.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhone.Build.0 = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.ActiveCfg = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|NetCoreOnly.Build.0 = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.ActiveCfg = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Debug|x86.Build.0 = Debug|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|Any CPU.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhone.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|NetCoreOnly.Build.0 = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.ActiveCfg = Release|Any CPU
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C}.Release|x86.Build.0 = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{7863EA94-F0FB-4386-BF8C-E5BFA761560A}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
@ -2623,8 +2579,6 @@ Global
{C7A69145-60B6-4882-97D6-A3921DD43978} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{F1FDC5B0-4654-416F-AE69-E3E9BBD87801} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{29132311-1848-4FD6-AE0C-4FF841151BD3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{9DEFC6B7-845B-4D8F-AFC0-D32BF0032B8C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{40759A76-D0F2-464E-8000-6FF0F5C4BD7C} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{7D2D3083-71DD-4CC9-8907-39A0D86FB322} = {3743B0F2-CC41-4F14-A8C8-267F579BF91E}
{BB1F7BB5-6AD4-4776-94D9-C09D0A972658} = {B9894058-278A-46B5-B6ED-AD613FCC03B3}
{39D7B147-1A5B-47C2-9D01-21FB7C47C4B3} = {9B9E3891-2366-4253-A952-D08BCEB71098}
@ -2637,6 +2591,7 @@ Global
{E2999E4A-9086-401F-898C-AEB0AD38E676} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{050CC912-FF49-4A8B-B534-9544017446DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{F40FC0A2-1BC3-401C-BFC1-928EC4D4A9CE} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
{4ADA61C8-D191-428D-9066-EF4F0D86520F} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution

2
build/Base.props

@ -2,4 +2,4 @@
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
</ItemGroup>
</Project>
</Project>

11
build/EmbedXaml.props

@ -0,0 +1,11 @@
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
</Project>

1
build/Rx.props

@ -5,6 +5,5 @@
<PackageReference Include="System.Reactive.Interfaces" Version="3.1.1" />
<PackageReference Include="System.Reactive.Linq" Version="3.1.1" />
<PackageReference Include="System.Reactive.PlatformServices" Version="3.1.1" />
<PackageReference Condition="$(TargetFramework.StartsWith('net4'))" Include="System.Reactive.Windows.Threading" Version="3.1.1" />
</ItemGroup>
</Project>

13
build/SampleApp.props

@ -0,0 +1,13 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(TargetFramework)'=='net461'" >
<OutputType>WinExe</OutputType>
</PropertyGroup>
<!-- Should be a Condition="'$(TargetFramework)'=='net461'" here but that doesn't work due
to https://github.com/dotnet/sdk/issues/1227 -->
<ItemGroup>
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" />
</ItemGroup>
<Import Condition="'$(TargetFramework)'=='net461'" Project="SharpDX.props" />
</Project>

13
build/SharedVersion.props

@ -0,0 +1,13 @@
<Project DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.6.2</Version>
<Copyright>Copyright 2016 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
</Project>

5
build/System.Drawing.Common.props

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

7
packages.cake

@ -370,14 +370,13 @@ public class Packages
new NuGetPackSettings()
{
Id = "Avalonia.Win32",
Dependencies = new []
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
},
}.Deps(new string[]{null}, "System.Drawing.Common"),
Files = new []
{
new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/Avalonia.Win32.dll", Target = "lib/net45" },
new NuSpecContent { Source = "Avalonia.Win32.NetStandard/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
new NuSpecContent { Source = "Avalonia.Win32/bin/" + parameters.DirSuffix + "/netstandard2.0/Avalonia.Win32.dll", Target = "lib/netstandard2.0" }
},
BasePath = context.Directory("./src/Windows"),
OutputDirectory = parameters.NugetRoot

2
parameters.cake

@ -97,7 +97,7 @@ public class Parameters
else
{
// Use AssemblyVersion with Build as version
Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-alpha";
Version += "-build" + context.EnvironmentVariable("APPVEYOR_BUILD_NUMBER") + "-beta";
}
}

38
readme.md

@ -2,9 +2,9 @@
# Avalonia
| Gitter Chat | Windows Build Status | Linux/Mac Build Status |
|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) |
| Gitter Chat | Windows Build Status | Linux/Mac Build Status | Open Collective |
|---|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build status](https://ci.appveyor.com/api/projects/status/hubk3k0w9idyibfg/branch/master?svg=true)](https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master) | [![Build Status](https://travis-ci.org/AvaloniaUI/Avalonia.svg?branch=master)](https://travis-ci.org/AvaloniaUI/Avalonia) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
## About
@ -35,7 +35,7 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
## Documentation
As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/guides/quickstart) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
As mentioned above, Avalonia is still in beta and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/docs/quickstart/) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
@ -48,3 +48,33 @@ See the [build instructions here](http://avaloniaui.net/contributing/build).
## Contributing
Please read the [contribution guidelines](http://avaloniaui.net/contributing/contributing) before submitting a pull request.
### Contributors
This project exists thanks to all the people who contribute. [[Contribute](http://avaloniaui.net/contributing/contributing)].
<a href="graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]
<a href="https://opencollective.com/Avalonia#backers" target="_blank"><img src="https://opencollective.com/Avalonia/backers.svg?width=890"></a>
### Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/Avalonia#sponsor)]
<a href="https://opencollective.com/Avalonia/sponsor/0/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/1/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/2/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/3/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/4/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/5/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/6/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/7/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/8/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/Avalonia/sponsor/9/website" target="_blank"><img src="https://opencollective.com/Avalonia/sponsor/9/avatar.svg"></a>

174
samples/BindingTest/BindingTest.csproj

@ -1,155 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{08B3E6B9-1CD5-443C-9F61-6D49D1C5F162}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>BindingTest</RootNamespace>
<AssemblyName>BindingTest</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="App.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TestItemView.xaml.cs">
<DependentUpon>TestItemView.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\DataAnnotationsErrorViewModel.cs" />
<Compile Include="ViewModels\IndeiErrorViewModel.cs" />
<Compile Include="ViewModels\ExceptionErrorViewModel.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
<Compile Include="ViewModels\NestedCommandViewModel.cs" />
<Compile Include="ViewModels\TestItem.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<EmbeddedResource Include="MainWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="TestItemView.xaml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Avalonia.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
<Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
<Name>Avalonia.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Avalonia.ReactiveUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />

15
samples/BindingTest/MainWindow.xaml

@ -1,11 +1,18 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:vm="clr-namespace:BindingTest.ViewModels;assembly=BindingTest"
xmlns:local="clr-namespace:BindingTest;assembly=BindingTest">
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:vm="clr-namespace:BindingTest.ViewModels"
xmlns:local="clr-namespace:BindingTest"
Title="AvaloniaUI Bindings Test"
Width="800"
Height="600">
<Window.Styles>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="18"/>
</Style>
</Window.Styles>
<Window.Resources>
<vm:TestItem x:Key="SharedItem" StringValue="shared" />
</Window.Resources>
<TabControl>
<TabItem Header="Basic">
@ -40,6 +47,10 @@
<TextBlock FontSize="16" Text="Binding Sources"/>
<TextBox Watermark="Value of first TextBox" UseFloatingWatermark="True"
Text="{Binding #first.Text, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
<TextBox Watermark="Value of SharedItem.StringValue (duplicate)" UseFloatingWatermark="True"
Text="{Binding StringValue, Source={StaticResource SharedItem}, Mode=TwoWay}"/>
</StackPanel>
<StackPanel Margin="18" Gap="4" Width="200" HorizontalAlignment="Left">
<TextBlock FontSize="16" Text="Scheduler"/>

36
samples/BindingTest/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("BindingTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("BindingTest")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("08b3e6b9-1cd5-443c-9f61-6d49d1c5f162")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

143
samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj

@ -1,138 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{2B888490-D14A-4BCA-AB4B-48676FA93C9B}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ControlCatalog.Desktop</RootNamespace>
<AssemblyName>ControlCatalog.Desktop</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<OutputType>Exe</OutputType>
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>x86</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Avalonia.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj">
<Project>{bb1f7bb5-6ad4-4776-94d9-c09d0a972658}</Project>
<Name>Avalonia.Gtk3</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3E53A01A-B331-47F3-B828-4A5717E77A24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417E941-21BC-467B-A771-0DE389353CE6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{D2221C82-4A25-4583-9B43-D791E3F6820C}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
<Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
<Name>Avalonia.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{F1BAA01A-F176-4C6A-B39D-5B40BB1B148F}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
<Project>{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{3E908F67-5543-4879-A1DC-08EACE79B3CD}</Project>
<Name>Avalonia.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{811A76CF-1CF6-440F-963B-BBE31BD72A82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj">
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\SkiaSharp.props" />
</Project>

1
samples/ControlCatalog.Desktop/Program.cs

@ -10,6 +10,7 @@ namespace ControlCatalog
{
internal class Program
{
[STAThread]
static void Main(string[] args)
{
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args

36
samples/ControlCatalog.Desktop/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ControlCatalog.Desktop")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ControlCatalog.Desktop")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2b888490-d14a-4bca-ab4b-48676fa93c9b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

2
samples/ControlCatalog.NetCore/Program.cs

@ -9,8 +9,10 @@ namespace ControlCatalog.NetCore
{
static class Program
{
static void Main(string[] args)
{
Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");

5
samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj

@ -170,15 +170,14 @@
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{D2D3083-71DD-4CC9-8907-39A0D86FB322}</Project>
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>
<IsAppExtension>false</IsAppExtension>
<IsWatchApp>false</IsWatchApp>
</ProjectReference>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj">
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
</ItemGroup>
<Import Project="..\..\build\Sprache.props" />
<Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project>

194
samples/ControlCatalog/ControlCatalog.csproj

@ -1,183 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnableDefaultCompileItems>False</EnableDefaultCompileItems>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<None Remove="Pages\ContextMenuPage.xaml" />
</ItemGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework is automatically included -->
<EmbeddedResource Include="App.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="MainView.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="DecoratedWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DialogsPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\BorderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CalendarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CanvasPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CarouselPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\CheckBoxPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ContextMenuPage.xaml" />
<EmbeddedResource Include="Pages\DropDownPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DatePickerPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ExpanderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ImagePage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\LayoutTransformControlPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\MenuPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ProgressBarPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\RadioButtonPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\SliderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\TextBoxPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\ToolTipPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainView.xaml.cs">
<DependentUpon>MainView.xaml</DependentUpon>
</Compile>
<Compile Include="DecoratedWindow.xaml.cs">
<DependentUpon>DecoratedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DialogsPage.xaml.cs">
<DependentUpon>DialogsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\BorderPage.xaml.cs">
<DependentUpon>BorderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ButtonPage.xaml.cs">
<DependentUpon>ButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CalendarPage.xaml.cs">
<DependentUpon>CalendarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CanvasPage.xaml.cs">
<DependentUpon>CanvasPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CarouselPage.xaml.cs">
<DependentUpon>CarouselPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ContextMenuPage.xaml.cs">
<DependentUpon>ContextMenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\CheckBoxPage.xaml.cs">
<DependentUpon>CheckBoxPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DropDownPage.xaml.cs">
<DependentUpon>DropDownPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DatePickerPage.xaml.cs">
<DependentUpon>DatePickerPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ExpanderPage.xaml.cs">
<DependentUpon>ExpanderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ImagePage.xaml.cs">
<DependentUpon>ImagePage.xaml</DependentUpon>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</DependentUpon>
</Compile>
<Compile Include="Pages\LayoutTransformControlPage.xaml.cs">
<DependentUpon>LayoutTransformControlPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\MenuPage.xaml.cs">
<DependentUpon>MenuPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ProgressBarPage.xaml.cs">
<DependentUpon>ProgressBarPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\RadioButtonPage.xaml.cs">
<DependentUpon>RadioButtonPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\SliderPage.xaml.cs">
<DependentUpon>SliderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\TreeViewPage.xaml.cs">
<DependentUpon>TreeViewPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\TextBoxPage.xaml.cs">
<DependentUpon>TextBoxPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ToolTipPage.xaml.cs">
<DependentUpon>ToolTipPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ScreenPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\delicate-arch-896885_640.jpg" />
<EmbeddedResource Include="Assets\github_icon.png" />
<EmbeddedResource Include="Assets\hirsch-899118_640.jpg" />
<EmbeddedResource Include="Assets\maple-leaf-888807_640.jpg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="SideBar.xaml">
<EmbeddedResource Include="**\*.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Assets\*" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
@ -194,20 +28,6 @@
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Assets\test_icon.ico" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\TreeViewPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>
</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="..\..\build\Serilog.props" />
</Project>

4
samples/ControlCatalog/DecoratedWindow.xaml

@ -1,7 +1,7 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Title="Avalonia Control Gallery"
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog" HasSystemDecorations="False">
Icon="resm:ControlCatalog.Assets.test_icon.ico"
xmlns:local="clr-namespace:ControlCatalog" HasSystemDecorations="False">
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">

10
samples/ControlCatalog/MainView.xaml

@ -1,23 +1,27 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog"
xmlns:pages="clr-namespace:ControlCatalog.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Classes="sidebar" Name="Sidebar">
<TabControl.Transition>
<CrossFade Duration="0.25"/>
</TabControl.Transition>
<TabItem Header="AutoCompleteBox"><pages:AutoCompleteBoxPage/></TabItem>
<TabItem Header="Border"><pages:BorderPage/></TabItem>
<TabItem Header="Button"><pages:ButtonPage/></TabItem>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="ButtonSpinner"><pages:ButtonSpinnerPage/></TabItem>
<TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
<TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
<TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
@ -25,4 +29,4 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
</TabControl>
</UserControl>
</UserControl>

2
samples/ControlCatalog/MainWindow.xaml

@ -1,6 +1,6 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Title="Avalonia Control Gallery"
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog">
xmlns:local="clr-namespace:ControlCatalog">
<local:MainView/>
</Window>

59
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml

@ -0,0 +1,59 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">AutoCompleteBox</TextBlock>
<TextBlock Classes="h2">A control into which the user can input text</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="8">
<StackPanel Orientation="Vertical">
<TextBlock Text="MinimumPrefixLength: 1"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPrefixLength="1"/>
<TextBlock Text="MinimumPrefixLength: 3"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPrefixLength="3"/>
<TextBlock Text="MinimumPopulateDelay: 1 Second"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MinimumPopulateDelay="1"/>
<TextBlock Text="MaxDropDownHeight: 60"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
MaxDropDownHeight="60"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
Watermark="Watermark"/>
<TextBlock Text="Disabled"/>
<AutoCompleteBox Width="200"
IsEnabled="False"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock Text="ValueMemeberSelector"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
ValueMemberSelector="Capital"/>
<TextBlock Text="ValueMemberBinding"/>
<AutoCompleteBox Width="200"
Margin="0,0,0,8"
ValueMemberBinding="{Binding Capital}"/>
<TextBlock Text="Multi-Binding"/>
<AutoCompleteBox Name="MultiBindingBox"
Width="200"
Margin="0,0,0,8"
FilterMode="Contains"/>
<TextBlock Text="Async Populate"/>
<AutoCompleteBox Name="AsyncBox"
Width="200"
Margin="0,0,0,8"
FilterMode="None"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

143
samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs

@ -0,0 +1,143 @@
using Avalonia.Controls;
using Avalonia.LogicalTree;
using Avalonia.Markup;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace ControlCatalog.Pages
{
public class AutoCompleteBoxPage : UserControl
{
public class StateData
{
public string Name { get; private set; }
public string Abbreviation { get; private set; }
public string Capital { get; private set; }
public StateData(string name, string abbreviatoin, string capital)
{
Name = name;
Abbreviation = abbreviatoin;
Capital = capital;
}
public override string ToString()
{
return Name;
}
}
private StateData[] BuildAllStates()
{
return new StateData[]
{
new StateData("Alabama","AL","Montgomery"),
new StateData("Alaska","AK","Juneau"),
new StateData("Arizona","AZ","Phoenix"),
new StateData("Arkansas","AR","Little Rock"),
new StateData("California","CA","Sacramento"),
new StateData("Colorado","CO","Denver"),
new StateData("Connecticut","CT","Hartford"),
new StateData("Delaware","DE","Dover"),
new StateData("Florida","FL","Tallahassee"),
new StateData("Georgia","GA","Atlanta"),
new StateData("Hawaii","HI","Honolulu"),
new StateData("Idaho","ID","Boise"),
new StateData("Illinois","IL","Springfield"),
new StateData("Indiana","IN","Indianapolis"),
new StateData("Iowa","IA","Des Moines"),
new StateData("Kansas","KS","Topeka"),
new StateData("Kentucky","KY","Frankfort"),
new StateData("Louisiana","LA","Baton Rouge"),
new StateData("Maine","ME","Augusta"),
new StateData("Maryland","MD","Annapolis"),
new StateData("Massachusetts","MA","Boston"),
new StateData("Michigan","MI","Lansing"),
new StateData("Minnesota","MN","St. Paul"),
new StateData("Mississippi","MS","Jackson"),
new StateData("Missouri","MO","Jefferson City"),
new StateData("Montana","MT","Helena"),
new StateData("Nebraska","NE","Lincoln"),
new StateData("Nevada","NV","Carson City"),
new StateData("New Hampshire","NH","Concord"),
new StateData("New Jersey","NJ","Trenton"),
new StateData("New Mexico","NM","Santa Fe"),
new StateData("New York","NY","Albany"),
new StateData("North Carolina","NC","Raleigh"),
new StateData("North Dakota","ND","Bismarck"),
new StateData("Ohio","OH","Columbus"),
new StateData("Oklahoma","OK","Oklahoma City"),
new StateData("Oregon","OR","Salem"),
new StateData("Pennsylvania","PA","Harrisburg"),
new StateData("Rhode Island","RI","Providence"),
new StateData("South Carolina","SC","Columbia"),
new StateData("South Dakota","SD","Pierre"),
new StateData("Tennessee","TN","Nashville"),
new StateData("Texas","TX","Austin"),
new StateData("Utah","UT","Salt Lake City"),
new StateData("Vermont","VT","Montpelier"),
new StateData("Virginia","VA","Richmond"),
new StateData("Washington","WA","Olympia"),
new StateData("West Virginia","WV","Charleston"),
new StateData("Wisconsin","WI","Madison"),
new StateData("Wyoming","WY","Cheyenne"),
};
}
public StateData[] States { get; private set; }
public AutoCompleteBoxPage()
{
this.InitializeComponent();
States = BuildAllStates();
foreach (AutoCompleteBox box in GetAllAutoCompleteBox())
{
box.Items = States;
}
var converter = new FuncMultiValueConverter<string, string>(parts =>
{
return String.Format("{0} ({1})", parts.ToArray());
});
var binding = new MultiBinding { Converter = converter };
binding.Bindings.Add(new Binding("Name"));
binding.Bindings.Add(new Binding("Abbreviation"));
var multibindingBox = this.FindControl<AutoCompleteBox>("MultiBindingBox");
multibindingBox.ValueMemberBinding = binding;
var asyncBox = this.FindControl<AutoCompleteBox>("AsyncBox");
asyncBox.AsyncPopulator = PopulateAsync;
}
private IEnumerable<AutoCompleteBox> GetAllAutoCompleteBox()
{
return
this.GetLogicalDescendants()
.OfType<AutoCompleteBox>();
}
private bool StringContains(string str, string query)
{
return str.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
}
private async Task<IEnumerable<object>> PopulateAsync(string searchText, CancellationToken cancellationToken)
{
await Task.Delay(TimeSpan.FromSeconds(1.5), cancellationToken);
return
States.Where(data => StringContains(data.Name, searchText) || StringContains(data.Capital, searchText))
.ToList();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

24
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml

@ -0,0 +1,24 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">ButtonSpinner</TextBlock>
<TextBlock Classes="h2">The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.</TextBlock>
<StackPanel Orientation="Vertical" Gap="8" Width="200" Margin="0,20,0,0">
<CheckBox Name="allowSpinCheck" IsChecked="True">AllowSpin</CheckBox>
<CheckBox Name="showSpinCheck" IsChecked="True">ShowButtonSpinner</CheckBox>
<ButtonSpinner Spin="OnSpin" Height="30"
AllowSpin="{Binding #allowSpinCheck.IsChecked}"
ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
</ButtonSpinner>
<ButtonSpinner Spin="OnSpin" Height="30" ButtonSpinnerLocation="Left"
AllowSpin="{Binding #allowSpinCheck.IsChecked}"
ShowButtonSpinner="{Binding #showSpinCheck.IsChecked}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="Everest"/>
</ButtonSpinner>
</StackPanel>
</StackPanel>
</UserControl>

54
samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs

@ -0,0 +1,54 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace ControlCatalog.Pages
{
public class ButtonSpinnerPage : UserControl
{
public ButtonSpinnerPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnSpin(object sender, SpinEventArgs e)
{
var spinner = (ButtonSpinner)sender;
var txtBox = (TextBlock)spinner.Content;
int value = Array.IndexOf(_mountains, txtBox.Text);
if (e.Direction == SpinDirection.Increase)
value++;
else
value--;
if (value < 0)
value = _mountains.Length - 1;
else if (value >= _mountains.Length)
value = 0;
txtBox.Text = _mountains[value];
}
private readonly string[] _mountains = new[]
{
"Everest",
"K2 (Mount Godwin Austen)",
"Kangchenjunga",
"Lhotse",
"Makalu",
"Cho Oyu",
"Dhaulagiri",
"Manaslu",
"Nanga Parbat",
"Annapurna"
};
}
}

19
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -0,0 +1,19 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">Drag+Drop</TextBlock>
<TextBlock Classes="h2">Example of Drag+Drop capabilities</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
<TextBlock Name="DragState">Drag Me</TextBlock>
</Border>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</UserControl>

71
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -0,0 +1,71 @@
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
using System.Text;
namespace ControlCatalog.Pages
{
public class DragAndDropPage : UserControl
{
private TextBlock _DropState;
private TextBlock _DragState;
private Border _DragMe;
private int DragCount = 0;
public DragAndDropPage()
{
this.InitializeComponent();
_DragMe.PointerPressed += DoDrag;
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
}
private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
DataObject dragData = new DataObject();
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
var result = await DragDrop.DoDragDrop(dragData, DragDropEffects.Copy);
switch(result)
{
case DragDropEffects.Copy:
_DragState.Text = "The text was copied"; break;
case DragDropEffects.Link:
_DragState.Text = "The text was linked"; break;
case DragDropEffects.None:
_DragState.Text = "The drag operation was canceled"; break;
}
}
private void DragOver(object sender, DragEventArgs e)
{
// Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
// Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
e.DragEffects = DragDropEffects.None;
}
private void Drop(object sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_DropState = this.Find<TextBlock>("DropState");
_DragState = this.Find<TextBlock>("DragState");
_DragMe = this.Find<Border>("DragMe");
}
}
}

80
samples/ControlCatalog/Pages/NumericUpDownPage.xaml

@ -0,0 +1,80 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Margin="2" Classes="h1">Numeric up-down control</TextBlock>
<TextBlock Margin="2" Classes="h2" TextWrapping="Wrap">Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.</TextBlock>
<TextBlock Margin="2,5,2,2" FontSize="14" FontWeight="Bold">Features:</TextBlock>
<Grid Margin="2" ColumnDefinitions="Auto,Auto,Auto,Auto" RowDefinitions="Auto,Auto">
<Grid Grid.Row="0" Grid.Column="0" ColumnDefinitions="Auto, Auto" RowDefinitions="35,35,35,35,35">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">ShowButtonSpinner:</TextBlock>
<CheckBox Grid.Row="0" Grid.Column="1" IsChecked="{Binding #upDown.ShowButtonSpinner}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">IsReadOnly:</TextBlock>
<CheckBox Grid.Row="1" Grid.Column="1" IsChecked="{Binding #upDown.IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">AllowSpin:</TextBlock>
<CheckBox Grid.Row="2" Grid.Column="1" IsChecked="{Binding #upDown.AllowSpin}" IsEnabled="{Binding #upDown.!IsReadOnly}" VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">ClipValueToMinMax:</TextBlock>
<CheckBox Grid.Row="3" Grid.Column="1" IsChecked="{Binding #upDown.ClipValueToMinMax}" VerticalAlignment="Center" Margin="2"/>
</Grid>
<Grid Grid.Row="0" Grid.Column="1" Margin="10,2,2,2" ColumnDefinitions="Auto, 120" RowDefinitions="35,35,35,35,35">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="2">FormatString:</TextBlock>
<DropDown Grid.Row="0" Grid.Column="1" Items="{Binding Formats}" SelectedItem="{Binding SelectedFormat}"
VerticalAlignment="Center" Margin="2">
<DropDown.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Gap="2">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</DropDown.ItemTemplate>
</DropDown>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="2">ButtonSpinnerLocation:</TextBlock>
<DropDown Grid.Row="1" Grid.Column="1" Items="{Binding SpinnerLocations}" SelectedItem="{Binding #upDown.ButtonSpinnerLocation}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="2">CultureInfo:</TextBlock>
<DropDown Grid.Row="2" Grid.Column="1" Items="{Binding Cultures}" SelectedItem="{Binding #upDown.CultureInfo}"
VerticalAlignment="Center" Margin="2"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="2">Watermark:</TextBlock>
<TextBox Grid.Row="3" Grid.Column="1" Text="{Binding #upDown.Watermark}" VerticalAlignment="Center" Margin="2" />
<TextBlock Grid.Row="4" Grid.Column="0" VerticalAlignment="Center" Margin="2">Text:</TextBlock>
<TextBox Grid.Row="4" Grid.Column="1" Text="{Binding #upDown.Text}" VerticalAlignment="Center" Margin="2" />
</Grid>
<Grid Grid.Row="0" Grid.Column="2" Margin="10,2,2,2" RowDefinitions="35,35,35,35,35" ColumnDefinitions="Auto, 120">
<TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Minimum:</TextBlock>
<NumericUpDown Grid.Row="0" Grid.Column="1" Value="{Binding #upDown.Minimum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Maximum:</TextBlock>
<NumericUpDown Grid.Row="1" Grid.Column="1" Value="{Binding #upDown.Maximum}"
CultureInfo="{Binding #upDown.CultureInfo}" VerticalAlignment="Center" Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Increment:</TextBlock>
<NumericUpDown Grid.Row="2" Grid.Column="1" Value="{Binding #upDown.Increment}" VerticalAlignment="Center"
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
<TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="10,2,2,2">Value:</TextBlock>
<NumericUpDown Grid.Row="3" Grid.Column="1" Value="{Binding #upDown.Value}" VerticalAlignment="Center"
Height="25" Margin="2" Width="70" HorizontalAlignment="Center"/>
</Grid>
</Grid>
<StackPanel Margin="2,10,2,2" Orientation="Horizontal" Gap="10">
<TextBlock FontSize="14" FontWeight="Bold" VerticalAlignment="Center">Usage of NumericUpDown:</TextBlock>
<NumericUpDown Name="upDown" Minimum="0" Maximum="10" Increment="0.5"
CultureInfo="en-US" VerticalAlignment="Center" Height="25" Width="100"
Watermark="Enter text" FormatString="{Binding SelectedFormat.Value}"/>
</StackPanel>
</StackPanel>
</UserControl>

94
samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Markup.Xaml;
using ReactiveUI;
namespace ControlCatalog.Pages
{
public class NumericUpDownPage : UserControl
{
public NumericUpDownPage()
{
this.InitializeComponent();
var viewModel = new NumbersPageViewModel();
DataContext = viewModel;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
public class NumbersPageViewModel : ReactiveObject
{
private IList<FormatObject> _formats;
private FormatObject _selectedFormat;
private IList<Location> _spinnerLocations;
public NumbersPageViewModel()
{
SelectedFormat = Formats.FirstOrDefault();
}
public IList<FormatObject> Formats
{
get
{
return _formats ?? (_formats = new List<FormatObject>()
{
new FormatObject() {Name = "Currency", Value = "C2"},
new FormatObject() {Name = "Fixed point", Value = "F2"},
new FormatObject() {Name = "General", Value = "G"},
new FormatObject() {Name = "Number", Value = "N"},
new FormatObject() {Name = "Percent", Value = "P"},
new FormatObject() {Name = "Degrees", Value = "{0:N2} °"},
});
}
}
public IList<Location> SpinnerLocations
{
get
{
if (_spinnerLocations == null)
{
_spinnerLocations = new List<Location>();
foreach (Location value in Enum.GetValues(typeof(Location)))
{
_spinnerLocations.Add(value);
}
}
return _spinnerLocations ;
}
}
public IList<CultureInfo> Cultures { get; } = new List<CultureInfo>()
{
new CultureInfo("en-US"),
new CultureInfo("en-GB"),
new CultureInfo("fr-FR"),
new CultureInfo("ar-DZ"),
new CultureInfo("zh-CN"),
new CultureInfo("cs-CZ")
};
public FormatObject SelectedFormat
{
get { return _selectedFormat; }
set { this.RaiseAndSetIfChanged(ref _selectedFormat, value); }
}
}
public class FormatObject
{
public string Value { get; set; }
public string Name { get; set; }
}
}

36
samples/ControlCatalog/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ControlCatalog")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ControlCatalog")]
[assembly: AssemblyCopyright("Copyright © 2015")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("61bec86c-f307-4295-b5b8-9428610d7d55")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

3
samples/Directory.Build.props

@ -0,0 +1,3 @@
<Project>
<Import Project="..\build\SharedVersion.props" />
</Project>

6
samples/RenderTest/MainWindow.xaml

@ -1,6 +1,8 @@
<Window xmlns="https://github.com/avaloniaui"
Title="Avalonia Render Test"
xmlns:pages="clr-namespace:RenderTest.Pages;assembly=RenderTest">
Title="AvaloniaUI Rendering Test"
xmlns:pages="clr-namespace:RenderTest.Pages"
Width="800"
Height="600">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Rendering">

36
samples/RenderTest/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RenderTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RenderTest")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f1fdc5b0-4654-416f-ae69-e3e9bbd87801")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

205
samples/RenderTest/RenderTest.csproj

@ -1,184 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RenderTest</RootNamespace>
<AssemblyName>RenderTest</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DrawingPage.xaml.cs">
<DependentUpon>DrawingPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ClippingPage.xaml.cs">
<DependentUpon>ClippingPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\AnimationsPage.xaml.cs">
<DependentUpon>AnimationsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="ViewModels\MainWindowViewModel.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="App.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Avalonia.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4a1abb09-9047-4bd5-a4ad-a055e52c5ee0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
<Project>{b61b66a3-b82d-4875-8001-89d3394fe0c9}</Project>
<Name>Avalonia.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Avalonia.ReactiveUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Skia\Avalonia.Skia\Avalonia.Skia.csproj">
<Project>{7d2d3083-71dd-4cc9-8907-39a0d86fb322}</Project>
<Name>Avalonia.Skia</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MainWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="SideBar.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\AnimationsPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\ClippingPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Pages\DrawingPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
</ItemGroup>
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />

4
samples/VirtualizationTest/MainWindow.xaml

@ -1,5 +1,7 @@
<Window xmlns="https://github.com/avaloniaui"
Title="Avalonia Virtualization Test">
Title="AvaloniaUI Virtualization Test"
Width="800"
Height="600">
<DockPanel LastChildFill="True" Margin="16">
<StackPanel DockPanel.Dock="Right"
Margin="16 0 0 0"

36
samples/VirtualizationTest/Properties/AssemblyInfo.cs

@ -1,36 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("VirtualizationTest")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("VirtualizationTest")]
[assembly: AssemblyCopyright("Copyright © 2016")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fbcaf3d0-2808-4934-8e96-3f607594517b")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

170
samples/VirtualizationTest/VirtualizationTest.csproj

@ -1,151 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FBCAF3D0-2808-4934-8E96-3F607594517B}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>VirtualizationTest</RootNamespace>
<AssemblyName>VirtualizationTest</AssemblyName>
<TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModels\ItemViewModel.cs" />
<Compile Include="ViewModels\MainWindowViewModel.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
<Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
<Name>Avalonia.Base</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
<Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
<Name>Avalonia.DesignerSupport</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
<Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
<Name>Avalonia.Diagnostics</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj">
<Project>{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}</Project>
<Name>Avalonia.DotNetFrameworkRuntime</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
<Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
<Name>Avalonia.Input</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
<Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
<Name>Avalonia.Interactivity</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
<Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
<Name>Avalonia.Layout</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
<Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
<Name>Avalonia.Logging.Serilog</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
<Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
<Name>Avalonia.ReactiveUI</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj">
<Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
<Name>Avalonia.Visuals</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
<Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
<Name>Avalonia.Styling</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
<Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
<Name>Avalonia.Themes.Default</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
<Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
<Name>Avalonia.Markup.Xaml</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
<Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
<Name>Avalonia.Markup</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
<Name>Avalonia.Direct2D1</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" Condition="'$(Platform)'!='Mono'">
<Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
<Name>Avalonia.Win32</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="App.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MainWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='netcoreapp2.0'" Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
<ProjectReference Condition="'$(TargetFramework)'=='net461'" Include="..\..\src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\build\SampleApp.props" />
<Import Project="..\..\build\EmbedXaml.props" />
<Import Project="..\..\build\Serilog.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\ReactiveUI.props" />

4
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@ -17,7 +17,9 @@
<None Remove="MiniCube.fx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="MiniCube.fx" />
<EmbeddedResource Include="MiniCube.fx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />

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

@ -36,7 +36,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_clientSize = value;
UpdateParams();
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen { get; }
public Point Position

28
src/Avalonia.Animation/Avalonia.Animation.csproj

@ -1,35 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Animation.xml</DocumentationFile>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Animation.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
</ItemGroup>

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

@ -1,6 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Reflection;
[assembly: AssemblyTitle("Avalonia.Animation")]

9
src/Avalonia.Base/AttachedProperty.cs

@ -9,7 +9,7 @@ namespace Avalonia
/// An attached avalonia property.
/// </summary>
/// <typeparam name="TValue">The type of the property's value.</typeparam>
public class AttachedProperty<TValue> : StyledPropertyBase<TValue>
public class AttachedProperty<TValue> : StyledProperty<TValue>
{
/// <summary>
/// Initializes a new instance of the <see cref="AttachedProperty{TValue}"/> class.
@ -35,11 +35,10 @@ namespace Avalonia
/// </summary>
/// <typeparam name="TOwner">The owner type.</typeparam>
/// <returns>The property.</returns>
public StyledProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
public new AttachedProperty<TValue> AddOwner<TOwner>() where TOwner : IAvaloniaObject
{
var result = new StyledProperty<TValue>(this, typeof(TOwner));
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result);
return result;
AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), this);
return this;
}
}
}

29
src/Avalonia.Base/Avalonia.Base.csproj

@ -1,36 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AssemblyName>Avalonia.Base</AssemblyName>
<RootNamespace>Avalonia</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Base.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Base.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Rx.props" />
<Import Project="..\..\build\JetBrains.Annotations.props" />

63
src/Avalonia.Base/AvaloniaObject.cs

@ -12,7 +12,6 @@ using Avalonia.Diagnostics;
using Avalonia.Logging;
using Avalonia.Threading;
using Avalonia.Utilities;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -72,7 +71,8 @@ namespace Avalonia
public AvaloniaObject()
{
VerifyAccess();
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
void Notify(AvaloniaProperty property)
{
object value = property.IsDirect ?
((IDirectPropertyAccessor)property).GetValue(this) :
@ -87,6 +87,16 @@ namespace Avalonia
property.NotifyInitialized(e);
}
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
{
Notify(property);
}
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()))
{
Notify(property);
}
}
/// <summary>
@ -218,11 +228,6 @@ namespace Avalonia
}
else
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
return GetValueInternal(property);
}
}
@ -377,11 +382,6 @@ namespace Avalonia
{
PriorityValue v;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
@ -804,11 +804,6 @@ namespace Avalonia
var originalValue = value;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!TypeUtilities.TryConvertImplicit(property.PropertyType, value, out value))
{
throw new ArgumentException(string.Format(
@ -836,18 +831,32 @@ namespace Avalonia
}
/// <summary>
/// Given a <see cref="AvaloniaProperty"/> returns a registered avalonia property that is
/// equal or throws if not found.
/// Given a direct property, returns a registered avalonia property that is equivalent or
/// throws if not found.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The registered property.</returns>
public AvaloniaProperty GetRegistered(AvaloniaProperty property)
private AvaloniaProperty GetRegistered(AvaloniaProperty property)
{
var result = AvaloniaPropertyRegistry.Instance.FindRegistered(this, property);
var direct = property as IDirectPropertyAccessor;
if (direct == null)
{
throw new AvaloniaInternalException(
"AvaloniaObject.GetRegistered should only be called for direct properties");
}
if (property.OwnerType.IsAssignableFrom(GetType()))
{
return property;
}
var result = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
.FirstOrDefault(x => x == property);
if (result == null)
{
ThrowNotRegistered(property);
throw new ArgumentException($"Property '{property.Name} not registered on '{this.GetType()}");
}
return result;
@ -898,15 +907,5 @@ namespace Avalonia
value,
priority);
}
/// <summary>
/// Throws an exception indicating that the specified property is not registered on this
/// object.
/// </summary>
/// <param name="p">The property</param>
private void ThrowNotRegistered(AvaloniaProperty p)
{
throw new ArgumentException($"Property '{p.Name} not registered on '{this.GetType()}");
}
}
}

8
src/Avalonia.Base/AvaloniaProperty.cs

@ -311,7 +311,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, typeof(TOwner), metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(typeof(TOwner), result);
registry.RegisterAttached(typeof(THost), result);
return result;
}
@ -344,7 +346,9 @@ namespace Avalonia
defaultBindingMode: defaultBindingMode);
var result = new AttachedProperty<TValue>(name, ownerType, metadata, inherits);
AvaloniaPropertyRegistry.Instance.Register(typeof(THost), result);
var registry = AvaloniaPropertyRegistry.Instance;
registry.Register(ownerType, result);
registry.RegisterAttached(typeof(THost), result);
return result;
}

283
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Avalonia
@ -14,23 +13,14 @@ namespace Avalonia
/// </summary>
public class AvaloniaPropertyRegistry
{
/// <summary>
/// The registered properties by type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered properties by type cached values to increase performance.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registeredCache =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
/// <summary>
/// The registered attached properties by owner type.
/// </summary>
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _registeredCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -39,51 +29,68 @@ namespace Avalonia
= new AvaloniaPropertyRegistry();
/// <summary>
/// Gets all attached <see cref="AvaloniaProperty"/>s registered by an owner.
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="ownerType">The owner type.</param>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetAttached(Type ownerType)
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
{
Dictionary<int, AvaloniaProperty> inner;
Contract.Requires<ArgumentNullException>(type != null);
if (_registeredCache.TryGetValue(type, out var result))
{
return result;
}
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(ownerType.TypeHandle);
var t = type;
result = new List<AvaloniaProperty>();
if (_attached.TryGetValue(ownerType, out inner))
while (t != null)
{
return inner.Values;
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(t.TypeHandle);
if (_registered.TryGetValue(t, out var registered))
{
result.AddRange(registered.Values);
}
t = t.BaseType;
}
return Enumerable.Empty<AvaloniaProperty>();
_registeredCache.Add(type, result);
return result;
}
/// <summary>
/// Gets all <see cref="AvaloniaProperty"/>s registered on a type.
/// Gets all attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegistered(Type type)
public IEnumerable<AvaloniaProperty> GetRegisteredAttached(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
while (type != null)
if (_attachedCache.TryGetValue(type, out var result))
{
// Ensure the type's static ctor has been run.
RuntimeHelpers.RunClassConstructor(type.TypeHandle);
return result;
}
Dictionary<int, AvaloniaProperty> inner;
var t = type;
result = new List<AvaloniaProperty>();
if (_registered.TryGetValue(type, out inner))
while (t != null)
{
if (_attached.TryGetValue(t, out var attached))
{
foreach (var p in inner)
{
yield return p.Value;
}
result.AddRange(attached.Values);
}
type = type.GetTypeInfo().BaseType;
t = t.BaseType;
}
_attachedCache.Add(type, result);
return result;
}
/// <summary>
@ -99,142 +106,92 @@ namespace Avalonia
}
/// <summary>
/// Finds a <see cref="AvaloniaProperty"/> registered on a type.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(Type type, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(Type type, string name)
{
Type currentType = type;
Dictionary<int, AvaloniaProperty> cache;
AvaloniaProperty result;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(name != null);
if (_registeredCache.TryGetValue(type, out cache))
if (name.Contains('.'))
{
if (cache.TryGetValue(property.Id, out result))
{
return result;
}
throw new InvalidOperationException("Attached properties not supported.");
}
while (currentType != null)
{
Dictionary<int, AvaloniaProperty> inner;
if (_registered.TryGetValue(currentType, out inner))
{
if (inner.TryGetValue(property.Id, out result))
{
if (cache == null)
{
_registeredCache[type] = cache = new Dictionary<int, AvaloniaProperty>();
}
cache[property.Id] = result;
return result;
}
}
currentType = currentType.GetTypeInfo().BaseType;
}
return null;
return GetRegistered(type).FirstOrDefault(x => x.Name == name);
}
/// <summary>
/// Finds <see cref="AvaloniaProperty"/> registered on an object.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="property">The property.</param>
/// <returns>The registered property or null if not found.</returns>
/// <remarks>
/// Calling AddOwner on a AvaloniaProperty creates a new AvaloniaProperty that is a
/// different object but is equal according to <see cref="object.Equals(object)"/>.
/// </remarks>
public AvaloniaProperty FindRegistered(object o, AvaloniaProperty property)
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
{
return FindRegistered(o.GetType(), property);
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
return FindRegistered(o.GetType(), name);
}
/// <summary>
/// Finds a registered property on a type by name.
/// Finds a registered attached property on a type by name.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(Type type, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(Type type, Type ownerType, string name)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(ownerType != null);
Contract.Requires<ArgumentNullException>(name != null);
var parts = name.Split('.');
var types = GetImplementedTypes(type).ToList();
if (parts.Length < 1 || parts.Length > 2)
if (name.Contains('.'))
{
throw new ArgumentException("Invalid property name.");
throw new InvalidOperationException("Attached properties not supported.");
}
string propertyName;
var results = GetRegistered(type);
if (parts.Length == 1)
{
propertyName = parts[0];
results = results.Where(x => !x.IsAttached || types.Contains(x.OwnerType.Name));
}
else
{
if (!types.Contains(parts[0]))
{
results = results.Where(x => x.OwnerType.Name == parts[0]);
}
propertyName = parts[1];
}
return results.FirstOrDefault(x => x.Name == propertyName);
return GetRegisteredAttached(type).FirstOrDefault(x => x.Name == name);
}
/// <summary>
/// Finds a registered property on an object by name.
/// Finds a registered non-attached property on a type by name.
/// </summary>
/// <param name="o">The object.</param>
/// <param name="name">
/// The property name. If an attached property it should be in the form
/// "OwnerType.PropertyName".
/// </param>
/// <param name="ownerType">The owner type.</param>
/// <param name="name">The property name.</param>
/// <returns>
/// The registered property or null if no matching property found.
/// </returns>
public AvaloniaProperty FindRegistered(AvaloniaObject o, string name)
/// <exception cref="InvalidOperationException">
/// The property name contains a '.'.
/// </exception>
public AvaloniaProperty FindRegisteredAttached(AvaloniaObject o, Type ownerType, string name)
{
return FindRegistered(o.GetType(), name);
}
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(name != null);
/// <summary>
/// Returns a type and all its base types.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The type and all its base types.</returns>
private IEnumerable<string> GetImplementedTypes(Type type)
{
while (type != null)
{
yield return type.Name;
type = type.GetTypeInfo().BaseType;
}
return FindRegisteredAttached(o.GetType(), ownerType, name);
}
/// <summary>
@ -245,7 +202,11 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(Type type, AvaloniaProperty property)
{
return FindRegistered(type, property) != null;
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
return Instance.GetRegistered(type).Any(x => x == property) ||
Instance.GetRegisteredAttached(type).Any(x => x == property);
}
/// <summary>
@ -256,6 +217,9 @@ namespace Avalonia
/// <returns>True if the property is registered, otherwise false.</returns>
public bool IsRegistered(object o, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(o != null);
Contract.Requires<ArgumentNullException>(property != null);
return IsRegistered(o.GetType(), property);
}
@ -274,34 +238,53 @@ namespace Avalonia
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
Dictionary<int, AvaloniaProperty> inner;
if (!_registered.TryGetValue(type, out inner))
if (!_registered.TryGetValue(type, out var inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_registered.Add(type, inner);
}
if (!inner.ContainsKey(property.Id))
else if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
_registeredCache.Clear();
}
if (property.IsAttached)
/// <summary>
/// Registers an attached <see cref="AvaloniaProperty"/> on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="property">The property.</param>
/// <remarks>
/// You won't usually want to call this method directly, instead use the
/// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
/// method.
/// </remarks>
public void RegisterAttached(Type type, AvaloniaProperty property)
{
Contract.Requires<ArgumentNullException>(type != null);
Contract.Requires<ArgumentNullException>(property != null);
if (!property.IsAttached)
{
if (!_attached.TryGetValue(property.OwnerType, out inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
_attached.Add(property.OwnerType, inner);
}
throw new InvalidOperationException(
"Cannot register a non-attached property as attached.");
}
if (!inner.ContainsKey(property.Id))
{
inner.Add(property.Id, property);
}
if (!_attached.TryGetValue(type, out var inner))
{
inner = new Dictionary<int, AvaloniaProperty>();
inner.Add(property.Id, property);
_attached.Add(type, inner);
}
else
{
inner.Add(property.Id, property);
}
_registeredCache.Clear();
_attachedCache.Clear();
}
}
}

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

@ -117,7 +117,7 @@ namespace Avalonia.Collections
_inner = new Dictionary<TKey, TValue>();
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Count"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[]"));
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]"));
if (CollectionChanged != null)
@ -222,4 +222,4 @@ namespace Avalonia.Collections
}
}
}
}
}

26
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@ -34,14 +34,18 @@ namespace Avalonia.Collections
/// <param name="reset">
/// An action called when the collection is reset.
/// </param>
/// <param name="weakSubscription">
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<T> added,
Action<T> removed,
Action reset)
Action reset,
bool weakSubscription = false)
{
return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset);
return collection.ForEachItem((_, i) => added(i), (_, i) => removed(i), reset, weakSubscription);
}
/// <summary>
@ -63,12 +67,16 @@ namespace Avalonia.Collections
/// An action called when the collection is reset. This will be followed by calls to
/// <paramref name="added"/> for each item present in the collection after the reset.
/// </param>
/// <param name="weakSubscription">
/// Indicates if a weak subscription should be used to track changes to the collection.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable ForEachItem<T>(
this IAvaloniaReadOnlyList<T> collection,
Action<int, T> added,
Action<int, T> removed,
Action reset)
Action reset,
bool weakSubscription = false)
{
void Add(int index, IList items)
{
@ -118,9 +126,17 @@ namespace Avalonia.Collections
};
Add(0, (IList)collection);
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
if (weakSubscription)
{
return collection.WeakSubscribe(handler);
}
else
{
collection.CollectionChanged += handler;
return Disposable.Create(() => collection.CollectionChanged -= handler);
}
}
public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(

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

@ -0,0 +1,127 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Avalonia.Utilities;
namespace Avalonia.Collections
{
public static class NotifyCollectionChangedExtensions
{
/// <summary>
/// Gets a weak observable for the CollectionChanged event.
/// </summary>
/// <param name="collection">The collection.</param>
/// <returns>An observable.</returns>
public static IObservable<NotifyCollectionChangedEventArgs> GetWeakCollectionChangedObservable(
this INotifyCollectionChanged collection)
{
Contract.Requires<ArgumentNullException>(collection != null);
return new WeakCollectionChangedObservable(new WeakReference<INotifyCollectionChanged>(collection));
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">
/// An action called when the collection event is raised.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable WeakSubscribe(
this INotifyCollectionChanged collection,
NotifyCollectionChangedEventHandler handler)
{
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(e => handler.Invoke(collection, e));
}
/// <summary>
/// Subcribes to the CollectionChanged event using a weak subscription.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="handler">
/// An action called when the collection event is raised.
/// </param>
/// <returns>A disposable used to terminate the subscription.</returns>
public static IDisposable WeakSubscribe(
this INotifyCollectionChanged collection,
Action<NotifyCollectionChangedEventArgs> handler)
{
Contract.Requires<ArgumentNullException>(collection != null);
Contract.Requires<ArgumentNullException>(handler != null);
return
collection.GetWeakCollectionChangedObservable()
.Subscribe(handler);
}
private class WeakCollectionChangedObservable : ObservableBase<NotifyCollectionChangedEventArgs>,
IWeakSubscriber<NotifyCollectionChangedEventArgs>
{
private WeakReference<INotifyCollectionChanged> _sourceReference;
private readonly Subject<NotifyCollectionChangedEventArgs> _changed = new Subject<NotifyCollectionChangedEventArgs>();
private int _count;
public WeakCollectionChangedObservable(WeakReference<INotifyCollectionChanged> source)
{
_sourceReference = source;
}
public void OnEvent(object sender, NotifyCollectionChangedEventArgs e)
{
_changed.OnNext(e);
}
protected override IDisposable SubscribeCore(IObserver<NotifyCollectionChangedEventArgs> observer)
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
if (_count++ == 0)
{
WeakSubscriptionManager.Subscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
return Observable.Using(() => Disposable.Create(DecrementCount), _ => _changed)
.Subscribe(observer);
}
else
{
_changed.OnCompleted();
observer.OnCompleted();
return Disposable.Empty;
}
}
private void DecrementCount()
{
if (--_count == 0)
{
if (_sourceReference.TryGetTarget(out INotifyCollectionChanged instance))
{
WeakSubscriptionManager.Unsubscribe(
instance,
nameof(instance.CollectionChanged),
this);
}
}
}
}
}
}

3
src/Avalonia.Base/DirectProperty.cs

@ -75,6 +75,9 @@ namespace Avalonia
/// </summary>
public Action<TOwner, TValue> Setter { get; }
/// <inheritdoc/>
Type IDirectPropertyAccessor.Owner => typeof(TOwner);
/// <summary>
/// Registers the direct property on another type.
/// </summary>

7
src/Avalonia.Base/IDirectPropertyAccessor.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia
{
/// <summary>
@ -14,6 +16,11 @@ namespace Avalonia
/// </summary>
bool IsReadOnly { get; }
/// <summary>
/// Gets the class that registered the property.
/// </summary>
Type Owner { get; }
/// <summary>
/// Gets the value of the property on the instance.
/// </summary>

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

@ -43,5 +43,21 @@ namespace Avalonia.Platform
/// The resource was not found.
/// </exception>
Stream Open(Uri uri, Uri baseUri = null);
/// <summary>
/// Opens the resource with the requested URI and returns the resource string and the
/// assembly containing the resource.
/// </summary>
/// <param name="uri">The URI.</param>
/// <param name="baseUri">
/// A base URI to use if <paramref name="uri"/> is relative.
/// </param>
/// <returns>
/// The stream containing the resource contents together with the assembly.
/// </returns>
/// <exception cref="FileNotFoundException">
/// The resource was not found.
/// </exception>
Tuple<Stream, Assembly> OpenAndGetAssembly(Uri uri, Uri baseUri = null);
}
}

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

@ -4,7 +4,6 @@
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("Avalonia.Base")]
[assembly: InternalsVisibleTo("Avalonia.Base.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

2
src/Avalonia.Base/Threading/Dispatcher.cs

@ -81,12 +81,14 @@ namespace Avalonia.Threading
/// <inheritdoc/>
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(action != null);
return _jobRunner?.InvokeAsync(action, priority);
}
/// <inheritdoc/>
public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{
Contract.Requires<ArgumentNullException>(action != null);
_jobRunner?.Post(action, priority);
}

219
src/Avalonia.Base/Utilities/StringTokenizer.cs

@ -0,0 +1,219 @@
using System;
using System.Globalization;
using static System.Char;
namespace Avalonia.Utilities
{
public struct StringTokenizer : IDisposable
{
private const char DefaultSeparatorChar = ',';
private readonly string _s;
private readonly int _length;
private readonly char _separator;
private readonly string _exceptionMessage;
private readonly IFormatProvider _formatProvider;
private int _index;
private int _tokenIndex;
private int _tokenLength;
public StringTokenizer(string s, IFormatProvider formatProvider, string exceptionMessage = null)
: this(s, GetSeparatorFromFormatProvider(formatProvider), exceptionMessage)
{
_formatProvider = formatProvider;
}
public StringTokenizer(string s, char separator = DefaultSeparatorChar, string exceptionMessage = null)
{
_s = s ?? throw new ArgumentNullException(nameof(s));
_length = s?.Length ?? 0;
_separator = separator;
_exceptionMessage = exceptionMessage;
_formatProvider = CultureInfo.InvariantCulture;
_index = 0;
_tokenIndex = -1;
_tokenLength = 0;
while (_index < _length && IsWhiteSpace(_s, _index))
{
_index++;
}
}
public string CurrentToken => _tokenIndex < 0 ? null : _s.Substring(_tokenIndex, _tokenLength);
public void Dispose()
{
if (_index != _length)
{
throw GetFormatException();
}
}
public bool TryReadInt32(out Int32 result, char? separator = null)
{
if (TryReadString(out var stringResult, separator) &&
int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result))
{
return true;
}
else
{
result = default(Int32);
return false;
}
}
public int ReadInt32(char? separator = null)
{
if (!TryReadInt32(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadDouble(out double result, char? separator = null)
{
if (TryReadString(out var stringResult, separator) &&
double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result))
{
return true;
}
else
{
result = default(double);
return false;
}
}
public double ReadDouble(char? separator = null)
{
if (!TryReadDouble(out var result, separator))
{
throw GetFormatException();
}
return result;
}
public bool TryReadString(out string result, char? separator = null)
{
var success = TryReadToken(separator ?? _separator);
result = CurrentToken;
return success;
}
public string ReadString(char? separator = null)
{
if (!TryReadString(out var result, separator))
{
throw GetFormatException();
}
return result;
}
private bool TryReadToken(char separator)
{
_tokenIndex = -1;
if (_index >= _length)
{
return false;
}
var c = _s[_index];
var index = _index;
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (IsWhiteSpace(c) || c == separator)
{
break;
}
_index++;
length++;
}
SkipToNextToken(separator);
_tokenIndex = index;
_tokenLength = length;
if (_tokenLength < 1)
{
throw GetFormatException();
}
return true;
}
private void SkipToNextToken(char separator)
{
if (_index < _length)
{
var c = _s[_index];
if (c != separator && !IsWhiteSpace(c))
{
throw GetFormatException();
}
var length = 0;
while (_index < _length)
{
c = _s[_index];
if (c == separator)
{
length++;
_index++;
if (length > 1)
{
throw GetFormatException();
}
}
else
{
if (!IsWhiteSpace(c))
{
break;
}
_index++;
}
}
if (length > 0 && _index >= _length)
{
throw GetFormatException();
}
}
}
private FormatException GetFormatException() =>
_exceptionMessage != null ? new FormatException(_exceptionMessage) : new FormatException();
private static char GetSeparatorFromFormatProvider(IFormatProvider provider)
{
var c = DefaultSeparatorChar;
var formatInfo = NumberFormatInfo.GetInstance(provider);
if (formatInfo.NumberDecimalSeparator.Length > 0 && c == formatInfo.NumberDecimalSeparator[0])
{
c = ';';
}
return c;
}
}
}

9
src/Avalonia.Controls/Application.cs

@ -2,16 +2,17 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Concurrency;
using System.Threading;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Rendering;
using Avalonia.Platform;
using Avalonia.Styling;
using Avalonia.Threading;
using System.Reactive.Concurrency;
namespace Avalonia
{
@ -234,7 +235,9 @@ namespace Avalonia
.Bind<IStyler>().ToConstant(_styler)
.Bind<ILayoutManager>().ToSingleton<LayoutManager>()
.Bind<IApplicationLifecycle>().ToConstant(this)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance);
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
}
}
}

2730
src/Avalonia.Controls/AutoCompleteBox.cs

File diff suppressed because it is too large

28
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -1,35 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Debug\Avalonia.Controls.xml</DocumentationFile>
<NoWarn>CS1591;CS0067</NoWarn>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>bin\Release\Avalonia.Controls.xml</DocumentationFile>
<NoWarn>CS1591</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />

51
src/Avalonia.Controls/Border.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia;
using Avalonia.Controls.Utils;
using Avalonia.Media;
namespace Avalonia.Controls
@ -8,7 +10,7 @@ namespace Avalonia.Controls
/// <summary>
/// A control which decorates a child with a border and background.
/// </summary>
public class Border : Decorator
public partial class Border : Decorator
{
/// <summary>
/// Defines the <see cref="Background"/> property.
@ -25,21 +27,24 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
AvaloniaProperty.Register<Border, double>(nameof(BorderThickness));
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
AvaloniaProperty.Register<Border, Thickness>(nameof(BorderThickness));
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
AvaloniaProperty.Register<Border, float>(nameof(CornerRadius));
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="Border"/> class.
/// </summary>
static Border()
{
AffectsRender(BackgroundProperty, BorderBrushProperty);
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty);
}
/// <summary>
@ -63,7 +68,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -72,7 +77,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -84,21 +89,7 @@ namespace Avalonia.Controls
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;
var borderThickness = BorderThickness;
var cornerRadius = CornerRadius;
var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
if (background != null)
{
context.FillRectangle(background, rect, cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
}
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
/// <summary>
@ -120,10 +111,12 @@ namespace Avalonia.Controls
{
if (Child != null)
{
var padding = Padding + new Thickness(BorderThickness);
var padding = Padding + BorderThickness;
Child.Arrange(new Rect(finalSize).Deflate(padding));
}
_borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
@ -131,19 +124,17 @@ namespace Avalonia.Controls
Size availableSize,
IControl child,
Thickness padding,
double borderThickness)
Thickness borderThickness)
{
padding += new Thickness(borderThickness);
padding += borderThickness;
if (child != null)
{
child.Measure(availableSize.Deflate(padding));
return child.DesiredSize.Inflate(padding);
}
else
{
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
}
}
}

2
src/Avalonia.Controls/Button.cs

@ -245,7 +245,7 @@ namespace Avalonia.Controls
{
base.OnPointerReleased(e);
if (e.MouseButton == MouseButton.Left)
if (IsPressed && e.MouseButton == MouseButton.Left)
{
e.Device.Capture(null);
IsPressed = false;

263
src/Avalonia.Controls/ButtonSpinner.cs

@ -0,0 +1,263 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
public enum Location
{
Left,
Right
}
/// <summary>
/// Represents a spinner control that includes two Buttons.
/// </summary>
public class ButtonSpinner : Spinner
{
/// <summary>
/// Defines the <see cref="AllowSpin"/> property.
/// </summary>
public static readonly StyledProperty<bool> AllowSpinProperty =
AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(AllowSpin), true);
/// <summary>
/// Defines the <see cref="ShowButtonSpinner"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShowButtonSpinnerProperty =
AvaloniaProperty.Register<ButtonSpinner, bool>(nameof(ShowButtonSpinner), true);
/// <summary>
/// Defines the <see cref="ButtonSpinnerLocation"/> property.
/// </summary>
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
AvaloniaProperty.Register<ButtonSpinner, Location>(nameof(ButtonSpinnerLocation), Location.Right);
private Button _decreaseButton;
/// <summary>
/// Gets or sets the DecreaseButton template part.
/// </summary>
private Button DecreaseButton
{
get { return _decreaseButton; }
set
{
if (_decreaseButton != null)
{
_decreaseButton.Click -= OnButtonClick;
}
_decreaseButton = value;
if (_decreaseButton != null)
{
_decreaseButton.Click += OnButtonClick;
}
}
}
private Button _increaseButton;
/// <summary>
/// Gets or sets the IncreaseButton template part.
/// </summary>
private Button IncreaseButton
{
get
{
return _increaseButton;
}
set
{
if (_increaseButton != null)
{
_increaseButton.Click -= OnButtonClick;
}
_increaseButton = value;
if (_increaseButton != null)
{
_increaseButton.Click += OnButtonClick;
}
}
}
/// <summary>
/// Initializes static members of the <see cref="ButtonSpinner"/> class.
/// </summary>
static ButtonSpinner()
{
AllowSpinProperty.Changed.Subscribe(AllowSpinChanged);
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Left, ":left");
PseudoClass(ButtonSpinnerLocationProperty, location => location == Location.Right, ":right");
}
/// <summary>
/// Gets or sets a value indicating whether the <see cref="ButtonSpinner"/> should allow to spin.
/// </summary>
public bool AllowSpin
{
get { return GetValue(AllowSpinProperty); }
set { SetValue(AllowSpinProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the spin buttons should be shown.
/// </summary>
public bool ShowButtonSpinner
{
get { return GetValue(ShowButtonSpinnerProperty); }
set { SetValue(ShowButtonSpinnerProperty, value); }
}
/// <summary>
/// Gets or sets current location of the <see cref="ButtonSpinner"/>.
/// </summary>
public Location ButtonSpinnerLocation
{
get { return GetValue(ButtonSpinnerLocationProperty); }
set { SetValue(ButtonSpinnerLocationProperty, value); }
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");
SetButtonUsage();
}
/// <inheritdoc />
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
base.OnPointerReleased(e);
Point mousePosition;
if (IncreaseButton != null && IncreaseButton.IsEnabled == false)
{
mousePosition = e.GetPosition(IncreaseButton);
if (mousePosition.X > 0 && mousePosition.X < IncreaseButton.Width &&
mousePosition.Y > 0 && mousePosition.Y < IncreaseButton.Height)
{
e.Handled = true;
}
}
if (DecreaseButton != null && DecreaseButton.IsEnabled == false)
{
mousePosition = e.GetPosition(DecreaseButton);
if (mousePosition.X > 0 && mousePosition.X < DecreaseButton.Width &&
mousePosition.Y > 0 && mousePosition.Y < DecreaseButton.Height)
{
e.Handled = true;
}
}
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Up:
{
if (AllowSpin)
{
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Increase));
e.Handled = true;
}
break;
}
case Key.Down:
{
if (AllowSpin)
{
OnSpin(new SpinEventArgs(SpinEvent, SpinDirection.Decrease));
e.Handled = true;
}
break;
}
case Key.Enter:
{
//Do not Spin on enter Key when spinners have focus
if (((IncreaseButton != null) && (IncreaseButton.IsFocused))
|| ((DecreaseButton != null) && DecreaseButton.IsFocused))
{
e.Handled = true;
}
break;
}
}
}
/// <inheritdoc />
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{
base.OnPointerWheelChanged(e);
if (!e.Handled && AllowSpin)
{
if (e.Delta.Y != 0)
{
var spinnerEventArgs = new SpinEventArgs(SpinEvent, (e.Delta.Y < 0) ? SpinDirection.Decrease : SpinDirection.Increase, true);
OnSpin(spinnerEventArgs);
e.Handled = spinnerEventArgs.Handled;
}
}
}
protected override void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
{
SetButtonUsage();
}
/// <summary>
/// Called when the <see cref="AllowSpin"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnAllowSpinChanged(bool oldValue, bool newValue)
{
SetButtonUsage();
}
/// <summary>
/// Called when the <see cref="AllowSpin"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void AllowSpinChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is ButtonSpinner spinner)
{
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
spinner.OnAllowSpinChanged(oldValue, newValue);
}
}
/// <summary>
/// Disables or enables the buttons based on the valid spin direction.
/// </summary>
private void SetButtonUsage()
{
if (IncreaseButton != null)
{
IncreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase);
}
if (DecreaseButton != null)
{
DecreaseButton.IsEnabled = AllowSpin && ((ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease);
}
}
/// <summary>
/// Called when user clicks one of the spin buttons.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void OnButtonClick(object sender, RoutedEventArgs e)
{
if (AllowSpin)
{
var direction = sender == IncreaseButton ? SpinDirection.Increase : SpinDirection.Decrease;
OnSpin(new SpinEventArgs(SpinEvent, direction));
}
}
}
}

2
src/Avalonia.Controls/ColumnDefinitions.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls
public ColumnDefinitions(string s)
: this()
{
AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new ColumnDefinition(x)));
AddRange(GridLength.ParseLengths(s).Select(x => new ColumnDefinition(x)));
}
}
}

7
src/Avalonia.Controls/ContextMenu.cs

@ -19,7 +19,7 @@ namespace Avalonia.Controls
{
ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
}
/// <summary>
@ -75,13 +75,14 @@ namespace Avalonia.Controls
{
if (control != null)
{
if(_popup == null)
if (_popup == null)
{
_popup = new Popup()
{
PlacementMode = PlacementMode.Pointer,
PlacementTarget = control,
StaysOpen = false
StaysOpen = false,
ObeyScreenEdges = true
};
_popup.Closed += PopupClosed;

1
src/Avalonia.Controls/DropDown.cs

@ -164,6 +164,7 @@ namespace Avalonia.Controls
else
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
}
}
base.OnPointerPressed(e);

822
src/Avalonia.Controls/Grid.cs

@ -4,7 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Collections;
using Avalonia.Controls.Utils;
using JetBrains.Annotations;
namespace Avalonia.Controls
{
@ -45,10 +48,6 @@ namespace Avalonia.Controls
private RowDefinitions _rowDefinitions;
private Segment[,] _rowMatrix;
private Segment[,] _colMatrix;
/// <summary>
/// Gets or sets the columns definitions for the grid.
/// </summary>
@ -183,6 +182,18 @@ namespace Avalonia.Controls
element.SetValue(RowSpanProperty, value);
}
/// <summary>
/// Gets the result of the last column measurement.
/// Use this result to reduce the arrange calculation.
/// </summary>
private GridLayout.MeasureResult _columnMeasureCache;
/// <summary>
/// Gets the result of the last row measurement.
/// Use this result to reduce the arrange calculation.
/// </summary>
private GridLayout.MeasureResult _rowMeasureCache;
/// <summary>
/// Measures the grid.
/// </summary>
@ -190,293 +201,74 @@ namespace Avalonia.Controls
/// <returns>The desired size of the control.</returns>
protected override Size MeasureOverride(Size constraint)
{
Size totalSize = constraint;
int colCount = ColumnDefinitions.Count;
int rowCount = RowDefinitions.Count;
double totalStarsX = 0;
double totalStarsY = 0;
bool emptyRows = rowCount == 0;
bool emptyCols = colCount == 0;
bool hasChildren = Children.Count > 0;
if (emptyRows)
{
rowCount = 1;
}
if (emptyCols)
{
colCount = 1;
}
CreateMatrices(rowCount, colCount);
// Situation 1/2:
// If the grid doesn't have any column/row definitions, it behaves like a normal panel.
// GridLayout supports this situation but we handle this separately for performance.
if (emptyRows)
{
_rowMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
_rowMatrix[0, 0].Stars = 1.0;
totalStarsY += 1.0;
}
else
if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
{
for (int i = 0; i < rowCount; i++)
var maxWidth = 0.0;
var maxHeight = 0.0;
foreach (var child in Children.OfType<Control>())
{
RowDefinition rowdef = RowDefinitions[i];
GridLength height = rowdef.Height;
rowdef.ActualHeight = double.PositiveInfinity;
_rowMatrix[i, i] = new Segment(0, rowdef.MinHeight, rowdef.MaxHeight, height.GridUnitType);
if (height.GridUnitType == GridUnitType.Pixel)
{
_rowMatrix[i, i].OfferedSize = Clamp(height.Value, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max);
_rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize;
rowdef.ActualHeight = _rowMatrix[i, i].OfferedSize;
}
else if (height.GridUnitType == GridUnitType.Star)
{
_rowMatrix[i, i].Stars = height.Value;
totalStarsY += height.Value;
}
else if (height.GridUnitType == GridUnitType.Auto)
{
_rowMatrix[i, i].OfferedSize = Clamp(0, _rowMatrix[i, i].Min, _rowMatrix[i, i].Max);
_rowMatrix[i, i].DesiredSize = _rowMatrix[i, i].OfferedSize;
}
child.Measure(constraint);
maxWidth = Math.Max(maxWidth, child.DesiredSize.Width);
maxHeight = Math.Max(maxHeight, child.DesiredSize.Height);
}
}
if (emptyCols)
{
_colMatrix[0, 0] = new Segment(0, 0, double.PositiveInfinity, GridUnitType.Star);
_colMatrix[0, 0].Stars = 1.0;
totalStarsX += 1.0;
}
else
{
for (int i = 0; i < colCount; i++)
{
ColumnDefinition coldef = ColumnDefinitions[i];
GridLength width = coldef.Width;
coldef.ActualWidth = double.PositiveInfinity;
_colMatrix[i, i] = new Segment(0, coldef.MinWidth, coldef.MaxWidth, width.GridUnitType);
if (width.GridUnitType == GridUnitType.Pixel)
{
_colMatrix[i, i].OfferedSize = Clamp(width.Value, _colMatrix[i, i].Min, _colMatrix[i, i].Max);
_colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize;
coldef.ActualWidth = _colMatrix[i, i].OfferedSize;
}
else if (width.GridUnitType == GridUnitType.Star)
{
_colMatrix[i, i].Stars = width.Value;
totalStarsX += width.Value;
}
else if (width.GridUnitType == GridUnitType.Auto)
{
_colMatrix[i, i].OfferedSize = Clamp(0, _colMatrix[i, i].Min, _colMatrix[i, i].Max);
_colMatrix[i, i].DesiredSize = _colMatrix[i, i].OfferedSize;
}
}
maxWidth = Math.Min(maxWidth, constraint.Width);
maxHeight = Math.Min(maxHeight, constraint.Height);
return new Size(maxWidth, maxHeight);
}
List<GridNode> sizes = new List<GridNode>();
GridNode node;
GridNode separator = new GridNode(null, 0, 0, 0);
int separatorIndex;
// Situation 2/2:
// If the grid defines some columns or rows.
// Debug Tip:
// - GridLayout doesn't hold any state, so you can drag the debugger execution
// arrow back to any statements and re-run them without any side-effect.
sizes.Add(separator);
var measureCache = new Dictionary<Control, Size>();
var (safeColumns, safeRows) = GetSafeColumnRows();
var columnLayout = new GridLayout(ColumnDefinitions);
var rowLayout = new GridLayout(RowDefinitions);
// Note: If a child stays in a * or Auto column/row, use constraint to measure it.
columnLayout.AppendMeasureConventions(safeColumns, child => MeasureOnce(child, constraint).Width);
rowLayout.AppendMeasureConventions(safeRows, child => MeasureOnce(child, constraint).Height);
// Pre-process the grid children so that we know what types of elements we have so
// we can apply our special measuring rules.
GridWalker gridWalker = new GridWalker(this, _rowMatrix, _colMatrix);
// Calculate measurement.
var columnResult = columnLayout.Measure(constraint.Width);
var rowResult = rowLayout.Measure(constraint.Height);
for (int i = 0; i < 6; i++)
// Use the results of the measurement to measure the rest of the children.
foreach (var child in Children.OfType<Control>())
{
// These bools tell us which grid element type we should be measuring. i.e.
// 'star/auto' means we should measure elements with a star row and auto col
bool autoAuto = i == 0;
bool starAuto = i == 1;
bool autoStar = i == 2;
bool starAutoAgain = i == 3;
bool nonStar = i == 4;
bool remainingStar = i == 5;
if (hasChildren)
{
ExpandStarCols(totalSize);
ExpandStarRows(totalSize);
}
var (column, columnSpan) = safeColumns[child];
var (row, rowSpan) = safeRows[child];
var width = Enumerable.Range(column, columnSpan).Select(x => columnResult.LengthList[x]).Sum();
var height = Enumerable.Range(row, rowSpan).Select(x => rowResult.LengthList[x]).Sum();
foreach (Control child in Children)
{
int col, row;
int colspan, rowspan;
double childSizeX = 0;
double childSizeY = 0;
bool starCol = false;
bool starRow = false;
bool autoCol = false;
bool autoRow = false;
col = Math.Min(GetColumn(child), colCount - 1);
row = Math.Min(GetRow(child), rowCount - 1);
colspan = Math.Min(GetColumnSpan(child), colCount - col);
rowspan = Math.Min(GetRowSpan(child), rowCount - row);
for (int r = row; r < row + rowspan; r++)
{
starRow |= _rowMatrix[r, r].Type == GridUnitType.Star;
autoRow |= _rowMatrix[r, r].Type == GridUnitType.Auto;
}
for (int c = col; c < col + colspan; c++)
{
starCol |= _colMatrix[c, c].Type == GridUnitType.Star;
autoCol |= _colMatrix[c, c].Type == GridUnitType.Auto;
}
// This series of if statements checks whether or not we should measure
// the current element and also if we need to override the sizes
// passed to the Measure call.
// If the element has Auto rows and Auto columns and does not span Star
// rows/cols it should only be measured in the auto_auto phase.
// There are similar rules governing auto/star and star/auto elements.
// NOTE: star/auto elements are measured twice. The first time with
// an override for height, the second time without it.
if (autoRow && autoCol && !starRow && !starCol)
{
if (!autoAuto)
{
continue;
}
childSizeX = double.PositiveInfinity;
childSizeY = double.PositiveInfinity;
}
else if (starRow && autoCol && !starCol)
{
if (!(starAuto || starAutoAgain))
{
continue;
}
if (starAuto && gridWalker.HasAutoStar)
{
childSizeY = double.PositiveInfinity;
}
childSizeX = double.PositiveInfinity;
}
else if (autoRow && starCol && !starRow)
{
if (!autoStar)
{
continue;
}
childSizeY = double.PositiveInfinity;
}
else if ((autoRow || autoCol) && !(starRow || starCol))
{
if (!nonStar)
{
continue;
}
if (autoRow)
{
childSizeY = double.PositiveInfinity;
}
if (autoCol)
{
childSizeX = double.PositiveInfinity;
}
}
else if (!(starRow || starCol))
{
if (!nonStar)
{
continue;
}
}
else
{
if (!remainingStar)
{
continue;
}
}
for (int r = row; r < row + rowspan; r++)
{
childSizeY += _rowMatrix[r, r].OfferedSize;
}
for (int c = col; c < col + colspan; c++)
{
childSizeX += _colMatrix[c, c].OfferedSize;
}
child.Measure(new Size(childSizeX, childSizeY));
Size desired = child.DesiredSize;
// Elements distribute their height based on two rules:
// 1) Elements with rowspan/colspan == 1 distribute their height first
// 2) Everything else distributes in a LIFO manner.
// As such, add all UIElements with rowspan/colspan == 1 after the separator in
// the list and everything else before it. Then to process, just keep popping
// elements off the end of the list.
if (!starAuto)
{
node = new GridNode(_rowMatrix, row + rowspan - 1, row, desired.Height);
separatorIndex = sizes.IndexOf(separator);
sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
}
node = new GridNode(_colMatrix, col + colspan - 1, col, desired.Width);
separatorIndex = sizes.IndexOf(separator);
sizes.Insert(node.Row == node.Column ? separatorIndex + 1 : separatorIndex, node);
}
MeasureOnce(child, new Size(width, height));
}
sizes.Remove(separator);
// Cache the measure result and return the desired size.
_columnMeasureCache = columnResult;
_rowMeasureCache = rowResult;
return new Size(columnResult.DesiredLength, rowResult.DesiredLength);
while (sizes.Count > 0)
// Measure each child only once.
// If a child has been measured, it will just return the desired size.
Size MeasureOnce(Control child, Size size)
{
if (measureCache.TryGetValue(child, out var desiredSize))
{
node = sizes.Last();
node.Matrix[node.Row, node.Column].DesiredSize = Math.Max(node.Matrix[node.Row, node.Column].DesiredSize, node.Size);
AllocateDesiredSize(rowCount, colCount);
sizes.Remove(node);
return desiredSize;
}
sizes.Add(separator);
}
// Once we have measured and distributed all sizes, we have to store
// the results. Every time we want to expand the rows/cols, this will
// be used as the baseline.
SaveMeasureResults();
sizes.Remove(separator);
double gridSizeX = 0;
double gridSizeY = 0;
for (int c = 0; c < colCount; c++)
{
gridSizeX += _colMatrix[c, c].DesiredSize;
child.Measure(size);
desiredSize = child.DesiredSize;
measureCache[child] = desiredSize;
return desiredSize;
}
for (int r = 0; r < rowCount; r++)
{
gridSizeY += _rowMatrix[r, r].DesiredSize;
}
return new Size(gridSizeX, gridSizeY);
}
/// <summary>
@ -486,456 +278,138 @@ namespace Avalonia.Controls
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
int colCount = ColumnDefinitions.Count;
int rowCount = RowDefinitions.Count;
int colMatrixDim = _colMatrix.GetLength(0);
int rowMatrixDim = _rowMatrix.GetLength(0);
RestoreMeasureResults();
// Situation 1/2:
// If the grid doesn't have any column/row definitions, it behaves like a normal panel.
// GridLayout supports this situation but we handle this separately for performance.
double totalConsumedX = 0;
double totalConsumedY = 0;
for (int c = 0; c < colMatrixDim; c++)
if (ColumnDefinitions.Count == 0 && RowDefinitions.Count == 0)
{
_colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize;
totalConsumedX += _colMatrix[c, c].OfferedSize;
}
for (int r = 0; r < rowMatrixDim; r++)
{
_rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize;
totalConsumedY += _rowMatrix[r, r].OfferedSize;
}
if (totalConsumedX != finalSize.Width)
{
ExpandStarCols(finalSize);
}
if (totalConsumedY != finalSize.Height)
{
ExpandStarRows(finalSize);
}
for (int c = 0; c < colCount; c++)
{
ColumnDefinitions[c].ActualWidth = _colMatrix[c, c].OfferedSize;
}
for (int r = 0; r < rowCount; r++)
{
RowDefinitions[r].ActualHeight = _rowMatrix[r, r].OfferedSize;
}
foreach (Control child in Children)
{
int col = Math.Min(GetColumn(child), colMatrixDim - 1);
int row = Math.Min(GetRow(child), rowMatrixDim - 1);
int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - col);
int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - row);
double childFinalX = 0;
double childFinalY = 0;
double childFinalW = 0;
double childFinalH = 0;
for (int c = 0; c < col; c++)
{
childFinalX += _colMatrix[c, c].OfferedSize;
}
for (int c = col; c < col + colspan; c++)
foreach (var child in Children.OfType<Control>())
{
childFinalW += _colMatrix[c, c].OfferedSize;
child.Arrange(new Rect(finalSize));
}
for (int r = 0; r < row; r++)
{
childFinalY += _rowMatrix[r, r].OfferedSize;
}
for (int r = row; r < row + rowspan; r++)
{
childFinalH += _rowMatrix[r, r].OfferedSize;
}
child.Arrange(new Rect(childFinalX, childFinalY, childFinalW, childFinalH));
return finalSize;
}
return finalSize;
}
private static double Clamp(double val, double min, double max)
{
if (val < min)
{
return min;
}
else if (val > max)
{
return max;
}
else
{
return val;
}
}
// Situation 2/2:
// If the grid defines some columns or rows.
// Debug Tip:
// - GridLayout doesn't hold any state, so you can drag the debugger execution
// arrow back to any statements and re-run them without any side-effect.
private static int ValidateColumn(AvaloniaObject o, int value)
{
if (value < 0)
{
throw new ArgumentException("Invalid Grid.Column value.");
}
var (safeColumns, safeRows) = GetSafeColumnRows();
var columnLayout = new GridLayout(ColumnDefinitions);
var rowLayout = new GridLayout(RowDefinitions);
return value;
}
// Calculate for arrange result.
var columnResult = columnLayout.Arrange(finalSize.Width, _columnMeasureCache);
var rowResult = rowLayout.Arrange(finalSize.Height, _rowMeasureCache);
private static int ValidateRow(AvaloniaObject o, int value)
{
if (value < 0)
// Arrange the children.
foreach (var child in Children.OfType<Control>())
{
throw new ArgumentException("Invalid Grid.Row value.");
}
var (column, columnSpan) = safeColumns[child];
var (row, rowSpan) = safeRows[child];
var x = Enumerable.Range(0, column).Sum(c => columnResult.LengthList[c]);
var y = Enumerable.Range(0, row).Sum(r => rowResult.LengthList[r]);
var width = Enumerable.Range(column, columnSpan).Sum(c => columnResult.LengthList[c]);
var height = Enumerable.Range(row, rowSpan).Sum(r => rowResult.LengthList[r]);
return value;
}
private void CreateMatrices(int rowCount, int colCount)
{
if (_rowMatrix == null || _colMatrix == null ||
_rowMatrix.GetLength(0) != rowCount ||
_colMatrix.GetLength(0) != colCount)
{
_rowMatrix = new Segment[rowCount, rowCount];
_colMatrix = new Segment[colCount, colCount];
}
else
{
Array.Clear(_rowMatrix, 0, _rowMatrix.Length);
Array.Clear(_colMatrix, 0, _colMatrix.Length);
child.Arrange(new Rect(x, y, width, height));
}
}
private void ExpandStarCols(Size availableSize)
{
int matrixCount = _colMatrix.GetLength(0);
int columnsCount = ColumnDefinitions.Count;
double width = availableSize.Width;
for (int i = 0; i < matrixCount; i++)
// Assign the actual width.
for (var i = 0; i < ColumnDefinitions.Count; i++)
{
if (_colMatrix[i, i].Type == GridUnitType.Star)
{
_colMatrix[i, i].OfferedSize = 0;
}
else
{
width = Math.Max(width - _colMatrix[i, i].OfferedSize, 0);
}
ColumnDefinitions[i].ActualWidth = columnResult.LengthList[i];
}
AssignSize(_colMatrix, 0, matrixCount - 1, ref width, GridUnitType.Star, false);
width = Math.Max(0, width);
if (columnsCount > 0)
// Assign the actual height.
for (var i = 0; i < RowDefinitions.Count; i++)
{
for (int i = 0; i < matrixCount; i++)
{
if (_colMatrix[i, i].Type == GridUnitType.Star)
{
ColumnDefinitions[i].ActualWidth = _colMatrix[i, i].OfferedSize;
}
}
}
}
private void ExpandStarRows(Size availableSize)
{
int matrixCount = _rowMatrix.GetLength(0);
int rowCount = RowDefinitions.Count;
double height = availableSize.Height;
// When expanding star rows, we need to zero out their height before
// calling AssignSize. AssignSize takes care of distributing the
// available size when there are Mins and Maxs applied.
for (int i = 0; i < matrixCount; i++)
{
if (_rowMatrix[i, i].Type == GridUnitType.Star)
{
_rowMatrix[i, i].OfferedSize = 0.0;
}
else
{
height = Math.Max(height - _rowMatrix[i, i].OfferedSize, 0);
}
RowDefinitions[i].ActualHeight = rowResult.LengthList[i];
}
AssignSize(_rowMatrix, 0, matrixCount - 1, ref height, GridUnitType.Star, false);
if (rowCount > 0)
{
for (int i = 0; i < matrixCount; i++)
{
if (_rowMatrix[i, i].Type == GridUnitType.Star)
{
RowDefinitions[i].ActualHeight = _rowMatrix[i, i].OfferedSize;
}
}
}
// Return the render size.
return finalSize;
}
private void AssignSize(
Segment[,] matrix,
int start,
int end,
ref double size,
GridUnitType type,
bool desiredSize)
/// <summary>
/// Get the safe column/columnspan and safe row/rowspan.
/// This method ensures that none of the children has a column/row outside the bounds of the definitions.
/// </summary>
[Pure]
private (Dictionary<Control, (int index, int span)> safeColumns,
Dictionary<Control, (int index, int span)> safeRows) GetSafeColumnRows()
{
double count = 0;
bool assigned;
// Count how many segments are of the correct type. If we're measuring Star rows/cols
// we need to count the number of stars instead.
for (int i = start; i <= end; i++)
{
double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
if (segmentSize < matrix[i, i].Max)
{
count += type == GridUnitType.Star ? matrix[i, i].Stars : 1;
}
}
do
{
double contribution = size / count;
assigned = false;
for (int i = start; i <= end; i++)
{
double segmentSize = desiredSize ? matrix[i, i].DesiredSize : matrix[i, i].OfferedSize;
if (!(matrix[i, i].Type == type && segmentSize < matrix[i, i].Max))
{
continue;
}
double newsize = segmentSize;
newsize += contribution * (type == GridUnitType.Star ? matrix[i, i].Stars : 1);
newsize = Math.Min(newsize, matrix[i, i].Max);
assigned |= newsize > segmentSize;
size -= newsize - segmentSize;
if (desiredSize)
{
matrix[i, i].DesiredSize = newsize;
}
else
{
matrix[i, i].OfferedSize = newsize;
}
}
}
while (assigned);
var columnCount = ColumnDefinitions.Count;
var rowCount = RowDefinitions.Count;
columnCount = columnCount == 0 ? 1 : columnCount;
rowCount = rowCount == 0 ? 1 : rowCount;
var safeColumns = Children.OfType<Control>().ToDictionary(child => child,
child => GetSafeSpan(columnCount, GetColumn(child), GetColumnSpan(child)));
var safeRows = Children.OfType<Control>().ToDictionary(child => child,
child => GetSafeSpan(rowCount, GetRow(child), GetRowSpan(child)));
return (safeColumns, safeRows);
}
private void AllocateDesiredSize(int rowCount, int colCount)
/// <summary>
/// Gets the safe row/column and rowspan/columnspan for a specified range.
/// The user may assign row/column properties outside the bounds of the row/column count, this method coerces them inside.
/// </summary>
/// <param name="length">The row or column count.</param>
/// <param name="userIndex">The row or column that the user assigned.</param>
/// <param name="userSpan">The rowspan or columnspan that the user assigned.</param>
/// <returns>The safe row/column and rowspan/columnspan.</returns>
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
private static (int index, int span) GetSafeSpan(int length, int userIndex, int userSpan)
{
// First allocate the heights of the RowDefinitions, then allocate
// the widths of the ColumnDefinitions.
for (int i = 0; i < 2; i++)
{
Segment[,] matrix = i == 0 ? _rowMatrix : _colMatrix;
int count = i == 0 ? rowCount : colCount;
for (int row = count - 1; row >= 0; row--)
{
for (int col = row; col >= 0; col--)
{
bool spansStar = false;
for (int j = row; j >= col; j--)
{
spansStar |= matrix[j, j].Type == GridUnitType.Star;
}
// This is the amount of pixels which must be available between the grid rows
// at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
// be at least 'matrix [row][col].size' pixels of height allocated between
// all the rows in the range col -> row.
double current = matrix[row, col].DesiredSize;
// Count how many pixels have already been allocated between the grid rows
// in the range col -> row. The amount of pixels allocated to each grid row/column
// is found on the diagonal of the matrix.
double totalAllocated = 0;
for (int k = row; k >= col; k--)
{
totalAllocated += matrix[k, k].DesiredSize;
}
// If the size requirement has not been met, allocate the additional required
// size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
// height has been assigned.
if (totalAllocated < current)
{
double additional = current - totalAllocated;
if (spansStar)
{
AssignSize(matrix, col, row, ref additional, GridUnitType.Star, true);
}
else
{
AssignSize(matrix, col, row, ref additional, GridUnitType.Pixel, true);
AssignSize(matrix, col, row, ref additional, GridUnitType.Auto, true);
}
}
}
}
}
int rowMatrixDim = _rowMatrix.GetLength(0);
int colMatrixDim = _colMatrix.GetLength(0);
var index = userIndex;
var span = userSpan;
for (int r = 0; r < rowMatrixDim; r++)
if (index < 0)
{
_rowMatrix[r, r].OfferedSize = _rowMatrix[r, r].DesiredSize;
span = index + span;
index = 0;
}
for (int c = 0; c < colMatrixDim; c++)
if (span <= 0)
{
_colMatrix[c, c].OfferedSize = _colMatrix[c, c].DesiredSize;
span = 1;
}
}
private void SaveMeasureResults()
{
int rowMatrixDim = _rowMatrix.GetLength(0);
int colMatrixDim = _colMatrix.GetLength(0);
for (int i = 0; i < rowMatrixDim; i++)
if (userIndex >= length)
{
for (int j = 0; j < rowMatrixDim; j++)
{
_rowMatrix[i, j].OriginalSize = _rowMatrix[i, j].OfferedSize;
}
index = length - 1;
span = 1;
}
for (int i = 0; i < colMatrixDim; i++)
else if (userIndex + userSpan > length)
{
for (int j = 0; j < colMatrixDim; j++)
{
_colMatrix[i, j].OriginalSize = _colMatrix[i, j].OfferedSize;
}
}
}
private void RestoreMeasureResults()
{
int rowMatrixDim = _rowMatrix.GetLength(0);
int colMatrixDim = _colMatrix.GetLength(0);
for (int i = 0; i < rowMatrixDim; i++)
{
for (int j = 0; j < rowMatrixDim; j++)
{
_rowMatrix[i, j].OfferedSize = _rowMatrix[i, j].OriginalSize;
}
span = length - userIndex;
}
for (int i = 0; i < colMatrixDim; i++)
{
for (int j = 0; j < colMatrixDim; j++)
{
_colMatrix[i, j].OfferedSize = _colMatrix[i, j].OriginalSize;
}
}
return (index, span);
}
private struct Segment
private static int ValidateColumn(AvaloniaObject o, int value)
{
public double OriginalSize;
public double Max;
public double Min;
public double DesiredSize;
public double OfferedSize;
public double Stars;
public GridUnitType Type;
public Segment(double offeredSize, double min, double max, GridUnitType type)
if (value < 0)
{
OriginalSize = 0;
Min = min;
Max = max;
DesiredSize = 0;
OfferedSize = offeredSize;
Stars = 0;
Type = type;
throw new ArgumentException("Invalid Grid.Column value.");
}
}
private struct GridNode
{
public readonly int Row;
public readonly int Column;
public readonly double Size;
public readonly Segment[,] Matrix;
public GridNode(Segment[,] matrix, int row, int col, double size)
{
Matrix = matrix;
Row = row;
Column = col;
Size = size;
}
return value;
}
private class GridWalker
private static int ValidateRow(AvaloniaObject o, int value)
{
public GridWalker(Grid grid, Segment[,] rowMatrix, Segment[,] colMatrix)
if (value < 0)
{
int rowMatrixDim = rowMatrix.GetLength(0);
int colMatrixDim = colMatrix.GetLength(0);
foreach (Control child in grid.Children)
{
bool starCol = false;
bool starRow = false;
bool autoCol = false;
bool autoRow = false;
int col = Math.Min(GetColumn(child), colMatrixDim - 1);
int row = Math.Min(GetRow(child), rowMatrixDim - 1);
int colspan = Math.Min(GetColumnSpan(child), colMatrixDim - 1);
int rowspan = Math.Min(GetRowSpan(child), rowMatrixDim - 1);
for (int r = row; r < row + rowspan; r++)
{
starRow |= rowMatrix[r, r].Type == GridUnitType.Star;
autoRow |= rowMatrix[r, r].Type == GridUnitType.Auto;
}
for (int c = col; c < col + colspan; c++)
{
starCol |= colMatrix[c, c].Type == GridUnitType.Star;
autoCol |= colMatrix[c, c].Type == GridUnitType.Auto;
}
HasAutoAuto |= autoRow && autoCol && !starRow && !starCol;
HasStarAuto |= starRow && autoCol;
HasAutoStar |= autoRow && starCol;
}
throw new ArgumentException("Invalid Grid.Row value.");
}
public bool HasAutoAuto { get; }
public bool HasStarAuto { get; }
public bool HasAutoStar { get; }
return value;
}
}
}
}

19
src/Avalonia.Controls/GridLength.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
@ -179,9 +180,8 @@ namespace Avalonia.Controls
/// Parses a string to return a <see cref="GridLength"/>.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
public static GridLength Parse(string s, CultureInfo culture)
public static GridLength Parse(string s)
{
s = s.ToUpperInvariant();
@ -192,12 +192,12 @@ namespace Avalonia.Controls
else if (s.EndsWith("*"))
{
var valueString = s.Substring(0, s.Length - 1).Trim();
var value = valueString.Length > 0 ? double.Parse(valueString, culture) : 1;
var value = valueString.Length > 0 ? double.Parse(valueString, CultureInfo.InvariantCulture) : 1;
return new GridLength(value, GridUnitType.Star);
}
else
{
var value = double.Parse(s, culture);
var value = double.Parse(s, CultureInfo.InvariantCulture);
return new GridLength(value, GridUnitType.Pixel);
}
}
@ -206,11 +206,16 @@ namespace Avalonia.Controls
/// Parses a string to return a collection of <see cref="GridLength"/>s.
/// </summary>
/// <param name="s">The string.</param>
/// <param name="culture">The current culture.</param>
/// <returns>The <see cref="GridLength"/>.</returns>
public static IEnumerable<GridLength> ParseLengths(string s, CultureInfo culture)
public static IEnumerable<GridLength> ParseLengths(string s)
{
return s.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries).Select(x => Parse(x, culture));
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture))
{
while (tokenizer.TryReadString(out var item))
{
yield return Parse(item);
}
}
}
}
}

12
src/Avalonia.Controls/ItemsControl.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
@ -54,6 +55,7 @@ namespace Avalonia.Controls
private IEnumerable _items = new AvaloniaList<object>();
private IItemContainerGenerator _itemContainerGenerator;
private IDisposable _itemsCollectionChangedSubscription;
/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
@ -326,12 +328,8 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param>
protected virtual void ItemsChanged(AvaloniaPropertyChangedEventArgs e)
{
var incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= ItemsCollectionChanged;
}
_itemsCollectionChangedSubscription?.Dispose();
_itemsCollectionChangedSubscription = null;
var oldValue = e.OldValue as IEnumerable;
var newValue = e.NewValue as IEnumerable;
@ -428,7 +426,7 @@ namespace Avalonia.Controls
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
_itemsCollectionChangedSubscription = incc.WeakSubscribe(ItemsCollectionChanged);
}
}

6
src/Avalonia.Controls/LayoutTransformControl.cs

@ -15,6 +15,9 @@ using System.Reactive.Linq;
namespace Avalonia.Controls
{
/// <summary>
/// Control that implements support for transformations as if applied by LayoutTransform.
/// </summary>
public class LayoutTransformControl : ContentControl
{
public static readonly AvaloniaProperty<Transform> LayoutTransformProperty =
@ -26,6 +29,9 @@ namespace Avalonia.Controls
.AddClassHandler<LayoutTransformControl>(x => x.OnLayoutTransformChanged);
}
/// <summary>
/// Gets or sets a graphics transformation that should apply to this element when layout is performed.
/// </summary>
public Transform LayoutTransform
{
get { return GetValue(LayoutTransformProperty); }

35
src/Avalonia.Controls/MenuItem.cs

@ -93,6 +93,7 @@ namespace Avalonia.Controls
static MenuItem()
{
SelectableMixin.Attach<MenuItem>(IsSelectedProperty);
CommandProperty.Changed.Subscribe(CommandChanged);
FocusableProperty.OverrideDefaultValue<MenuItem>(true);
IconProperty.Changed.AddClassHandler<MenuItem>(x => x.IconChanged);
ItemsPanelProperty.OverrideDefaultValue<MenuItem>(DefaultPanel);
@ -424,6 +425,40 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Called when the <see cref="Command"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private static void CommandChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is MenuItem menuItem)
{
if (e.OldValue is ICommand oldCommand)
{
oldCommand.CanExecuteChanged -= menuItem.CanExecuteChanged;
}
if (e.NewValue is ICommand newCommand)
{
newCommand.CanExecuteChanged += menuItem.CanExecuteChanged;
}
menuItem.CanExecuteChanged(menuItem, EventArgs.Empty);
}
}
/// <summary>
/// Called when the <see cref="ICommand.CanExecuteChanged"/> event fires.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event args.</param>
private void CanExecuteChanged(object sender, EventArgs e)
{
// HACK: Just set the IsEnabled property for the moment. This needs to be changed to
// use IsEnabledCore etc. but it will do for now.
IsEnabled = Command == null || Command.CanExecute(CommandParameter);
}
/// <summary>
/// Called when the <see cref="Icon"/> property changes.
/// </summary>

998
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -0,0 +1,998 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Controls
{
/// <summary>
/// Control that represents a TextBox with button spinners that allow incrementing and decrementing numeric values.
/// </summary>
public class NumericUpDown : TemplatedControl
{
/// <summary>
/// Defines the <see cref="AllowSpin"/> property.
/// </summary>
public static readonly StyledProperty<bool> AllowSpinProperty =
ButtonSpinner.AllowSpinProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ButtonSpinnerLocation"/> property.
/// </summary>
public static readonly StyledProperty<Location> ButtonSpinnerLocationProperty =
ButtonSpinner.ButtonSpinnerLocationProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ShowButtonSpinner"/> property.
/// </summary>
public static readonly StyledProperty<bool> ShowButtonSpinnerProperty =
ButtonSpinner.ShowButtonSpinnerProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="ClipValueToMinMax"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, bool> ClipValueToMinMaxProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, bool>(nameof(ClipValueToMinMax),
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
/// <summary>
/// Defines the <see cref="CultureInfo"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, CultureInfo> CultureInfoProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, CultureInfo>(nameof(CultureInfo), o => o.CultureInfo,
(o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
/// <summary>
/// Defines the <see cref="FormatString"/> property.
/// </summary>
public static readonly StyledProperty<string> FormatStringProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(FormatString), string.Empty);
/// <summary>
/// Defines the <see cref="Increment"/> property.
/// </summary>
public static readonly StyledProperty<double> IncrementProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Increment), 1.0d, validate: OnCoerceIncrement);
/// <summary>
/// Defines the <see cref="IsReadOnly"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsReadOnlyProperty =
AvaloniaProperty.Register<NumericUpDown, bool>(nameof(IsReadOnly));
/// <summary>
/// Defines the <see cref="Maximum"/> property.
/// </summary>
public static readonly StyledProperty<double> MaximumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Maximum), double.MaxValue, validate: OnCoerceMaximum);
/// <summary>
/// Defines the <see cref="Minimum"/> property.
/// </summary>
public static readonly StyledProperty<double> MinimumProperty =
AvaloniaProperty.Register<NumericUpDown, double>(nameof(Minimum), double.MinValue, validate: OnCoerceMinimum);
/// <summary>
/// Defines the <see cref="ParsingNumberStyle"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, NumberStyles> ParsingNumberStyleProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, NumberStyles>(nameof(ParsingNumberStyle),
updown => updown.ParsingNumberStyle, (updown, style) => updown.ParsingNumberStyle = style);
/// <summary>
/// Defines the <see cref="Text"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, string> TextProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, string>(nameof(Text), o => o.Text, (o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="Value"/> property.
/// </summary>
public static readonly DirectProperty<NumericUpDown, double> ValueProperty =
AvaloniaProperty.RegisterDirect<NumericUpDown, double>(nameof(Value), updown => updown.Value,
(updown, v) => updown.Value = v, defaultBindingMode: BindingMode.TwoWay);
/// <summary>
/// Defines the <see cref="Watermark"/> property.
/// </summary>
public static readonly StyledProperty<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
private IDisposable _textBoxTextChangedSubscription;
private double _value;
private string _text;
private bool _internalValueSet;
private bool _clipValueToMinMax;
private bool _isSyncingTextAndValueProperties;
private bool _isTextChangedFromUI;
private CultureInfo _cultureInfo;
private NumberStyles _parsingNumberStyle = NumberStyles.Any;
/// <summary>
/// Gets the Spinner template part.
/// </summary>
private Spinner Spinner { get; set; }
/// <summary>
/// Gets the TextBox template part.
/// </summary>
private TextBox TextBox { get; set; }
/// <summary>
/// Gets or sets the ability to perform increment/decrement operations via the keyboard, button spinners, or mouse wheel.
/// </summary>
public bool AllowSpin
{
get { return GetValue(AllowSpinProperty); }
set { SetValue(AllowSpinProperty, value); }
}
/// <summary>
/// Gets or sets current location of the <see cref="ButtonSpinner"/>.
/// </summary>
public Location ButtonSpinnerLocation
{
get { return GetValue(ButtonSpinnerLocationProperty); }
set { SetValue(ButtonSpinnerLocationProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the spin buttons should be shown.
/// </summary>
public bool ShowButtonSpinner
{
get { return GetValue(ShowButtonSpinnerProperty); }
set { SetValue(ShowButtonSpinnerProperty, value); }
}
/// <summary>
/// Gets or sets if the value should be clipped when minimum/maximum is reached.
/// </summary>
public bool ClipValueToMinMax
{
get { return _clipValueToMinMax; }
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
}
/// <summary>
/// Gets or sets the current CultureInfo.
/// </summary>
public CultureInfo CultureInfo
{
get { return _cultureInfo; }
set { SetAndRaise(CultureInfoProperty, ref _cultureInfo, value); }
}
/// <summary>
/// Gets or sets the display format of the <see cref="Value"/>.
/// </summary>
public string FormatString
{
get { return GetValue(FormatStringProperty); }
set { SetValue(FormatStringProperty, value); }
}
/// <summary>
/// Gets or sets the amount in which to increment the <see cref="Value"/>.
/// </summary>
public double Increment
{
get { return GetValue(IncrementProperty); }
set { SetValue(IncrementProperty, value); }
}
/// <summary>
/// Gets or sets if the control is read only.
/// </summary>
public bool IsReadOnly
{
get { return GetValue(IsReadOnlyProperty); }
set { SetValue(IsReadOnlyProperty, value); }
}
/// <summary>
/// Gets or sets the maximum allowed value.
/// </summary>
public double Maximum
{
get { return GetValue(MaximumProperty); }
set { SetValue(MaximumProperty, value); }
}
/// <summary>
/// Gets or sets the minimum allowed value.
/// </summary>
public double Minimum
{
get { return GetValue(MinimumProperty); }
set { SetValue(MinimumProperty, value); }
}
/// <summary>
/// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
/// </summary>
public NumberStyles ParsingNumberStyle
{
get { return _parsingNumberStyle; }
set { SetAndRaise(ParsingNumberStyleProperty, ref _parsingNumberStyle, value); }
}
/// <summary>
/// Gets or sets the formatted string representation of the value.
/// </summary>
public string Text
{
get { return _text; }
set { SetAndRaise(TextProperty, ref _text, value); }
}
/// <summary>
/// Gets or sets the value.
/// </summary>
public double Value
{
get { return _value; }
set
{
value = OnCoerceValue(value);
SetAndRaise(ValueProperty, ref _value, value);
}
}
/// <summary>
/// Gets or sets the object to use as a watermark if the <see cref="Value"/> is null.
/// </summary>
public string Watermark
{
get { return GetValue(WatermarkProperty); }
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Initializes new instance of <see cref="NumericUpDown"/> class.
/// </summary>
public NumericUpDown()
{
Initialized += (sender, e) =>
{
if (!_internalValueSet && IsInitialized)
{
SyncTextAndValueProperties(false, null, true);
}
SetValidSpinDirection();
};
}
/// <summary>
/// Initializes static members of the <see cref="NumericUpDown"/> class.
/// </summary>
static NumericUpDown()
{
CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);
IsReadOnlyProperty.Changed.Subscribe(OnIsReadOnlyChanged);
MaximumProperty.Changed.Subscribe(OnMaximumChanged);
MinimumProperty.Changed.Subscribe(OnMinimumChanged);
TextProperty.Changed.Subscribe(OnTextChanged);
ValueProperty.Changed.Subscribe(OnValueChanged);
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
if (TextBox != null)
{
TextBox.PointerPressed -= TextBoxOnPointerPressed;
_textBoxTextChangedSubscription?.Dispose();
}
TextBox = e.NameScope.Find<TextBox>("PART_TextBox");
if (TextBox != null)
{
TextBox.Text = Text;
TextBox.PointerPressed += TextBoxOnPointerPressed;
_textBoxTextChangedSubscription = TextBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBoxOnTextChanged());
}
if (Spinner != null)
{
Spinner.Spin -= OnSpinnerSpin;
}
Spinner = e.NameScope.Find<Spinner>("PART_Spinner");
if (Spinner != null)
{
Spinner.Spin += OnSpinnerSpin;
}
SetValidSpinDirection();
}
/// <inheritdoc />
protected override void OnKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
var commitSuccess = CommitInput();
e.Handled = !commitSuccess;
break;
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnCultureInfoChanged(CultureInfo oldValue, CultureInfo newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="FormatString"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnFormatStringChanged(string oldValue, string newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(false, null);
}
}
/// <summary>
/// Called when the <see cref="Increment"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnIncrementChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
}
/// <summary>
/// Called when the <see cref="IsReadOnly"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnIsReadOnlyChanged(bool oldValue, bool newValue)
{
SetValidSpinDirection();
}
/// <summary>
/// Called when the <see cref="Maximum"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMaximumChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
if (ClipValueToMinMax)
{
Value = MathUtilities.Clamp(Value, Minimum, Maximum);
}
}
/// <summary>
/// Called when the <see cref="Minimum"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnMinimumChanged(double oldValue, double newValue)
{
if (IsInitialized)
{
SetValidSpinDirection();
}
if (ClipValueToMinMax)
{
Value = MathUtilities.Clamp(Value, Minimum, Maximum);
}
}
/// <summary>
/// Called when the <see cref="Text"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnTextChanged(string oldValue, string newValue)
{
if (IsInitialized)
{
SyncTextAndValueProperties(true, Text);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnValueChanged(double oldValue, double newValue)
{
if (!_internalValueSet && IsInitialized)
{
SyncTextAndValueProperties(false, null, true);
}
SetValidSpinDirection();
RaiseValueChangedEvent(oldValue, newValue);
}
/// <summary>
/// Called when the <see cref="Increment"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceIncrement(double baseValue)
{
return baseValue;
}
/// <summary>
/// Called when the <see cref="Maximum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMaximum(double baseValue)
{
return Math.Max(baseValue, Minimum);
}
/// <summary>
/// Called when the <see cref="Minimum"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceMinimum(double baseValue)
{
return Math.Min(baseValue, Maximum);
}
/// <summary>
/// Called when the <see cref="Value"/> property has to be coerced.
/// </summary>
/// <param name="baseValue">The value.</param>
protected virtual double OnCoerceValue(double baseValue)
{
return baseValue;
}
/// <summary>
/// Raises the OnSpin event when spinning is initiated by the end-user.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnSpin(SpinEventArgs e)
{
if (e == null)
{
throw new ArgumentNullException("e");
}
var handler = Spinned;
handler?.Invoke(this, e);
if (e.Direction == SpinDirection.Increase)
{
DoIncrement();
}
else
{
DoDecrement();
}
}
/// <summary>
/// Raises the <see cref="ValueChanged"/> event.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void RaiseValueChangedEvent(double oldValue, double newValue)
{
var e = new NumericUpDownValueChangedEventArgs(ValueChangedEvent, oldValue, newValue);
RaiseEvent(e);
}
/// <summary>
/// Converts the formatted text to a value.
/// </summary>
private double ConvertTextToValue(string text)
{
double result = 0;
if (string.IsNullOrEmpty(text))
{
return result;
}
// Since the conversion from Value to text using a FormartString may not be parsable,
// we verify that the already existing text is not the exact same value.
var currentValueText = ConvertValueToText();
if (Equals(currentValueText, text))
{
return Value;
}
result = ConvertTextToValueCore(currentValueText, text);
if (ClipValueToMinMax)
{
return MathUtilities.Clamp(result, Minimum, Maximum);
}
ValidateMinMax(result);
return result;
}
/// <summary>
/// Converts the value to formatted text.
/// </summary>
/// <returns></returns>
private string ConvertValueToText()
{
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0"))
{
return string.Format(CultureInfo, FormatString, Value);
}
return Value.ToString(FormatString, CultureInfo);
}
/// <summary>
/// Called by OnSpin when the spin direction is SpinDirection.Increase.
/// </summary>
private void OnIncrement()
{
var result = Value + Increment;
Value = MathUtilities.Clamp(result, Minimum, Maximum);
}
/// <summary>
/// Called by OnSpin when the spin direction is SpinDirection.Descrease.
/// </summary>
private void OnDecrement()
{
var result = Value - Increment;
Value = MathUtilities.Clamp(result, Minimum, Maximum);
}
/// <summary>
/// Sets the valid spin directions.
/// </summary>
private void SetValidSpinDirection()
{
var validDirections = ValidSpinDirections.None;
// Zero increment always prevents spin.
if (Increment != 0 && !IsReadOnly)
{
if (Value < Maximum)
{
validDirections = validDirections | ValidSpinDirections.Increase;
}
if (Value > Minimum)
{
validDirections = validDirections | ValidSpinDirections.Decrease;
}
}
if (Spinner != null)
{
Spinner.ValidSpinDirection = validDirections;
}
}
/// <summary>
/// Called when the <see cref="CultureInfo"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (CultureInfo)e.OldValue;
var newValue = (CultureInfo)e.NewValue;
upDown.OnCultureInfoChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Increment"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void IncrementChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnIncrementChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="FormatString"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void FormatStringChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (string)e.OldValue;
var newValue = (string)e.NewValue;
upDown.OnFormatStringChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="IsReadOnly"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnIsReadOnlyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (bool)e.OldValue;
var newValue = (bool)e.NewValue;
upDown.OnIsReadOnlyChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Maximum"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnMaximumChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnMaximumChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Minimum"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnMinimumChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnMinimumChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Text"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (string)e.OldValue;
var newValue = (string)e.NewValue;
upDown.OnTextChanged(oldValue, newValue);
}
}
/// <summary>
/// Called when the <see cref="Value"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is NumericUpDown upDown)
{
var oldValue = (double)e.OldValue;
var newValue = (double)e.NewValue;
upDown.OnValueChanged(oldValue, newValue);
}
}
private void SetValueInternal(double value)
{
_internalValueSet = true;
try
{
Value = value;
}
finally
{
_internalValueSet = false;
}
}
private static double OnCoerceMaximum(NumericUpDown upDown, double value)
{
return upDown.OnCoerceMaximum(value);
}
private static double OnCoerceMinimum(NumericUpDown upDown, double value)
{
return upDown.OnCoerceMinimum(value);
}
private static double OnCoerceIncrement(NumericUpDown upDown, double value)
{
return upDown.OnCoerceIncrement(value);
}
private void TextBoxOnTextChanged()
{
try
{
_isTextChangedFromUI = true;
if (TextBox != null)
{
Text = TextBox.Text;
}
}
finally
{
_isTextChangedFromUI = false;
}
}
private void OnSpinnerSpin(object sender, SpinEventArgs e)
{
if (AllowSpin && !IsReadOnly)
{
var spin = !e.UsingMouseWheel;
spin |= ((TextBox != null) && TextBox.IsFocused);
if (spin)
{
e.Handled = true;
OnSpin(e);
}
}
}
private void DoDecrement()
{
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Decrease) == ValidSpinDirections.Decrease)
{
OnDecrement();
}
}
private void DoIncrement()
{
if (Spinner == null || (Spinner.ValidSpinDirection & ValidSpinDirections.Increase) == ValidSpinDirections.Increase)
{
OnIncrement();
}
}
public event EventHandler<SpinEventArgs> Spinned;
private void TextBoxOnPointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.Device.Captured != Spinner)
{
Dispatcher.UIThread.InvokeAsync(() => { e.Device.Capture(Spinner); }, DispatcherPriority.Input);
}
}
/// <summary>
/// Defines the <see cref="ValueChanged"/> event.
/// </summary>
public static readonly RoutedEvent<NumericUpDownValueChangedEventArgs> ValueChangedEvent =
RoutedEvent.Register<NumericUpDown, NumericUpDownValueChangedEventArgs>(nameof(ValueChanged), RoutingStrategies.Bubble);
/// <summary>
/// Raised when the <see cref="Value"/> changes.
/// </summary>
public event EventHandler<SpinEventArgs> ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
private bool CommitInput()
{
return SyncTextAndValueProperties(true, Text);
}
/// <summary>
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
/// </summary>
/// <param name="updateValueFromText">If value should be updated from text.</param>
/// <param name="text">The text.</param>
private bool SyncTextAndValueProperties(bool updateValueFromText, string text)
{
return SyncTextAndValueProperties(updateValueFromText, text, false);
}
/// <summary>
/// Synchronize <see cref="Text"/> and <see cref="Value"/> properties.
/// </summary>
/// <param name="updateValueFromText">If value should be updated from text.</param>
/// <param name="text">The text.</param>
/// <param name="forceTextUpdate">Force text update.</param>
private bool SyncTextAndValueProperties(bool updateValueFromText, string text, bool forceTextUpdate)
{
if (_isSyncingTextAndValueProperties)
return true;
_isSyncingTextAndValueProperties = true;
var parsedTextIsValid = true;
try
{
if (updateValueFromText)
{
if (!string.IsNullOrEmpty(text))
{
try
{
var newValue = ConvertTextToValue(text);
if (!Equals(newValue, Value))
{
SetValueInternal(newValue);
}
}
catch
{
parsedTextIsValid = false;
}
}
}
// Do not touch the ongoing text input from user.
if (!_isTextChangedFromUI)
{
var keepEmpty = !forceTextUpdate && string.IsNullOrEmpty(Text);
if (!keepEmpty)
{
var newText = ConvertValueToText();
if (!Equals(Text, newText))
{
Text = newText;
}
}
// Sync Text and textBox
if (TextBox != null)
{
TextBox.Text = Text;
}
}
if (_isTextChangedFromUI && !parsedTextIsValid)
{
// Text input was made from the user and the text
// repesents an invalid value. Disable the spinner in this case.
if (Spinner != null)
{
Spinner.ValidSpinDirection = ValidSpinDirections.None;
}
}
else
{
SetValidSpinDirection();
}
}
finally
{
_isSyncingTextAndValueProperties = false;
}
return parsedTextIsValid;
}
private double ConvertTextToValueCore(string currentValueText, string text)
{
double result;
if (IsPercent(FormatString))
{
result = decimal.ToDouble(ParsePercent(text, CultureInfo));
}
else
{
// Problem while converting new text
if (!double.TryParse(text, ParsingNumberStyle, CultureInfo, out var outputValue))
{
var shouldThrow = true;
// Check if CurrentValueText is also failing => it also contains special characters. ex : 90°
if (!double.TryParse(currentValueText, ParsingNumberStyle, CultureInfo, out var _))
{
// extract non-digit characters
var currentValueTextSpecialCharacters = currentValueText.Where(c => !char.IsDigit(c));
var textSpecialCharacters = text.Where(c => !char.IsDigit(c));
// same non-digit characters on currentValueText and new text => remove them on new Text to parse it again.
if (currentValueTextSpecialCharacters.Except(textSpecialCharacters).ToList().Count == 0)
{
foreach (var character in textSpecialCharacters)
{
text = text.Replace(character.ToString(), string.Empty);
}
// if without the special characters, parsing is good, do not throw
if (double.TryParse(text, ParsingNumberStyle, CultureInfo, out outputValue))
{
shouldThrow = false;
}
}
}
if (shouldThrow)
{
throw new InvalidDataException("Input string was not in a correct format.");
}
}
result = outputValue;
}
return result;
}
private void ValidateMinMax(double value)
{
if (value < Minimum)
{
throw new ArgumentOutOfRangeException(nameof(Minimum), string.Format("Value must be greater than Minimum value of {0}", Minimum));
}
else if (value > Maximum)
{
throw new ArgumentOutOfRangeException(nameof(Maximum), string.Format("Value must be less than Maximum value of {0}", Maximum));
}
}
/// <summary>
/// Parse percent format text
/// </summary>
/// <param name="text">Text to parse.</param>
/// <param name="cultureInfo">The culture info.</param>
private static decimal ParsePercent(string text, IFormatProvider cultureInfo)
{
var info = NumberFormatInfo.GetInstance(cultureInfo);
text = text.Replace(info.PercentSymbol, null);
var result = decimal.Parse(text, NumberStyles.Any, info);
result = result / 100;
return result;
}
private bool IsPercent(string stringToTest)
{
var PIndex = stringToTest.IndexOf("P", StringComparison.Ordinal);
if (PIndex >= 0)
{
//stringToTest contains a "P" between 2 "'", it's considered as text, not percent
var isText = stringToTest.Substring(0, PIndex).Contains("'")
&& stringToTest.Substring(PIndex, FormatString.Length - PIndex).Contains("'");
return !isText;
}
return false;
}
}
}

16
src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs

@ -0,0 +1,16 @@
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
public class NumericUpDownValueChangedEventArgs : RoutedEventArgs
{
public NumericUpDownValueChangedEventArgs(RoutedEvent routedEvent, double oldValue, double newValue) : base(routedEvent)
{
OldValue = oldValue;
NewValue = newValue;
}
public double OldValue { get; }
public double NewValue { get; }
}
}

10
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -55,7 +55,7 @@ namespace Avalonia.Platform
/// Gets the platform window handle.
/// </summary>
IPlatformHandle Handle { get; }
/// <summary>
/// Gets the maximum size of a window on the system.
/// </summary>
@ -65,7 +65,13 @@ namespace Avalonia.Platform
/// Sets the client size of the toplevel.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Gets platform specific display information
/// </summary>

11
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -44,5 +44,16 @@ namespace Avalonia.Platform
/// Enables or disables the taskbar icon
/// </summary>
void ShowTaskbarIcon(bool value);
/// <summary>
/// Enables or disables resizing of the window
/// </summary>
void CanResize(bool value);
/// <summary>
/// Gets or sets a method called before the underlying implementation is destroyed.
/// Return true to prevent the underlying implementation from closing.
/// </summary>
Func<bool> Closing { get; set; }
}
}

210
src/Avalonia.Controls/Platform/InProcessDragSource.cs

@ -0,0 +1,210 @@
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Platform
{
class InProcessDragSource : IPlatformDragSource
{
private const InputModifiers MOUSE_INPUTMODIFIERS = InputModifiers.LeftMouseButton|InputModifiers.MiddleMouseButton|InputModifiers.RightMouseButton;
private readonly IDragDropDevice _dragDrop;
private readonly IInputManager _inputManager;
private readonly Subject<DragDropEffects> _result = new Subject<DragDropEffects>();
private DragDropEffects _allowedEffects;
private IDataObject _draggedData;
private IInputElement _lastRoot;
private Point _lastPosition;
private StandardCursorType _lastCursorType;
private object _originalCursor;
private InputModifiers? _initialInputModifiers;
public InProcessDragSource()
{
_inputManager = AvaloniaLocator.Current.GetService<IInputManager>();
_dragDrop = AvaloniaLocator.Current.GetService<IDragDropDevice>();
}
public async Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
Dispatcher.UIThread.VerifyAccess();
if (_draggedData == null)
{
_draggedData = data;
_lastRoot = null;
_lastPosition = default(Point);
_allowedEffects = allowedEffects;
using (_inputManager.PreProcess.OfType<RawMouseEventArgs>().Subscribe(ProcessMouseEvents))
{
using (_inputManager.PreProcess.OfType<RawKeyEventArgs>().Subscribe(ProcessKeyEvents))
{
var effect = await _result.FirstAsync();
return effect;
}
}
}
return DragDropEffects.None;
}
private DragDropEffects RaiseEventAndUpdateCursor(RawDragEventType type, IInputElement root, Point pt, InputModifiers modifiers)
{
_lastPosition = pt;
RawDragEvent rawEvent = new RawDragEvent(_dragDrop, type, root, pt, _draggedData, _allowedEffects);
var tl = root.GetSelfAndVisualAncestors().OfType<TopLevel>().FirstOrDefault();
tl.PlatformImpl.Input(rawEvent);
var effect = GetPreferredEffect(rawEvent.Effects & _allowedEffects, modifiers);
UpdateCursor(root, effect);
return effect;
}
private DragDropEffects GetPreferredEffect(DragDropEffects effect, InputModifiers modifiers)
{
if (effect == DragDropEffects.Copy || effect == DragDropEffects.Move || effect == DragDropEffects.Link || effect == DragDropEffects.None)
return effect; // No need to check for the modifiers.
if (effect.HasFlag(DragDropEffects.Link) && modifiers.HasFlag(InputModifiers.Alt))
return DragDropEffects.Link;
if (effect.HasFlag(DragDropEffects.Copy) && modifiers.HasFlag(InputModifiers.Control))
return DragDropEffects.Copy;
return DragDropEffects.Move;
}
private StandardCursorType GetCursorForDropEffect(DragDropEffects effects)
{
if (effects.HasFlag(DragDropEffects.Copy))
return StandardCursorType.DragCopy;
if (effects.HasFlag(DragDropEffects.Move))
return StandardCursorType.DragMove;
if (effects.HasFlag(DragDropEffects.Link))
return StandardCursorType.DragLink;
return StandardCursorType.No;
}
private void UpdateCursor(IInputElement root, DragDropEffects effect)
{
if (_lastRoot != root)
{
if (_lastRoot is InputElement ieLast)
{
if (_originalCursor == AvaloniaProperty.UnsetValue)
ieLast.ClearValue(InputElement.CursorProperty);
else
ieLast.Cursor = _originalCursor as Cursor;
}
if (root is InputElement ieNew)
{
if (!ieNew.IsSet(InputElement.CursorProperty))
_originalCursor = AvaloniaProperty.UnsetValue;
else
_originalCursor = root.Cursor;
}
else
_originalCursor = null;
_lastCursorType = StandardCursorType.Arrow;
_lastRoot = root;
}
if (root is InputElement ie)
{
var ct = GetCursorForDropEffect(effect);
if (ct != _lastCursorType)
{
_lastCursorType = ct;
ie.Cursor = new Cursor(ct);
}
}
}
private void CancelDragging()
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, InputModifiers.None);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
}
private void ProcessKeyEvents(RawKeyEventArgs e)
{
if (e.Type == RawKeyEventType.KeyDown && e.Key == Key.Escape)
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastPosition, e.Modifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(DragDropEffects.None);
e.Handled = true;
}
else if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl || e.Key == Key.LeftAlt || e.Key == Key.RightAlt)
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, _lastRoot, _lastPosition, e.Modifiers);
}
private void ProcessMouseEvents(RawMouseEventArgs e)
{
if (!_initialInputModifiers.HasValue)
_initialInputModifiers = e.InputModifiers & MOUSE_INPUTMODIFIERS;
void CheckDraggingAccepted(InputModifiers changedMouseButton)
{
if (_initialInputModifiers.Value.HasFlag(changedMouseButton))
{
var result = RaiseEventAndUpdateCursor(RawDragEventType.Drop, e.Root, e.Position, e.InputModifiers);
UpdateCursor(null, DragDropEffects.None);
_result.OnNext(result);
}
else
CancelDragging();
e.Handled = true;
}
switch (e.Type)
{
case RawMouseEventType.LeftButtonDown:
case RawMouseEventType.RightButtonDown:
case RawMouseEventType.MiddleButtonDown:
case RawMouseEventType.NonClientLeftButtonDown:
CancelDragging();
e.Handled = true;
return;
case RawMouseEventType.LeaveWindow:
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, e.Root, e.Position, e.InputModifiers); break;
case RawMouseEventType.LeftButtonUp:
CheckDraggingAccepted(InputModifiers.LeftMouseButton); break;
case RawMouseEventType.MiddleButtonUp:
CheckDraggingAccepted(InputModifiers.MiddleMouseButton); break;
case RawMouseEventType.RightButtonUp:
CheckDraggingAccepted(InputModifiers.RightMouseButton); break;
case RawMouseEventType.Move:
var mods = e.InputModifiers & MOUSE_INPUTMODIFIERS;
if (_initialInputModifiers.Value != mods)
{
CancelDragging();
e.Handled = true;
return;
}
if (e.Root != _lastRoot)
{
if (_lastRoot != null)
RaiseEventAndUpdateCursor(RawDragEventType.DragLeave, _lastRoot, _lastRoot.PointToClient(e.Root.PointToScreen(e.Position)), e.InputModifiers);
RaiseEventAndUpdateCursor(RawDragEventType.DragEnter, e.Root, e.Position, e.InputModifiers);
}
else
RaiseEventAndUpdateCursor(RawDragEventType.DragOver, e.Root, e.Position, e.InputModifiers);
break;
}
}
}
}

153
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -4,6 +4,7 @@
using System;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -31,7 +32,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -57,7 +58,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
public static readonly StyledProperty<float> CornerRadiusProperty =
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
@ -76,17 +77,20 @@ namespace Avalonia.Controls.Presenters
/// Defines the <see cref="Padding"/> property.
/// </summary>
public static readonly StyledProperty<Thickness> PaddingProperty =
Border.PaddingProperty.AddOwner<ContentPresenter>();
Decorator.PaddingProperty.AddOwner<ContentPresenter>();
private IControl _child;
private bool _createdChild;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
/// <summary>
/// Initializes static members of the <see cref="ContentPresenter"/> class.
/// </summary>
static ContentPresenter()
{
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty, CornerRadiusProperty);
AffectsMeasure(BorderThicknessProperty);
ContentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
ContentTemplateProperty.Changed.AddClassHandler<ContentPresenter>(x => x.ContentChanged);
TemplatedParentProperty.Changed.AddClassHandler<ContentPresenter>(x => x.TemplatedParentChanged);
@ -120,7 +124,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the thickness of the border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -157,7 +161,7 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
public float CornerRadius
public CornerRadius CornerRadius
{
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
@ -221,7 +225,7 @@ namespace Avalonia.Controls.Presenters
{
var content = Content;
var oldChild = Child;
var newChild = CreateChild();
var newChild = CreateChild();
// Remove the old child if we're not recycling it.
if (oldChild != null && newChild != oldChild)
@ -277,21 +281,7 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
public override void Render(DrawingContext context)
{
var background = Background;
var borderBrush = BorderBrush;
var borderThickness = BorderThickness;
var cornerRadius = CornerRadius;
var rect = new Rect(Bounds.Size).Deflate(BorderThickness);
if (background != null)
{
context.FillRectangle(background, rect, cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect, cornerRadius);
}
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
}
/// <summary>
@ -344,7 +334,11 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
return ArrangeOverrideImpl(finalSize, new Vector());
finalSize = ArrangeOverrideImpl(finalSize, new Vector());
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return finalSize;
}
/// <summary>
@ -372,74 +366,69 @@ namespace Avalonia.Controls.Presenters
internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
{
if (Child != null)
{
var padding = Padding;
var borderThickness = BorderThickness;
var horizontalContentAlignment = HorizontalContentAlignment;
var verticalContentAlignment = VerticalContentAlignment;
var useLayoutRounding = UseLayoutRounding;
var availableSizeMinusMargins = new Size(
Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness;
var originY = offset.Y + padding.Top + borderThickness;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
}
if (Child == null) return finalSize;
if (verticalContentAlignment != VerticalAlignment.Stretch)
{
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
}
size = LayoutHelper.ApplyLayoutConstraints(Child, size);
var padding = Padding;
var borderThickness = BorderThickness;
var horizontalContentAlignment = HorizontalContentAlignment;
var verticalContentAlignment = VerticalContentAlignment;
var useLayoutRounding = UseLayoutRounding;
var availableSizeMinusMargins = new Size(
Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness.Left - borderThickness.Right),
Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness.Top - borderThickness.Bottom));
var size = availableSizeMinusMargins;
var scale = GetLayoutScale();
var originX = offset.X + padding.Left + borderThickness.Left;
var originY = offset.Y + padding.Top + borderThickness.Top;
if (horizontalContentAlignment != HorizontalAlignment.Stretch)
{
size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
}
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
Math.Ceiling(size.Height * scale) / scale);
availableSizeMinusMargins = new Size(
Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
}
if (verticalContentAlignment != VerticalAlignment.Stretch)
{
size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
}
switch (horizontalContentAlignment)
{
case HorizontalAlignment.Center:
case HorizontalAlignment.Stretch:
originX += (availableSizeMinusMargins.Width - size.Width) / 2;
break;
case HorizontalAlignment.Right:
originX += availableSizeMinusMargins.Width - size.Width;
break;
}
if (useLayoutRounding)
{
size = new Size(
Math.Ceiling(size.Width * scale) / scale,
Math.Ceiling(size.Height * scale) / scale);
availableSizeMinusMargins = new Size(
Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
}
switch (verticalContentAlignment)
{
case VerticalAlignment.Center:
case VerticalAlignment.Stretch:
originY += (availableSizeMinusMargins.Height - size.Height) / 2;
break;
case VerticalAlignment.Bottom:
originY += availableSizeMinusMargins.Height - size.Height;
break;
}
switch (horizontalContentAlignment)
{
case HorizontalAlignment.Center:
originX += (availableSizeMinusMargins.Width - size.Width) / 2;
break;
case HorizontalAlignment.Right:
originX += availableSizeMinusMargins.Width - size.Width;
break;
}
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
}
switch (verticalContentAlignment)
{
case VerticalAlignment.Center:
originY += (availableSizeMinusMargins.Height - size.Height) / 2;
break;
case VerticalAlignment.Bottom:
originY += availableSizeMinusMargins.Height - size.Height;
break;
}
Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
if (useLayoutRounding)
{
originX = Math.Floor(originX * scale) / scale;
originY = Math.Floor(originY * scale) / scale;
}
Child.Arrange(new Rect(originX, originY, Math.Max(0, size.Width), Math.Max(0, size.Height)));
return finalSize;
}

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -224,7 +224,7 @@ namespace Avalonia.Controls.Presenters
CanVerticallyScroll ? Math.Max(Child.DesiredSize.Height, finalSize.Height) : finalSize.Height);
ArrangeOverrideImpl(size, -Offset);
Viewport = finalSize;
Extent = Child.Bounds.Size;
Extent = Child.Bounds.Size.Inflate(Child.Margin);
return finalSize;
}

66
src/Avalonia.Controls/Primitives/Popup.cs

@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
set => SetValue(ObeyScreenEdgesProperty, value);
}
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
@ -215,7 +231,17 @@ namespace Avalonia.Controls.Primitives
{
var window = _topLevel as Window;
if (window != null)
{
window.Deactivated += WindowDeactivated;
}
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
}
}
_topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
_nonClientListener = InputManager.Instance.Process.Subscribe(ListenForNonClientClick);
}
@ -224,6 +250,11 @@ namespace Avalonia.Controls.Primitives
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
_ignoreIsOpenChanged = true;
IsOpen = true;
_ignoreIsOpenChanged = false;
@ -244,6 +275,14 @@ namespace Avalonia.Controls.Primitives
var window = _topLevel as Window;
if (window != null)
window.Deactivated -= WindowDeactivated;
else
{
var parentPopuproot = _topLevel as PopupRoot;
if (parentPopuproot != null && parentPopuproot.Parent != null)
{
((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
}
}
_nonClientListener?.Dispose();
_nonClientListener = null;
}
@ -328,8 +367,10 @@ namespace Avalonia.Controls.Primitives
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
{
return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
@ -381,9 +422,7 @@ namespace Avalonia.Controls.Primitives
{
if (!StaysOpen)
{
var root = ((IVisual)e.Source).GetVisualRoot();
if (root != this.PopupRoot)
if (!IsChildOrThis((IVisual)e.Source))
{
Close();
e.Handled = true;
@ -391,6 +430,17 @@ namespace Avalonia.Controls.Primitives
}
}
private bool IsChildOrThis(IVisual child)
{
IVisual root = child.GetVisualRoot();
while (root is PopupRoot)
{
if (root == PopupRoot) return true;
root = ((PopupRoot)root).Parent.GetVisualRoot();
}
return false;
}
private void WindowDeactivated(object sender, EventArgs e)
{
if (!StaysOpen)
@ -398,5 +448,13 @@ namespace Avalonia.Controls.Primitives
Close();
}
}
private void ParentClosed(object sender, EventArgs e)
{
if (!StaysOpen)
{
Close();
}
}
}
}

26
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -2,10 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Styling;
@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
{
var window = this.GetSelfAndLogicalAncestors().OfType<Window>().First();
var screen = window.Screens.ScreenFromPoint(Position);
var screenX = Position.X + Bounds.Width - screen.Bounds.X;
var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{

8
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Defines the <see cref="BorderThickness"/> property.
/// </summary>
public static readonly StyledProperty<double> BorderThicknessProperty =
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<TemplatedControl>();
/// <summary>
@ -132,7 +132,7 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Gets or sets the thickness of the control's border.
/// </summary>
public double BorderThickness
public Thickness BorderThickness
{
get { return GetValue(BorderThicknessProperty); }
set { SetValue(BorderThicknessProperty, value); }
@ -207,7 +207,7 @@ namespace Avalonia.Controls.Primitives
/// <param name="control">The control.</param>
/// <returns>The property value.</returns>
/// <see cref="SetIsTemplateFocusTarget(Control, bool)"/>
public bool GetIsTemplateFocusTarget(Control control)
public static bool GetIsTemplateFocusTarget(Control control)
{
return control.GetValue(IsTemplateFocusTargetProperty);
}
@ -223,7 +223,7 @@ namespace Avalonia.Controls.Primitives
/// attached property is set to true on an element in the control template, then the focus
/// adorner will be shown around that control instead.
/// </remarks>
public void SetIsTemplateFocusTarget(Control control, bool value)
public static void SetIsTemplateFocusTarget(Control control, bool value)
{
control.SetValue(IsTemplateFocusTargetProperty, value);
}

26
src/Avalonia.Controls/ProgressBar.cs

@ -26,12 +26,13 @@ namespace Avalonia.Controls
static ProgressBar()
{
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Vertical, ":vertical");
PseudoClass(OrientationProperty, o => o == Avalonia.Controls.Orientation.Horizontal, ":horizontal");
ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
OrientationProperty.Changed.AddClassHandler<ProgressBar>(
(p, e) => { if (p._indicator != null) p.UpdateOrientation((Orientation)e.NewValue); });
}
public bool IsIndeterminate
@ -59,7 +60,6 @@ namespace Avalonia.Controls
_indicator = e.NameScope.Get<Border>("PART_Indicator");
UpdateIndicator(Bounds.Size);
UpdateOrientation(Orientation);
UpdateIsIndeterminate(IsIndeterminate);
}
@ -86,26 +86,6 @@ namespace Avalonia.Controls
}
}
private void UpdateOrientation(Orientation orientation)
{
if (orientation == Orientation.Horizontal)
{
MinHeight = 14;
MinWidth = 200;
_indicator.HorizontalAlignment = HorizontalAlignment.Left;
_indicator.VerticalAlignment = VerticalAlignment.Stretch;
}
else
{
MinHeight = 200;
MinWidth = 14;
_indicator.HorizontalAlignment = HorizontalAlignment.Stretch;
_indicator.VerticalAlignment = VerticalAlignment.Bottom;
}
}
private void UpdateIsIndeterminate(bool isIndeterminate)
{
if (isIndeterminate)

1
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@ -5,7 +5,6 @@ using System.Reflection;
using System.Runtime.CompilerServices;
using Avalonia.Metadata;
[assembly: AssemblyTitle("Avalonia.Controls")]
[assembly: InternalsVisibleTo("Avalonia.Controls.UnitTests")]
[assembly: InternalsVisibleTo("Avalonia.DesignerSupport")]

4
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Remote
{
private readonly IAvaloniaRemoteTransportConnection _connection;
private FrameMessage _lastFrame;
private WritableBitmap _bitmap;
private WriteableBitmap _bitmap;
public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
{
_connection = connection;
@ -62,7 +62,7 @@ namespace Avalonia.Controls.Remote
var fmt = (PixelFormat) _lastFrame.Format;
if (_bitmap == null || _bitmap.PixelWidth != _lastFrame.Width ||
_bitmap.PixelHeight != _lastFrame.Height)
_bitmap = new WritableBitmap(_lastFrame.Width, _lastFrame.Height, fmt);
_bitmap = new WriteableBitmap(_lastFrame.Width, _lastFrame.Height, fmt);
using (var l = _bitmap.Lock())
{
var lineLen = (fmt == PixelFormat.Rgb565 ? 2 : 4) * _lastFrame.Width;

29
src/Avalonia.Controls/RepeatButton.cs

@ -6,17 +6,32 @@ namespace Avalonia.Controls
{
public class RepeatButton : Button
{
/// <summary>
/// Defines the <see cref="Interval"/> property.
/// </summary>
public static readonly StyledProperty<int> IntervalProperty =
AvaloniaProperty.Register<Button, int>(nameof(Interval), 100);
/// <summary>
/// Defines the <see cref="Delay"/> property.
/// </summary>
public static readonly StyledProperty<int> DelayProperty =
AvaloniaProperty.Register<Button, int>(nameof(Delay), 100);
AvaloniaProperty.Register<Button, int>(nameof(Delay), 300);
private DispatcherTimer _repeatTimer;
/// <summary>
/// Gets or sets the amount of time, in milliseconds, of repeating clicks.
/// </summary>
public int Interval
{
get { return GetValue(IntervalProperty); }
set { SetValue(IntervalProperty, value); }
}
/// <summary>
/// Gets or sets the amount of time, in milliseconds, to wait before repeating begins.
/// </summary>
public int Delay
{
get { return GetValue(DelayProperty); }
@ -28,7 +43,7 @@ namespace Avalonia.Controls
if (_repeatTimer == null)
{
_repeatTimer = new DispatcherTimer();
_repeatTimer.Tick += (o, e) => OnClick();
_repeatTimer.Tick += RepeatTimerOnTick;
}
if (_repeatTimer.IsEnabled) return;
@ -37,6 +52,16 @@ namespace Avalonia.Controls
_repeatTimer.Start();
}
private void RepeatTimerOnTick(object sender, EventArgs e)
{
var interval = TimeSpan.FromMilliseconds(Interval);
if (_repeatTimer.Interval != interval)
{
_repeatTimer.Interval = interval;
}
OnClick();
}
private void StopTimer()
{
_repeatTimer?.Stop();

2
src/Avalonia.Controls/RowDefinitions.cs

@ -27,7 +27,7 @@ namespace Avalonia.Controls
public RowDefinitions(string s)
: this()
{
AddRange(GridLength.ParseLengths(s, CultureInfo.InvariantCulture).Select(x => new RowDefinition(x)));
AddRange(GridLength.ParseLengths(s).Select(x => new RowDefinition(x)));
}
}
}

2
src/Avalonia.Controls/Screens.cs

@ -39,7 +39,7 @@ namespace Avalonia.Controls
return currMaxScreen;
}
public Screen SceenFromPoint(Point point)
public Screen ScreenFromPoint(Point point)
{
return All.FirstOrDefault(x=>x.Bounds.Contains(point));
}

174
src/Avalonia.Controls/Spinner.cs

@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia.Interactivity;
namespace Avalonia.Controls
{
/// <summary>
/// Represents spin directions that are valid.
/// </summary>
[Flags]
public enum ValidSpinDirections
{
/// <summary>
/// Can not increase nor decrease.
/// </summary>
None = 0,
/// <summary>
/// Can increase.
/// </summary>
Increase = 1,
/// <summary>
/// Can decrease.
/// </summary>
Decrease = 2
}
/// <summary>
/// Represents spin directions that could be initiated by the end-user.
/// </summary>
public enum SpinDirection
{
/// <summary>
/// Represents a spin initiated by the end-user in order to Increase a value.
/// </summary>
Increase = 0,
/// <summary>
/// Represents a spin initiated by the end-user in order to Decrease a value.
/// </summary>
Decrease = 1
}
/// <summary>
/// Provides data for the Spinner.Spin event.
/// </summary>
public class SpinEventArgs : RoutedEventArgs
{
/// <summary>
/// Gets the SpinDirection for the spin that has been initiated by the end-user.
/// </summary>
public SpinDirection Direction { get; }
/// <summary>
/// Get or set whheter the spin event originated from a mouse wheel event.
/// </summary>
public bool UsingMouseWheel{ get; }
/// <summary>
/// Initializes a new instance of the SpinEventArgs class.
/// </summary>
/// <param name="direction">Spin direction.</param>
public SpinEventArgs(SpinDirection direction)
{
Direction = direction;
}
public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction)
: base(routedEvent)
{
Direction = direction;
}
public SpinEventArgs(SpinDirection direction, bool usingMouseWheel)
{
Direction = direction;
UsingMouseWheel = usingMouseWheel;
}
public SpinEventArgs(RoutedEvent routedEvent, SpinDirection direction, bool usingMouseWheel)
: base(routedEvent)
{
Direction = direction;
UsingMouseWheel = usingMouseWheel;
}
}
/// <summary>
/// Base class for controls that represents controls that can spin.
/// </summary>
public abstract class Spinner : ContentControl
{
/// <summary>
/// Defines the <see cref="ValidSpinDirection"/> property.
/// </summary>
public static readonly StyledProperty<ValidSpinDirections> ValidSpinDirectionProperty =
AvaloniaProperty.Register<Spinner, ValidSpinDirections>(nameof(ValidSpinDirection),
ValidSpinDirections.Increase | ValidSpinDirections.Decrease);
/// <summary>
/// Defines the <see cref="Spin"/> event.
/// </summary>
public static readonly RoutedEvent<SpinEventArgs> SpinEvent =
RoutedEvent.Register<Spinner, SpinEventArgs>(nameof(Spin), RoutingStrategies.Bubble);
/// <summary>
/// Initializes static members of the <see cref="Spinner"/> class.
/// </summary>
static Spinner()
{
ValidSpinDirectionProperty.Changed.Subscribe(OnValidSpinDirectionPropertyChanged);
}
/// <summary>
/// Occurs when spinning is initiated by the end-user.
/// </summary>
public event EventHandler<SpinEventArgs> Spin
{
add { AddHandler(SpinEvent, value); }
remove { RemoveHandler(SpinEvent, value); }
}
/// <summary>
/// Gets or sets <see cref="ValidSpinDirections"/> allowed for this control.
/// </summary>
public ValidSpinDirections ValidSpinDirection
{
get { return GetValue(ValidSpinDirectionProperty); }
set { SetValue(ValidSpinDirectionProperty, value); }
}
/// <summary>
/// Called when valid spin direction changed.
/// </summary>
/// <param name="oldValue">The old value.</param>
/// <param name="newValue">The new value.</param>
protected virtual void OnValidSpinDirectionChanged(ValidSpinDirections oldValue, ValidSpinDirections newValue)
{
}
/// <summary>
/// Raises the OnSpin event when spinning is initiated by the end-user.
/// </summary>
/// <param name="e">Spin event args.</param>
protected virtual void OnSpin(SpinEventArgs e)
{
var valid = e.Direction == SpinDirection.Increase
? ValidSpinDirections.Increase
: ValidSpinDirections.Decrease;
//Only raise the event if spin is allowed.
if ((ValidSpinDirection & valid) == valid)
{
RaiseEvent(e);
}
}
/// <summary>
/// Called when the <see cref="ValidSpinDirection"/> property value changed.
/// </summary>
/// <param name="e">The event args.</param>
private static void OnValidSpinDirectionPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Sender is Spinner spinner)
{
var oldValue = (ValidSpinDirections)e.OldValue;
var newValue = (ValidSpinDirections)e.NewValue;
spinner.OnValidSpinDirectionChanged(oldValue, newValue);
}
}
}
}

4
src/Avalonia.Controls/TextBlock.cs

@ -120,6 +120,7 @@ namespace Avalonia.Controls
.Subscribe(_ =>
{
InvalidateFormattedText();
InvalidateMeasure();
});
}
@ -370,8 +371,6 @@ namespace Avalonia.Controls
_constraint = _formattedText.Constraint;
_formattedText = null;
}
InvalidateMeasure();
}
/// <summary>
@ -402,6 +401,7 @@ namespace Avalonia.Controls
{
base.OnAttachedToLogicalTree(e);
InvalidateFormattedText();
InvalidateMeasure();
}
}
}

37
src/Avalonia.Controls/TextBox.cs

@ -85,6 +85,7 @@ namespace Avalonia.Controls
private int _selectionEnd;
private TextPresenter _presenter;
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
@ -198,7 +199,11 @@ namespace Avalonia.Controls
if (!_ignoreTextChanges)
{
CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0);
SetAndRaise(TextProperty, ref _text, value);
if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing)
{
_undoRedoHelper.Clear();
}
}
}
}
@ -256,6 +261,8 @@ namespace Avalonia.Controls
{
_presenter?.ShowCaret();
}
e.Handled = true;
}
protected override void OnLostFocus(RoutedEventArgs e)
@ -268,7 +275,11 @@ namespace Avalonia.Controls
protected override void OnTextInput(TextInputEventArgs e)
{
HandleTextInput(e.Text);
if (!e.Handled)
{
HandleTextInput(e.Text);
e.Handled = true;
}
}
private void HandleTextInput(string input)
@ -364,14 +375,30 @@ namespace Avalonia.Controls
case Key.Z:
if (modifiers == InputModifiers.Control)
{
_undoRedoHelper.Undo();
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Y:
if (modifiers == InputModifiers.Control)
{
_undoRedoHelper.Redo();
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
@ -788,7 +815,7 @@ namespace Avalonia.Controls
int pos = 0;
int i;
for (i = 0; i < lines.Count; ++i)
for (i = 0; i < lines.Count - 1; ++i)
{
var line = lines[i];
pos += line.Length;

4
src/Avalonia.Controls/UserControl.cs

@ -6,6 +6,9 @@ using Avalonia.Styling;
namespace Avalonia.Controls
{
/// <summary>
/// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic.
/// </summary>
public class UserControl : ContentControl, IStyleable, INameScope
{
private readonly NameScope _nameScope = new NameScope();
@ -24,6 +27,7 @@ namespace Avalonia.Controls
remove { _nameScope.Unregistered -= value; }
}
/// <inheritdoc/>
Type IStyleable.StyleKey => typeof(ContentControl);
/// <inheritdoc/>

279
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -0,0 +1,279 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Media;
namespace Avalonia.Controls.Utils
{
internal class BorderRenderHelper
{
private bool _useComplexRendering;
private StreamGeometry _backgroundGeometryCache;
private StreamGeometry _borderGeometryCache;
public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
{
if (borderThickness.IsUniform && cornerRadius.IsUniform)
{
_backgroundGeometryCache = null;
_borderGeometryCache = null;
_useComplexRendering = false;
}
else
{
_useComplexRendering = true;
var boundRect = new Rect(finalSize);
var innerRect = boundRect.Deflate(borderThickness);
var innerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, false);
StreamGeometry backgroundGeometry = null;
if (innerRect.Width != 0 && innerRect.Height != 0)
{
backgroundGeometry = new StreamGeometry();
using (var ctx = backgroundGeometry.Open())
{
CreateGeometry(ctx, innerRect, innerCoordinates);
}
_backgroundGeometryCache = backgroundGeometry;
}
else
{
_backgroundGeometryCache = null;
}
if (boundRect.Width != 0 && innerRect.Height != 0)
{
var outerCoordinates = new BorderCoordinates(cornerRadius, borderThickness, true);
var borderGeometry = new StreamGeometry();
using (var ctx = borderGeometry.Open())
{
CreateGeometry(ctx, boundRect, outerCoordinates);
if (backgroundGeometry != null)
{
CreateGeometry(ctx, innerRect, innerCoordinates);
}
}
_borderGeometryCache = borderGeometry;
}
else
{
_borderGeometryCache = null;
}
}
}
public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
{
if (_useComplexRendering)
{
var backgroundGeometry = _backgroundGeometryCache;
if (backgroundGeometry != null)
{
context.DrawGeometry(background, null, backgroundGeometry);
}
var borderGeometry = _borderGeometryCache;
if (borderGeometry != null)
{
context.DrawGeometry(borderBrush, null, borderGeometry);
}
}
else
{
var borderThickness = borders.Left;
var cornerRadius = (float)radii.TopLeft;
var rect = new Rect(size);
if (background != null)
{
context.FillRectangle(background, rect.Deflate(borders), cornerRadius);
}
if (borderBrush != null && borderThickness > 0)
{
context.DrawRectangle(new Pen(borderBrush, borderThickness), rect.Deflate(borderThickness), cornerRadius);
}
}
}
private static void CreateGeometry(StreamGeometryContext context, Rect boundRect, BorderCoordinates borderCoordinates)
{
var topLeft = new Point(borderCoordinates.LeftTop, 0);
var topRight = new Point(boundRect.Width - borderCoordinates.RightTop, 0);
var rightTop = new Point(boundRect.Width, borderCoordinates.TopRight);
var rightBottom = new Point(boundRect.Width, boundRect.Height - borderCoordinates.BottomRight);
var bottomRight = new Point(boundRect.Width - borderCoordinates.RightBottom, boundRect.Height);
var bottomLeft = new Point(borderCoordinates.LeftBottom, boundRect.Height);
var leftBottom = new Point(0, boundRect.Height - borderCoordinates.BottomLeft);
var leftTop = new Point(0, borderCoordinates.TopLeft);
if (topLeft.X > topRight.X)
{
var scaledX = borderCoordinates.LeftTop / (borderCoordinates.LeftTop + borderCoordinates.RightTop) * boundRect.Width;
topLeft = new Point(scaledX, topLeft.Y);
topRight = new Point(scaledX, topRight.Y);
}
if (rightTop.Y > rightBottom.Y)
{
var scaledY = borderCoordinates.TopRight / (borderCoordinates.TopRight + borderCoordinates.BottomRight) * boundRect.Height;
rightTop = new Point(rightTop.X, scaledY);
rightBottom = new Point(rightBottom.X, scaledY);
}
if (bottomRight.X < bottomLeft.X)
{
var scaledX = borderCoordinates.LeftBottom / (borderCoordinates.LeftBottom + borderCoordinates.RightBottom) * boundRect.Width;
bottomRight = new Point(scaledX, bottomRight.Y);
bottomLeft = new Point(scaledX, bottomLeft.Y);
}
if (leftBottom.Y < leftTop.Y)
{
var scaledY = borderCoordinates.TopLeft / (borderCoordinates.TopLeft + borderCoordinates.BottomLeft) * boundRect.Height;
leftBottom = new Point(leftBottom.X, scaledY);
leftTop = new Point(leftTop.X, scaledY);
}
var offset = new Vector(boundRect.TopLeft.X, boundRect.TopLeft.Y);
topLeft += offset;
topRight += offset;
rightTop += offset;
rightBottom += offset;
bottomRight += offset;
bottomLeft += offset;
leftBottom += offset;
leftTop += offset;
context.BeginFigure(topLeft, true);
//Top
context.LineTo(topRight);
//TopRight corner
var radiusX = boundRect.TopRight.X - topRight.X;
var radiusY = rightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(rightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
}
//Right
context.LineTo(rightBottom);
//BottomRight corner
radiusX = boundRect.BottomRight.X - bottomRight.X;
radiusY = boundRect.BottomRight.Y - rightBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(bottomRight, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
//Bottom
context.LineTo(bottomLeft);
//BottomLeft corner
radiusX = bottomLeft.X - boundRect.BottomLeft.X;
radiusY = boundRect.BottomLeft.Y - leftBottom.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(leftBottom, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
//Left
context.LineTo(leftTop);
//TopLeft corner
radiusX = topLeft.X - boundRect.TopLeft.X;
radiusY = leftTop.Y - boundRect.TopLeft.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(topLeft, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
context.EndFigure(true);
}
private struct BorderCoordinates
{
internal BorderCoordinates(CornerRadius cornerRadius, Thickness borderThickness, bool isOuter)
{
var left = 0.5 * borderThickness.Left;
var top = 0.5 * borderThickness.Top;
var right = 0.5 * borderThickness.Right;
var bottom = 0.5 * borderThickness.Bottom;
if (isOuter)
{
if (cornerRadius.TopLeft == 0)
{
LeftTop = TopLeft = 0.0;
}
else
{
LeftTop = cornerRadius.TopLeft + left;
TopLeft = cornerRadius.TopLeft + top;
}
if (cornerRadius.TopRight == 0)
{
TopRight = RightTop = 0;
}
else
{
TopRight = cornerRadius.TopRight + top;
RightTop = cornerRadius.TopRight + right;
}
if (cornerRadius.BottomRight == 0)
{
RightBottom = BottomRight = 0;
}
else
{
RightBottom = cornerRadius.BottomRight + right;
BottomRight = cornerRadius.BottomRight + bottom;
}
if (cornerRadius.BottomLeft == 0)
{
BottomLeft = LeftBottom = 0;
}
else
{
BottomLeft = cornerRadius.BottomLeft + bottom;
LeftBottom = cornerRadius.BottomLeft + left;
}
}
else
{
LeftTop = Math.Max(0, cornerRadius.TopLeft - left);
TopLeft = Math.Max(0, cornerRadius.TopLeft - top);
TopRight = Math.Max(0, cornerRadius.TopRight - top);
RightTop = Math.Max(0, cornerRadius.TopRight - right);
RightBottom = Math.Max(0, cornerRadius.BottomRight - right);
BottomRight = Math.Max(0, cornerRadius.BottomRight - bottom);
BottomLeft = Math.Max(0, cornerRadius.BottomLeft - bottom);
LeftBottom = Math.Max(0, cornerRadius.BottomLeft - left);
}
}
internal readonly double LeftTop;
internal readonly double TopLeft;
internal readonly double TopRight;
internal readonly double RightTop;
internal readonly double RightBottom;
internal readonly double BottomRight;
internal readonly double BottomLeft;
internal readonly double LeftBottom;
}
}
}

700
src/Avalonia.Controls/Utils/GridLayout.cs

@ -0,0 +1,700 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia.Layout;
using JetBrains.Annotations;
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Contains algorithms that can help to measure and arrange a Grid.
/// </summary>
internal class GridLayout
{
/// <summary>
/// Initialize a new <see cref="GridLayout"/> instance from the column definitions.
/// The instance doesn't care about whether the definitions are rows or columns.
/// It will not calculate the column or row differently.
/// </summary>
internal GridLayout([NotNull] ColumnDefinitions columns)
{
if (columns == null) throw new ArgumentNullException(nameof(columns));
_conventions = columns.Count == 0
? new List<LengthConvention> { new LengthConvention() }
: columns.Select(x => new LengthConvention(x.Width, x.MinWidth, x.MaxWidth)).ToList();
}
/// <summary>
/// Initialize a new <see cref="GridLayout"/> instance from the row definitions.
/// The instance doesn't care about whether the definitions are rows or columns.
/// It will not calculate the column or row differently.
/// </summary>
internal GridLayout([NotNull] RowDefinitions rows)
{
if (rows == null) throw new ArgumentNullException(nameof(rows));
_conventions = rows.Count == 0
? new List<LengthConvention> { new LengthConvention() }
: rows.Select(x => new LengthConvention(x.Height, x.MinHeight, x.MaxHeight)).ToList();
}
/// <summary>
/// Gets the layout tolerance. If any length offset is less than this value, we will treat them the same.
/// </summary>
private const double LayoutTolerance = 1.0 / 256.0;
/// <summary>
/// Gets all the length conventions that come from column/row definitions.
/// These conventions provide cell limitations, such as the expected pixel length, the min/max pixel length and the * count.
/// </summary>
[NotNull]
private readonly List<LengthConvention> _conventions;
/// <summary>
/// Gets all the length conventions that come from the grid children.
/// </summary>
[NotNull]
private readonly List<AdditionalLengthConvention> _additionalConventions =
new List<AdditionalLengthConvention>();
/// <summary>
/// Appending these elements into the convention list helps lay them out according to their desired sizes.
/// <para/>
/// Some elements are not only in a single grid cell, they have one or more column/row spans,
/// and these elements may affect the grid layout especially the measuring procedure.<para/>
/// Append these elements into the convention list can help to layout them correctly through
/// their desired size. Only a small subset of children need to be measured before layout starts
/// and they will be called via the<paramref name="getDesiredLength"/> callback.
/// </summary>
/// <typeparam name="T">The grid children type.</typeparam>
/// <param name="source">
/// Contains the safe column/row index and its span.
/// Notice that we will not verify whether the range is in the column/row count,
/// so you should get the safe column/row info first.
/// </param>
/// <param name="getDesiredLength">
/// This callback will be called if the <see cref="GridLayout"/> thinks that a child should be
/// measured first. Usually, these are the children that have the * or Auto length.
/// </param>
internal void AppendMeasureConventions<T>([NotNull] IDictionary<T, (int index, int span)> source,
[NotNull] Func<T, double> getDesiredLength)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (getDesiredLength == null) throw new ArgumentNullException(nameof(getDesiredLength));
// M1/7. Find all the Auto and * length columns/rows. (M1/7 means the 1st procedure of measurement.)
// Only these columns/rows' layout can be affected by the child desired size.
//
// Find all columns/rows that have Auto or * length. We'll measure the children in advance.
// Only these kind of columns/rows will affect the Grid layout.
// Please note:
// - If the column / row has Auto length, the Grid.DesiredSize and the column width
// will be affected by the child's desired size.
// - If the column / row has* length, the Grid.DesiredSize will be affected by the
// child's desired size but the column width not.
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// _conventions: | min | max | | | min | | min max | max |
// _additionalC: |<- desired ->| |< desired >|
// _additionalC: |< desired >| |<- desired ->|
// 寻找所有行列范围中包含 Auto 和 * 的元素,使用全部可用尺寸提前测量。
// 因为只有这部分元素的布局才会被 Grid 的子元素尺寸影响。
// 请注意:
// - Auto 长度的行列必定会受到子元素布局影响,会影响到行列的布局长度和 Grid 本身的 DesiredSize;
// - 而对于 * 长度,只有 Grid.DesiredSize 会受到子元素布局影响,而行列长度不会受影响。
// Find all the Auto and * length columns/rows.
var found = new Dictionary<T, (int index, int span)>();
for (var i = 0; i < _conventions.Count; i++)
{
var index = i;
var convention = _conventions[index];
if (convention.Length.IsAuto || convention.Length.IsStar)
{
foreach (var pair in source.Where(x =>
x.Value.index <= index && index < x.Value.index + x.Value.span))
{
found[pair.Key] = pair.Value;
}
}
}
// Append these layout into the additional convention list.
foreach (var pair in found)
{
var t = pair.Key;
var (index, span) = pair.Value;
var desiredLength = getDesiredLength(t);
if (Math.Abs(desiredLength) > LayoutTolerance)
{
_additionalConventions.Add(new AdditionalLengthConvention(index, span, desiredLength));
}
}
}
/// <summary>
/// Run measure procedure according to the <paramref name="containerLength"/> and gets the <see cref="MeasureResult"/>.
/// </summary>
/// <param name="containerLength">
/// The container length. Usually, it is the constraint of the <see cref="Layoutable.MeasureOverride"/> method.
/// </param>
/// <returns>
/// The measured result that containing the desired size and all the column/row lengths.
/// </returns>
[NotNull, Pure]
internal MeasureResult Measure(double containerLength)
{
// Prepare all the variables that this method needs to use.
var conventions = _conventions.Select(x => x.Clone()).ToList();
var starCount = conventions.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
var aggregatedLength = 0.0;
double starUnitLength;
// M2/7. Aggregate all the pixel lengths. Then we can get the remaining length by `containerLength - aggregatedLength`.
// We mark the aggregated length as "fix" because we can completely determine their values. Same as below.
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// |#fix#| |#fix#|
//
// 将全部的固定像素长度的行列长度累加。这样,containerLength - aggregatedLength 便能得到剩余长度。
// 我们会将所有能够确定下长度的行列标记为 fix。下同。
// 请注意:
// - 我们并没有直接从 containerLength 一直减下去,而是使用 aggregatedLength 进行累加,是因为无穷大相减得到的是 NaN,不利于后续计算。
aggregatedLength += conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
// M3/7. Fix all the * lengths that have reached the minimum.
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// | min | max | | | min | | min max | max |
// | fix | |#fix#| fix |
var shouldTestStarMin = true;
while (shouldTestStarMin)
{
// Calculate the unit * length to estimate the length of each column/row that has * length.
// Under this estimated length, check if there is a minimum value that has a length less than its constraint.
// If there is such a *, then fix the size of this cell, and then loop it again until there is no * that can be constrained by the minimum value.
//
// 计算单位 * 的长度,以便预估出每一个 * 行列的长度。
// 在此预估的长度下,从前往后寻找是否存在某个 * 长度已经小于其约束的最小值。
// 如果发现存在这样的 *,那么将此单元格的尺寸固定下来(Fix),然后循环重来,直至再也没有能被最小值约束的 *。
var @fixed = false;
starUnitLength = (containerLength - aggregatedLength) / starCount;
foreach (var convention in conventions.Where(x => x.Length.IsStar))
{
var (star, min) = (convention.Length.Value, convention.MinLength);
var starLength = star * starUnitLength;
if (starLength < min)
{
convention.Fix(min);
starLength = min;
aggregatedLength += starLength;
starCount -= star;
@fixed = true;
break;
}
}
shouldTestStarMin = @fixed;
}
// M4/7. Determine the absolute pixel size of all columns/rows that have an Auto length.
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// | min | max | | | min | | min max | max |
// |#fix#| | fix |#fix#| fix | fix |
var shouldTestAuto = true;
while (shouldTestAuto)
{
var @fixed = false;
starUnitLength = (containerLength - aggregatedLength) / starCount;
for (var i = 0; i < conventions.Count; i++)
{
var convention = conventions[i];
if (!convention.Length.IsAuto)
{
continue;
}
var more = ApplyAdditionalConventionsForAuto(conventions, i, starUnitLength);
convention.Fix(more);
aggregatedLength += more;
@fixed = true;
break;
}
shouldTestAuto = @fixed;
}
// M5/7. Expand the stars according to the additional conventions (usually the child desired length).
// We can't fix this kind of length, so we just mark them as desired (des).
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// | min | max | | | min | | min max | max |
// |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#|
var desiredStarMin = AggregateAdditionalConventionsForStars(conventions);
aggregatedLength += desiredStarMin;
// M6/7. Determine the desired length of the grid for current container length. Its value is stored in desiredLength.
// Assume if the container has infinite length, the grid desired length is stored in greedyDesiredLength.
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// | min | max | | | min | | min max | max |
// |#des#| fix |#des#| fix | fix | fix | fix | #des# |#des#|
// Note: This table will be stored as the intermediate result into the MeasureResult and it will be reused by Arrange procedure.
//
// desiredLength = Math.Max(0.0, des + fix + des + fix + fix + fix + fix + des + des)
// greedyDesiredLength = des + fix + des + fix + fix + fix + fix + des + des
var desiredLength = containerLength - aggregatedLength >= 0.0 ? aggregatedLength : containerLength;
var greedyDesiredLength = aggregatedLength;
// M7/7. Expand all the rest stars. These stars have no conventions or only have
// max value they can be expanded from zero to constraint.
//
// +-----------------------------------------------------------+
// | * | A | * | P | A | * | P | * | * |
// +-----------------------------------------------------------+
// | min | max | | | min | | min max | max |
// |#fix#| fix |#fix#| fix | fix | fix | fix | #fix# |#fix#|
// Note: This table will be stored as the final result into the MeasureResult.
var dynamicConvention = ExpandStars(conventions, containerLength);
Clip(dynamicConvention, containerLength);
// Returns the measuring result.
return new MeasureResult(containerLength, desiredLength, greedyDesiredLength,
conventions, dynamicConvention);
}
/// <summary>
/// Run arrange procedure according to the <paramref name="measure"/> and gets the <see cref="ArrangeResult"/>.
/// </summary>
/// <param name="finalLength">
/// The container length. Usually, it is the finalSize of the <see cref="Layoutable.ArrangeOverride"/> method.
/// </param>
/// <param name="measure">
/// The result that the measuring procedure returns. If it is null, a new measure procedure will run.
/// </param>
/// <returns>
/// The measured result that containing the desired size and all the column/row length.
/// </returns>
[NotNull, Pure]
public ArrangeResult Arrange(double finalLength, [CanBeNull] MeasureResult measure)
{
measure = measure ?? Measure(finalLength);
// If the arrange final length does not equal to the measure length, we should measure again.
if (finalLength - measure.ContainerLength > LayoutTolerance)
{
// If the final length is larger, we will rerun the whole measure.
measure = Measure(finalLength);
}
else if (finalLength - measure.ContainerLength < -LayoutTolerance)
{
// If the final length is smaller, we measure the M6/6 procedure only.
var dynamicConvention = ExpandStars(measure.LeanLengthList, finalLength);
measure = new MeasureResult(finalLength, measure.DesiredLength, measure.GreedyDesiredLength,
measure.LeanLengthList, dynamicConvention);
}
return new ArrangeResult(measure.LengthList);
}
/// <summary>
/// Use the <see cref="_additionalConventions"/> to calculate the fixed length of the Auto column/row.
/// </summary>
/// <param name="conventions">The convention list that all the * with minimum length are fixed.</param>
/// <param name="index">The column/row index that should be fixed.</param>
/// <param name="starUnitLength">The unit * length for the current rest length.</param>
/// <returns>The final length of the Auto length column/row.</returns>
[Pure]
private double ApplyAdditionalConventionsForAuto(IReadOnlyList<LengthConvention> conventions,
int index, double starUnitLength)
{
// 1. Calculate all the * length with starUnitLength.
// 2. Exclude all the fixed length and all the * length.
// 3. Compare the rest of the desired length and the convention.
// +-----------------+
// | * | A | * |
// +-----------------+
// | exl | | exl |
// |< desired >|
// |< desired >|
var more = 0.0;
foreach (var additional in _additionalConventions)
{
// If the additional convention's last column/row contains the Auto column/row, try to determine the Auto column/row length.
if (index == additional.Index + additional.Span - 1)
{
var min = Enumerable.Range(additional.Index, additional.Span)
.Select(x =>
{
var c = conventions[x];
if (c.Length.IsAbsolute) return c.Length.Value;
if (c.Length.IsStar) return c.Length.Value * starUnitLength;
return 0.0;
}).Sum();
more = Math.Max(additional.Min - min, more);
}
}
return Math.Min(conventions[index].MaxLength, more);
}
/// <summary>
/// Calculate the total desired length of all the * length.
/// Bug Warning:
/// - The behavior of this method is undefined! Different UI Frameworks have different behaviors.
/// - We ignore all the span columns/rows and just take single cells into consideration.
/// </summary>
/// <param name="conventions">All the conventions that have almost been fixed except the rest *.</param>
/// <returns>The total desired length of all the * length.</returns>
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
private double AggregateAdditionalConventionsForStars(
IReadOnlyList<LengthConvention> conventions)
{
// 1. Determine all one-span column's desired widths or row's desired heights.
// 2. Order the multi-span conventions by its last index
// (Notice that the sorted data is much smaller than the source.)
// 3. Determine each multi-span last index by calculating the maximun desired size.
// Before we determine the behavior of this method, we just aggregate the one-span * columns.
var fixedLength = conventions.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
// Prepare a lengthList variable indicating the fixed length of each column/row.
var lengthList = conventions.Select(x => x.Length.IsAbsolute ? x.Length.Value : 0.0).ToList();
foreach (var group in _additionalConventions
.Where(x => x.Span == 1 && conventions[x.Index].Length.IsStar)
.ToLookup(x => x.Index))
{
lengthList[group.Key] = Math.Max(lengthList[group.Key], group.Max(x => x.Min));
}
// Now the lengthList is fixed by every one-span columns/rows.
// Then we should determine the multi-span column's/row's length.
foreach (var group in _additionalConventions
.Where(x => x.Span > 1)
.ToLookup(x => x.Index + x.Span - 1)
// Order the multi-span columns/rows by last index.
.OrderBy(x => x.Key))
{
var length = group.Max(x => x.Min - Enumerable.Range(x.Index, x.Span - 1).Sum(r => lengthList[r]));
lengthList[group.Key] = Math.Max(lengthList[group.Key], length > 0 ? length : 0);
}
return lengthList.Sum() - fixedLength;
}
/// <summary>
/// This method implements the last procedure (M7/7) of measure.
/// It expands all the * length to the fixed length according to the <paramref name="constraint"/>.
/// </summary>
/// <param name="conventions">All the conventions that have almost been fixed except the remaining *.</param>
/// <param name="constraint">The container length.</param>
/// <returns>The final pixel length list.</returns>
[Pure]
private static List<double> ExpandStars(IEnumerable<LengthConvention> conventions, double constraint)
{
// Initial.
var dynamicConvention = conventions.Select(x => x.Clone()).ToList();
constraint -= dynamicConvention.Where(x => x.Length.IsAbsolute).Sum(x => x.Length.Value);
var starUnitLength = 0.0;
// M6/6.
if (constraint >= 0)
{
var starCount = dynamicConvention.Where(x => x.Length.IsStar).Sum(x => x.Length.Value);
var shouldTestStarMax = true;
while (shouldTestStarMax)
{
var @fixed = false;
starUnitLength = constraint / starCount;
foreach (var convention in dynamicConvention.Where(x =>
x.Length.IsStar && !double.IsPositiveInfinity(x.MaxLength)))
{
var (star, max) = (convention.Length.Value, convention.MaxLength);
var starLength = star * starUnitLength;
if (starLength > max)
{
convention.Fix(max);
starLength = max;
constraint -= starLength;
starCount -= star;
@fixed = true;
break;
}
}
shouldTestStarMax = @fixed;
}
}
Debug.Assert(dynamicConvention.All(x => !x.Length.IsAuto));
var starUnit = starUnitLength;
var result = dynamicConvention.Select(x =>
{
if (x.Length.IsStar)
{
return double.IsInfinity(starUnit) ? double.PositiveInfinity : starUnit * x.Length.Value;
}
return x.Length.Value;
}).ToList();
return result;
}
/// <summary>
/// If the container length is not infinity. It may be not enough to contain all the columns/rows.
/// We should clip the columns/rows that have been out of the container bounds.
/// Note: This method may change the items value of <paramref name="lengthList"/>.
/// </summary>
/// <param name="lengthList">A list of all the column widths and row heights with a fixed pixel length</param>
/// <param name="constraint">the container length. It can be positive infinity.</param>
private static void Clip([NotNull] IList<double> lengthList, double constraint)
{
if (double.IsInfinity(constraint))
{
return;
}
var measureLength = 0.0;
for (var i = 0; i < lengthList.Count; i++)
{
var length = lengthList[i];
if (constraint - measureLength > length)
{
measureLength += length;
}
else
{
lengthList[i] = constraint - measureLength;
measureLength = constraint;
}
}
}
/// <summary>
/// Contains the convention of each column/row.
/// This is mostly the same as <see cref="RowDefinition"/> or <see cref="ColumnDefinition"/>.
/// We use this because we can treat the column and the row the same.
/// </summary>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
internal class LengthConvention : ICloneable
{
/// <summary>
/// Initialize a new instance of <see cref="LengthConvention"/>.
/// </summary>
public LengthConvention()
{
Length = new GridLength(1.0, GridUnitType.Star);
MinLength = 0.0;
MaxLength = double.PositiveInfinity;
}
/// <summary>
/// Initialize a new instance of <see cref="LengthConvention"/>.
/// </summary>
public LengthConvention(GridLength length, double minLength, double maxLength)
{
Length = length;
MinLength = minLength;
MaxLength = maxLength;
if (length.IsAbsolute)
{
_isFixed = true;
}
}
/// <summary>
/// Gets the <see cref="GridLength"/> of a column or a row.
/// </summary>
internal GridLength Length { get; private set; }
/// <summary>
/// Gets the minimum convention for a column or a row.
/// </summary>
internal double MinLength { get; }
/// <summary>
/// Gets the maximum convention for a column or a row.
/// </summary>
internal double MaxLength { get; }
/// <summary>
/// Fix the <see cref="LengthConvention"/>.
/// If all columns/rows are fixed, we can get the size of all columns/rows in pixels.
/// </summary>
/// <param name="pixel">
/// The pixel length that should be used to fix the convention.
/// </param>
/// <exception cref="InvalidOperationException">
/// If the convention is pixel length, this exception will throw.
/// </exception>
public void Fix(double pixel)
{
if (_isFixed)
{
throw new InvalidOperationException("Cannot fix the length convention if it is fixed.");
}
Length = new GridLength(pixel);
_isFixed = true;
}
/// <summary>
/// Gets a value that indicates whether this convention is fixed.
/// </summary>
private bool _isFixed;
/// <summary>
/// Helps the debugger to display the intermediate column/row calculation result.
/// </summary>
private string DebuggerDisplay =>
$"{(_isFixed ? Length.Value.ToString(CultureInfo.InvariantCulture) : (Length.GridUnitType == GridUnitType.Auto ? "Auto" : $"{Length.Value}*"))}, ∈[{MinLength}, {MaxLength}]";
/// <inheritdoc />
object ICloneable.Clone() => Clone();
/// <summary>
/// Get a deep copy of this convention list.
/// We need this because we want to store some intermediate states.
/// </summary>
internal LengthConvention Clone() => new LengthConvention(Length, MinLength, MaxLength);
}
/// <summary>
/// Contains the convention that comes from the grid children.
/// Some children span multiple columns or rows, so even a simple column/row can have multiple conventions.
/// </summary>
[DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")]
internal struct AdditionalLengthConvention
{
/// <summary>
/// Initialize a new instance of <see cref="AdditionalLengthConvention"/>.
/// </summary>
public AdditionalLengthConvention(int index, int span, double min)
{
Index = index;
Span = span;
Min = min;
}
/// <summary>
/// Gets the start index of this additional convention.
/// </summary>
public int Index { get; }
/// <summary>
/// Gets the span of this additional convention.
/// </summary>
public int Span { get; }
/// <summary>
/// Gets the minimum length of this additional convention.
/// This value is usually provided by the child's desired length.
/// </summary>
public double Min { get; }
/// <summary>
/// Helps the debugger to display the intermediate column/row calculation result.
/// </summary>
private string DebuggerDisplay =>
$"{{{string.Join(",", Enumerable.Range(Index, Span))}}}, ∈[{Min},∞)";
}
/// <summary>
/// Stores the result of the measuring procedure.
/// This result can be used to measure children and assign the desired size.
/// Passing this result to <see cref="Arrange"/> can reduce calculation.
/// </summary>
[DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
internal class MeasureResult
{
/// <summary>
/// Initialize a new instance of <see cref="MeasureResult"/>.
/// </summary>
internal MeasureResult(double containerLength, double desiredLength, double greedyDesiredLength,
IReadOnlyList<LengthConvention> leanConventions, IReadOnlyList<double> expandedConventions)
{
ContainerLength = containerLength;
DesiredLength = desiredLength;
GreedyDesiredLength = greedyDesiredLength;
LeanLengthList = leanConventions;
LengthList = expandedConventions;
}
/// <summary>
/// Gets the container length for this result.
/// This property will be used by <see cref="Arrange"/> to determine whether to measure again or not.
/// </summary>
public double ContainerLength { get; }
/// <summary>
/// Gets the desired length of this result.
/// Just return this value as the desired size in <see cref="Layoutable.MeasureOverride"/>.
/// </summary>
public double DesiredLength { get; }
/// <summary>
/// Gets the desired length if the container has infinite length.
/// </summary>
public double GreedyDesiredLength { get; }
/// <summary>
/// Contains the column/row calculation intermediate result.
/// This value is used by <see cref="Arrange"/> for reducing repeat calculation.
/// </summary>
public IReadOnlyList<LengthConvention> LeanLengthList { get; }
/// <summary>
/// Gets the length list for each column/row.
/// </summary>
public IReadOnlyList<double> LengthList { get; }
}
/// <summary>
/// Stores the result of the measuring procedure.
/// This result can be used to arrange children and assign the render size.
/// </summary>
[DebuggerDisplay("{" + nameof(LengthList) + ",nq}")]
internal class ArrangeResult
{
/// <summary>
/// Initialize a new instance of <see cref="ArrangeResult"/>.
/// </summary>
internal ArrangeResult(IReadOnlyList<double> lengthList)
{
LengthList = lengthList;
}
/// <summary>
/// Gets the length list for each column/row.
/// </summary>
public IReadOnlyList<double> LengthList { get; }
}
}
}

64
src/Avalonia.Controls/Utils/ISelectionAdapter.cs

@ -0,0 +1,64 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Collections;
using Avalonia.Interactivity;
using Avalonia.Input;
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Defines an item collection, selection members, and key handling for the
/// selection adapter contained in the drop-down portion of an
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
public interface ISelectionAdapter
{
/// <summary>
/// Gets or sets the selected item.
/// </summary>
/// <value>The currently selected item.</value>
object SelectedItem { get; set; }
/// <summary>
/// Occurs when the
/// <see cref="P:Avalonia.Controls.Utils.ISelectionAdapter.SelectedItem" />
/// property value changes.
/// </summary>
event EventHandler<SelectionChangedEventArgs> SelectionChanged;
/// <summary>
/// Gets or sets a collection that is used to generate content for the
/// selection adapter.
/// </summary>
/// <value>The collection that is used to generate content for the
/// selection adapter.</value>
IEnumerable Items { get; set; }
/// <summary>
/// Occurs when a selected item is not cancelled and is committed as the
/// selected item.
/// </summary>
event EventHandler<RoutedEventArgs> Commit;
/// <summary>
/// Occurs when a selection has been canceled.
/// </summary>
event EventHandler<RoutedEventArgs> Cancel;
/// <summary>
/// Provides handling for the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
/// when a key is pressed while the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
/// </summary>
/// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
/// that contains data about the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
void HandleKeyDown(KeyEventArgs e);
}
}

342
src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs

@ -0,0 +1,342 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Input;
using Avalonia.LogicalTree;
using System.Collections;
using System.Diagnostics;
namespace Avalonia.Controls.Utils
{
/// <summary>
/// Represents the selection adapter contained in the drop-down portion of
/// an <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control.
/// </summary>
public class SelectingItemsControlSelectionAdapter : ISelectionAdapter
{
/// <summary>
/// The SelectingItemsControl instance.
/// </summary>
private SelectingItemsControl _selector;
/// <summary>
/// Gets or sets a value indicating whether the selection change event
/// should not be fired.
/// </summary>
private bool IgnoringSelectionChanged { get; set; }
/// <summary>
/// Gets or sets the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
/// <value>The underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.</value>
public SelectingItemsControl SelectorControl
{
get { return _selector; }
set
{
if (_selector != null)
{
_selector.SelectionChanged -= OnSelectionChanged;
_selector.PointerReleased -= OnSelectorPointerReleased;
}
_selector = value;
if (_selector != null)
{
_selector.SelectionChanged += OnSelectionChanged;
_selector.PointerReleased += OnSelectorPointerReleased;
}
}
}
/// <summary>
/// Occurs when the
/// <see cref="P:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.SelectedItem" />
/// property value changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
/// <summary>
/// Occurs when an item is selected and is committed to the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
public event EventHandler<RoutedEventArgs> Commit;
/// <summary>
/// Occurs when a selection is canceled before it is committed.
/// </summary>
public event EventHandler<RoutedEventArgs> Cancel;
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />
/// class.
/// </summary>
public SelectingItemsControlSelectionAdapter()
{
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapterr" />
/// class with the specified
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
/// <param name="selector">The
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" /> control
/// to wrap as a
/// <see cref="T:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter" />.</param>
public SelectingItemsControlSelectionAdapter(SelectingItemsControl selector)
{
SelectorControl = selector;
}
/// <summary>
/// Gets or sets the selected item of the selection adapter.
/// </summary>
/// <value>The selected item of the underlying selection adapter.</value>
public object SelectedItem
{
get
{
return SelectorControl?.SelectedItem;
}
set
{
IgnoringSelectionChanged = true;
if (SelectorControl != null)
{
SelectorControl.SelectedItem = value;
}
// Attempt to reset the scroll viewer's position
if (value == null)
{
ResetScrollViewer();
}
IgnoringSelectionChanged = false;
}
}
/// <summary>
/// Gets or sets a collection that is used to generate the content of
/// the selection adapter.
/// </summary>
/// <value>The collection used to generate content for the selection
/// adapter.</value>
public IEnumerable Items
{
get
{
return SelectorControl?.Items;
}
set
{
if (SelectorControl != null)
{
SelectorControl.Items = value;
}
}
}
/// <summary>
/// If the control contains a ScrollViewer, this will reset the viewer
/// to be scrolled to the top.
/// </summary>
private void ResetScrollViewer()
{
if (SelectorControl != null)
{
ScrollViewer sv = SelectorControl.GetLogicalDescendants().OfType<ScrollViewer>().FirstOrDefault();
if (sv != null)
{
sv.Offset = new Vector(0, 0);
}
}
}
/// <summary>
/// Handles the mouse left button up event on the selector control.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnSelectorPointerReleased(object sender, PointerReleasedEventArgs e)
{
if (e.MouseButton == MouseButton.Left)
{
OnCommit();
}
}
/// <summary>
/// Handles the SelectionChanged event on the SelectingItemsControl control.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The selection changed event data.</param>
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (IgnoringSelectionChanged)
{
return;
}
SelectionChanged?.Invoke(sender, e);
}
/// <summary>
/// Increments the
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
/// property of the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
protected void SelectedIndexIncrement()
{
if (SelectorControl != null)
{
SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.ItemCount ? -1 : SelectorControl.SelectedIndex + 1;
}
}
/// <summary>
/// Decrements the
/// <see cref="P:Avalonia.Controls.Primitives.SelectingItemsControl.SelectedIndex" />
/// property of the underlying
/// <see cref="T:Avalonia.Controls.Primitives.SelectingItemsControl" />
/// control.
/// </summary>
protected void SelectedIndexDecrement()
{
if (SelectorControl != null)
{
int index = SelectorControl.SelectedIndex;
if (index >= 0)
{
SelectorControl.SelectedIndex--;
}
else if (index == -1)
{
SelectorControl.SelectedIndex = SelectorControl.ItemCount - 1;
}
}
}
/// <summary>
/// Provides handling for the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event that occurs
/// when a key is pressed while the drop-down portion of the
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> has focus.
/// </summary>
/// <param name="e">A <see cref="T:Avalonia.Input.KeyEventArgs" />
/// that contains data about the
/// <see cref="E:Avalonia.Input.InputElement.KeyDown" /> event.</param>
public void HandleKeyDown(KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
OnCommit();
e.Handled = true;
break;
case Key.Up:
SelectedIndexDecrement();
e.Handled = true;
break;
case Key.Down:
if ((e.Modifiers & InputModifiers.Alt) == InputModifiers.None)
{
SelectedIndexIncrement();
e.Handled = true;
}
break;
case Key.Escape:
OnCancel();
e.Handled = true;
break;
default:
break;
}
}
/// <summary>
/// Raises the
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Commit" />
/// event.
/// </summary>
protected virtual void OnCommit()
{
OnCommit(this, new RoutedEventArgs());
}
/// <summary>
/// Fires the Commit event.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnCommit(object sender, RoutedEventArgs e)
{
Commit?.Invoke(sender, e);
AfterAdapterAction();
}
/// <summary>
/// Raises the
/// <see cref="E:Avalonia.Controls.Utils.SelectingItemsControlSelectionAdapter.Cancel" />
/// event.
/// </summary>
protected virtual void OnCancel()
{
OnCancel(this, new RoutedEventArgs());
}
/// <summary>
/// Fires the Cancel event.
/// </summary>
/// <param name="sender">The source object.</param>
/// <param name="e">The event data.</param>
private void OnCancel(object sender, RoutedEventArgs e)
{
Cancel?.Invoke(sender, e);
AfterAdapterAction();
}
/// <summary>
/// Change the selection after the actions are complete.
/// </summary>
private void AfterAdapterAction()
{
IgnoringSelectionChanged = true;
if (SelectorControl != null)
{
SelectorControl.SelectedItem = null;
SelectorControl.SelectedIndex = -1;
}
IgnoringSelectionChanged = false;
}
}
}

8
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@ -59,7 +59,7 @@ namespace Avalonia.Controls.Utils
public void UpdateLastState()
{
_states.Last.Value = _host.UndoRedoState;
UpdateLastState(_host.UndoRedoState);
}
public void DiscardRedo()
@ -91,6 +91,12 @@ namespace Avalonia.Controls.Utils
}
}
public void Clear()
{
_states.Clear();
_currentNode = null;
}
bool WeakTimer.IWeakTimerSubscriber.Tick()
{
Snapshot();

101
src/Avalonia.Controls/Window.cs

@ -13,6 +13,7 @@ using Avalonia.Styling;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using System.ComponentModel;
namespace Avalonia.Controls
{
@ -85,9 +86,22 @@ namespace Avalonia.Controls
public static readonly StyledProperty<WindowIcon> IconProperty =
AvaloniaProperty.Register<Window, WindowIcon>(nameof(Icon));
/// <summary>
/// Defines the <see cref="WindowStartupLocation"/> proeprty.
/// </summary>
public static readonly DirectProperty<Window, WindowStartupLocation> WindowStartupLocationProperty =
AvaloniaProperty.RegisterDirect<Window, WindowStartupLocation>(
nameof(WindowStartupLocation),
o => o.WindowStartupLocation,
(o, v) => o.WindowStartupLocation = v);
public static readonly StyledProperty<bool> CanResizeProperty =
AvaloniaProperty.Register<Window, bool>(nameof(CanResize), true);
private readonly NameScope _nameScope = new NameScope();
private object _dialogResult;
private readonly Size _maxPlatformClientSize;
private WindowStartupLocation _windowStartupLoction;
/// <summary>
/// Initializes static members of the <see cref="Window"/> class.
@ -102,6 +116,8 @@ namespace Avalonia.Controls
ShowInTaskbarProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.ShowTaskbarIcon((bool)e.NewValue));
IconProperty.Changed.AddClassHandler<Window>((s, e) => s.PlatformImpl?.SetIcon(((WindowIcon)e.NewValue).PlatformImpl));
CanResizeProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.CanResize((bool)e.NewValue));
}
/// <summary>
@ -119,6 +135,7 @@ namespace Avalonia.Controls
public Window(IWindowImpl impl)
: base(impl)
{
impl.Closing = HandleClosing;
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
Screens = new Screens(PlatformImpl?.Screen);
}
@ -196,6 +213,17 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Enables or disables resizing of the window.
/// Note that if <see cref="HasSystemDecorations"/> is set to False then this property
/// has no effect and should be treated as a recommendation for the user setting HasSystemDecorations.
/// </summary>
public bool CanResize
{
get { return GetValue(CanResizeProperty); }
set { SetValue(CanResizeProperty, value); }
}
/// <summary>
/// Gets or sets the icon of the window.
/// </summary>
@ -205,26 +233,38 @@ namespace Avalonia.Controls
set { SetValue(IconProperty, value); }
}
/// <summary>
/// Gets or sets the startup location of the window.
/// </summary>
public WindowStartupLocation WindowStartupLocation
{
get { return _windowStartupLoction; }
set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLoction, value); }
}
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
/// <inheritdoc/>
Type IStyleable.StyleKey => typeof(Window);
/// <summary>
/// Fired before a window is closed.
/// </summary>
public event EventHandler<CancelEventArgs> Closing;
/// <summary>
/// Closes the window.
/// </summary>
public void Close()
{
s_windows.Remove(this);
PlatformImpl?.Dispose();
IsVisible = false;
Close(false);
}
protected override void HandleApplicationExiting()
{
base.HandleApplicationExiting();
Close();
Close(true);
}
/// <summary>
@ -239,7 +279,35 @@ namespace Avalonia.Controls
public void Close(object dialogResult)
{
_dialogResult = dialogResult;
Close();
Close(false);
}
internal void Close(bool ignoreCancel)
{
var cancelClosing = false;
try
{
cancelClosing = HandleClosing();
}
finally
{
if (ignoreCancel || !cancelClosing)
{
s_windows.Remove(this);
PlatformImpl?.Dispose();
IsVisible = false;
}
}
}
/// <summary>
/// Handles a closing notification from <see cref="IWindowImpl.Closing"/>.
/// </summary>
protected virtual bool HandleClosing()
{
var args = new CancelEventArgs();
Closing?.Invoke(this, args);
return args.Cancel;
}
/// <summary>
@ -274,6 +342,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -314,6 +383,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -337,7 +407,7 @@ namespace Avalonia.Controls
modal?.Dispose();
SetIsEnabled(affectedWindows, true);
activated?.Activate();
result.SetResult((TResult)_dialogResult);
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
return result.Task;
@ -352,6 +422,25 @@ namespace Avalonia.Controls
}
}
void SetWindowStartupLocation()
{
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(Bounds.Position);
if (screen != null)
Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
{
if (Owner != null)
{
var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
}
}
}
/// <inheritdoc/>
void INameScope.Register(string name, object element)
{

24
src/Avalonia.Controls/WindowBase.cs

@ -29,14 +29,29 @@ namespace Avalonia.Controls
public static readonly DirectProperty<WindowBase, bool> IsActiveProperty =
AvaloniaProperty.RegisterDirect<WindowBase, bool>(nameof(IsActive), o => o.IsActive);
/// <summary>
/// Defines the <see cref="Owner"/> property.
/// </summary>
public static readonly DirectProperty<WindowBase, WindowBase> OwnerProperty =
AvaloniaProperty.RegisterDirect<WindowBase, WindowBase>(
nameof(Owner),
o => o.Owner,
(o, v) => o.Owner = v);
private bool _hasExecutedInitialLayoutPass;
private bool _isActive;
private bool _ignoreVisibilityChange;
private WindowBase _owner;
static WindowBase()
{
IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
MinWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
}
public WindowBase(IWindowBaseImpl impl) : this(impl, AvaloniaLocator.Current)
@ -100,6 +115,15 @@ namespace Avalonia.Controls
private set;
}
/// <summary>
/// Gets or sets the owner of the window.
/// </summary>
public WindowBase Owner
{
get { return _owner; }
set { SetAndRaise(OwnerProperty, ref _owner, value); }
}
/// <summary>
/// Activates the window.
/// </summary>

23
src/Avalonia.Controls/WindowStartupLocation.cs

@ -0,0 +1,23 @@
namespace Avalonia.Controls
{
/// <summary>
/// Determines the startup location of the window.
/// </summary>
public enum WindowStartupLocation
{
/// <summary>
/// The startup location is defined by the Position property.
/// </summary>
Manual,
/// <summary>
/// The startup location is the center of the screen.
/// </summary>
CenterScreen,
/// <summary>
/// The startup location is the center of the owner window. If the owner window is not specified, the startup location will be <see cref="Manual"/>.
/// </summary>
CenterOwner
}
}

8
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
@ -30,7 +31,8 @@ namespace Avalonia.DesignerSupport
new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(assemblyPath));
}
var loaded = loader.Load(stream, null, baseUri);
var localAsm = assemblyPath != null ? Assembly.LoadFile(Path.GetFullPath(assemblyPath)) : null;
var loaded = loader.Load(stream, localAsm, null, baseUri);
var styles = loaded as Styles;
if (styles != null)
{
@ -54,7 +56,7 @@ namespace Avalonia.DesignerSupport
}
};
}
if (loaded is Application)
else if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"};
else
control = (Control) loaded;
@ -73,4 +75,4 @@ namespace Avalonia.DesignerSupport
return window;
}
}
}
}

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

Loading…
Cancel
Save