Browse Source

Merge branch 'master' into local-assembly-for-xaml

Conflicts:
	samples/BindingTest/MainWindow.xaml
	src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoaderPortableXaml.cs
pull/1389/head
Steven Kirk 8 years ago
parent
commit
92cd84a353
  1. 17
      .github/PULL_REQUEST_TEMPLATE.md
  2. 55
      Avalonia.sln
  3. 1
      build/Rx.props
  4. 5
      build/System.Drawing.Common.props
  5. 7
      packages.cake
  6. 2
      parameters.cake
  7. 46
      readme.md
  8. 8
      samples/BindingTest/MainWindow.xaml
  9. 1
      samples/ControlCatalog.Desktop/Program.cs
  10. 2
      samples/ControlCatalog.NetCore/Program.cs
  11. 5
      samples/ControlCatalog.iOS/ControlCatalog.iOS.csproj
  12. 188
      samples/ControlCatalog/ControlCatalog.csproj
  13. 7
      samples/ControlCatalog/MainView.xaml
  14. 59
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
  15. 143
      samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
  16. 24
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
  17. 54
      samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml.cs
  18. 46
      samples/ControlCatalog/Pages/DatePickerPage.xaml
  19. 36
      samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
  20. 19
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  21. 71
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  22. 80
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml
  23. 94
      samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
  24. 36
      samples/ControlCatalog/Properties/AssemblyInfo.cs
  25. 4
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  26. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  27. 9
      src/Avalonia.Base/AttachedProperty.cs
  28. 50
      src/Avalonia.Base/AvaloniaObject.cs
  29. 8
      src/Avalonia.Base/AvaloniaProperty.cs
  30. 283
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  31. 4
      src/Avalonia.Base/Collections/AvaloniaDictionary.cs
  32. 26
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  33. 127
      src/Avalonia.Base/Collections/NotifyCollectionChangedExtensions.cs
  34. 3
      src/Avalonia.Base/DirectProperty.cs
  35. 7
      src/Avalonia.Base/IDirectPropertyAccessor.cs
  36. 2
      src/Avalonia.Base/Threading/Dispatcher.cs
  37. 205
      src/Avalonia.Base/Utilities/StringTokenizer.cs
  38. 9
      src/Avalonia.Controls/Application.cs
  39. 2726
      src/Avalonia.Controls/AutoCompleteBox.cs
  40. 51
      src/Avalonia.Controls/Border.cs
  41. 2
      src/Avalonia.Controls/Button.cs
  42. 263
      src/Avalonia.Controls/ButtonSpinner.cs
  43. 1188
      src/Avalonia.Controls/Calendar/DatePicker.cs
  44. 2
      src/Avalonia.Controls/ColumnDefinitions.cs
  45. 7
      src/Avalonia.Controls/ContextMenu.cs
  46. 1
      src/Avalonia.Controls/DropDown.cs
  47. 19
      src/Avalonia.Controls/GridLength.cs
  48. 12
      src/Avalonia.Controls/ItemsControl.cs
  49. 6
      src/Avalonia.Controls/LayoutTransformControl.cs
  50. 35
      src/Avalonia.Controls/MenuItem.cs
  51. 998
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  52. 16
      src/Avalonia.Controls/NumericUpDown/NumericUpDownValueChangedEventArgs.cs
  53. 10
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  54. 11
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  55. 210
      src/Avalonia.Controls/Platform/InProcessDragSource.cs
  56. 153
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  57. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  58. 66
      src/Avalonia.Controls/Primitives/Popup.cs
  59. 26
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  60. 8
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  61. 26
      src/Avalonia.Controls/ProgressBar.cs
  62. 4
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  63. 29
      src/Avalonia.Controls/RepeatButton.cs
  64. 2
      src/Avalonia.Controls/RowDefinitions.cs
  65. 2
      src/Avalonia.Controls/Screens.cs
  66. 174
      src/Avalonia.Controls/Spinner.cs
  67. 4
      src/Avalonia.Controls/TextBlock.cs
  68. 37
      src/Avalonia.Controls/TextBox.cs
  69. 4
      src/Avalonia.Controls/UserControl.cs
  70. 279
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  71. 64
      src/Avalonia.Controls/Utils/ISelectionAdapter.cs
  72. 342
      src/Avalonia.Controls/Utils/SelectingItemsControlSelectionAdapter.cs
  73. 8
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs
  74. 99
      src/Avalonia.Controls/Window.cs
  75. 24
      src/Avalonia.Controls/WindowBase.cs
  76. 23
      src/Avalonia.Controls/WindowStartupLocation.cs
  77. 4
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  78. 9
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  79. 9
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  80. 2
      src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs
  81. 3
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  82. 2
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  83. 5
      src/Avalonia.Input/Cursors.cs
  84. 15
      src/Avalonia.Input/DataFormats.cs
  85. 43
      src/Avalonia.Input/DataObject.cs
  86. 54
      src/Avalonia.Input/DragDrop.cs
  87. 111
      src/Avalonia.Input/DragDropDevice.cs
  88. 13
      src/Avalonia.Input/DragDropEffects.cs
  89. 18
      src/Avalonia.Input/DragEventArgs.cs
  90. 39
      src/Avalonia.Input/IDataObject.cs
  91. 2
      src/Avalonia.Input/KeyboardDevice.cs
  92. 17
      src/Avalonia.Input/Navigation/TabNavigation.cs
  93. 14
      src/Avalonia.Input/Platform/IPlatformDragSource.cs
  94. 8
      src/Avalonia.Input/Raw/IDragDropDevice.cs
  95. 26
      src/Avalonia.Input/Raw/RawDragEvent.cs
  96. 10
      src/Avalonia.Input/Raw/RawDragEventType.cs
  97. 2
      src/Avalonia.Themes.Default/Accents/BaseLight.xaml
  98. 43
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  99. 86
      src/Avalonia.Themes.Default/ButtonSpinner.xaml
  100. 126
      src/Avalonia.Themes.Default/DatePicker.xaml

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

55
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
@ -126,10 +126,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\Rende
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}"
@ -196,14 +192,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 +362,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 +372,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 +1989,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 +2578,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}

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>

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

46
readme.md

@ -2,15 +2,15 @@
# 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
Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (GTK), MacOS, Android and iOS.
<b>Avalonia is now in alpha.</b> This means that framework is now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework.
**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development.
| Control catalog | Desktop platforms | Mobile platforms |
|---|---|---|
@ -35,16 +35,46 @@ 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/tutorial/gettingstarted) 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/spec/architecture) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
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/.
Contributions are always welcome!
## Building and Using
See the [build instructions here](http://avaloniaui.net/guidelines/build).
See the [build instructions here](http://avaloniaui.net/contributing/build).
## Contributing
Please read the [contribution guidelines](http://avaloniaui.net/guidelines/contributing) before submitting a pull request.
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>

8
samples/BindingTest/MainWindow.xaml

@ -1,4 +1,5 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:vm="clr-namespace:BindingTest.ViewModels"
xmlns:local="clr-namespace:BindingTest">
<Window.Styles>
@ -6,6 +7,9 @@
<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 +44,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"/>

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

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>

188
samples/ControlCatalog/ControlCatalog.csproj

@ -1,177 +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\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\ExpanderPage.xaml.cs">
<DependentUpon>ExpanderPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\ImagePage.xaml.cs">
<DependentUpon>ImagePage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\LayoutTransformControlPage.xaml.cs">
<DependentUpon>LayoutTransformControlPage.xaml</DependentUpon>
<Compile Update="**\*.xaml.cs">
<DependentUpon>%(Filename)</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" />
@ -188,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>

7
samples/ControlCatalog/MainView.xaml

@ -5,18 +5,23 @@
<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="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>
@ -24,4 +29,4 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
</TabControl>
</UserControl>
</UserControl>

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

46
samples/ControlCatalog/Pages/DatePickerPage.xaml

@ -0,0 +1,46 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Orientation="Vertical" Gap="4">
<TextBlock Classes="h1">DatePicker</TextBlock>
<TextBlock Classes="h2">A control for selecting dates with a calendar drop-down</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Gap="16">
<StackPanel Orientation="Vertical"
Width="200">
<TextBlock Text="SelectedDateFormat: Short"/>
<DatePicker Name="DatePicker1"
SelectedDateFormat="Short"
Margin="0,0,0,8"/>
<TextBlock Text="SelectedDateFormat: Long"/>
<DatePicker Name="DatePicker2"
SelectedDateFormat="Long"
Margin="0,0,0,8"/>
<TextBlock Text="SelectedDateFormat: Custom"/>
<DatePicker Name="DatePicker3"
SelectedDateFormat="Custom"
CustomDateFormatString="ddd, MMM d"
Margin="0,0,0,8"/>
<TextBlock Text="Blackout Dates"/>
<DatePicker Name="DatePicker4"
Margin="0,0,0,8"/>
<DatePicker Margin="0,0,0,8"
Watermark="Watermark"/>
<DatePicker Margin="0,0,0,8"
Name="DatePicker5"
Watermark="Floating Watermark"
UseFloatingWatermark="True"/>
<TextBlock Text="Disabled"/>
<DatePicker IsEnabled="False"/>
</StackPanel>
</StackPanel>
</StackPanel>
</UserControl>

36
samples/ControlCatalog/Pages/DatePickerPage.xaml.cs

@ -0,0 +1,36 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
namespace ControlCatalog.Pages
{
public class DatePickerPage : UserControl
{
public DatePickerPage()
{
InitializeComponent();
var dp1 = this.FindControl<DatePicker>("DatePicker1");
var dp2 = this.FindControl<DatePicker>("DatePicker2");
var dp3 = this.FindControl<DatePicker>("DatePicker3");
var dp4 = this.FindControl<DatePicker>("DatePicker4");
var dp5 = this.FindControl<DatePicker>("DatePicker5");
dp1.SelectedDate = DateTime.Today;
dp2.SelectedDate = DateTime.Today.AddDays(10);
dp3.SelectedDate = DateTime.Today.AddDays(20);
dp5.SelectedDate = DateTime.Today;
dp4.TemplateApplied += (s, e) =>
{
dp4.BlackoutDates.AddDatesInPast();
};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

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")]

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

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

50
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
{
@ -218,11 +217,6 @@ namespace Avalonia
}
else
{
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
return GetValueInternal(property);
}
}
@ -377,11 +371,6 @@ namespace Avalonia
{
PriorityValue v;
if (!AvaloniaPropertyRegistry.Instance.IsRegistered(this, property))
{
ThrowNotRegistered(property);
}
if (!_values.TryGetValue(property, out v))
{
v = CreatePriorityValue(property);
@ -804,11 +793,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 +820,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 +896,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>

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

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

@ -0,0 +1,205 @@
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)
{
var success = TryReadString(out var stringResult, separator);
result = success ? int.Parse(stringResult, _formatProvider) : 0;
return success;
}
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)
{
var success = TryReadString(out var stringResult, separator);
result = success ? double.Parse(stringResult, _formatProvider) : 0;
return success;
}
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>();
}
}
}

2726
src/Avalonia.Controls/AutoCompleteBox.cs

File diff suppressed because it is too large

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

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

File diff suppressed because it is too large

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

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)

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

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();

99
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,15 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Enables or disables resizing of the window
/// </summary>
public bool CanResize
{
get { return GetValue(CanResizeProperty); }
set { SetValue(CanResizeProperty, value); }
}
/// <summary>
/// Gets or sets the icon of the window.
/// </summary>
@ -205,26 +231,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 +277,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 +340,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -314,6 +381,7 @@ namespace Avalonia.Controls
s_windows.Add(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.Instance.ExecuteInitialLayoutPass(this);
@ -337,7 +405,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 +420,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
}
}

4
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -56,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;
@ -75,4 +75,4 @@ namespace Avalonia.DesignerSupport
return window;
}
}
}
}

9
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -39,6 +39,7 @@ namespace Avalonia.DesignerSupport.Remote
public Action<Point> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Func<bool> Closing { get; set; }
public IPlatformHandle Handle { get; }
public WindowState WindowState { get; set; }
public Size MaxClientSize { get; } = new Size(4096, 4096);
@ -66,6 +67,10 @@ namespace Avalonia.DesignerSupport.Remote
RenderIfNeeded();
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public IScreenImpl Screen { get; } = new ScreenStub();
public void Activate()
@ -92,5 +97,9 @@ namespace Avalonia.DesignerSupport.Remote
public void ShowTaskbarIcon(bool value)
{
}
public void CanResize(bool value)
{
}
}
}

9
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -26,6 +26,7 @@ namespace Avalonia.DesignerSupport.Remote
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public Point Position { get; set; }
@ -77,6 +78,10 @@ namespace Avalonia.DesignerSupport.Remote
public IScreenImpl Screen { get; } = new ScreenStub();
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}
public void SetTitle(string title)
{
}
@ -94,6 +99,10 @@ namespace Avalonia.DesignerSupport.Remote
public void ShowTaskbarIcon(bool value)
{
}
public void CanResize(bool value)
{
}
}
class ClipboardStub : IClipboard

2
src/Avalonia.Diagnostics/ViewModels/DevToolsViewModel.cs

@ -86,7 +86,7 @@ namespace Avalonia.Diagnostics.ViewModels
private void UpdateFocusedControl()
{
_focusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
}
}
}

3
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@ -10,6 +10,9 @@ using Avalonia.Shared.PlatformSupport;
namespace Avalonia
{
/// <summary>
/// Initializes platform-specific services for an <see cref="Application"/>.
/// </summary>
public sealed class AppBuilder : AppBuilderBase<AppBuilder>
{
/// <summary>

2
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -19,7 +19,7 @@
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
<ProjectReference Include="..\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
</ItemGroup>
<Import Project="..\..\build\NetCore.props" />
<Import Project="..\Shared\PlatformSupport\PlatformSupport.projitems" />

5
src/Avalonia.Input/Cursors.cs

@ -38,7 +38,10 @@ namespace Avalonia.Input
TopLeftCorner,
TopRightCorner,
BottomLeftCorner,
BottomRightCorner
BottomRightCorner,
DragMove,
DragCopy,
DragLink,
// Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/
// We might enable them later, preferably, by loading pixmax direclty from theme with fallback image

15
src/Avalonia.Input/DataFormats.cs

@ -0,0 +1,15 @@
namespace Avalonia.Input
{
public static class DataFormats
{
/// <summary>
/// Dataformat for plaintext
/// </summary>
public static string Text = nameof(Text);
/// <summary>
/// Dataformat for one or more filenames
/// </summary>
public static string FileNames = nameof(FileNames);
}
}

43
src/Avalonia.Input/DataObject.cs

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Input
{
public class DataObject : IDataObject
{
private readonly Dictionary<string, object> _items = new Dictionary<string, object>();
public bool Contains(string dataFormat)
{
return _items.ContainsKey(dataFormat);
}
public object Get(string dataFormat)
{
if (_items.ContainsKey(dataFormat))
return _items[dataFormat];
return null;
}
public IEnumerable<string> GetDataFormats()
{
return _items.Keys;
}
public IEnumerable<string> GetFileNames()
{
return Get(DataFormats.FileNames) as IEnumerable<string>;
}
public string GetText()
{
return Get(DataFormats.Text) as string;
}
public void Set(string dataFormat, object value)
{
_items[dataFormat] = value;
}
}
}

54
src/Avalonia.Input/DragDrop.cs

@ -0,0 +1,54 @@
using System.Threading.Tasks;
using Avalonia.Interactivity;
using Avalonia.Input.Platform;
namespace Avalonia.Input
{
public static class DragDrop
{
/// <summary>
/// Event which is raised, when a drag-and-drop operation enters the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragEnterEvent = RoutedEvent.Register<DragEventArgs>("DragEnter", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation leaves the element.
/// </summary>
public static RoutedEvent<RoutedEventArgs> DragLeaveEvent = RoutedEvent.Register<RoutedEventArgs>("DragLeave", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation is updated while over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DragOverEvent = RoutedEvent.Register<DragEventArgs>("DragOver", RoutingStrategies.Bubble, typeof(DragDrop));
/// <summary>
/// Event which is raised, when a drag-and-drop operation should complete over the element.
/// </summary>
public static RoutedEvent<DragEventArgs> DropEvent = RoutedEvent.Register<DragEventArgs>("Drop", RoutingStrategies.Bubble, typeof(DragDrop));
public static AvaloniaProperty<bool> AllowDropProperty = AvaloniaProperty.RegisterAttached<Interactive, bool>("AllowDrop", typeof(DragDrop), inherits: true);
/// <summary>
/// Gets a value indicating whether the given element can be used as the target of a drag-and-drop operation.
/// </summary>
public static bool GetAllowDrop(Interactive interactive)
{
return interactive.GetValue(AllowDropProperty);
}
/// <summary>
/// Sets a value indicating whether the given interactive can be used as the target of a drag-and-drop operation.
/// </summary>
public static void SetAllowDrop(Interactive interactive, bool value)
{
interactive.SetValue(AllowDropProperty, value);
}
/// <summary>
/// Starts a dragging operation with the given <see cref="IDataObject"/> and returns the applied drop effect from the target.
/// <seealso cref="DataObject"/>
/// </summary>
public static Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects)
{
var src = AvaloniaLocator.Current.GetService<IPlatformDragSource>();
return src?.DoDragDrop(data, allowedEffects) ?? Task.FromResult(DragDropEffects.None);
}
}
}

111
src/Avalonia.Input/DragDropDevice.cs

@ -0,0 +1,111 @@
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using System.Linq;
using Avalonia.Input.Raw;
namespace Avalonia.Input
{
public class DragDropDevice : IDragDropDevice
{
public static readonly DragDropDevice Instance = new DragDropDevice();
private Interactive _lastTarget = null;
private Interactive GetTarget(IInputElement root, Point local)
{
var target = root.InputHitTest(local)?.GetSelfAndVisualAncestors()?.OfType<Interactive>()?.FirstOrDefault();
if (target != null && DragDrop.GetAllowDrop(target))
return target;
return null;
}
private DragDropEffects RaiseDragEvent(Interactive target, RoutedEvent<DragEventArgs> routedEvent, DragDropEffects operation, IDataObject data)
{
if (target == null)
return DragDropEffects.None;
var args = new DragEventArgs(routedEvent, data)
{
RoutedEvent = routedEvent,
DragEffects = operation
};
target.RaiseEvent(args);
return args.DragEffects;
}
private DragDropEffects DragEnter(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
_lastTarget = GetTarget(inputRoot, point);
return RaiseDragEvent(_lastTarget, DragDrop.DragEnterEvent, effects, data);
}
private DragDropEffects DragOver(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
var target = GetTarget(inputRoot, point);
if (target == _lastTarget)
return RaiseDragEvent(target, DragDrop.DragOverEvent, effects, data);
try
{
if (_lastTarget != null)
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
return RaiseDragEvent(target, DragDrop.DragEnterEvent, effects, data);
}
finally
{
_lastTarget = target;
}
}
private void DragLeave(IInputElement inputRoot)
{
if (_lastTarget == null)
return;
try
{
_lastTarget.RaiseEvent(new RoutedEventArgs(DragDrop.DragLeaveEvent));
}
finally
{
_lastTarget = null;
}
}
private DragDropEffects Drop(IInputElement inputRoot, Point point, IDataObject data, DragDropEffects effects)
{
try
{
return RaiseDragEvent(_lastTarget, DragDrop.DropEvent, effects, data);
}
finally
{
_lastTarget = null;
}
}
public void ProcessRawEvent(RawInputEventArgs e)
{
if (!e.Handled && e is RawDragEvent margs)
ProcessRawEvent(margs);
}
private void ProcessRawEvent(RawDragEvent e)
{
switch (e.Type)
{
case RawDragEventType.DragEnter:
e.Effects = DragEnter(e.InputRoot, e.Location, e.Data, e.Effects);
break;
case RawDragEventType.DragOver:
e.Effects = DragOver(e.InputRoot, e.Location, e.Data, e.Effects);
break;
case RawDragEventType.DragLeave:
DragLeave(e.InputRoot);
break;
case RawDragEventType.Drop:
e.Effects = Drop(e.InputRoot, e.Location, e.Data, e.Effects);
break;
}
}
}
}

13
src/Avalonia.Input/DragDropEffects.cs

@ -0,0 +1,13 @@
using System;
namespace Avalonia.Input
{
[Flags]
public enum DragDropEffects
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
}
}

18
src/Avalonia.Input/DragEventArgs.cs

@ -0,0 +1,18 @@
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class DragEventArgs : RoutedEventArgs
{
public DragDropEffects DragEffects { get; set; }
public IDataObject Data { get; private set; }
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data)
: base(routedEvent)
{
this.Data = data;
}
}
}

39
src/Avalonia.Input/IDataObject.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
namespace Avalonia.Input
{
/// <summary>
/// Interface to access information about the data of a drag-and-drop operation.
/// </summary>
public interface IDataObject
{
/// <summary>
/// Lists all formats which are present in the DataObject.
/// <seealso cref="DataFormats"/>
/// </summary>
IEnumerable<string> GetDataFormats();
/// <summary>
/// Checks wether a given DataFormat is present in this object
/// <seealso cref="DataFormats"/>
/// </summary>
bool Contains(string dataFormat);
/// <summary>
/// Returns the dragged text if the DataObject contains any text.
/// <seealso cref="DataFormats.Text"/>
/// </summary>
string GetText();
/// <summary>
/// Returns a list of filenames if the DataObject contains filenames.
/// <seealso cref="DataFormats.FileNames"/>
/// </summary>
IEnumerable<string> GetFileNames();
/// <summary>
/// Tries to get the data of the given DataFormat.
/// </summary>
object Get(string dataFormat);
}
}

2
src/Avalonia.Input/KeyboardDevice.cs

@ -46,13 +46,13 @@ namespace Avalonia.Input
if (element != FocusedElement)
{
var interactive = FocusedElement as IInteractive;
FocusedElement = element;
interactive?.RaiseEvent(new RoutedEventArgs
{
RoutedEvent = InputElement.LostFocusEvent,
});
FocusedElement = element;
interactive = element as IInteractive;
interactive?.RaiseEvent(new GotFocusEventArgs

17
src/Avalonia.Input/Navigation/TabNavigation.cs

@ -221,17 +221,16 @@ namespace Avalonia.Input.Navigation
return parent;
}
var siblings = parent.GetVisualChildren()
var allSiblings = parent.GetVisualChildren()
.OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendants);
var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault();
var siblings = direction == NavigationDirection.Next ?
allSiblings.SkipWhile(x => x != container).Skip(1) :
allSiblings.TakeWhile(x => x != container).Reverse();
if (sibling != null)
foreach (var sibling in siblings)
{
var customNext = GetCustomNext(sibling, direction);
if (customNext.handled)
{
return customNext.next;
@ -239,13 +238,17 @@ namespace Avalonia.Input.Navigation
if (sibling.CanFocus())
{
next = sibling;
return sibling;
}
else
{
next = direction == NavigationDirection.Next ?
GetFocusableDescendants(sibling, direction).FirstOrDefault() :
GetFocusableDescendants(sibling, direction).LastOrDefault();
if(next != null)
{
return next;
}
}
}

14
src/Avalonia.Input/Platform/IPlatformDragSource.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Interactivity;
namespace Avalonia.Input.Platform
{
public interface IPlatformDragSource
{
Task<DragDropEffects> DoDragDrop(IDataObject data, DragDropEffects allowedEffects);
}
}

8
src/Avalonia.Input/Raw/IDragDropDevice.cs

@ -0,0 +1,8 @@
using Avalonia.Input;
namespace Avalonia.Input.Raw
{
public interface IDragDropDevice : IInputDevice
{
}
}

26
src/Avalonia.Input/Raw/RawDragEvent.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Input;
using Avalonia.Input.Raw;
namespace Avalonia.Input.Raw
{
public class RawDragEvent : RawInputEventArgs
{
public IInputElement InputRoot { get; }
public Point Location { get; }
public IDataObject Data { get; }
public DragDropEffects Effects { get; set; }
public RawDragEventType Type { get; }
public RawDragEvent(IDragDropDevice inputDevice, RawDragEventType type,
IInputElement inputRoot, Point location, IDataObject data, DragDropEffects effects)
:base(inputDevice, 0)
{
Type = type;
InputRoot = inputRoot;
Location = location;
Data = data;
Effects = effects;
}
}
}

10
src/Avalonia.Input/Raw/RawDragEventType.cs

@ -0,0 +1,10 @@
namespace Avalonia.Input.Raw
{
public enum RawDragEventType
{
DragEnter,
DragOver,
DragLeave,
Drop
}
}

2
src/Avalonia.Themes.Default/Accents/BaseLight.xaml

@ -20,7 +20,7 @@
<SolidColorBrush x:Key="ErrorBrush">Red</SolidColorBrush>
<SolidColorBrush x:Key="ErrorBrushLight">#10ff0000</SolidColorBrush>
<sys:Double x:Key="ThemeBorderThickness">2</sys:Double>
<Thickness x:Key="ThemeBorderThickness">2</Thickness>
<sys:Double x:Key="ThemeDisabledOpacity">0.5</sys:Double>
<sys:Double x:Key="FontSizeSmall">10</sys:Double>

43
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@ -0,0 +1,43 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="AutoCompleteBox">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Panel>
<TextBox Name="PART_TextBox"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}" />
<Popup Name="PART_Popup"
MinWidth="{TemplateBinding Bounds.Width}"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}"
StaysOpen="False">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
<ListBox Name="PART_SelectingItemsControl"
BorderThickness="0"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding ValueMemberSelector}"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto" />
</Border>
</Popup>
</Panel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="AutoCompleteBox ListBoxItem:pointerover">
<Setter Property="Background" Value="#ffd0d0d0"/>
</Style>
</Styles>

86
src/Avalonia.Themes.Default/ButtonSpinner.xaml

@ -0,0 +1,86 @@
<Styles xmlns="https://github.com/avaloniaui">
<Style Selector="ButtonSpinner">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderLightBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton">
<Setter Property="RepeatButton.Background" Value="Transparent"/>
<Setter Property="RepeatButton.BorderBrush" Value="Transparent"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton:pointerover">
<Setter Property="RepeatButton.Background" Value="{DynamicResource ThemeControlMidBrush}"/>
<Setter Property="RepeatButton.BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton#PART_IncreaseButton">
<Setter Property="Content">
<Template>
<Path Fill="{DynamicResource ThemeForegroundBrush}"
Width="8"
Height="4"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M0,5 L4.5,.5 9,5 6,5 4.5,3.5 3,5 z"/>
</Template>
</Setter>
</Style>
<Style Selector="ButtonSpinner /template/ RepeatButton#PART_DecreaseButton">
<Setter Property="Content">
<Template>
<Path Fill="{DynamicResource ThemeForegroundBrush}"
Width="8"
Height="4"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M0,0 L3,0 4.5,1.5 6,0 9,0 4.5,4.5 z"/>
</Template>
</Setter>
</Style>
<Style Selector="ButtonSpinner:right">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid ColumnDefinitions="*,Auto">
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="0"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"/>
<Grid Grid.Column="1" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}">
<RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
<RepeatButton Grid.Row="1" Name="PART_DecreaseButton"/>
</Grid>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ButtonSpinner:left">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid ColumnDefinitions="Auto,*">
<Grid Grid.Column="0" RowDefinitions="*,*" IsVisible="{TemplateBinding ShowButtonSpinner}">
<RepeatButton Grid.Row="0" Name="PART_IncreaseButton"/>
<RepeatButton Grid.Row="1" Name="PART_DecreaseButton"/>
</Grid>
<ContentPresenter Name="PART_ContentPresenter" Grid.Column="1"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

126
src/Avalonia.Themes.Default/DatePicker.xaml

@ -0,0 +1,126 @@
<!--
// (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.
-->
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="DatePicker">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="Template">
<ControlTemplate>
<Grid ColumnDefinitions="*,Auto">
<Grid.Styles>
<Style Selector="Button.CalendarDropDown">
<Setter Property="Template">
<ControlTemplate>
<Grid Height="18"
Width="19"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0"
Background="#FFFFFFFF"
ColumnDefinitions="*,*,*,*"
RowDefinitions="23*,19*,19*,19*"
ClipToBounds="False">
<Border Name="Highlight"
Margin="-1"
Grid.ColumnSpan="4"
Grid.Row="0"
Grid.RowSpan="4"
BorderThickness="1"
BorderBrush="{DynamicResource HighlightBrush}" />
<Border Name="Background"
Margin="0,-1,0,0"
Grid.ColumnSpan="4"
Grid.Row="1"
Grid.RowSpan="3"
BorderThickness="1"
BorderBrush="{DynamicResource ThemeBorderDarkBrush}"
CornerRadius=".5" />
<Rectangle Grid.ColumnSpan="4"
Grid.RowSpan="1"
StrokeThickness="1"
Stroke="{DynamicResource ThemeBorderDarkBrush}"
Fill="{DynamicResource ThemeAccentBrush}">
</Rectangle>
<Path HorizontalAlignment="Center"
Margin="4,3,4,3"
VerticalAlignment="Center"
RenderTransformOrigin="0.5,0.5"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="4"
Grid.RowSpan="3"
Fill="{DynamicResource ThemeBorderDarkBrush}"
Stretch="Fill"
Data="M11.426758,8.4305077 L11.749023,8.4305077 L11.749023,16.331387 L10.674805,16.331387 L10.674805,10.299648 L9.0742188,11.298672 L9.0742188,10.294277 C9.4788408,10.090176 9.9094238,9.8090878 10.365967,9.4510155 C10.82251,9.0929432 11.176106,8.7527733 11.426758,8.4305077 z M14.65086,8.4305077 L18.566387,8.4305077 L18.566387,9.3435936 L15.671368,9.3435936 L15.671368,11.255703 C15.936341,11.058764 16.27293,10.960293 16.681133,10.960293 C17.411602,10.960293 17.969301,11.178717 18.354229,11.615566 C18.739157,12.052416 18.931622,12.673672 18.931622,13.479336 C18.931622,15.452317 18.052553,16.438808 16.294415,16.438808 C15.560365,16.438808 14.951641,16.234707 14.468243,15.826504 L14.881817,14.929531 C15.368796,15.326992 15.837872,15.525723 16.289043,15.525723 C17.298809,15.525723 17.803692,14.895514 17.803692,13.635098 C17.803692,12.460618 17.305971,11.873379 16.310528,11.873379 C15.83071,11.873379 15.399232,12.079271 15.016094,12.491055 L14.65086,12.238613 z" />
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Fill="#FFFFFFFF" StrokeThickness="0" Grid.ColumnSpan="4" Width="3" Height="3"/>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="Button.CalendarDropDown /template/ Border#Highlight">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="Button.CalendarDropDown:pressed /template/ Border#Highlight">
<Setter Property="IsVisible" Value="True"/>
</Style>
<Style Selector="Button.CalendarDropDown:pointerover /template/ Border#Background">
<Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
</Style>
</Grid.Styles>
<TextBox Name="PART_TextBox"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
Watermark="{TemplateBinding Watermark}"
UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
Grid.Column="0"/>
<Button Name="PART_Button"
Grid.Column="1"
Width="20"
Classes="CalendarDropDown"
Foreground="{TemplateBinding Foreground}"
Background="#00FFFFFF"
BorderThickness="0"
Margin="2,0,2,0"
Padding="0"
ClipToBounds="False"
Focusable="False"/>
<Popup Name="PART_Popup"
PlacementTarget="{TemplateBinding}"
StaysOpen="False">
<Calendar Name="PART_Calendar"
FirstDayOfWeek="{TemplateBinding FirstDayOfWeek}"
IsTodayHighlighted="{TemplateBinding IsTodayHighlighted}"/>
</Popup>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DatePicker:focus /template/ TextBox#PART_TextBox">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderDarkBrush}"/>
</Style>
</Styles>

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

Loading…
Cancel
Save