Browse Source

Merge branch 'master' into pr/558

pull/582/head
Steven Kirk 10 years ago
parent
commit
34a6bb7f26
  1. 27
      .travis.yml
  2. 1365
      Avalonia.mono.sln
  3. 16
      Avalonia.sln
  4. 2
      nuget/template/Avalonia.Skia.Desktop.nuspec
  5. 4
      readme.md
  6. 3
      samples/BindingTest/App.xaml.cs
  7. 3
      samples/ControlCatalog.Desktop/Program.cs
  8. 10
      samples/ControlCatalog/Pages/CanvasPage.xaml
  9. 1
      samples/ControlCatalog/Pages/MenuPage.xaml
  10. 3
      samples/TestApplication/Program.cs
  11. 14
      samples/VirtualizationTest/MainWindow.xaml
  12. 3
      samples/VirtualizationTest/Program.cs
  13. 38
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  14. 3
      samples/XamlTestApplication/Program.cs
  15. 8
      samples/XamlTestApplicationPcl/TestScrollable.cs
  16. 3
      samples/XamlTestApplicationPcl/Views/MainWindow.cs
  17. 11
      samples/XamlTestApplicationPcl/Views/MainWindow.xaml
  18. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  19. 8
      src/Android/Avalonia.Android/Avalonia.Android.v2.ncrunchproject
  20. 46
      src/Avalonia.Controls/AppBuilder.cs
  21. 51
      src/Avalonia.Controls/Application.cs
  22. 6
      src/Avalonia.Controls/Avalonia.Controls.csproj
  23. 2
      src/Avalonia.Controls/Canvas.cs
  24. 13
      src/Avalonia.Controls/ControlExtensions.cs
  25. 22
      src/Avalonia.Controls/Design.cs
  26. 14
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  27. 5
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  28. 27
      src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
  29. 2
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  30. 22
      src/Avalonia.Controls/IVirtualizingController.cs
  31. 23
      src/Avalonia.Controls/IVirtualizingPanel.cs
  32. 2
      src/Avalonia.Controls/LayoutTransformControl.cs
  33. 7
      src/Avalonia.Controls/Menu.cs
  34. 7
      src/Avalonia.Controls/MenuItem.cs
  35. 17
      src/Avalonia.Controls/Mixins/SelectableMixin.cs
  36. 16
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  37. 2
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  38. 65
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  39. 28
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  40. 183
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  41. 48
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  42. 5
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  43. 61
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  44. 3
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  45. 12
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  46. 47
      src/Avalonia.Controls/Primitives/Popup.cs
  47. 12
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  48. 4
      src/Avalonia.Controls/Primitives/TabStrip.cs
  49. 2
      src/Avalonia.Controls/ScrollViewer.cs
  50. 33
      src/Avalonia.Controls/StackPanel.cs
  51. 3
      src/Avalonia.Controls/Templates/FuncTemplate`1.cs
  52. 6
      src/Avalonia.Controls/Templates/ITemplate`1.cs
  53. 59
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  54. 11
      src/Avalonia.Controls/Window.cs
  55. 18
      src/Avalonia.Controls/WrapPanel.cs
  56. 52
      src/Avalonia.DesignerSupport/DesignerApi.cs
  57. 64
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  58. 4
      src/Avalonia.Input/Avalonia.Input.csproj
  59. 2
      src/Avalonia.Input/FocusManager.cs
  60. 2
      src/Avalonia.Input/IKeyboardNavigationHandler.cs
  61. 2
      src/Avalonia.Input/INavigableContainer.cs
  62. 16
      src/Avalonia.Input/InputExtensions.cs
  63. 34
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  64. 27
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  65. 26
      src/Avalonia.Input/Navigation/TabNavigation.cs
  66. 14
      src/Avalonia.Input/NavigationDirection.cs
  67. 35
      src/Avalonia.Layout/LayoutManager.cs
  68. 11
      src/Avalonia.Layout/Layoutable.cs
  69. 39
      src/Avalonia.SceneGraph/Media/DrawingContext.cs
  70. 10
      src/Avalonia.SceneGraph/Media/Geometry.cs
  71. 14
      src/Avalonia.SceneGraph/Media/IDrawingContext.cs
  72. 3
      src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs
  73. 39
      src/Avalonia.SceneGraph/Media/PathMarkupParser.cs
  74. 7
      src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs
  75. 16
      src/Avalonia.SceneGraph/Rect.cs
  76. 12
      src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
  77. 55
      src/Avalonia.SceneGraph/Visual.cs
  78. 41
      src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs
  79. 14
      src/Avalonia.SceneGraph/VisualTree/IVisual.cs
  80. 24
      src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs
  81. 11
      src/Avalonia.Styling/Avalonia.Styling.csproj
  82. 0
      src/Avalonia.Styling/Controls/INameScope.cs
  83. 6
      src/Avalonia.Styling/Controls/NameScope.cs
  84. 0
      src/Avalonia.Styling/Controls/NameScopeEventArgs.cs
  85. 0
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  86. 10
      src/Avalonia.Styling/Styling/ITemplate.cs
  87. 37
      src/Avalonia.Styling/Styling/Setter.cs
  88. 196
      src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs
  89. 9
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs
  90. 5
      src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs
  91. 1
      src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs
  92. 9
      src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs
  93. 15
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  94. 8
      src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs
  95. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  96. 10
      src/Markup/Avalonia.Markup.Xaml/Templates/FocusAdornerTemplate.cs
  97. 10
      src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs
  98. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs
  99. 11
      src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs
  100. 2
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

27
.travis.yml

@ -0,0 +1,27 @@
language: csharp
mono:
- nightly
solution: Avalonia.mono.sln
before_install:
- mkdir -p .nuget
- wget -O .nuget/nuget.exe https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
install:
- sudo apt-get install -y gtk-sharp2
- mono .nuget/nuget.exe restore Avalonia.mono.sln
- mono .nuget/nuget.exe install xunit.runner.console -Version 2.1.0 -OutputDirectory testrunner
script:
- xbuild /p:Configuration=Release Avalonia.mono.sln
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Base.UnitTests/bin/Release/Avalonia.Base.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Input.UnitTests/bin/Release/Avalonia.Input.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Interactivity.UnitTests/bin/Release/Avalonia.Interactivity.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Layout.UnitTests/bin/Release/Avalonia.Layout.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Markup.UnitTests/bin/Release/Avalonia.Markup.UnitTests.dll
- mono ./testrunner/xunit.runner.console.2.1.0/tools/xunit.console.exe ./tests/Avalonia.Styling.UnitTests/bin/Release/Avalonia.Styling.UnitTests.dll
notifications:
email: false
webhooks:
urls:
- https://webhooks.gitter.im/e/98f653320ef2b7506c05
on_success: change
on_failure: always
on_start: never

1365
Avalonia.mono.sln

File diff suppressed because it is too large

16
Avalonia.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.25123.0 VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject EndProject
@ -15,6 +15,9 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Direct2D1", "src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj", "{3E908F67-5543-4879-A1DC-08EACE79B3CD}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer", "src\Windows\Avalonia.Designer\Avalonia.Designer.csproj", "{EC42600F-049B-43FF-AED1-8314D61B2749}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Designer", "src\Windows\Avalonia.Designer\Avalonia.Designer.csproj", "{EC42600F-049B-43FF-AED1-8314D61B2749}"
ProjectSection(ProjectDependencies) = postProject
{2B888490-D14A-4BCA-AB4B-48676FA93C9B} = {2B888490-D14A-4BCA-AB4B-48676FA93C9B}
EndProjectSection
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Input", "src\Avalonia.Input\Avalonia.Input.csproj", "{62024B2D-53EB-4638-B26B-85EEAA54866E}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Input", "src\Avalonia.Input\Avalonia.Input.csproj", "{62024B2D-53EB-4638-B26B-85EEAA54866E}"
EndProject EndProject
@ -156,23 +159,33 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.TestApp", "tests\Avalonia.DesignerSupport.TestApp\Avalonia.DesignerSupport.TestApp.csproj", "{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}"
EndProject EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.RenderTests.shproj", "{48840EDD-24BF-495D-911E-2EB12AE75D3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
EndProject EndProject
Global Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{fb05ac90-89ba-4f2f-a924-f37875fb547c}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{e106cf37-4066-4615-b684-172a6d30b058}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{2f59f3d0-748d-4652-b01e-e0d954756308}*SharedItemsImports = 13
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{dabfd304-d6a4-4752-8123-c2ccf7ac7831}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{db070a10-bf39-4752-8456-86e9d5928478}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{925dd807-b651-475f-9f7c-cbeb974ce43d}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13 samples\TestApplicationShared\TestApplicationShared.projitems*{78345174-5b52-4a14-b9fd-d5f2428137f0}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{54f237d5-a70a-4752-9656-0c70b1a7b047}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{ff69b927-c545-49ae-8e16-3d14d621aa12}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{d35a9f3d-8bb0-496e-bf72-444038a7debb}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{811a76cf-1cf6-440f-963b-bbe31bd72a82}*SharedItemsImports = 4
src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4 src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{47be08a7-5985-410b-9ffc-2264b8ea595f}*SharedItemsImports = 4
samples\TestApplicationShared\TestApplicationShared.projitems*{8c923867-8a8f-4f6b-8b80-47d9e8436166}*SharedItemsImports = 4
tests\Avalonia.RenderTests\Avalonia.RenderTests.projitems*{48840edd-24bf-495d-911e-2eb12ae75d3b}*SharedItemsImports = 13
samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4 samples\TestApplicationShared\TestApplicationShared.projitems*{e3a1060b-50d0-44e8-88b6-f44ef2e5bd72}*SharedItemsImports = 4
src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Shared\RenderHelpers\RenderHelpers.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4 src\Skia\Avalonia.Skia\Avalonia.Skia.projitems*{bd43f7c0-396b-4aa1-bad9-dfde54d51298}*SharedItemsImports = 4
@ -1983,6 +1996,7 @@ Global
{D35A9F3D-8BB0-496E-BF72-444038A7DEBB} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {D35A9F3D-8BB0-496E-BF72-444038A7DEBB} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{52F55355-D120-42AC-8116-8410A7D602FA} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {52F55355-D120-42AC-8116-8410A7D602FA} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{F1381F98-4D24-409A-A6C5-1C5B1E08BB08} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B} {F1381F98-4D24-409A-A6C5-1C5B1E08BB08} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{48840EDD-24BF-495D-911E-2EB12AE75D3B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{FBCAF3D0-2808-4934-8E96-3F607594517B} = {9B9E3891-2366-4253-A952-D08BCEB71098} {FBCAF3D0-2808-4934-8E96-3F607594517B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

2
nuget/template/Avalonia.Skia.Desktop.nuspec

@ -13,7 +13,7 @@
<copyright>Copyright 2015</copyright> <copyright>Copyright 2015</copyright>
<tags>Avalonia</tags> <tags>Avalonia</tags>
<dependencies> <dependencies>
<dependency id="SkiaSharp" version="1.49.2.1-beta"/> <dependency id="SkiaSharp" version="1.49.3.0-beta"/>
<dependency id="Avalonia" version="#VERSION#" /> <dependency id="Avalonia" version="#VERSION#" />
</dependencies> </dependencies>
</metadata> </metadata>

4
readme.md

@ -3,6 +3,8 @@
[![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://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)
A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android. A multi-platform .NET UI framework. It can run on Windows, Linux, Mac OS X, iOS and Android.
![](docs/images/screen.png) ![](docs/images/screen.png)
@ -45,7 +47,7 @@ framework.
As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can 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](docs/tutorial/gettingstarted.md) for an take a look at the [getting started page](docs/tutorial/gettingstarted.md) for an
overview of how to get started but probably the best thing to do for now is to already know a little bit 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/Avalonia/Avalonia). 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](docs/spec/architecture.md) that is currently a little bit There's also a high-level [architecture document](docs/spec/architecture.md) that is currently a little bit
out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/. out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.

3
samples/BindingTest/App.xaml.cs

@ -19,8 +19,7 @@ namespace BindingTest
InitializeLogging(); InitializeLogging();
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UseWin32() .UsePlatformDetect()
.UseDirect2D1()
.Start<MainWindow>(); .Start<MainWindow>();
} }

3
samples/ControlCatalog.Desktop/Program.cs

@ -17,8 +17,7 @@ namespace ControlCatalog
// TODO: Make this work with GTK/Skia/Cairo depending on command-line args // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
// again. // again.
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UseWin32() .UsePlatformDetect()
.UseDirect2D1()
.Start<MainWindow>(); .Start<MainWindow>();
} }

10
samples/ControlCatalog/Pages/CanvasPage.xaml

@ -3,7 +3,15 @@
<TextBlock Classes="h1">Canvas</TextBlock> <TextBlock Classes="h1">Canvas</TextBlock>
<TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock> <TextBlock Classes="h2">A panel which lays out its children by explicit coordinates</TextBlock>
<Canvas Background="Yellow" Width="300" Height="400"> <Canvas Background="Yellow" Width="300" Height="400">
<Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31"/> <Rectangle Fill="Blue" Width="63" Height="41" Canvas.Left="40" Canvas.Top="31">
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black"/>
<GradientStop Offset="1" Color="Transparent"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.OpacityMask> </Rectangle>
<Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/> <Ellipse Fill="Green" Width="58" Height="58" Canvas.Left="88" Canvas.Top="100"/>
<Path Fill="Orange" Data="M 0,0 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/> <Path Fill="Orange" Data="M 0,0 c 50,0 50,-50 c 50,0 50,50 h -50 v 50 l -50,-50 Z" Canvas.Left="30" Canvas.Top="250"/>
<Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250"> <Path Fill="OrangeRed" Canvas.Left="180" Canvas.Top="250">

1
samples/ControlCatalog/Pages/MenuPage.xaml

@ -10,6 +10,7 @@
<Menu> <Menu>
<MenuItem Header="_First"> <MenuItem Header="_First">
<MenuItem Header="Standard _Menu Item"/> <MenuItem Header="Standard _Menu Item"/>
<Separator/>
<MenuItem Header="Menu with _Submenu"> <MenuItem Header="Menu with _Submenu">
<MenuItem Header="Submenu _1"/> <MenuItem Header="Submenu _1"/>
<MenuItem Header="Submenu _2"/> <MenuItem Header="Submenu _2"/>

3
samples/TestApplication/Program.cs

@ -35,8 +35,7 @@ namespace TestApplication
var app = new App(); var app = new App();
AppBuilder.Configure(app) AppBuilder.Configure(app)
.UseWin32() .UsePlatformDetect()
.UseDirect2D1()
.SetupWithoutStarting(); .SetupWithoutStarting();
app.Run(); app.Run();

14
samples/VirtualizationTest/MainWindow.xaml

@ -5,6 +5,10 @@
Margin="16 0 0 0" Margin="16 0 0 0"
MinWidth="150" MinWidth="150"
Gap="4"> Gap="4">
<DropDown Items="{Binding VirtualizationModes}"
SelectedItem="{Binding VirtualizationMode}"/>
<DropDown Items="{Binding Orientations}"
SelectedItem="{Binding Orientation}"/>
<TextBox Watermark="Item Count" <TextBox Watermark="Item Count"
UseFloatingWatermark="True" UseFloatingWatermark="True"
Text="{Binding ItemCount}"/> Text="{Binding ItemCount}"/>
@ -23,12 +27,20 @@
<Button Command="{Binding AddItemCommand}">Add Item</Button> <Button Command="{Binding AddItemCommand}">Add Item</Button>
<Button Command="{Binding RemoveItemCommand}">Remove Item</Button> <Button Command="{Binding RemoveItemCommand}">Remove Item</Button>
<Button Command="{Binding RecreateCommand}">Recreate</Button> <Button Command="{Binding RecreateCommand}">Recreate</Button>
<Button Command="{Binding SelectFirstCommand}">Select First</Button>
<Button Command="{Binding SelectLastCommand}">Select Last</Button>
</StackPanel> </StackPanel>
<ListBox Name="listBox" <ListBox Name="listBox"
Items="{Binding Items}" Items="{Binding Items}"
SelectedItems="{Binding SelectedItems}" SelectedItems="{Binding SelectedItems}"
SelectionMode="Multiple"> SelectionMode="Multiple"
VirtualizationMode="{Binding VirtualizationMode}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="{Binding Orientation}"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding Header}"/> <TextBlock Text="{Binding Header}"/>

3
samples/VirtualizationTest/Program.cs

@ -16,8 +16,7 @@ namespace VirtualizationTest
InitializeLogging(); InitializeLogging();
AppBuilder.Configure<App>() AppBuilder.Configure<App>()
.UseWin32() .UsePlatformDetect()
.UseDirect2D1()
.Start<MainWindow>(); .Start<MainWindow>();
} }

38
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@ -2,8 +2,10 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using ReactiveUI; using ReactiveUI;
namespace VirtualizationTest.ViewModels namespace VirtualizationTest.ViewModels
@ -15,6 +17,8 @@ namespace VirtualizationTest.ViewModels
private int _newItemIndex; private int _newItemIndex;
private IReactiveList<ItemViewModel> _items; private IReactiveList<ItemViewModel> _items;
private string _prefix = "Item"; private string _prefix = "Item";
private Orientation _orientation;
private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
public MainWindowViewModel() public MainWindowViewModel()
{ {
@ -27,6 +31,12 @@ namespace VirtualizationTest.ViewModels
RemoveItemCommand = ReactiveCommand.Create(); RemoveItemCommand = ReactiveCommand.Create();
RemoveItemCommand.Subscribe(_ => Remove()); RemoveItemCommand.Subscribe(_ => Remove());
SelectFirstCommand = ReactiveCommand.Create();
SelectFirstCommand.Subscribe(_ => SelectItem(0));
SelectLastCommand = ReactiveCommand.Create();
SelectLastCommand.Subscribe(_ => SelectItem(Items.Count - 1));
} }
public string NewItemString public string NewItemString
@ -50,11 +60,29 @@ namespace VirtualizationTest.ViewModels
private set { this.RaiseAndSetIfChanged(ref _items, value); } private set { this.RaiseAndSetIfChanged(ref _items, value); }
} }
public ReactiveCommand<object> AddItemCommand { get; private set; } public Orientation Orientation
{
get { return _orientation; }
set { this.RaiseAndSetIfChanged(ref _orientation, value); }
}
public ReactiveCommand<object> RecreateCommand { get; private set; } public IEnumerable<Orientation> Orientations =>
Enum.GetValues(typeof(Orientation)).Cast<Orientation>();
public ItemVirtualizationMode VirtualizationMode
{
get { return _virtualizationMode; }
set { this.RaiseAndSetIfChanged(ref _virtualizationMode, value); }
}
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand<object> AddItemCommand { get; private set; }
public ReactiveCommand<object> RecreateCommand { get; private set; }
public ReactiveCommand<object> RemoveItemCommand { get; private set; } public ReactiveCommand<object> RemoveItemCommand { get; private set; }
public ReactiveCommand<object> SelectFirstCommand { get; private set; }
public ReactiveCommand<object> SelectLastCommand { get; private set; }
private void ResizeItems(int count) private void ResizeItems(int count)
{ {
@ -103,5 +131,11 @@ namespace VirtualizationTest.ViewModels
.Select(x => new ItemViewModel(x, _prefix)); .Select(x => new ItemViewModel(x, _prefix));
Items = new ReactiveList<ItemViewModel>(items); Items = new ReactiveList<ItemViewModel>(items);
} }
private void SelectItem(int index)
{
SelectedItems.Clear();
SelectedItems.Add(Items[index]);
}
} }
} }

3
samples/XamlTestApplication/Program.cs

@ -21,8 +21,7 @@ namespace XamlTestApplication
InitializeLogging(); InitializeLogging();
AppBuilder.Configure<XamlTestApp>() AppBuilder.Configure<XamlTestApp>()
.UseWin32() .UsePlatformDetect()
.UseDirect2D1()
.Start<Views.MainWindow>(); .Start<Views.MainWindow>();
} }

8
samples/XamlTestApplicationPcl/TestScrollable.cs

@ -2,6 +2,7 @@ using System;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -75,7 +76,12 @@ namespace XamlTestApplication
} }
} }
public bool BringIntoView(IVisual target, Rect targetRect) public bool BringIntoView(IControl target, Rect targetRect)
{
throw new NotImplementedException();
}
public IControl GetControlInDirection(NavigationDirection direction, IControl from)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

3
samples/XamlTestApplicationPcl/Views/MainWindow.cs

@ -27,9 +27,6 @@ namespace XamlTestApplication.Views
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
_exitMenu = this.FindControl<MenuItem>("exitMenu"); _exitMenu = this.FindControl<MenuItem>("exitMenu");
_exitMenu.Click += (s, e) => Application.Current.Exit(); _exitMenu.Click += (s, e) => Application.Current.Exit();
var virtualList = this.FindControl<ListBox>("virtualList");
virtualList.Items = Enumerable.Range(0, 200).Select(x => $"Item {x}").ToList();
} }
} }
} }

11
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@ -24,16 +24,7 @@
<TabControl.Transition> <TabControl.Transition>
<PageSlide Duration="0.25" /> <PageSlide Duration="0.25" />
</TabControl.Transition> </TabControl.Transition>
<TabItem Header="Virtualization">
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Right" Orientation="Vertical">
</StackPanel>
<ListBox Name="virtualList"/>
</DockPanel>
</TabItem>
<TabItem Header="Buttons"> <TabItem Header="Buttons">
<ScrollViewer CanScrollHorizontally="False"> <ScrollViewer CanScrollHorizontally="False">
<StackPanel Margin="10" Gap="4"> <StackPanel Margin="10" Gap="4">

1
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -38,7 +38,6 @@ namespace Avalonia.Android
.Bind<IWindowingPlatform>().ToConstant(this); .Bind<IWindowingPlatform>().ToConstant(this);
SkiaPlatform.Initialize(); SkiaPlatform.Initialize();
Application.RegisterPlatformCallback(() => { });
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity; _scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;

8
src/Android/Avalonia.Android/Avalonia.Android.v2.ncrunchproject

@ -7,7 +7,7 @@
<AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking> <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
<AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking> <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
<AllowCodeAnalysis>false</AllowCodeAnalysis> <AllowCodeAnalysis>false</AllowCodeAnalysis>
<IgnoreThisComponentCompletely>false</IgnoreThisComponentCompletely> <IgnoreThisComponentCompletely>true</IgnoreThisComponentCompletely>
<RunPreBuildEvents>false</RunPreBuildEvents> <RunPreBuildEvents>false</RunPreBuildEvents>
<RunPostBuildEvents>false</RunPostBuildEvents> <RunPostBuildEvents>false</RunPostBuildEvents>
<PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully> <PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
@ -17,9 +17,9 @@
<DetectStackOverflow>true</DetectStackOverflow> <DetectStackOverflow>true</DetectStackOverflow>
<IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace> <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
<DefaultTestTimeout>60000</DefaultTestTimeout> <DefaultTestTimeout>60000</DefaultTestTimeout>
<UseBuildConfiguration /> <UseBuildConfiguration></UseBuildConfiguration>
<UseBuildPlatform /> <UseBuildPlatform></UseBuildPlatform>
<ProxyProcessPath /> <ProxyProcessPath></ProxyProcessPath>
<UseCPUArchitecture>AutoDetect</UseCPUArchitecture> <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
<MSTestThreadApartmentState>STA</MSTestThreadApartmentState> <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
<BuildProcessArchitecture>x86</BuildProcessArchitecture> <BuildProcessArchitecture>x86</BuildProcessArchitecture>

46
src/Avalonia.Controls/AppBuilder.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Reflection;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -98,23 +99,64 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="initializer">The method to call to initialize the windowing subsystem.</param> /// <param name="initializer">The method to call to initialize the windowing subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns> /// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder WithWindowingSubsystem(Action initializer) public AppBuilder UseWindowingSubsystem(Action initializer)
{ {
WindowingSubsystem = initializer; WindowingSubsystem = initializer;
return this; return this;
} }
/// <summary>
/// Specifies a windowing subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseWindowingSubsystem(string dll) => UseWindowingSubsystem(GetInitializer(dll));
/// <summary> /// <summary>
/// Specifies a rendering subsystem to use. /// Specifies a rendering subsystem to use.
/// </summary> /// </summary>
/// <param name="initializer">The method to call to initialize the rendering subsystem.</param> /// <param name="initializer">The method to call to initialize the rendering subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns> /// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder WithRenderingSubsystem(Action initializer) public AppBuilder UseRenderingSubsystem(Action initializer)
{ {
RenderingSubsystem = initializer; RenderingSubsystem = initializer;
return this; return this;
} }
/// <summary>
/// Specifies a rendering subsystem to use.
/// </summary>
/// <param name="dll">The dll in which to look for subsystem.</param>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseRenderingSubsystem(string dll) => UseRenderingSubsystem(GetInitializer(dll));
static Action GetInitializer(string assemblyName) => () =>
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]);
init.Invoke(null, null);
};
public AppBuilder UsePlatformDetect()
{
var platformId = (int)
((dynamic) Type.GetType("System.Environment").GetRuntimeProperty("OSVersion").GetValue(null)).Platform;
if (platformId == 4 || platformId == 6)
{
UseRenderingSubsystem("Avalonia.Cairo");
UseWindowingSubsystem("Avalonia.Gtk");
}
else
{
UseRenderingSubsystem("Avalonia.Direct2D1");
UseWindowingSubsystem("Avalonia.Win32");
}
return this;
}
/// <summary> /// <summary>
/// Sets up the platform-speciic services for the <see cref="Application"/>. /// Sets up the platform-speciic services for the <see cref="Application"/>.
/// </summary> /// </summary>

51
src/Avalonia.Controls/Application.cs

@ -26,16 +26,12 @@ namespace Avalonia
/// - A global set of <see cref="Styles"/>. /// - A global set of <see cref="Styles"/>.
/// - A <see cref="FocusManager"/>. /// - A <see cref="FocusManager"/>.
/// - An <see cref="InputManager"/>. /// - An <see cref="InputManager"/>.
/// - Loads and initializes rendering and windowing subsystems with
/// <see cref="InitializeSubsystems(int)"/> and <see cref="InitializeSubsystem(string)"/>.
/// - Registers services needed by the rest of Avalonia in the <see cref="RegisterServices"/> /// - Registers services needed by the rest of Avalonia in the <see cref="RegisterServices"/>
/// method. /// method.
/// - Tracks the lifetime of the application. /// - Tracks the lifetime of the application.
/// </remarks> /// </remarks>
public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle public class Application : IGlobalDataTemplates, IGlobalStyles, IStyleRoot, IApplicationLifecycle
{ {
static Action _platformInitializationCallback;
/// <summary> /// <summary>
/// The application-global data templates. /// The application-global data templates.
/// </summary> /// </summary>
@ -121,11 +117,6 @@ namespace Avalonia
/// </summary> /// </summary>
IStyleHost IStyleHost.StylingParent => null; IStyleHost IStyleHost.StylingParent => null;
public static void RegisterPlatformCallback(Action cb)
{
_platformInitializationCallback = cb;
}
/// <summary> /// <summary>
/// Initializes the application by loading XAML etc. /// Initializes the application by loading XAML etc.
/// </summary> /// </summary>
@ -189,47 +180,5 @@ namespace Avalonia
.Bind<IRenderQueueManager>().ToTransient<RenderQueueManager>() .Bind<IRenderQueueManager>().ToTransient<RenderQueueManager>()
.Bind<IApplicationLifecycle>().ToConstant(this); .Bind<IApplicationLifecycle>().ToConstant(this);
} }
/// <summary>
/// Initializes the rendering and windowing subsystems according to platform.
/// </summary>
/// <param name="platformID">The value of Environment.OSVersion.Platform.</param>
protected void InitializeSubsystems(int platformID)
{
if (_platformInitializationCallback != null)
{
_platformInitializationCallback();
}
else if (platformID == 4 || platformID == 6)
{
InitializeSubsystem("Avalonia.Cairo");
InitializeSubsystem("Avalonia.Gtk");
}
else
{
InitializeSubsystem("Avalonia.Direct2D1");
InitializeSubsystem("Avalonia.Win32");
}
}
/// <summary>
/// Initializes the rendering or windowing subsystem defined by the specified assemblt.
/// </summary>
/// <param name="assemblyName">The name of the assembly.</param>
protected static void InitializeSubsystem(string assemblyName)
{
var assembly = Assembly.Load(new AssemblyName(assemblyName));
var platformClassName = assemblyName.Replace("Avalonia.", string.Empty) + "Platform";
var platformClassFullName = assemblyName + "." + platformClassName;
var platformClass = assembly.GetType(platformClassFullName);
var init = platformClass.GetRuntimeMethod("Initialize", new Type[0]);
init.Invoke(null, null);
}
internal static void InitializeWin32Subsystem()
{
InitializeSubsystem("Avalonia.Direct2D1");
InitializeSubsystem("Avalonia.Win32");
}
} }
} }

6
src/Avalonia.Controls/Avalonia.Controls.csproj

@ -51,21 +51,19 @@
<Compile Include="DockPanel.cs" /> <Compile Include="DockPanel.cs" />
<Compile Include="Expander.cs" /> <Compile Include="Expander.cs" />
<Compile Include="Generators\ItemContainerInfo.cs" /> <Compile Include="Generators\ItemContainerInfo.cs" />
<Compile Include="Generators\MenuItemContainerGenerator.cs" />
<Compile Include="Generators\TreeContainerIndex.cs" /> <Compile Include="Generators\TreeContainerIndex.cs" />
<Compile Include="HotkeyManager.cs" /> <Compile Include="HotkeyManager.cs" />
<Compile Include="IApplicationLifecycle.cs" /> <Compile Include="IApplicationLifecycle.cs" />
<Compile Include="IScrollable.cs" /> <Compile Include="IScrollable.cs" />
<Compile Include="INameScope.cs" />
<Compile Include="IPseudoClasses.cs" /> <Compile Include="IPseudoClasses.cs" />
<Compile Include="DropDownItem.cs" /> <Compile Include="DropDownItem.cs" />
<Compile Include="ISetInheritanceParent.cs" /> <Compile Include="ISetInheritanceParent.cs" />
<Compile Include="ItemVirtualizationMode.cs" /> <Compile Include="ItemVirtualizationMode.cs" />
<Compile Include="IVirtualizingController.cs" />
<Compile Include="IVirtualizingPanel.cs" /> <Compile Include="IVirtualizingPanel.cs" />
<Compile Include="LayoutTransformControl.cs" /> <Compile Include="LayoutTransformControl.cs" />
<Compile Include="Mixins\ContentControlMixin.cs" /> <Compile Include="Mixins\ContentControlMixin.cs" />
<Compile Include="NameScope.cs" />
<Compile Include="NameScopeEventArgs.cs" />
<Compile Include="NameScopeExtensions.cs" />
<Compile Include="Platform\ITopLevelRenderer.cs" /> <Compile Include="Platform\ITopLevelRenderer.cs" />
<Compile Include="Platform\IWindowingPlatform.cs" /> <Compile Include="Platform\IWindowingPlatform.cs" />
<Compile Include="Platform\PlatformManager.cs" /> <Compile Include="Platform\PlatformManager.cs" />

2
src/Avalonia.Controls/Canvas.cs

@ -136,7 +136,7 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param> /// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param> /// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns> /// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from) IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{ {
// TODO: Implement this // TODO: Implement this
return null; return null;

13
src/Avalonia.Controls/ControlExtensions.cs

@ -20,6 +20,8 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param> /// <param name="control">The control.</param>
public static void BringIntoView(this IControl control) public static void BringIntoView(this IControl control)
{ {
Contract.Requires<ArgumentNullException>(control != null);
control.BringIntoView(new Rect(control.Bounds.Size)); control.BringIntoView(new Rect(control.Bounds.Size));
} }
@ -30,6 +32,8 @@ namespace Avalonia.Controls
/// <param name="rect">The area of the control to being into view.</param> /// <param name="rect">The area of the control to being into view.</param>
public static void BringIntoView(this IControl control, Rect rect) public static void BringIntoView(this IControl control, Rect rect)
{ {
Contract.Requires<ArgumentNullException>(control != null);
var ev = new RequestBringIntoViewEventArgs var ev = new RequestBringIntoViewEventArgs
{ {
RoutedEvent = Control.RequestBringIntoViewEvent, RoutedEvent = Control.RequestBringIntoViewEvent,
@ -49,6 +53,9 @@ namespace Avalonia.Controls
/// <returns>The control or null if not found.</returns> /// <returns>The control or null if not found.</returns>
public static T FindControl<T>(this IControl control, string name) where T : class, IControl public static T FindControl<T>(this IControl control, string name) where T : class, IControl
{ {
Contract.Requires<ArgumentNullException>(control != null);
Contract.Requires<ArgumentNullException>(name != null);
var nameScope = control.FindNameScope(); var nameScope = control.FindNameScope();
if (nameScope == null) if (nameScope == null)
@ -66,6 +73,8 @@ namespace Avalonia.Controls
/// <returns>The control's name scope, or null if not found.</returns> /// <returns>The control's name scope, or null if not found.</returns>
public static INameScope FindNameScope(this IControl control) public static INameScope FindNameScope(this IControl control)
{ {
Contract.Requires<ArgumentNullException>(control != null);
return control.GetSelfAndLogicalAncestors() return control.GetSelfAndLogicalAncestors()
.OfType<Control>() .OfType<Control>()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x)) .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
@ -80,6 +89,8 @@ namespace Avalonia.Controls
/// <param name="value">True to add the pseudoclass or false to remove.</param> /// <param name="value">True to add the pseudoclass or false to remove.</param>
public static void Set(this IPseudoClasses classes, string name, bool value) public static void Set(this IPseudoClasses classes, string name, bool value)
{ {
Contract.Requires<ArgumentNullException>(classes != null);
if (value) if (value)
{ {
classes.Add(name); classes.Add(name);
@ -99,6 +110,8 @@ namespace Avalonia.Controls
/// <returns>A disposable used to cancel the subscription.</returns> /// <returns>A disposable used to cancel the subscription.</returns>
public static IDisposable Set(this IPseudoClasses classes, string name, IObservable<bool> trigger) public static IDisposable Set(this IPseudoClasses classes, string name, IObservable<bool> trigger)
{ {
Contract.Requires<ArgumentNullException>(classes != null);
return trigger.Subscribe(x => classes.Set(name, x)); return trigger.Subscribe(x => classes.Set(name, x));
} }
} }

22
src/Avalonia.Controls/Design.cs

@ -1,4 +1,6 @@
using System.Runtime.CompilerServices;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public static class Design public static class Design
@ -43,7 +45,25 @@ namespace Avalonia.Controls
{ {
return control.GetValue(DataContextProperty); return control.GetValue(DataContextProperty);
} }
static readonly ConditionalWeakTable<object, Control> Substitutes = new ConditionalWeakTable<object, Control>();
public static readonly AttachedProperty<Control> PreviewWithProperty = AvaloniaProperty
.RegisterAttached<AvaloniaObject, Control>("PreviewWith", typeof (Design));
public static void SetPreviewWith(object target, Control control)
{
Substitutes.Remove(target);
Substitutes.Add(target, control);
}
public static Control GetPreviewWith(object target)
{
Control rv;
Substitutes.TryGetValue(target, out rv);
return rv;
}
internal static void ApplyDesignerProperties(Control target, Control source) internal static void ApplyDesignerProperties(Control target, Control source)
{ {
if (source.IsSet(WidthProperty)) if (source.IsSet(WidthProperty))

14
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -187,11 +188,16 @@ namespace Avalonia.Controls.Generators
if (result == null) if (result == null)
{ {
result = new ContentPresenter result = new ContentPresenter();
result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style);
if (ItemTemplate != null)
{ {
ContentTemplate = ItemTemplate, result.SetValue(
Content = item, ContentPresenter.ContentTemplateProperty,
}; ItemTemplate,
BindingPriority.TemplatedParent);
}
} }
return result; return result;

5
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -5,6 +5,7 @@ using System;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
namespace Avalonia.Controls.Generators namespace Avalonia.Controls.Generators
{ {
@ -62,10 +63,10 @@ namespace Avalonia.Controls.Generators
if (ContentTemplateProperty != null) if (ContentTemplateProperty != null)
{ {
result.SetValue(ContentTemplateProperty, ItemTemplate); result.SetValue(ContentTemplateProperty, ItemTemplate, BindingPriority.Style);
} }
result.SetValue(ContentProperty, item); result.SetValue(ContentProperty, item, BindingPriority.Style);
if (!(item is IControl)) if (!(item is IControl))
{ {

27
src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Avalonia.Controls.Generators
{
public class MenuItemContainerGenerator : ItemContainerGenerator<MenuItem>
{
/// <summary>
/// Initializes a new instance of the <see cref="ItemContainerGenerator{T}"/> class.
/// </summary>
/// <param name="owner">The owner control.</param>
public MenuItemContainerGenerator(IControl owner)
: base(owner, MenuItem.HeaderProperty, null)
{
}
/// <inheritdoc/>
protected override IControl CreateContainer(object item)
{
var separator = item as Separator;
return separator != null ? separator : base.CreateContainer(item);
}
}
}

2
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -78,7 +78,7 @@ namespace Avalonia.Controls.Generators
var template = GetTreeDataTemplate(item, ItemTemplate); var template = GetTreeDataTemplate(item, ItemTemplate);
var result = new T(); var result = new T();
result.SetValue(ContentProperty, template.Build(item)); result.SetValue(ContentProperty, template.Build(item), BindingPriority.Style);
var itemsSelector = template.ItemsSelector(item); var itemsSelector = template.ItemsSelector(item);

22
src/Avalonia.Controls/IVirtualizingController.cs

@ -0,0 +1,22 @@
// 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.
namespace Avalonia.Controls
{
/// <summary>
/// Interface implemented by controls that act as controllers for an
/// <see cref="IVirtualizingPanel"/>.
/// </summary>
public interface IVirtualizingController
{
/// <summary>
/// Called when the <see cref="IVirtualizingPanel"/>'s controls should be updated.
/// </summary>
/// <remarks>
/// The controller should respond to this method being called by either adding
/// children up until <see cref="IVirtualizingPanel.IsFull"/> becomes true or
/// removing <see cref="IVirtualizingPanel.OverflowCount"/> controls.
/// </remarks>
void UpdateControls();
}
}

23
src/Avalonia.Controls/IVirtualizingPanel.cs

@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
@ -10,14 +8,35 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public interface IVirtualizingPanel : IPanel public interface IVirtualizingPanel : IPanel
{ {
/// <summary>
/// Gets or sets the controller for the virtualizing panel.
/// </summary>
/// <remarks>
/// A virtualizing controller is responsible for maintaing the controls in the virtualizing
/// panel. This property will be set by the controller when virtualization is initialized.
/// Note that this property may remain null if the panel is added to a control that does
/// not act as a virtualizing controller.
/// </remarks>
IVirtualizingController Controller { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether the panel is full. /// Gets a value indicating whether the panel is full.
/// </summary> /// </summary>
/// <remarks>
/// This property should return false until enough children are added to fill the space
/// passed into the last measure in the direction of scroll. It should be updated
/// immediately after a child is added or removed.
/// </remarks>
bool IsFull { get; } bool IsFull { get; }
/// <summary> /// <summary>
/// Gets the number of items that can be removed while keeping the panel full. /// Gets the number of items that can be removed while keeping the panel full.
/// </summary> /// </summary>
/// <remarks>
/// This property should return the number of children that are completely out of the
/// panel's current bounds in the direction of scroll. It should be updated after an
/// arrange.
/// </remarks>
int OverflowCount { get; } int OverflowCount { get; }
/// <summary> /// <summary>

2
src/Avalonia.Controls/LayoutTransformControl.cs

@ -139,7 +139,7 @@ namespace Avalonia.Controls
if (null != TransformRoot) if (null != TransformRoot)
{ {
TransformRoot.RenderTransform = _matrixTransform; TransformRoot.RenderTransform = _matrixTransform;
TransformRoot.TransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute); TransformRoot.RenderTransformOrigin = new RelativePoint(0, 0, RelativeUnit.Absolute);
} }
ApplyLayoutTransform(); ApplyLayoutTransform();

7
src/Avalonia.Controls/Menu.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
@ -131,6 +132,12 @@ namespace Avalonia.Controls
_subscription.Dispose(); _subscription.Dispose();
} }
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<MenuItem>(this, MenuItem.HeaderProperty, null);
}
/// <summary> /// <summary>
/// Called when a key is pressed within the menu. /// Called when a key is pressed within the menu.
/// </summary> /// </summary>

7
src/Avalonia.Controls/MenuItem.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins; using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
@ -204,6 +205,12 @@ namespace Avalonia.Controls
IsSelected = true; IsSelected = true;
} }
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
}
/// <summary> /// <summary>
/// Called when a key is pressed in the <see cref="MenuItem"/>. /// Called when a key is pressed in the <see cref="MenuItem"/>.
/// </summary> /// </summary>

17
src/Avalonia.Controls/Mixins/SelectableMixin.cs

@ -51,22 +51,7 @@ namespace Avalonia.Controls.Mixins
if (sender != null) if (sender != null)
{ {
var itemsControl = sender.Parent as SelectingItemsControl; ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
if ((bool)x.NewValue)
{
((IPseudoClasses)sender.Classes).Add(":selected");
if (((IVisual)sender).IsAttachedToVisualTree &&
itemsControl?.AutoScrollToSelectedItem == true)
{
sender.BringIntoView();
}
}
else
{
((IPseudoClasses)sender.Classes).Remove(":selected");
}
sender.RaiseEvent(new RoutedEventArgs sender.RaiseEvent(new RoutedEventArgs
{ {

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

@ -277,9 +277,23 @@ namespace Avalonia.Controls.Presenters
Child = newChild; Child = newChild;
if (oldChild?.Parent == this)
{
LogicalChildren.Remove(oldChild);
}
if (newChild.Parent == null) if (newChild.Parent == null)
{ {
((ISetLogicalParent)newChild).SetParent((ILogical)this.TemplatedParent ?? this); var templatedLogicalParent = TemplatedParent as ILogical;
if (templatedLogicalParent != null)
{
((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
}
else
{
LogicalChildren.Add(newChild);
}
} }
VisualChildren.Add(newChild); VisualChildren.Add(newChild);

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

@ -6,5 +6,7 @@ namespace Avalonia.Controls.Presenters
public interface IItemsPresenter : IPresenter public interface IItemsPresenter : IPresenter
{ {
IPanel Panel { get; } IPanel Panel { get; }
void ScrollIntoView(object item);
} }
} }

65
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -6,6 +6,7 @@ using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
@ -13,8 +14,10 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>. /// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
/// </summary> /// </summary>
internal abstract class ItemVirtualizer internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable
{ {
private bool disposedValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ItemVirtualizer"/> class. /// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
/// </summary> /// </summary>
@ -22,6 +25,8 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizer(ItemsPresenter owner) public ItemVirtualizer(ItemsPresenter owner)
{ {
Owner = owner; Owner = owner;
Items = owner.Items;
ItemCount = owner.Items.Count();
} }
/// <summary> /// <summary>
@ -115,34 +120,45 @@ namespace Avalonia.Controls.Presenters
{ {
var virtualizingPanel = owner.Panel as IVirtualizingPanel; var virtualizingPanel = owner.Panel as IVirtualizingPanel;
var scrollable = (ILogicalScrollable)owner; var scrollable = (ILogicalScrollable)owner;
ItemVirtualizer result = null;
if (virtualizingPanel != null && scrollable.InvalidateScroll != null) if (virtualizingPanel != null && scrollable.InvalidateScroll != null)
{ {
switch (owner.VirtualizationMode) switch (owner.VirtualizationMode)
{ {
case ItemVirtualizationMode.Simple: case ItemVirtualizationMode.Simple:
return new ItemVirtualizerSimple(owner); result = new ItemVirtualizerSimple(owner);
break;
} }
} }
return new ItemVirtualizerNone(owner); if (result == null)
{
result = new ItemVirtualizerNone(owner);
}
if (virtualizingPanel != null)
{
virtualizingPanel.Controller = result;
}
return result;
} }
/// <summary> /// <inheritdoc/>
/// Called by the <see cref="Owner"/> when it carries out an arrange. public virtual void UpdateControls()
/// </summary> {
/// <param name="finalSize">The final size passed to the arrange.</param> }
public abstract void Arranging(Size finalSize);
/// <summary> /// <summary>
/// Called when a request is made to bring an item into view. /// Gets the next control in the specified direction.
/// </summary> /// </summary>
/// <param name="target">The item to bring into view.</param> /// <param name="direction">The movement direction.</param>
/// <param name="targetRect">The rect on the item to bring into view.</param> /// <param name="from">The control from which movement begins.</param>
/// <returns>True if the request was handled; otherwise false.</returns> /// <returns>The control.</returns>
public virtual bool BringIntoView(IVisual target, Rect targetRect) public virtual IControl GetControlInDirection(NavigationDirection direction, IControl from)
{ {
return false; return null;
} }
/// <summary> /// <summary>
@ -157,5 +173,26 @@ namespace Avalonia.Controls.Presenters
Items = items; Items = items;
ItemCount = items.Count(); ItemCount = items.Count();
} }
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public virtual void ScrollIntoView(object item)
{
}
/// <inheritdoc/>
public virtual void Dispose()
{
VirtualizingPanel.Controller = null;
VirtualizingPanel.Children.Clear();
Owner.ItemContainerGenerator.Clear();
}
/// <summary>
/// Invalidates the current scroll.
/// </summary>
protected void InvalidateScroll() => ((ILogicalScrollable)Owner).InvalidateScroll();
} }
} }

28
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@ -19,6 +19,10 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizerNone(ItemsPresenter owner) public ItemVirtualizerNone(ItemsPresenter owner)
: base(owner) : base(owner)
{ {
if (Items != null && owner.Panel != null)
{
AddContainers(0, Items);
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -52,12 +56,6 @@ namespace Avalonia.Controls.Presenters
get { throw new NotSupportedException(); } get { throw new NotSupportedException(); }
} }
/// <inheritdoc/>
public override void Arranging(Size finalSize)
{
// We don't need to do anything here.
}
/// <inheritdoc/> /// <inheritdoc/>
public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e) public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
{ {
@ -112,6 +110,24 @@ namespace Avalonia.Controls.Presenters
Owner.InvalidateMeasure(); Owner.InvalidateMeasure();
} }
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public override void ScrollIntoView(object item)
{
if (Items != null)
{
var index = Items.IndexOf(item);
if (index != -1)
{
var container = Owner.ItemContainerGenerator.ContainerFromIndex(index);
container?.BringIntoView();
}
}
}
private IList<ItemContainerInfo> AddContainers(int index, IEnumerable items) private IList<ItemContainerInfo> AddContainers(int index, IEnumerable items)
{ {
var generator = Owner.ItemContainerGenerator; var generator = Owner.ItemContainerGenerator;

183
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -5,8 +5,11 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Utils; using Avalonia.Controls.Utils;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
{ {
@ -23,6 +26,8 @@ namespace Avalonia.Controls.Presenters
public ItemVirtualizerSimple(ItemsPresenter owner) public ItemVirtualizerSimple(ItemsPresenter owner)
: base(owner) : base(owner)
{ {
// Don't need to add children here as UpdateControls should be called by the panel
// measure/arrange.
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -48,7 +53,9 @@ namespace Avalonia.Controls.Presenters
if (delta != 0) if (delta != 0)
{ {
if ((NextIndex - 1) + delta < ItemCount) var newLastIndex = (NextIndex - 1) + delta;
if (newLastIndex < ItemCount)
{ {
if (panel.PixelOffset > 0) if (panel.PixelOffset > 0)
{ {
@ -63,10 +70,14 @@ namespace Avalonia.Controls.Presenters
} }
else else
{ {
// We're moving to a partially obscured item at the end of the list. // We're moving to a partially obscured item at the end of the list so
// offset the panel by the height of the first item.
var firstIndex = ItemCount - panel.Children.Count; var firstIndex = ItemCount - panel.Children.Count;
RecycleContainersForMove(firstIndex - FirstIndex); RecycleContainersForMove(firstIndex - FirstIndex);
panel.PixelOffset = VirtualizingPanel.PixelOverflow;
panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
panel.Children[0].Bounds.Height :
panel.Children[0].Bounds.Width;
} }
} }
} }
@ -84,10 +95,10 @@ namespace Avalonia.Controls.Presenters
} }
/// <inheritdoc/> /// <inheritdoc/>
public override void Arranging(Size finalSize) public override void UpdateControls()
{ {
CreateAndRemoveContainers(); CreateAndRemoveContainers();
((ILogicalScrollable)Owner).InvalidateScroll(); InvalidateScroll();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -95,15 +106,18 @@ namespace Avalonia.Controls.Presenters
{ {
base.ItemsChanged(items, e); base.ItemsChanged(items, e);
var panel = VirtualizingPanel;
if (items != null) if (items != null)
{ {
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
CreateAndRemoveContainers();
if (e.NewStartingIndex >= FirstIndex && if (e.NewStartingIndex >= FirstIndex &&
e.NewStartingIndex + e.NewItems.Count <= NextIndex) e.NewStartingIndex + e.NewItems.Count <= NextIndex)
{ {
CreateAndRemoveContainers();
RecycleContainers(); RecycleContainers();
} }
@ -125,6 +139,7 @@ namespace Avalonia.Controls.Presenters
case NotifyCollectionChangedAction.Reset: case NotifyCollectionChangedAction.Reset:
RecycleContainersOnRemove(); RecycleContainersOnRemove();
CreateAndRemoveContainers();
break; break;
} }
} }
@ -132,9 +147,94 @@ namespace Avalonia.Controls.Presenters
{ {
Owner.ItemContainerGenerator.Clear(); Owner.ItemContainerGenerator.Clear();
VirtualizingPanel.Children.Clear(); VirtualizingPanel.Children.Clear();
FirstIndex = NextIndex = 0;
} }
((ILogicalScrollable)Owner).InvalidateScroll(); // If we are scrolled to view a partially visible last item but controls were added
// then we need to return to a non-offset scroll position.
if (panel.PixelOffset != 0 && FirstIndex + panel.Children.Count < ItemCount)
{
panel.PixelOffset = 0;
RecycleContainersForMove(1);
}
InvalidateScroll();
}
public override IControl GetControlInDirection(NavigationDirection direction, IControl from)
{
var generator = Owner.ItemContainerGenerator;
var panel = VirtualizingPanel;
var itemIndex = generator.IndexFromContainer(from);
var vertical = VirtualizingPanel.ScrollDirection == Orientation.Vertical;
if (itemIndex == -1)
{
return null;
}
var newItemIndex = -1;
switch (direction)
{
case NavigationDirection.First:
newItemIndex = 0;
break;
case NavigationDirection.Last:
newItemIndex = ItemCount - 1;
break;
case NavigationDirection.Up:
if (vertical)
{
newItemIndex = itemIndex - 1;
}
break;
case NavigationDirection.Down:
if (vertical)
{
newItemIndex = itemIndex + 1;
}
break;
case NavigationDirection.Left:
if (!vertical)
{
newItemIndex = itemIndex - 1;
}
break;
case NavigationDirection.Right:
if (!vertical)
{
newItemIndex = itemIndex + 1;
}
break;
case NavigationDirection.PageUp:
newItemIndex = Math.Max(0, itemIndex - (int)ViewportValue);
break;
case NavigationDirection.PageDown:
newItemIndex = Math.Min(ItemCount - 1, itemIndex + (int)ViewportValue);
break;
}
return ScrollIntoView(newItemIndex);
}
/// <inheritdoc/>
public override void ScrollIntoView(object item)
{
var index = Items.IndexOf(item);
if (index != -1)
{
ScrollIntoView(index);
}
} }
/// <summary> /// <summary>
@ -146,7 +246,7 @@ namespace Avalonia.Controls.Presenters
var generator = Owner.ItemContainerGenerator; var generator = Owner.ItemContainerGenerator;
var panel = VirtualizingPanel; var panel = VirtualizingPanel;
if (!panel.IsFull && Items != null) if (!panel.IsFull && Items != null && panel.IsAttachedToVisualTree)
{ {
var memberSelector = Owner.MemberSelector; var memberSelector = Owner.MemberSelector;
var index = NextIndex; var index = NextIndex;
@ -336,5 +436,70 @@ namespace Avalonia.Controls.Presenters
Owner.ItemContainerGenerator.Dematerialize(FirstIndex + index, count); Owner.ItemContainerGenerator.Dematerialize(FirstIndex + index, count);
NextIndex -= count; NextIndex -= count;
} }
/// <summary>
/// Scrolls the item with the specified index into view.
/// </summary>
/// <param name="index">The item index.</param>
/// <returns>The container that was brought into view.</returns>
private IControl ScrollIntoView(int index)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;
var newOffset = -1.0;
if (index >= 0 && index < ItemCount)
{
if (index < FirstIndex)
{
newOffset = index;
}
else if (index >= NextIndex)
{
newOffset = index - Math.Ceiling(ViewportValue - 1);
}
else if (OffsetValue + ViewportValue >= ItemCount)
{
newOffset = OffsetValue - 1;
}
if (newOffset != -1)
{
OffsetValue = newOffset;
}
var container = generator.ContainerFromIndex(index);
var layoutManager = LayoutManager.Instance;
// We need to do a layout here because it's possible that the container we moved to
// is only partially visible due to differing item sizes. If the container is only
// partially visible, scroll again. Don't do this if there's no layout manager:
// it means we're running a unit test.
if (layoutManager != null)
{
layoutManager.ExecuteLayoutPass();
if (!new Rect(panel.Bounds.Size).Contains(container.Bounds))
{
OffsetValue += 1;
}
}
return container;
}
return null;
}
/// <summary>
/// Ensures an offset value is within the value range.
/// </summary>
/// <param name="value">The value.</param>
/// <returns>The coerced value.</returns>
private double CoerceOffset(double value)
{
var max = Math.Max(ExtentValue - ViewportValue, 0);
return MathUtilities.Clamp(value, 0, max);
}
} }
} }

48
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -5,7 +5,6 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.VisualTree;
using static Avalonia.Utilities.MathUtilities; using static Avalonia.Utilities.MathUtilities;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
@ -33,6 +32,9 @@ namespace Avalonia.Controls.Presenters
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(
typeof(ItemsPresenter), typeof(ItemsPresenter),
KeyboardNavigationMode.Once); KeyboardNavigationMode.Once);
VirtualizationModeProperty.Changed
.AddClassHandler<ItemsPresenter>(x => x.VirtualizationModeChanged);
} }
/// <summary> /// <summary>
@ -67,23 +69,46 @@ namespace Avalonia.Controls.Presenters
Action ILogicalScrollable.InvalidateScroll { get; set; } Action ILogicalScrollable.InvalidateScroll { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
Size ILogicalScrollable.ScrollSize => new Size(0, 1); Size ILogicalScrollable.ScrollSize => new Size(1, 1);
/// <inheritdoc/> /// <inheritdoc/>
Size ILogicalScrollable.PageScrollSize => new Size(0, 1); Size ILogicalScrollable.PageScrollSize => new Size(0, 1);
/// <inheritdoc/> /// <inheritdoc/>
bool ILogicalScrollable.BringIntoView(IVisual target, Rect targetRect) bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect)
{ {
return _virtualizer?.BringIntoView(target, targetRect) ?? false; return false;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize) IControl ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl from)
{
return _virtualizer?.GetControlInDirection(direction, from);
}
public override void ScrollIntoView(object item)
{ {
var result = base.ArrangeOverride(finalSize); _virtualizer?.ScrollIntoView(item);
_virtualizer.Arranging(finalSize); }
return result;
protected override Size MeasureOverride(Size availableSize)
{
// If infinity is passed as the available size and we're virtualized then we need to
// fill the available space, but to do that we *don't* want to materialize all our
// items! Take a look at the root of the tree for a MaxClientSize and use that as
// the available size.
if (availableSize == Size.Infinity && VirtualizationMode != ItemVirtualizationMode.None)
{
var window = VisualRoot as Window;
if (window != null)
{
availableSize = window.PlatformImpl.MaxClientSize;
}
}
Panel.Measure(availableSize);
return Panel.DesiredSize;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -116,5 +141,12 @@ namespace Avalonia.Controls.Presenters
var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0); var maxY = Math.Max(scrollable.Extent.Height - scrollable.Viewport.Height, 0);
return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY)); return new Vector(Clamp(value.X, 0, maxX), Clamp(value.Y, 0, maxY));
} }
private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e)
{
_virtualizer?.Dispose();
_virtualizer = ItemVirtualizer.Create(this);
((ILogicalScrollable)this).InvalidateScroll?.Invoke();
}
} }
} }

5
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -163,6 +163,11 @@ namespace Avalonia.Controls.Presenters
} }
} }
/// <inheritdoc/>
public virtual void ScrollIntoView(object item)
{
}
/// <summary> /// <summary>
/// Creates the <see cref="ItemContainerGenerator"/> for the control. /// Creates the <see cref="ItemContainerGenerator"/> for the control.
/// </summary> /// </summary>

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

@ -59,6 +59,7 @@ namespace Avalonia.Controls.Presenters
static ScrollContentPresenter() static ScrollContentPresenter()
{ {
ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true); ClipToBoundsProperty.OverrideDefaultValue(typeof(ScrollContentPresenter), true);
ChildProperty.Changed.AddClassHandler<ScrollContentPresenter>(x => x.ChildChanged);
AffectsArrange(OffsetProperty); AffectsArrange(OffsetProperty);
} }
@ -118,10 +119,11 @@ namespace Avalonia.Controls.Presenters
} }
var scrollable = Child as ILogicalScrollable; var scrollable = Child as ILogicalScrollable;
var control = target as IControl;
if (scrollable?.IsLogicalScrollEnabled == true) if (scrollable?.IsLogicalScrollEnabled == true && control != null)
{ {
return scrollable.BringIntoView(target, targetRect); return scrollable.BringIntoView(control, targetRect);
} }
var transform = target.TransformToVisual(Child); var transform = target.TransformToVisual(Child);
@ -229,26 +231,32 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerWheelChanged(PointerWheelEventArgs e) protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
{ {
if (Extent.Height > Viewport.Height) if (Extent.Height > Viewport.Height || Extent.Width > Viewport.Width)
{ {
var scrollable = Child as ILogicalScrollable; var scrollable = Child as ILogicalScrollable;
bool isLogical = scrollable?.IsLogicalScrollEnabled == true;
if (scrollable?.IsLogicalScrollEnabled == true) double x = Offset.X;
{ double y = Offset.Y;
var y = Offset.Y + (-e.Delta.Y * scrollable.ScrollSize.Height);
if (Extent.Height > Viewport.Height)
{
double height = isLogical ? scrollable.ScrollSize.Height : 50;
y += -e.Delta.Y * height;
y = Math.Max(y, 0); y = Math.Max(y, 0);
y = Math.Min(y, Extent.Height - Viewport.Height); y = Math.Min(y, Extent.Height - Viewport.Height);
Offset = new Vector(Offset.X, y);
e.Handled = true;
} }
else
if (Extent.Width > Viewport.Width)
{ {
var y = Offset.Y + (-e.Delta.Y * 50); double width = isLogical ? scrollable.ScrollSize.Width : 50;
y = Math.Max(y, 0); x += -e.Delta.X * width;
y = Math.Min(y, Extent.Height - Viewport.Height); x = Math.Max(x, 0);
Offset = new Vector(Offset.X, y); x = Math.Min(x, Extent.Width - Viewport.Width);
e.Handled = true;
} }
Offset = new Vector(x, y);
e.Handled = true;
} }
} }
@ -257,6 +265,16 @@ namespace Avalonia.Controls.Presenters
e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect); e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
} }
private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
{
UpdateScrollableSubscription((IControl)e.NewValue);
if (e.OldValue != null)
{
Offset = default(Vector);
}
}
private void UpdateScrollableSubscription(IControl child) private void UpdateScrollableSubscription(IControl child)
{ {
var scrollable = child as ILogicalScrollable; var scrollable = child as ILogicalScrollable;
@ -268,7 +286,7 @@ namespace Avalonia.Controls.Presenters
{ {
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable); scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
if (scrollable?.IsLogicalScrollEnabled == true) if (scrollable.IsLogicalScrollEnabled == true)
{ {
_logicalScrollSubscription = new CompositeDisposable( _logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x), this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
@ -285,15 +303,10 @@ namespace Avalonia.Controls.Presenters
if (logicalScroll != scrollable.IsLogicalScrollEnabled) if (logicalScroll != scrollable.IsLogicalScrollEnabled)
{ {
UpdateScrollableSubscription(Child); UpdateScrollableSubscription(Child);
Offset = default(Vector);
if (!scrollable.IsLogicalScrollEnabled) InvalidateMeasure();
{
Offset = default(Vector);
InvalidateMeasure();
}
} }
else if (scrollable.IsLogicalScrollEnabled)
if (scrollable.IsLogicalScrollEnabled)
{ {
Viewport = scrollable.Viewport; Viewport = scrollable.Viewport;
Extent = scrollable.Extent; Extent = scrollable.Extent;
@ -301,4 +314,4 @@ namespace Avalonia.Controls.Presenters
} }
} }
} }
} }

3
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -58,6 +59,8 @@ namespace Avalonia.Controls.Primitives
if (info != null) if (info != null)
{ {
child.RenderTransform = new MatrixTransform(info.Bounds.Transform);
child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
child.Arrange(info.Bounds.Bounds); child.Arrange(info.Bounds.Bounds);
} }
else else

12
src/Avalonia.Controls/Primitives/ILogicalScrollable.cs

@ -2,7 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.VisualTree; using Avalonia.Input;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -56,6 +56,14 @@ namespace Avalonia.Controls.Primitives
/// <param name="target">The target visual.</param> /// <param name="target">The target visual.</param>
/// <param name="targetRect">The portion of the target visual to bring into view.</param> /// <param name="targetRect">The portion of the target visual to bring into view.</param>
/// <returns>True if the scroll offset was changed; otherwise false.</returns> /// <returns>True if the scroll offset was changed; otherwise false.</returns>
bool BringIntoView(IVisual target, Rect targetRect); bool BringIntoView(IControl target, Rect targetRect);
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
IControl GetControlInDirection(NavigationDirection direction, IControl from);
} }
} }

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

@ -10,6 +10,7 @@ using Avalonia.LogicalTree;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Avalonia.Layout;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
{ {
@ -39,6 +40,18 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<PlacementMode> PlacementModeProperty = public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> HorizontalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(HorizontalOffset));
/// <summary>
/// Defines the <see cref="VerticalOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> VerticalOffsetProperty =
AvaloniaProperty.Register<Popup, double>(nameof(VerticalOffset));
/// <summary> /// <summary>
/// Defines the <see cref="PlacementTarget"/> property. /// Defines the <see cref="PlacementTarget"/> property.
/// </summary> /// </summary>
@ -122,6 +135,24 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); } set { SetValue(PlacementModeProperty, value); }
} }
/// <summary>
/// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double HorizontalOffset
{
get { return GetValue(HorizontalOffsetProperty); }
set { SetValue(HorizontalOffsetProperty, value); }
}
/// <summary>
/// Gets or sets the Vertical offset of the popup in relation to the <see cref="PlacementTarget"/>
/// </summary>
public double VerticalOffset
{
get { return GetValue(VerticalOffsetProperty); }
set { SetValue(VerticalOffsetProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the control that is used to determine the popup's position. /// Gets or sets the control that is used to determine the popup's position.
/// </summary> /// </summary>
@ -287,18 +318,26 @@ namespace Avalonia.Controls.Primitives
if (target?.GetVisualRoot() == null) if (target?.GetVisualRoot() == null)
{ {
mode = PlacementMode.Pointer; mode = PlacementMode.Pointer;
} }
switch (mode) switch (mode)
{ {
case PlacementMode.Pointer: case PlacementMode.Pointer:
return MouseDevice.Instance?.Position ?? default(Point); if (MouseDevice.Instance != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(HorizontalOffset * (PopupRoot as ILayoutRoot).LayoutScaling, VerticalOffset * (PopupRoot as ILayoutRoot).LayoutScaling);
return MouseDevice.Instance.Position + screenOffset;
}
return default(Point);
case PlacementMode.Bottom: case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0, target.Bounds.Height)) ?? zero;
return target?.PointToScreen(new Point(0 + HorizontalOffset, target.Bounds.Height + VerticalOffset)) ?? zero;
case PlacementMode.Right: case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width, 0)) ?? zero; return target?.PointToScreen(new Point(target.Bounds.Width + HorizontalOffset, 0 + VerticalOffset)) ?? zero;
default: default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode"); throw new InvalidOperationException("Invalid value for Popup.PlacementMode");

12
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -280,6 +280,12 @@ namespace Avalonia.Controls.Primitives
} }
} }
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public void ScrollIntoView(object item) => Presenter?.ScrollIntoView(item);
/// <summary> /// <summary>
/// Tries to get the container that was the source of an event. /// Tries to get the container that was the source of an event.
/// </summary> /// </summary>
@ -723,6 +729,12 @@ namespace Avalonia.Controls.Primitives
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
SelectedItemsAdded(e.NewItems.Cast<object>().ToList()); SelectedItemsAdded(e.NewItems.Cast<object>().ToList());
if (AutoScrollToSelectedItem)
{
ScrollIntoView(e.NewItems[0]);
}
added = e.NewItems; added = e.NewItems;
break; break;

4
src/Avalonia.Controls/Primitives/TabStrip.cs

@ -9,6 +9,9 @@ namespace Avalonia.Controls.Primitives
{ {
public class TabStrip : SelectingItemsControl public class TabStrip : SelectingItemsControl
{ {
private static readonly FuncTemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new WrapPanel { Orientation = Orientation.Horizontal });
private static IMemberSelector s_MemberSelector = new FuncMemberSelector<object, object>(SelectHeader); private static IMemberSelector s_MemberSelector = new FuncMemberSelector<object, object>(SelectHeader);
static TabStrip() static TabStrip()
@ -16,6 +19,7 @@ namespace Avalonia.Controls.Primitives
MemberSelectorProperty.OverrideDefaultValue<TabStrip>(s_MemberSelector); MemberSelectorProperty.OverrideDefaultValue<TabStrip>(s_MemberSelector);
SelectionModeProperty.OverrideDefaultValue<TabStrip>(SelectionMode.AlwaysSelected); SelectionModeProperty.OverrideDefaultValue<TabStrip>(SelectionMode.AlwaysSelected);
FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false); FocusableProperty.OverrideDefaultValue(typeof(TabStrip), false);
ItemsPanelProperty.OverrideDefaultValue<TabStrip>(DefaultPanel);
} }
protected override IItemContainerGenerator CreateItemContainerGenerator() protected override IItemContainerGenerator CreateItemContainerGenerator()

2
src/Avalonia.Controls/ScrollViewer.cs

@ -370,7 +370,7 @@ namespace Avalonia.Controls
private static double Max(double x, double y) private static double Max(double x, double y)
{ {
var result = Math.Max(x, y); var result = Math.Max(x, y);
return double.IsNaN(result) ? 0 : Math.Round(result); return double.IsNaN(result) ? 0 : result;
} }
private static Vector ValidateOffset(AvaloniaObject o, Vector value) private static Vector ValidateOffset(AvaloniaObject o, Vector value)

33
src/Avalonia.Controls/StackPanel.cs

@ -72,37 +72,52 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param> /// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param> /// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns> /// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from) IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{
var fromControl = from as IControl;
return (fromControl != null) ? GetControlInDirection(direction, fromControl) : null;
}
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
/// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns>
protected virtual IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{ {
var horiz = Orientation == Orientation.Horizontal; var horiz = Orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from); int index = Children.IndexOf((IControl)from);
switch (direction) switch (direction)
{ {
case FocusNavigationDirection.First: case NavigationDirection.First:
index = 0; index = 0;
break; break;
case FocusNavigationDirection.Last: case NavigationDirection.Last:
index = Children.Count - 1; index = Children.Count - 1;
break; break;
case FocusNavigationDirection.Next: case NavigationDirection.Next:
++index; ++index;
break; break;
case FocusNavigationDirection.Previous: case NavigationDirection.Previous:
--index; --index;
break; break;
case FocusNavigationDirection.Left: case NavigationDirection.Left:
index = horiz ? index - 1 : -1; index = horiz ? index - 1 : -1;
break; break;
case FocusNavigationDirection.Right: case NavigationDirection.Right:
index = horiz ? index + 1 : -1; index = horiz ? index + 1 : -1;
break; break;
case FocusNavigationDirection.Up: case NavigationDirection.Up:
index = horiz ? -1 : index - 1; index = horiz ? -1 : index - 1;
break; break;
case FocusNavigationDirection.Down: case NavigationDirection.Down:
index = horiz ? -1 : index + 1; index = horiz ? -1 : index + 1;
break; break;
default:
index = -1;
break;
} }
if (index >= 0 && index < Children.Count) if (index >= 0 && index < Children.Count)

3
src/Avalonia.Controls/Templates/FuncTemplate`1.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Styling;
namespace Avalonia.Controls.Templates namespace Avalonia.Controls.Templates
{ {
@ -34,5 +35,7 @@ namespace Avalonia.Controls.Templates
{ {
return _func(); return _func();
} }
object ITemplate.Build() => Build();
} }
} }

6
src/Avalonia.Controls/Templates/ITemplate`1.cs

@ -1,13 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Styling;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
/// <summary> /// <summary>
/// Creates a control. /// Creates a control.
/// </summary> /// </summary>
/// <typeparam name="TControl">The type of control.</typeparam> /// <typeparam name="TControl">The type of control.</typeparam>
public interface ITemplate<TControl> where TControl : IControl public interface ITemplate<TControl> : ITemplate where TControl : IControl
{ {
/// <summary> /// <summary>
/// Creates the control. /// Creates the control.
@ -15,6 +17,6 @@ namespace Avalonia.Controls
/// <returns> /// <returns>
/// The created control. /// The created control.
/// </returns> /// </returns>
TControl Build(); new TControl Build();
} }
} }

59
src/Avalonia.Controls/VirtualizingStackPanel.cs

@ -3,12 +3,17 @@
using System; using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel
{ {
private Size _availableSpace;
private double _takenSpace; private double _takenSpace;
private int _canBeRemoved; private int _canBeRemoved;
private double _averageItemSize; private double _averageItemSize;
@ -20,15 +25,14 @@ namespace Avalonia.Controls
get get
{ {
return Orientation == Orientation.Horizontal ? return Orientation == Orientation.Horizontal ?
_takenSpace >= AvailableSpace.Width : _takenSpace >= _availableSpace.Width :
_takenSpace >= AvailableSpace.Height; _takenSpace >= _availableSpace.Height;
} }
} }
IVirtualizingController IVirtualizingPanel.Controller { get; set; }
int IVirtualizingPanel.OverflowCount => _canBeRemoved; int IVirtualizingPanel.OverflowCount => _canBeRemoved;
Orientation IVirtualizingPanel.ScrollDirection => Orientation; Orientation IVirtualizingPanel.ScrollDirection => Orientation;
double IVirtualizingPanel.AverageItemSize => _averageItemSize; double IVirtualizingPanel.AverageItemSize => _averageItemSize;
double IVirtualizingPanel.PixelOverflow double IVirtualizingPanel.PixelOverflow
@ -36,8 +40,8 @@ namespace Avalonia.Controls
get get
{ {
var bounds = Orientation == Orientation.Horizontal ? var bounds = Orientation == Orientation.Horizontal ?
Bounds.Width : Bounds.Height; _availableSpace.Width : _availableSpace.Height;
return Math.Max(0, (_takenSpace - _pixelOffset) - bounds); return Math.Max(0, _takenSpace - bounds);
} }
} }
@ -55,16 +59,29 @@ namespace Avalonia.Controls
} }
} }
// TODO: We need to put a reasonable limit on this, probably based on the max window size. private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
private Size AvailableSpace => ((ILayoutable)this).PreviousMeasure ?? Bounds.Size;
protected override Size MeasureOverride(Size availableSize)
{
if (availableSize != ((ILayoutable)this).PreviousMeasure)
{
_availableSpace = availableSize;
Controller?.UpdateControls();
}
return base.MeasureOverride(availableSize);
}
protected override Size ArrangeOverride(Size finalSize) protected override Size ArrangeOverride(Size finalSize)
{ {
_availableSpace = finalSize;
_canBeRemoved = 0; _canBeRemoved = 0;
_takenSpace = 0; _takenSpace = 0;
_averageItemSize = 0; _averageItemSize = 0;
_averageCount = 0; _averageCount = 0;
var result = base.ArrangeOverride(finalSize); var result = base.ArrangeOverride(finalSize);
_takenSpace += _pixelOffset;
Controller?.UpdateControls();
return result; return result;
} }
@ -92,6 +109,21 @@ namespace Avalonia.Controls
} }
} }
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
var logicalScrollable = Parent as ILogicalScrollable;
var fromControl = from as IControl;
if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
{
return logicalScrollable.GetControlInDirection(direction, fromControl);
}
else
{
return base.GetControlInDirection(direction, from);
}
}
internal override void ArrangeChild( internal override void ArrangeChild(
IControl child, IControl child,
Rect rect, Rect rect,
@ -103,7 +135,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height); rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
child.Arrange(rect); child.Arrange(rect);
if (rect.Y >= AvailableSpace.Height) if (rect.Y >= _availableSpace.Height)
{ {
++_canBeRemoved; ++_canBeRemoved;
} }
@ -120,7 +152,7 @@ namespace Avalonia.Controls
rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height); rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
child.Arrange(rect); child.Arrange(rect);
if (rect.X >= AvailableSpace.Width) if (rect.X >= _availableSpace.Width)
{ {
++_canBeRemoved; ++_canBeRemoved;
} }
@ -139,7 +171,7 @@ namespace Avalonia.Controls
var bounds = Bounds; var bounds = Bounds;
var gap = Gap; var gap = Gap;
child.Measure(AvailableSpace); child.Measure(_availableSpace);
++_averageCount; ++_averageCount;
if (Orientation == Orientation.Vertical) if (Orientation == Orientation.Vertical)
@ -173,6 +205,11 @@ namespace Avalonia.Controls
_takenSpace -= width + gap; _takenSpace -= width + gap;
RemoveFromAverageItemSize(width); RemoveFromAverageItemSize(width);
} }
if (_canBeRemoved > 0)
{
--_canBeRemoved;
}
} }
private void AddToAverageItemSize(double value) private void AddToAverageItemSize(double value)

11
src/Avalonia.Controls/Window.cs

@ -89,7 +89,16 @@ namespace Avalonia.Controls
/// Initializes a new instance of the <see cref="Window"/> class. /// Initializes a new instance of the <see cref="Window"/> class.
/// </summary> /// </summary>
public Window() public Window()
: base(PlatformManager.CreateWindow()) : this(PlatformManager.CreateWindow())
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// </summary>
/// <param name="impl">The window implementation.</param>
public Window(IWindowImpl impl)
: base(impl)
{ {
_maxPlatformClientSize = this.PlatformImpl.MaxClientSize; _maxPlatformClientSize = this.PlatformImpl.MaxClientSize;
} }

18
src/Avalonia.Controls/WrapPanel.cs

@ -48,35 +48,35 @@ namespace Avalonia.Controls
/// <param name="direction">The movement direction.</param> /// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param> /// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns> /// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(FocusNavigationDirection direction, IInputElement from) IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from)
{ {
var horiz = Orientation == Orientation.Horizontal; var horiz = Orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from); int index = Children.IndexOf((IControl)from);
switch (direction) switch (direction)
{ {
case FocusNavigationDirection.First: case NavigationDirection.First:
index = 0; index = 0;
break; break;
case FocusNavigationDirection.Last: case NavigationDirection.Last:
index = Children.Count - 1; index = Children.Count - 1;
break; break;
case FocusNavigationDirection.Next: case NavigationDirection.Next:
++index; ++index;
break; break;
case FocusNavigationDirection.Previous: case NavigationDirection.Previous:
--index; --index;
break; break;
case FocusNavigationDirection.Left: case NavigationDirection.Left:
index = horiz ? index - 1 : -1; index = horiz ? index - 1 : -1;
break; break;
case FocusNavigationDirection.Right: case NavigationDirection.Right:
index = horiz ? index + 1 : -1; index = horiz ? index + 1 : -1;
break; break;
case FocusNavigationDirection.Up: case NavigationDirection.Up:
index = horiz ? -1 : index - 1; index = horiz ? -1 : index - 1;
break; break;
case FocusNavigationDirection.Down: case NavigationDirection.Down:
index = horiz ? -1 : index + 1; index = horiz ? -1 : index + 1;
break; break;
} }

52
src/Avalonia.DesignerSupport/DesignerApi.cs

@ -7,33 +7,42 @@ using System.Threading.Tasks;
namespace Avalonia.DesignerSupport namespace Avalonia.DesignerSupport
{ {
class DesignerApi class DesignerApiDictionary
{ {
private readonly Dictionary<string, object> _inner; public Dictionary<string, object> Dictionary { get; set; }
public DesignerApi(Dictionary<string, object> inner) public DesignerApiDictionary(Dictionary<string, object> dictionary)
{ {
_inner = inner; Dictionary = dictionary;
} }
object Get([CallerMemberName] string name = null) protected object Get([CallerMemberName] string name = null)
{ {
object rv; object rv;
_inner.TryGetValue(name, out rv); Dictionary.TryGetValue(name, out rv);
return rv; return rv;
} }
void Set(object value, [CallerMemberName] string name = null) protected void Set(object value, [CallerMemberName] string name = null)
{ {
_inner[name] = value; Dictionary[name] = value;
} }
}
class DesignerApi : DesignerApiDictionary
{
public Action<string> UpdateXaml public Action<string> UpdateXaml
{ {
get { return (Action<string>) Get(); } get { return (Action<string>) Get(); }
set {Set(value); } set {Set(value); }
} }
public Action<Dictionary<string, object>> UpdateXaml2
{
get { return (Action<Dictionary<string, object>>)Get(); }
set { Set(value); }
}
public Action OnResize public Action OnResize
{ {
get { return (Action) Get(); } get { return (Action) Get(); }
@ -52,5 +61,32 @@ namespace Avalonia.DesignerSupport
get { return (Action<double>) Get(); } get { return (Action<double>) Get(); }
} }
public DesignerApi(Dictionary<string, object> dictionary) : base(dictionary)
{
}
}
class DesignerApiXamlFileInfo : DesignerApiDictionary
{
public string Xaml
{
get { return (string)Get(); }
set { Set(value); }
}
public string AssemblyPath
{
get { return (string) Get(); }
set { Set(value); }
}
public DesignerApiXamlFileInfo(Dictionary<string, object> dictionary) : base(dictionary)
{
}
public DesignerApiXamlFileInfo(): base(new Dictionary<string, object>())
{
}
} }
} }

64
src/Avalonia.DesignerSupport/DesignerAssist.cs

@ -36,7 +36,7 @@ namespace Avalonia.DesignerSupport
public static void Init(Dictionary<string, object> shared) public static void Init(Dictionary<string, object> shared)
{ {
Design.IsDesignMode = true; Design.IsDesignMode = true;
Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, SetScalingFactor = SetScalingFactor}; Api = new DesignerApi(shared) {UpdateXaml = UpdateXaml, UpdateXaml2 = UpdateXaml2, SetScalingFactor = SetScalingFactor};
var plat = (IPclPlatformWrapper) Activator.CreateInstance(Assembly.Load(new AssemblyName("Avalonia.Win32")) var plat = (IPclPlatformWrapper) Activator.CreateInstance(Assembly.Load(new AssemblyName("Avalonia.Win32"))
.DefinedTypes.First(typeof (IPclPlatformWrapper).GetTypeInfo().IsAssignableFrom).AsType()); .DefinedTypes.First(typeof (IPclPlatformWrapper).GetTypeInfo().IsAssignableFrom).AsType());
@ -58,10 +58,9 @@ namespace Avalonia.DesignerSupport
//Ignore, Assembly.DefinedTypes threw an exception, we can't do anything about that //Ignore, Assembly.DefinedTypes threw an exception, we can't do anything about that
} }
} }
AppBuilder.Configure(app == null ? new DesignerApp() : (Application) Activator.CreateInstance(app.AsType())) AppBuilder.Configure(app == null ? new DesignerApp() : (Application) Activator.CreateInstance(app.AsType()))
.WithWindowingSubsystem(Application.InitializeWin32Subsystem) .UseWindowingSubsystem("Avalonia.Win32")
.WithRenderingSubsystem(() => { }) .UseRenderingSubsystem("Avalonia.Direct2D1")
.SetupWithoutStarting(); .SetupWithoutStarting();
} }
@ -74,22 +73,65 @@ namespace Avalonia.DesignerSupport
static Window s_currentWindow; static Window s_currentWindow;
private static void UpdateXaml(string xaml) private static void UpdateXaml(string xaml) => UpdateXaml2(new DesignerApiXamlFileInfo
{
Xaml = xaml
}.Dictionary);
private static void UpdateXaml2(Dictionary<string, object> dic)
{ {
var xamlInfo = new DesignerApiXamlFileInfo(dic);
Window window; Window window;
Control original; Control control;
using (PlatformManager.DesignerMode()) using (PlatformManager.DesignerMode())
{ {
var loader = new AvaloniaXamlLoader(); var loader = new AvaloniaXamlLoader();
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)); var stream = new MemoryStream(Encoding.UTF8.GetBytes(xamlInfo.Xaml));
original = (Control)loader.Load(stream);
window = original as Window;
Uri baseUri = null;
if (xamlInfo.AssemblyPath != null)
{
//Fabricate fake Uri
baseUri =
new Uri("resm:Fake.xaml?assembly=" + Path.GetFileNameWithoutExtension(xamlInfo.AssemblyPath));
}
var loaded = loader.Load(stream, null, baseUri);
var styles = loaded as Styles;
if (styles != null)
{
var substitute = Design.GetPreviewWith(styles) ??
styles.Select(Design.GetPreviewWith).FirstOrDefault(s => s != null);
if (substitute != null)
{
substitute.Styles.AddRange(styles);
control = substitute;
}
else
control = new StackPanel
{
Children =
{
new TextBlock {Text = "Styles can't be previewed without Design.PreviewWith. Add"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = " <Border Padding=20><!-- YOUR CONTROL FOR PREVIEW HERE--></Border>"},
new TextBlock {Text = "<Design.PreviewWith>"},
new TextBlock {Text = "before setters in your first Style"}
}
};
}
if (loaded is Application)
control = new TextBlock {Text = "Application can't be previewed in design view"};
else
control = (Control) loaded;
window = control as Window;
if (window == null) if (window == null)
{ {
window = new Window() {Content = original}; window = new Window() {Content = (Control)control};
} }
if (!window.IsSet(Window.SizeToContentProperty)) if (!window.IsSet(Window.SizeToContentProperty))
@ -99,7 +141,7 @@ namespace Avalonia.DesignerSupport
s_currentWindow?.Close(); s_currentWindow?.Close();
s_currentWindow = window; s_currentWindow = window;
window.Show(); window.Show();
Design.ApplyDesignerProperties(window, original); Design.ApplyDesignerProperties(window, control);
Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle); Api.OnWindowCreated?.Invoke(window.PlatformImpl.Handle.Handle);
Api.OnResize?.Invoke(); Api.OnResize?.Invoke();
} }

4
src/Avalonia.Input/Avalonia.Input.csproj

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@ -79,7 +79,7 @@
<Compile Include="IMainMenu.cs" /> <Compile Include="IMainMenu.cs" />
<Compile Include="IAccessKeyHandler.cs" /> <Compile Include="IAccessKeyHandler.cs" />
<Compile Include="FocusManager.cs" /> <Compile Include="FocusManager.cs" />
<Compile Include="FocusNavigationDirection.cs" /> <Compile Include="NavigationDirection.cs" />
<Compile Include="ICloseable.cs" /> <Compile Include="ICloseable.cs" />
<Compile Include="IFocusManager.cs" /> <Compile Include="IFocusManager.cs" />
<Compile Include="IFocusScope.cs" /> <Compile Include="IFocusScope.cs" />

2
src/Avalonia.Input/FocusManager.cs

@ -79,7 +79,7 @@ namespace Avalonia.Input
{ {
IInputElement element; IInputElement element;
if (_focusScopes.TryGetValue(scope, out element)) if (_focusScopes.TryGetValue(scope, out element) && element != null)
{ {
Focus(element, method); Focus(element, method);
return; return;

2
src/Avalonia.Input/IKeyboardNavigationHandler.cs

@ -25,7 +25,7 @@ namespace Avalonia.Input
/// <param name="modifiers">Any input modifiers active at the time of focus.</param> /// <param name="modifiers">Any input modifiers active at the time of focus.</param>
void Move( void Move(
IInputElement element, IInputElement element,
FocusNavigationDirection direction, NavigationDirection direction,
InputModifiers modifiers = InputModifiers.None); InputModifiers modifiers = InputModifiers.None);
} }
} }

2
src/Avalonia.Input/INavigableContainer.cs

@ -14,6 +14,6 @@ namespace Avalonia.Input
/// <param name="direction">The movement direction.</param> /// <param name="direction">The movement direction.</param>
/// <param name="from">The control from which movement begins.</param> /// <param name="from">The control from which movement begins.</param>
/// <returns>The control.</returns> /// <returns>The control.</returns>
IInputElement GetControl(FocusNavigationDirection direction, IInputElement from); IInputElement GetControl(NavigationDirection direction, IInputElement from);
} }
} }

16
src/Avalonia.Input/InputExtensions.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.VisualTree;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -24,14 +25,13 @@ namespace Avalonia.Input
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
if (element.Bounds.Contains(p) && if (element.IsVisible &&
element.IsVisible &&
element.IsHitTestVisible && element.IsHitTestVisible &&
element.IsEnabledCore) element.IsEnabledCore)
{ {
p -= element.Bounds.Position; bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)element).Contains(p);
if (element.VisualChildren.Any()) if ((containsPoint || !element.ClipToBounds) && element.VisualChildren.Any())
{ {
foreach (var child in ZSort(element.VisualChildren.OfType<IInputElement>())) foreach (var child in ZSort(element.VisualChildren.OfType<IInputElement>()))
{ {
@ -42,7 +42,10 @@ namespace Avalonia.Input
} }
} }
yield return element; if (containsPoint)
{
yield return element;
}
} }
} }
@ -68,7 +71,6 @@ namespace Avalonia.Input
}) })
.OrderBy(x => x, null) .OrderBy(x => x, null)
.Select(x => x.Element); .Select(x => x.Element);
} }
private class ZOrderElement : IComparable<ZOrderElement> private class ZOrderElement : IComparable<ZOrderElement>
@ -92,4 +94,4 @@ namespace Avalonia.Input
} }
} }
} }
} }

34
src/Avalonia.Input/KeyboardNavigationHandler.cs

@ -48,11 +48,11 @@ namespace Avalonia.Input
/// </returns> /// </returns>
public static IInputElement GetNext( public static IInputElement GetNext(
IInputElement element, IInputElement element,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
if (direction == FocusNavigationDirection.Next || direction == FocusNavigationDirection.Previous) if (direction == NavigationDirection.Next || direction == NavigationDirection.Previous)
{ {
return TabNavigation.GetNextInTabOrder(element, direction); return TabNavigation.GetNextInTabOrder(element, direction);
} }
@ -70,7 +70,7 @@ namespace Avalonia.Input
/// <param name="modifiers">Any input modifiers active at the time of focus.</param> /// <param name="modifiers">Any input modifiers active at the time of focus.</param>
public void Move( public void Move(
IInputElement element, IInputElement element,
FocusNavigationDirection direction, NavigationDirection direction,
InputModifiers modifiers = InputModifiers.None) InputModifiers modifiers = InputModifiers.None)
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
@ -79,8 +79,8 @@ namespace Avalonia.Input
if (next != null) if (next != null)
{ {
var method = direction == FocusNavigationDirection.Next || var method = direction == NavigationDirection.Next ||
direction == FocusNavigationDirection.Previous ? direction == NavigationDirection.Previous ?
NavigationMethod.Tab : NavigationMethod.Directional; NavigationMethod.Tab : NavigationMethod.Directional;
FocusManager.Instance.Focus(next, method, modifiers); FocusManager.Instance.Focus(next, method, modifiers);
} }
@ -97,25 +97,37 @@ namespace Avalonia.Input
if (current != null) if (current != null)
{ {
FocusNavigationDirection? direction = null; NavigationDirection? direction = null;
switch (e.Key) switch (e.Key)
{ {
case Key.Tab: case Key.Tab:
direction = (e.Modifiers & InputModifiers.Shift) == 0 ? direction = (e.Modifiers & InputModifiers.Shift) == 0 ?
FocusNavigationDirection.Next : FocusNavigationDirection.Previous; NavigationDirection.Next : NavigationDirection.Previous;
break; break;
case Key.Up: case Key.Up:
direction = FocusNavigationDirection.Up; direction = NavigationDirection.Up;
break; break;
case Key.Down: case Key.Down:
direction = FocusNavigationDirection.Down; direction = NavigationDirection.Down;
break; break;
case Key.Left: case Key.Left:
direction = FocusNavigationDirection.Left; direction = NavigationDirection.Left;
break; break;
case Key.Right: case Key.Right:
direction = FocusNavigationDirection.Right; direction = NavigationDirection.Right;
break;
case Key.PageUp:
direction = NavigationDirection.PageUp;
break;
case Key.PageDown:
direction = NavigationDirection.PageDown;
break;
case Key.Home:
direction = NavigationDirection.First;
break;
case Key.End:
direction = NavigationDirection.Last;
break; break;
} }

27
src/Avalonia.Input/Navigation/DirectionalNavigation.cs

@ -24,18 +24,17 @@ namespace Avalonia.Input.Navigation
/// </returns> /// </returns>
public static IInputElement GetNext( public static IInputElement GetNext(
IInputElement element, IInputElement element,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>( Contract.Requires<ArgumentException>(
direction != FocusNavigationDirection.Next && direction != NavigationDirection.Next &&
direction != FocusNavigationDirection.Previous); direction != NavigationDirection.Previous);
var container = element.GetVisualParent<IInputElement>(); var container = element.GetVisualParent<IInputElement>();
if (container != null) if (container != null)
{ {
var isForward = IsForward(direction);
var mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container); var mode = KeyboardNavigation.GetDirectionalNavigation((InputElement)container);
switch (mode) switch (mode)
@ -63,12 +62,12 @@ namespace Avalonia.Input.Navigation
/// </summary> /// </summary>
/// <param name="direction">The direction.</param> /// <param name="direction">The direction.</param>
/// <returns>True if the direction is forward.</returns> /// <returns>True if the direction is forward.</returns>
private static bool IsForward(FocusNavigationDirection direction) private static bool IsForward(NavigationDirection direction)
{ {
return direction == FocusNavigationDirection.Next || return direction == NavigationDirection.Next ||
direction == FocusNavigationDirection.Last || direction == NavigationDirection.Last ||
direction == FocusNavigationDirection.Right || direction == NavigationDirection.Right ||
direction == FocusNavigationDirection.Down; direction == NavigationDirection.Down;
} }
/// <summary> /// <summary>
@ -77,7 +76,7 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, FocusNavigationDirection direction) private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
{ {
return IsForward(direction) ? return IsForward(direction) ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendents(container).FirstOrDefault() :
@ -121,9 +120,9 @@ namespace Avalonia.Input.Navigation
private static IInputElement GetNextInContainer( private static IInputElement GetNextInContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
if (direction == FocusNavigationDirection.Down) if (direction == NavigationDirection.Down)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendent = GetFocusableDescendents(element).FirstOrDefault();
@ -156,7 +155,7 @@ namespace Avalonia.Input.Navigation
element = null; element = null;
} }
if (element != null && direction == FocusNavigationDirection.Up) if (element != null && direction == NavigationDirection.Up)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendent = GetFocusableDescendents(element).LastOrDefault();
@ -180,7 +179,7 @@ namespace Avalonia.Input.Navigation
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement GetFirstInNextContainer(
IInputElement container, IInputElement container,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
var parent = container.GetVisualParent<IInputElement>(); var parent = container.GetVisualParent<IInputElement>();
var isForward = IsForward(direction); var isForward = IsForward(direction);

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

@ -24,12 +24,12 @@ namespace Avalonia.Input.Navigation
/// </returns> /// </returns>
public static IInputElement GetNextInTabOrder( public static IInputElement GetNextInTabOrder(
IInputElement element, IInputElement element,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
Contract.Requires<ArgumentNullException>(element != null); Contract.Requires<ArgumentNullException>(element != null);
Contract.Requires<ArgumentException>( Contract.Requires<ArgumentException>(
direction == FocusNavigationDirection.Next || direction == NavigationDirection.Next ||
direction == FocusNavigationDirection.Previous); direction == NavigationDirection.Previous);
var container = element.GetVisualParent<IInputElement>(); var container = element.GetVisualParent<IInputElement>();
@ -63,9 +63,9 @@ namespace Avalonia.Input.Navigation
/// <param name="container">The element.</param> /// <param name="container">The element.</param>
/// <param name="direction">The direction to search.</param> /// <param name="direction">The direction to search.</param>
/// <returns>The element or null if not found.##</returns> /// <returns>The element or null if not found.##</returns>
private static IInputElement GetFocusableDescendent(IInputElement container, FocusNavigationDirection direction) private static IInputElement GetFocusableDescendent(IInputElement container, NavigationDirection direction)
{ {
return direction == FocusNavigationDirection.Next ? return direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendents(container).LastOrDefault();
} }
@ -128,9 +128,9 @@ namespace Avalonia.Input.Navigation
private static IInputElement GetNextInContainer( private static IInputElement GetNextInContainer(
IInputElement element, IInputElement element,
IInputElement container, IInputElement container,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
if (direction == FocusNavigationDirection.Next) if (direction == NavigationDirection.Next)
{ {
var descendent = GetFocusableDescendents(element).FirstOrDefault(); var descendent = GetFocusableDescendents(element).FirstOrDefault();
@ -165,7 +165,7 @@ namespace Avalonia.Input.Navigation
element = null; element = null;
} }
if (element != null && direction == FocusNavigationDirection.Previous) if (element != null && direction == NavigationDirection.Previous)
{ {
var descendent = GetFocusableDescendents(element).LastOrDefault(); var descendent = GetFocusableDescendents(element).LastOrDefault();
@ -189,14 +189,14 @@ namespace Avalonia.Input.Navigation
/// <returns>The first element, or null if there are no more elements.</returns> /// <returns>The first element, or null if there are no more elements.</returns>
private static IInputElement GetFirstInNextContainer( private static IInputElement GetFirstInNextContainer(
IInputElement container, IInputElement container,
FocusNavigationDirection direction) NavigationDirection direction)
{ {
var parent = container.GetVisualParent<IInputElement>(); var parent = container.GetVisualParent<IInputElement>();
IInputElement next = null; IInputElement next = null;
if (parent != null) if (parent != null)
{ {
if (direction == FocusNavigationDirection.Previous && parent.CanFocus()) if (direction == NavigationDirection.Previous && parent.CanFocus())
{ {
return parent; return parent;
} }
@ -204,7 +204,7 @@ namespace Avalonia.Input.Navigation
var siblings = parent.GetVisualChildren() var siblings = parent.GetVisualChildren()
.OfType<IInputElement>() .OfType<IInputElement>()
.Where(FocusExtensions.CanFocusDescendents); .Where(FocusExtensions.CanFocusDescendents);
var sibling = direction == FocusNavigationDirection.Next ? var sibling = direction == NavigationDirection.Next ?
siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() : siblings.SkipWhile(x => x != container).Skip(1).FirstOrDefault() :
siblings.TakeWhile(x => x != container).LastOrDefault(); siblings.TakeWhile(x => x != container).LastOrDefault();
@ -216,7 +216,7 @@ namespace Avalonia.Input.Navigation
} }
else else
{ {
next = direction == FocusNavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(sibling).FirstOrDefault() : GetFocusableDescendents(sibling).FirstOrDefault() :
GetFocusableDescendents(sibling).LastOrDefault(); GetFocusableDescendents(sibling).LastOrDefault();
} }
@ -229,7 +229,7 @@ namespace Avalonia.Input.Navigation
} }
else else
{ {
next = direction == FocusNavigationDirection.Next ? next = direction == NavigationDirection.Next ?
GetFocusableDescendents(container).FirstOrDefault() : GetFocusableDescendents(container).FirstOrDefault() :
GetFocusableDescendents(container).LastOrDefault(); GetFocusableDescendents(container).LastOrDefault();
} }

14
src/Avalonia.Input/FocusNavigationDirection.cs → src/Avalonia.Input/NavigationDirection.cs

@ -4,9 +4,9 @@
namespace Avalonia.Input namespace Avalonia.Input
{ {
/// <summary> /// <summary>
/// Describes how focus should be moved. /// Describes how focus should be moved by directional or tab keys.
/// </summary> /// </summary>
public enum FocusNavigationDirection public enum NavigationDirection
{ {
/// <summary> /// <summary>
/// Move the focus to the next control in the tab order. /// Move the focus to the next control in the tab order.
@ -47,5 +47,15 @@ namespace Avalonia.Input
/// Move the focus down. /// Move the focus down.
/// </summary> /// </summary>
Down, Down,
/// <summary>
/// Move the focus up a page.
/// </summary>
PageUp,
/// <summary>
/// Move the focus down a page.
/// </summary>
PageDown,
} }
} }

35
src/Avalonia.Layout/LayoutManager.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Threading; using Avalonia.Threading;
@ -13,8 +14,8 @@ namespace Avalonia.Layout
/// </summary> /// </summary>
public class LayoutManager : ILayoutManager public class LayoutManager : ILayoutManager
{ {
private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>(); private readonly HashSet<ILayoutable> _toMeasure = new HashSet<ILayoutable>();
private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>(); private readonly HashSet<ILayoutable> _toArrange = new HashSet<ILayoutable>();
private bool _queued; private bool _queued;
private bool _running; private bool _running;
@ -29,8 +30,8 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toMeasure.Enqueue(control); _toMeasure.Add(control);
_toArrange.Enqueue(control); _toArrange.Add(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -40,7 +41,7 @@ namespace Avalonia.Layout
Contract.Requires<ArgumentNullException>(control != null); Contract.Requires<ArgumentNullException>(control != null);
Dispatcher.UIThread.VerifyAccess(); Dispatcher.UIThread.VerifyAccess();
_toArrange.Enqueue(control); _toArrange.Add(control);
QueueLayoutPass(); QueueLayoutPass();
} }
@ -107,7 +108,7 @@ namespace Avalonia.Layout
{ {
while (_toMeasure.Count > 0) while (_toMeasure.Count > 0)
{ {
var next = _toMeasure.Dequeue(); var next = _toMeasure.First();
Measure(next); Measure(next);
} }
} }
@ -116,7 +117,7 @@ namespace Avalonia.Layout
{ {
while (_toArrange.Count > 0 && _toMeasure.Count == 0) while (_toArrange.Count > 0 && _toMeasure.Count == 0)
{ {
var next = _toArrange.Dequeue(); var next = _toArrange.First();
Arrange(next); Arrange(next);
} }
} }
@ -124,29 +125,45 @@ namespace Avalonia.Layout
private void Measure(ILayoutable control) private void Measure(ILayoutable control)
{ {
var root = control as ILayoutRoot; var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null) if (root != null)
{ {
root.Measure(root.MaxClientSize); root.Measure(root.MaxClientSize);
} }
else if (control.PreviousMeasure.HasValue) else if (parent != null)
{
Measure(parent);
}
if (!control.IsMeasureValid)
{ {
control.Measure(control.PreviousMeasure.Value); control.Measure(control.PreviousMeasure.Value);
} }
_toMeasure.Remove(control);
} }
private void Arrange(ILayoutable control) private void Arrange(ILayoutable control)
{ {
var root = control as ILayoutRoot; var root = control as ILayoutRoot;
var parent = control.VisualParent as ILayoutable;
if (root != null) if (root != null)
{ {
root.Arrange(new Rect(root.DesiredSize)); root.Arrange(new Rect(root.DesiredSize));
} }
else if (control.PreviousArrange.HasValue) else if (parent != null)
{
Arrange(parent);
}
if (control.PreviousArrange.HasValue)
{ {
control.Arrange(control.PreviousArrange.Value); control.Arrange(control.PreviousArrange.Value);
} }
_toArrange.Remove(control);
} }
private void QueueLayoutPass() private void QueueLayoutPass()

11
src/Avalonia.Layout/Layoutable.cs

@ -603,6 +603,17 @@ namespace Avalonia.Layout
return finalSize; return finalSize;
} }
/// <inheritdoc/>
protected override sealed void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
foreach (ILayoutable i in this.GetSelfAndVisualDescendents())
{
i.InvalidateMeasure();
}
base.OnVisualParentChanged(oldParent, newParent);
}
/// <summary> /// <summary>
/// Calls <see cref="InvalidateMeasure"/> on the control on which a property changed. /// Calls <see cref="InvalidateMeasure"/> on the control on which a property changed.
/// </summary> /// </summary>

39
src/Avalonia.SceneGraph/Media/DrawingContext.cs

@ -44,6 +44,8 @@ namespace Avalonia.Media
private Matrix _currentTransform = Matrix.Identity; private Matrix _currentTransform = Matrix.Identity;
private Matrix _currentContainerTransform = Matrix.Identity;
/// <summary> /// <summary>
/// Gets the current transform of the drawing context. /// Gets the current transform of the drawing context.
/// </summary> /// </summary>
@ -57,7 +59,7 @@ namespace Avalonia.Media
} }
} }
private Matrix _currentContainerTransform = Matrix.Identity; internal Matrix CurrentContainerTransform => _currentContainerTransform;
/// <summary> /// <summary>
/// Draws a bitmap image. /// Draws a bitmap image.
@ -125,7 +127,9 @@ namespace Avalonia.Media
Matrix, Matrix,
Opacity, Opacity,
Clip, Clip,
MatrixContainer MatrixContainer,
GeometryClip,
OpacityMask
} }
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix)) public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default(Matrix))
@ -147,10 +151,14 @@ namespace Avalonia.Media
_context._states.Pop(); _context._states.Pop();
if (_type == PushedStateType.Matrix) if (_type == PushedStateType.Matrix)
_context.CurrentTransform = _matrix; _context.CurrentTransform = _matrix;
else if(_type == PushedStateType.Clip) else if (_type == PushedStateType.Clip)
_context._impl.PopClip(); _context._impl.PopClip();
else if(_type == PushedStateType.Opacity) else if (_type == PushedStateType.Opacity)
_context._impl.PopOpacity(); _context._impl.PopOpacity();
else if (_type == PushedStateType.GeometryClip)
_context._impl.PopGeometryClip();
else if (_type == PushedStateType.OpacityMask)
_context._impl.PopOpacityMask();
else if (_type == PushedStateType.MatrixContainer) else if (_type == PushedStateType.MatrixContainer)
{ {
var cont = _context._transformContainers.Pop(); var cont = _context._transformContainers.Pop();
@ -172,6 +180,18 @@ namespace Avalonia.Media
return new PushedState(this, PushedState.PushedStateType.Clip); return new PushedState(this, PushedState.PushedStateType.Clip);
} }
/// <summary>
/// Pushes a clip geometry.
/// </summary>
/// <param name="clip">The clip geometry.</param>
/// <returns>A disposable used to undo the clip geometry.</returns>
public PushedState PushGeometryClip(Geometry clip)
{
Contract.Requires<ArgumentNullException>(clip != null);
_impl.PushGeometryClip(clip);
return new PushedState(this, PushedState.PushedStateType.GeometryClip);
}
/// <summary> /// <summary>
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
@ -184,6 +204,17 @@ namespace Avalonia.Media
return new PushedState(this, PushedState.PushedStateType.Opacity); return new PushedState(this, PushedState.PushedStateType.Opacity);
} }
/// <summary>
/// Pushes an opacity mask.
/// </summary>
/// <param name="mask">The opacity mask.</param>
/// <returns>A disposable to undo the opacity mask.</returns>
public PushedState PushOpacityMask(IBrush mask, Rect bounds)
{
_impl.PushOpacityMask(mask, bounds);
return new PushedState(this, PushedState.PushedStateType.OpacityMask);
}
/// <summary> /// <summary>
/// Pushes a matrix transformation. /// Pushes a matrix transformation.
/// </summary> /// </summary>

10
src/Avalonia.SceneGraph/Media/Geometry.cs

@ -66,5 +66,15 @@ namespace Avalonia.Media
{ {
return PlatformImpl.GetRenderBounds(strokeThickness); return PlatformImpl.GetRenderBounds(strokeThickness);
} }
/// <summary>
/// Indicates whether the geometry contains the specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
public bool FillContains(Point point)
{
return PlatformImpl.FillContains(point);
}
} }
} }

14
src/Avalonia.SceneGraph/Media/IDrawingContext.cs

@ -69,7 +69,6 @@ namespace Avalonia.Media
/// Pushes a clip rectange. /// Pushes a clip rectange.
/// </summary> /// </summary>
/// <param name="clip">The clip rectangle.</param> /// <param name="clip">The clip rectangle.</param>
/// <returns>A disposable used to undo the clip rectangle.</returns>
void PushClip(Rect clip); void PushClip(Rect clip);
void PopClip(); void PopClip();
@ -78,9 +77,20 @@ namespace Avalonia.Media
/// Pushes an opacity value. /// Pushes an opacity value.
/// </summary> /// </summary>
/// <param name="opacity">The opacity.</param> /// <param name="opacity">The opacity.</param>
/// <returns>A disposable used to undo the opacity.</returns>
void PushOpacity(double opacity); void PushOpacity(double opacity);
void PopOpacity(); void PopOpacity();
void PushOpacityMask(IBrush mask, Rect bounds);
void PopOpacityMask();
/// <summary>
/// Pushes a clip geometry.
/// </summary>
/// <param name="clip">The clip geometry.</param>
void PushGeometryClip(Geometry clip);
void PopGeometryClip();
} }
} }

3
src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs

@ -55,7 +55,8 @@ namespace Avalonia.Media.Imaging
/// </summary> /// </summary>
public IBitmapImpl PlatformImpl public IBitmapImpl PlatformImpl
{ {
get; } get;
}
/// <summary> /// <summary>
/// Saves the bitmap to a file. /// Saves the bitmap to a file.

39
src/Avalonia.SceneGraph/Media/PathMarkupParser.cs

@ -34,6 +34,12 @@ namespace Avalonia.Media
{ 'z', Command.Close }, { 'z', Command.Close },
}; };
private static readonly Dictionary<char, FillRule> FillRules = new Dictionary<char, FillRule>
{
{'0', FillRule.EvenOdd },
{'1', FillRule.NonZero }
};
private StreamGeometry _geometry; private StreamGeometry _geometry;
private readonly StreamGeometryContext _context; private readonly StreamGeometryContext _context;
@ -90,8 +96,7 @@ namespace Avalonia.Media
switch (command) switch (command)
{ {
case Command.FillRule: case Command.FillRule:
// TODO: Implement. _context.SetFillRule(ReadFillRule(reader));
reader.Read();
break; break;
case Command.Move: case Command.Move:
@ -226,6 +231,24 @@ namespace Avalonia.Media
} }
} }
private static FillRule ReadFillRule(StringReader reader)
{
int i = reader.Read();
if (i == -1)
{
throw new InvalidDataException("Invalid fill rule");
}
char c = (char)i;
FillRule rule;
if (!FillRules.TryGetValue(c, out rule))
{
throw new InvalidDataException("Invalid fill rule");
}
return rule;
}
private static double ReadDouble(StringReader reader) private static double ReadDouble(StringReader reader)
{ {
ReadWhitespace(reader); ReadWhitespace(reader);
@ -248,8 +271,16 @@ namespace Avalonia.Media
{ {
b.Append(c); b.Append(c);
reader.Read(); reader.Read();
readSign = c == '+' || c == '-';
readPoint = c == '.'; if (!readSign)
{
readSign = c == '+' || c == '-';
}
if (!readPoint)
{
readPoint = c == '.';
}
if (c == 'E') if (c == 'E')
{ {

7
src/Avalonia.SceneGraph/Platform/IGeometryImpl.cs

@ -24,5 +24,12 @@ namespace Avalonia.Platform
/// <param name="strokeThickness">The stroke thickness.</param> /// <param name="strokeThickness">The stroke thickness.</param>
/// <returns>The bounding rectangle.</returns> /// <returns>The bounding rectangle.</returns>
Rect GetRenderBounds(double strokeThickness); Rect GetRenderBounds(double strokeThickness);
/// <summary>
/// Indicates whether the geometry contains the specified point.
/// </summary>
/// <param name="point">The point.</param>
/// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
bool FillContains(Point point);
} }
} }

16
src/Avalonia.SceneGraph/Rect.cs

@ -224,14 +224,24 @@ namespace Avalonia
} }
/// <summary> /// <summary>
/// Determines whether a points in in the bounds of the rectangle. /// Determines whether a point in in the bounds of the rectangle.
/// </summary> /// </summary>
/// <param name="p">The point.</param> /// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns> /// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool Contains(Point p) public bool Contains(Point p)
{ {
return p.X >= _x && p.X < _x + _width && return p.X >= _x && p.X <= _x + _width &&
p.Y >= _y && p.Y < _y + _height; p.Y >= _y && p.Y <= _y + _height;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.
/// </summary>
/// <param name="r">The rectangle.</param>
/// <returns>true if the rectangle is fully contained; otherwise false.</returns>
public bool Contains(Rect r)
{
return Contains(r.TopLeft) && Contains(r.BottomRight);
} }
/// <summary> /// <summary>

12
src/Avalonia.SceneGraph/Rendering/RendererMixin.cs

@ -104,7 +104,7 @@ namespace Avalonia.Rendering
if (visual.RenderTransform != null) if (visual.RenderTransform != null)
{ {
var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(origin); var offset = Matrix.CreateTranslation(origin);
renderTransform = (-offset) * visual.RenderTransform.Value * (offset); renderTransform = (-offset) * visual.RenderTransform.Value * (offset);
} }
@ -119,9 +119,17 @@ namespace Avalonia.Rendering
using (context.PushPostTransform(m)) using (context.PushPostTransform(m))
using (context.PushOpacity(opacity)) using (context.PushOpacity(opacity))
using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState)) using (clipToBounds ? context.PushClip(bounds) : default(DrawingContext.PushedState))
using (visual.Clip != null ? context.PushGeometryClip(visual.Clip) : default(DrawingContext.PushedState))
using (visual.OpacityMask != null ? context.PushOpacityMask(visual.OpacityMask, bounds) : default(DrawingContext.PushedState))
using (context.PushTransformContainer()) using (context.PushTransformContainer())
{ {
visual.Render(context); visual.Render(context);
var transformed =
new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
if (visual is Visual)
{
BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
}
var lst = GetSortedVisualList(visual.VisualChildren); var lst = GetSortedVisualList(visual.VisualChildren);
@ -164,7 +172,7 @@ namespace Avalonia.Rendering
} }
else else
{ {
var origin = visual.TransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height)); var origin = visual.RenderTransformOrigin.ToPixels(new Size(visual.Bounds.Width, visual.Bounds.Height));
var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin); var offset = Matrix.CreateTranslation(visual.Bounds.Position + origin);
var m = (-offset) * visual.RenderTransform.Value * (offset); var m = (-offset) * visual.RenderTransform.Value * (offset);
return visual.Bounds.TransformToAABB(m); return visual.Bounds.TransformToAABB(m);

55
src/Avalonia.SceneGraph/Visual.cs

@ -39,6 +39,12 @@ namespace Avalonia
public static readonly StyledProperty<bool> ClipToBoundsProperty = public static readonly StyledProperty<bool> ClipToBoundsProperty =
AvaloniaProperty.Register<Visual, bool>(nameof(ClipToBounds)); AvaloniaProperty.Register<Visual, bool>(nameof(ClipToBounds));
/// <summary>
/// Defines the <see cref="Clip"/> property.
/// </summary>
public static readonly StyledProperty<Geometry> ClipProperty =
AvaloniaProperty.Register<Visual, Geometry>(nameof(Clip));
/// <summary> /// <summary>
/// Defines the <see cref="IsVisibleProperty"/> property. /// Defines the <see cref="IsVisibleProperty"/> property.
/// </summary> /// </summary>
@ -51,6 +57,12 @@ namespace Avalonia
public static readonly StyledProperty<double> OpacityProperty = public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Visual, double>(nameof(Opacity), 1); AvaloniaProperty.Register<Visual, double>(nameof(Opacity), 1);
/// <summary>
/// Defines the <see cref="OpacityMask"/> property.
/// </summary>
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
AvaloniaProperty.Register<Visual, IBrush>(nameof(OpacityMask));
/// <summary> /// <summary>
/// Defines the <see cref="RenderTransform"/> property. /// Defines the <see cref="RenderTransform"/> property.
/// </summary> /// </summary>
@ -58,10 +70,10 @@ namespace Avalonia
AvaloniaProperty.Register<Visual, Transform>(nameof(RenderTransform)); AvaloniaProperty.Register<Visual, Transform>(nameof(RenderTransform));
/// <summary> /// <summary>
/// Defines the <see cref="TransformOrigin"/> property. /// Defines the <see cref="RenderTransformOrigin"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<RelativePoint> TransformOriginProperty = public static readonly StyledProperty<RelativePoint> RenderTransformOriginProperty =
AvaloniaProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center); AvaloniaProperty.Register<Visual, RelativePoint>(nameof(RenderTransformOrigin), defaultValue: RelativePoint.Center);
/// <summary> /// <summary>
/// Defines the <see cref="IVisual.VisualParent"/> property. /// Defines the <see cref="IVisual.VisualParent"/> property.
@ -127,6 +139,15 @@ namespace Avalonia
set { SetValue(ClipToBoundsProperty, value); } set { SetValue(ClipToBoundsProperty, value); }
} }
/// <summary>
/// Gets or sets the geometry clip for this visual.
/// </summary>
public Geometry Clip
{
get { return GetValue(ClipProperty); }
set { SetValue(ClipProperty, value); }
}
/// <summary> /// <summary>
/// Gets a value indicating whether this scene graph node and all its parents are visible. /// Gets a value indicating whether this scene graph node and all its parents are visible.
/// </summary> /// </summary>
@ -153,6 +174,16 @@ namespace Avalonia
set { SetValue(OpacityProperty, value); } set { SetValue(OpacityProperty, value); }
} }
/// <summary>
/// Gets the opacity mask of the scene graph node.
/// </summary>
public IBrush OpacityMask
{
get { return GetValue(OpacityMaskProperty); }
set { SetValue(OpacityMaskProperty, value); }
}
/// <summary> /// <summary>
/// Gets the render transform of the scene graph node. /// Gets the render transform of the scene graph node.
/// </summary> /// </summary>
@ -165,10 +196,10 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the transform origin of the scene graph node. /// Gets the transform origin of the scene graph node.
/// </summary> /// </summary>
public RelativePoint TransformOrigin public RelativePoint RenderTransformOrigin
{ {
get { return GetValue(TransformOriginProperty); } get { return GetValue(RenderTransformOriginProperty); }
set { SetValue(TransformOriginProperty, value); } set { SetValue(RenderTransformOriginProperty, value); }
} }
/// <summary> /// <summary>
@ -353,6 +384,16 @@ namespace Avalonia
DetachedFromVisualTree?.Invoke(this, e); DetachedFromVisualTree?.Invoke(this, e);
} }
/// <summary>
/// Called when the control's visual parent changes.
/// </summary>
/// <param name="oldParent">The old visual parent.</param>
/// <param name="newParent">The new visual parent.</param>
protected virtual void OnVisualParentChanged(IVisual oldParent, IVisual newParent)
{
RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
}
/// <summary> /// <summary>
/// Called when a property changes that should invalidate the visual. /// Called when a property changes that should invalidate the visual.
/// </summary> /// </summary>
@ -468,7 +509,7 @@ namespace Avalonia
OnAttachedToVisualTreeCore(e); OnAttachedToVisualTreeCore(e);
} }
RaisePropertyChanged(VisualParentProperty, old, value, BindingPriority.LocalValue); OnVisualParentChanged(old, value);
} }
/// <summary> /// <summary>

41
src/Avalonia.SceneGraph/VisualTree/BoundsTracker.cs

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reactive.Linq; using System.Reactive.Linq;
using Avalonia.Media;
namespace Avalonia.VisualTree namespace Avalonia.VisualTree
{ {
@ -16,6 +17,9 @@ namespace Avalonia.VisualTree
/// </remarks> /// </remarks>
public class BoundsTracker public class BoundsTracker
{ {
private static AttachedProperty<TransformedBounds> TransformedBoundsProperty =
AvaloniaProperty.RegisterAttached<BoundsTracker, Visual, TransformedBounds>("TransformedBounds");
/// <summary> /// <summary>
/// Starts tracking the specified visual. /// Starts tracking the specified visual.
/// </summary> /// </summary>
@ -23,42 +27,19 @@ namespace Avalonia.VisualTree
/// <returns>An observable that returns the tracked bounds.</returns> /// <returns>An observable that returns the tracked bounds.</returns>
public IObservable<TransformedBounds> Track(Visual visual) public IObservable<TransformedBounds> Track(Visual visual)
{ {
return Track(visual, (Visual)visual.GetVisualRoot()); return visual.GetObservable(TransformedBoundsProperty);
} }
/// <summary> internal static void SetTransformedBounds(Visual visual, TransformedBounds bounds)
/// Starts tracking the specified visual relative to another control.
/// </summary>
/// <param name="visual">The visual.</param>
/// <param name="relativeTo">The control that the tracking should be relative to.</param>
/// <returns>An observable that returns the tracked bounds.</returns>
public IObservable<TransformedBounds> Track(Visual visual, Visual relativeTo)
{ {
var visuals = visual.GetSelfAndVisualAncestors() visual.SetValue(TransformedBoundsProperty, bounds);
.TakeWhile(x => x != relativeTo)
.Reverse();
var boundsSubscriptions = new List<IObservable<Rect>>();
foreach (var v in visuals.Cast<Visual>())
{
boundsSubscriptions.Add(v.GetObservable(Visual.BoundsProperty));
}
var bounds = boundsSubscriptions.CombineLatest().Select(ExtractBounds);
// TODO: Track transform and clip rectangle.
return bounds.Select(x => new TransformedBounds(x, new Rect(), Matrix.Identity));
} }
/// <summary> /// <summary>
/// Sums a collection of rectangles. /// Gets the transformed bounds of the visual.
/// </summary> /// </summary>
/// <param name="rects">The collection of rectangles.</param> /// <param name="visual">The visual.</param>
/// <returns>The summed rectangle.</returns> /// <returns>The transformed bounds.</returns>
private static Rect ExtractBounds(IList<Rect> rects) public static TransformedBounds GetTransformedBounds(Visual visual) => visual.GetValue(TransformedBoundsProperty);
{
var position = rects.Select(x => x.Position).Aggregate((a, b) => a + b);
return new Rect(position, rects.Last().Size);
}
} }
} }

14
src/Avalonia.SceneGraph/VisualTree/IVisual.cs

@ -41,6 +41,11 @@ namespace Avalonia.VisualTree
/// </summary> /// </summary>
bool ClipToBounds { get; set; } bool ClipToBounds { get; set; }
/// <summary>
/// Gets or sets the geometry clip for this visual.
/// </summary>
Geometry Clip { get; set; }
/// <summary> /// <summary>
/// Gets a value indicating whether this scene graph node is attached to a visual root. /// Gets a value indicating whether this scene graph node is attached to a visual root.
/// </summary> /// </summary>
@ -61,15 +66,20 @@ namespace Avalonia.VisualTree
/// </summary> /// </summary>
double Opacity { get; set; } double Opacity { get; set; }
/// <summary>
/// Gets or sets the opacity mask of the scene graph node.
/// </summary>
IBrush OpacityMask { get; set; }
/// <summary> /// <summary>
/// Gets or sets the render transform of the scene graph node. /// Gets or sets the render transform of the scene graph node.
/// </summary> /// </summary>
Transform RenderTransform { get; set; } Transform RenderTransform { get; set; }
/// <summary> /// <summary>
/// Gets or sets the transform origin of the scene graph node. /// Gets or sets the render transform origin of the scene graph node.
/// </summary> /// </summary>
RelativePoint TransformOrigin { get; set; } RelativePoint RenderTransformOrigin { get; set; }
/// <summary> /// <summary>
/// Gets the scene graph node's child nodes. /// Gets the scene graph node's child nodes.

24
src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs

@ -1,15 +1,17 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Media;
namespace Avalonia.VisualTree namespace Avalonia.VisualTree
{ {
/// <summary> /// <summary>
/// Holds information about the bounds of a control, together with a transform and a clip/ /// Holds information about the bounds of a control, together with a transform and a clip.
/// </summary> /// </summary>
public class TransformedBounds public struct TransformedBounds
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TransformedBounds"/> class. /// Initializes a new instance of the <see cref="TransformedBounds"/> struct.
/// </summary> /// </summary>
/// <param name="bounds">The control's bounds.</param> /// <param name="bounds">The control's bounds.</param>
/// <param name="clip">The control's clip rectangle.</param> /// <param name="clip">The control's clip rectangle.</param>
@ -35,5 +37,19 @@ namespace Avalonia.VisualTree
/// Gets the control's transform. /// Gets the control's transform.
/// </summary> /// </summary>
public Matrix Transform { get; } public Matrix Transform { get; }
public bool Contains(Point point)
{
if (Transform.HasInverse)
{
Point trPoint = point * Transform.Invert();
return Bounds.Contains(trPoint);
}
else
{
return Bounds.Contains(point);
}
}
} }
} }

11
src/Avalonia.Styling/Avalonia.Styling.csproj

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup> <PropertyGroup>
@ -43,12 +43,17 @@
<Compile Include="..\Shared\SharedAssemblyInfo.cs"> <Compile Include="..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link> <Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile> </Compile>
<Compile Include="Controls\NameScopeEventArgs.cs" />
<Compile Include="Controls\NameScopeExtensions.cs" />
<Compile Include="LogicalTree\ILogical.cs" /> <Compile Include="LogicalTree\ILogical.cs" />
<Compile Include="LogicalTree\LogicalExtensions.cs" /> <Compile Include="LogicalTree\LogicalExtensions.cs" />
<Compile Include="LogicalTree\LogicalTreeAttachmentEventArgs.cs" /> <Compile Include="LogicalTree\LogicalTreeAttachmentEventArgs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\ActivatedSubject.cs" /> <Compile Include="Styling\ActivatedSubject.cs" />
<Compile Include="Styling\ActivatedValue.cs" /> <Compile Include="Styling\ActivatedValue.cs" />
<Compile Include="Controls\INameScope.cs" />
<Compile Include="Styling\ITemplate.cs" />
<Compile Include="Controls\NameScope.cs" />
<Compile Include="Styling\TemplateSelector.cs" /> <Compile Include="Styling\TemplateSelector.cs" />
<Compile Include="Styling\DescendentSelector.cs" /> <Compile Include="Styling\DescendentSelector.cs" />
<Compile Include="Styling\ChildSelector.cs" /> <Compile Include="Styling\ChildSelector.cs" />
@ -92,6 +97,10 @@
<None Include="Styling\packages.config" /> <None Include="Styling\packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj">
<Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
<Name>Avalonia.Animation</Name>
</ProjectReference>
<ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj"> <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj">
<Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project> <Project>{B09B78D8-9B26-48B0-9149-D64A2F120F3F}</Project>
<Name>Avalonia.Base</Name> <Name>Avalonia.Base</Name>

0
src/Avalonia.Controls/INameScope.cs → src/Avalonia.Styling/Controls/INameScope.cs

6
src/Avalonia.Controls/NameScope.cs → src/Avalonia.Styling/Controls/NameScope.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Defines the NameScope attached property. /// Defines the NameScope attached property.
/// </summary> /// </summary>
public static readonly AttachedProperty<INameScope> NameScopeProperty = public static readonly AttachedProperty<INameScope> NameScopeProperty =
AvaloniaProperty.RegisterAttached<NameScope, Control, INameScope>("NameScope"); AvaloniaProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
private readonly Dictionary<string, object> _inner = new Dictionary<string, object>(); private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
@ -34,7 +34,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <returns>The value of the NameScope attached property.</returns> /// <returns>The value of the NameScope attached property.</returns>
public static INameScope GetNameScope(Control visual) public static INameScope GetNameScope(Visual visual)
{ {
return visual.GetValue(NameScopeProperty); return visual.GetValue(NameScopeProperty);
} }
@ -44,7 +44,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
/// <param name="visual">The visual.</param> /// <param name="visual">The visual.</param>
/// <param name="value">The value to set.</param> /// <param name="value">The value to set.</param>
public static void SetNameScope(Control visual, INameScope value) public static void SetNameScope(Visual visual, INameScope value)
{ {
visual.SetValue(NameScopeProperty, value); visual.SetValue(NameScopeProperty, value);
} }

0
src/Avalonia.Controls/NameScopeEventArgs.cs → src/Avalonia.Styling/Controls/NameScopeEventArgs.cs

0
src/Avalonia.Controls/NameScopeExtensions.cs → src/Avalonia.Styling/Controls/NameScopeExtensions.cs

10
src/Avalonia.Styling/Styling/ITemplate.cs

@ -0,0 +1,10 @@
// 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.
namespace Avalonia.Styling
{
public interface ITemplate
{
object Build();
}
}

37
src/Avalonia.Styling/Styling/Setter.cs

@ -4,6 +4,7 @@
using System; using System;
using System.Reactive.Disposables; using System.Reactive.Disposables;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -19,6 +20,8 @@ namespace Avalonia.Styling
/// </remarks> /// </remarks>
public class Setter : ISetter public class Setter : ISetter
{ {
private object _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Setter"/> class. /// Initializes a new instance of the <see cref="Setter"/> class.
/// </summary> /// </summary>
@ -54,8 +57,22 @@ namespace Avalonia.Styling
[DependsOn(nameof(Property))] [DependsOn(nameof(Property))]
public object Value public object Value
{ {
get; get
set; {
return _value;
}
set
{
if (value is IStyleable)
{
throw new ArgumentException(
"Cannot assign a control to Style.Value. Wrap the control in a <Template>.",
"value");
}
_value = value;
}
} }
/// <summary> /// <summary>
@ -75,17 +92,27 @@ namespace Avalonia.Styling
throw new InvalidOperationException("Setter.Property must be set."); throw new InvalidOperationException("Setter.Property must be set.");
} }
var binding = Value as IBinding; var value = Value;
var binding = value as IBinding;
if (binding == null) if (binding == null)
{ {
var template = value as ITemplate;
if (template != null)
{
var materialized = template.Build();
NameScope.SetNameScope((Visual)materialized, new NameScope());
value = materialized;
}
if (activator == null) if (activator == null)
{ {
return control.Bind(Property, ObservableEx.SingleValue(Value), BindingPriority.Style); return control.Bind(Property, ObservableEx.SingleValue(value), BindingPriority.Style);
} }
else else
{ {
var activated = new ActivatedValue(activator, Value, description); var activated = new ActivatedValue(activator, value, description);
return control.Bind(Property, activated, BindingPriority.StyleTrigger); return control.Bind(Property, activated, BindingPriority.StyleTrigger);
} }
} }

196
src/Gtk/Avalonia.Cairo/Media/DrawingContext.cs

@ -15,7 +15,7 @@ namespace Avalonia.Cairo.Media
using Cairo = global::Cairo; using Cairo = global::Cairo;
/// <summary> /// <summary>
/// Draws using Direct2D1. /// Draws using Cairo.
/// </summary> /// </summary>
public class DrawingContext : IDrawingContextImpl, IDisposable public class DrawingContext : IDrawingContextImpl, IDisposable
{ {
@ -24,6 +24,8 @@ namespace Avalonia.Cairo.Media
/// </summary> /// </summary>
private readonly Cairo.Context _context; private readonly Cairo.Context _context;
private readonly Stack<BrushImpl> _maskStack = new Stack<BrushImpl>();
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DrawingContext"/> class. /// Initializes a new instance of the <see cref="DrawingContext"/> class.
/// </summary> /// </summary>
@ -83,31 +85,31 @@ namespace Avalonia.Cairo.Media
_context.Scale(scale.X, scale.Y); _context.Scale(scale.X, scale.Y);
destRect /= scale; destRect /= scale;
if (opacityOverride < 1.0f) { if (opacityOverride < 1.0f) {
_context.PushGroup (); _context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf ( Gdk.CairoHelper.SetSourcePixbuf (
_context, _context,
impl.Surface, impl.Surface,
-sourceRect.X + destRect.X, -sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y); -sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ()); _context.Rectangle (destRect.ToCairo ());
_context.Fill (); _context.Fill ();
_context.PopGroupToSource (); _context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride); _context.PaintWithAlpha (opacityOverride);
} else { } else {
_context.PushGroup (); _context.PushGroup ();
Gdk.CairoHelper.SetSourcePixbuf ( Gdk.CairoHelper.SetSourcePixbuf (
_context, _context,
impl.Surface, impl.Surface,
-sourceRect.X + destRect.X, -sourceRect.X + destRect.X,
-sourceRect.Y + destRect.Y); -sourceRect.Y + destRect.Y);
_context.Rectangle (destRect.ToCairo ()); _context.Rectangle (destRect.ToCairo ());
_context.Fill (); _context.Fill ();
_context.PopGroupToSource (); _context.PopGroupToSource ();
_context.PaintWithAlpha (opacityOverride); _context.PaintWithAlpha (opacityOverride);
} }
_context.Restore(); _context.Restore();
} }
@ -121,12 +123,12 @@ namespace Avalonia.Cairo.Media
{ {
var size = new Rect(p1, p2).Size; var size = new Rect(p1, p2).Size;
using (var p = SetPen(pen, size)) using (var p = SetPen(pen, size))
{ {
_context.MoveTo(p1.ToCairo()); _context.MoveTo(p1.ToCairo());
_context.LineTo(p2.ToCairo()); _context.LineTo(p2.ToCairo());
_context.Stroke(); _context.Stroke();
} }
} }
/// <summary> /// <summary>
@ -177,11 +179,11 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param> /// <param name="rect">The rectangle bounds.</param>
public void DrawRectangle(Pen pen, Rect rect, float cornerRadius) public void DrawRectangle(Pen pen, Rect rect, float cornerRadius)
{ {
using (var p = SetPen(pen, rect.Size)) using (var p = SetPen(pen, rect.Size))
{ {
_context.Rectangle(rect.ToCairo ()); _context.Rectangle(rect.ToCairo ());
_context.Stroke(); _context.Stroke();
} }
} }
/// <summary> /// <summary>
@ -195,10 +197,10 @@ namespace Avalonia.Cairo.Media
var layout = ((FormattedTextImpl)text.PlatformImpl).Layout; var layout = ((FormattedTextImpl)text.PlatformImpl).Layout;
_context.MoveTo(origin.X, origin.Y); _context.MoveTo(origin.X, origin.Y);
using (var b = SetBrush(foreground, new Size(0, 0))) using (var b = SetBrush(foreground, new Size(0, 0)))
{ {
Pango.CairoHelper.ShowLayout(_context, layout); Pango.CairoHelper.ShowLayout(_context, layout);
} }
} }
/// <summary> /// <summary>
@ -208,11 +210,11 @@ namespace Avalonia.Cairo.Media
/// <param name="rect">The rectangle bounds.</param> /// <param name="rect">The rectangle bounds.</param>
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius) public void FillRectangle(IBrush brush, Rect rect, float cornerRadius)
{ {
using (var b = SetBrush(brush, rect.Size)) using (var b = SetBrush(brush, rect.Size))
{ {
_context.Rectangle(rect.ToCairo ()); _context.Rectangle(rect.ToCairo ());
_context.Fill(); _context.Fill();
} }
} }
/// <summary> /// <summary>
@ -269,50 +271,57 @@ namespace Avalonia.Cairo.Media
}); });
} }
private double opacityOverride = 1.0f; private double opacityOverride = 1.0f;
private IDisposable SetBrush(IBrush brush, Size destinationSize) private IDisposable SetBrush(IBrush brush, Size destinationSize)
{ {
_context.Save (); _context.Save();
BrushImpl impl = CreateBrushImpl(brush, destinationSize);
_context.SetSource(impl.PlatformBrush);
return Disposable.Create(() =>
{
impl.Dispose();
_context.Restore();
});
}
private BrushImpl CreateBrushImpl(IBrush brush, Size destinationSize)
{
var solid = brush as SolidColorBrush; var solid = brush as SolidColorBrush;
var linearGradientBrush = brush as LinearGradientBrush; var linearGradientBrush = brush as LinearGradientBrush;
var radialGradientBrush = brush as RadialGradientBrush; var radialGradientBrush = brush as RadialGradientBrush;
var imageBrush = brush as ImageBrush; var imageBrush = brush as ImageBrush;
var visualBrush = brush as VisualBrush; var visualBrush = brush as VisualBrush;
BrushImpl impl = null; BrushImpl impl = null;
if (solid != null) if (solid != null)
{ {
impl = new SolidColorBrushImpl(solid, opacityOverride); impl = new SolidColorBrushImpl(solid, opacityOverride);
} }
else if (linearGradientBrush != null) else if (linearGradientBrush != null)
{ {
impl = new LinearGradientBrushImpl(linearGradientBrush, destinationSize); impl = new LinearGradientBrushImpl(linearGradientBrush, destinationSize);
} }
else if (radialGradientBrush != null) else if (radialGradientBrush != null)
{ {
impl = new RadialGradientBrushImpl(radialGradientBrush, destinationSize); impl = new RadialGradientBrushImpl(radialGradientBrush, destinationSize);
} }
else if (imageBrush != null) else if (imageBrush != null)
{ {
impl = new ImageBrushImpl(imageBrush, destinationSize); impl = new ImageBrushImpl(imageBrush, destinationSize);
} }
else if (visualBrush != null) else if (visualBrush != null)
{ {
impl = new VisualBrushImpl(visualBrush, destinationSize); impl = new VisualBrushImpl(visualBrush, destinationSize);
} }
else else
{ {
impl = new SolidColorBrushImpl(null, opacityOverride); impl = new SolidColorBrushImpl(null, opacityOverride);
} }
_context.SetSource(impl.PlatformBrush); return impl;
return Disposable.Create(() =>
{
impl.Dispose();
_context.Restore();
});
} }
private IDisposable SetPen(Pen pen, Size destinationSize) private IDisposable SetPen(Pen pen, Size destinationSize)
@ -335,10 +344,35 @@ namespace Avalonia.Cairo.Media
_context.LineJoin = Cairo.LineJoin.Miter; _context.LineJoin = Cairo.LineJoin.Miter;
_context.LineCap = Cairo.LineCap.Butt; _context.LineCap = Cairo.LineCap.Butt;
if (pen.Brush == null) if (pen.Brush == null)
return Disposable.Empty; return Disposable.Empty;
return SetBrush(pen.Brush, destinationSize); return SetBrush(pen.Brush, destinationSize);
}
public void PushGeometryClip(Geometry clip)
{
_context.Save();
_context.AppendPath(((StreamGeometryImpl)clip.PlatformImpl).Path);
_context.Clip();
}
public void PopGeometryClip()
{
_context.Restore();
}
public void PushOpacityMask(IBrush mask, Rect bounds)
{
_context.PushGroup();
var impl = CreateBrushImpl(mask, bounds.Size);
_maskStack.Push(impl);
}
public void PopOpacityMask()
{
_context.PopGroupToSource();
_context.Mask(_maskStack.Pop().PlatformBrush);
} }
} }
} }

9
src/Gtk/Avalonia.Cairo/Media/StreamGeometryContextImpl.cs

@ -62,6 +62,15 @@ namespace Avalonia.Cairo.Media
} }
} }
internal bool FillContains(Point point)
{
using (var context = new Cairo.Context(new Cairo.ImageSurface(Cairo.Format.Argb32, 0, 0)))
{
context.AppendPath(Path);
return context.InFill(point.X, point.Y);
}
}
public void LineTo(Point point) public void LineTo(Point point)
{ {
if (this.Path == null) if (this.Path == null)

5
src/Gtk/Avalonia.Cairo/Media/StreamGeometryImpl.cs

@ -67,5 +67,10 @@ namespace Avalonia.Cairo.Media
{ {
return _impl; return _impl;
} }
public bool FillContains(Point point)
{
return _impl.FillContains(point);
}
} }
} }

1
src/Markup/Avalonia.Markup.Xaml/Context/AvaloniaObjectAssembler.cs

@ -23,6 +23,7 @@ namespace Avalonia.Markup.Xaml.Context
Settings settings = null) Settings settings = null)
{ {
var mapping = new DeferredLoaderMapping(); var mapping = new DeferredLoaderMapping();
mapping.Map<Template>(x => x.Content, new TemplateLoader());
mapping.Map<ControlTemplate>(x => x.Content, new TemplateLoader()); mapping.Map<ControlTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<DataTemplate>(x => x.Content, new TemplateLoader()); mapping.Map<DataTemplate>(x => x.Content, new TemplateLoader());
mapping.Map<FocusAdornerTemplate>(x => x.Content, new TemplateLoader()); mapping.Map<FocusAdornerTemplate>(x => x.Content, new TemplateLoader());

9
src/Markup/Avalonia.Markup.Xaml/Context/PropertyAccessor.cs

@ -136,7 +136,14 @@ namespace Avalonia.Markup.Xaml.Context
if (control != null) if (control != null)
{ {
DelayedBinding.Add(control, property, binding); if (property != Control.DataContextProperty)
{
DelayedBinding.Add(control, property, binding);
}
else
{
control.Bind(property, binding);
}
} }
else else
{ {

15
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@ -20,6 +20,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary> /// </summary>
public Binding() public Binding()
{ {
FallbackValue = AvaloniaProperty.UnsetValue;
} }
/// <summary> /// <summary>
@ -27,6 +28,7 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary> /// </summary>
/// <param name="path">The binding path.</param> /// <param name="path">The binding path.</param>
public Binding(string path) public Binding(string path)
: this()
{ {
Path = path; Path = path;
} }
@ -122,12 +124,23 @@ namespace Avalonia.Markup.Xaml.Data
throw new NotSupportedException(); throw new NotSupportedException();
} }
var fallback = FallbackValue;
// If we're binding to DataContext and our fallback is UnsetValue then override
// the fallback value to null, as broken bindings to DataContext must reset the
// DataContext in order to not propagate incorrect DataContexts to child controls.
// See Avalonia.Markup.Xaml.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
if (targetProperty == Control.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
{
fallback = null;
}
var subject = new ExpressionSubject( var subject = new ExpressionSubject(
observer, observer,
targetProperty?.PropertyType ?? typeof(object), targetProperty?.PropertyType ?? typeof(object),
fallback,
Converter ?? DefaultValueConverter.Instance, Converter ?? DefaultValueConverter.Instance,
ConverterParameter, ConverterParameter,
FallbackValue,
Priority); Priority);
return new InstancedBinding(subject, Mode, Priority); return new InstancedBinding(subject, Mode, Priority);

8
src/Markup/Avalonia.Markup.Xaml/Data/DelayedBinding.cs

@ -14,11 +14,11 @@ namespace Avalonia.Markup.Xaml.Data
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The XAML engine applies its bindings in a delayed manner where bindings are only applied /// The XAML engine applies its bindings in a delayed manner where bindings are only applied
/// when a control has finished initializing. This was done because applying bindings as soon /// when a control has finished initializing. This is done because applying bindings as soon
/// as controls are created means that long-form bindings (i.e. bindings that don't use the /// as controls are created means that long-form bindings (i.e. bindings that don't use the
/// `{Binding}` markup extension) don't work as the binding is applied to the property before /// `{Binding}` markup extension but instead use `&lt;Binding&gt;`) don't work, as the binding
/// the binding properties are set, and looking at WPF it uses a similar mechanism for bindings /// is applied to the property before the properties on the `Binding` object are set. Looking
/// that come from XAML. /// at WPF it uses a similar mechanism for bindings that come from XAML.
/// </remarks> /// </remarks>
public static class DelayedBinding public static class DelayedBinding
{ {

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -36,7 +36,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public IValueConverter Converter { get; set; } public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; } public object ConverterParameter { get; set; }
public string ElementName { get; set; } public string ElementName { get; set; }
public object FallbackValue { get; set; } public object FallbackValue { get; set; } = AvaloniaProperty.UnsetValue;
public BindingMode Mode { get; set; } public BindingMode Mode { get; set; }
public string Path { get; set; } public string Path { get; set; }
public BindingPriority Priority { get; set; } = BindingPriority.LocalValue; public BindingPriority Priority { get; set; } = BindingPriority.LocalValue;

10
src/Markup/Avalonia.Markup.Xaml/Templates/FocusAdornerTemplate.cs

@ -3,17 +3,11 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
public class FocusAdornerTemplate : ITemplate<IControl> public class FocusAdornerTemplate : Template
{ {
[Content]
public TemplateContent Content { get; set; }
public IControl Build()
{
return Content.Load();
}
} }
} }

10
src/Markup/Avalonia.Markup.Xaml/Templates/ItemsPanelTemplate.cs

@ -1,11 +1,9 @@
using System; // Copyright (c) The Avalonia Project. All rights reserved.
using System.Collections.Generic; // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
@ -18,5 +16,7 @@ namespace Avalonia.Markup.Xaml.Templates
{ {
return (IPanel)Content.Load(); return (IPanel)Content.Load();
} }
object ITemplate.Build() => Build();
} }
} }

2
src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs

@ -51,6 +51,8 @@ namespace Avalonia.Markup.Xaml.Templates
object result = _memberValueNode.CurrentValue.Target; object result = _memberValueNode.CurrentValue.Target;
_expressionNode.Target = null;
if (result == AvaloniaProperty.UnsetValue) if (result == AvaloniaProperty.UnsetValue)
{ {
return null; return null;

11
src/Markup/Avalonia.Markup.Xaml/Templates/Template.cs

@ -1,10 +1,19 @@
// Copyright (c) The Avalonia Project. All rights reserved. // Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml.Templates namespace Avalonia.Markup.Xaml.Templates
{ {
public class Template public class Template : ITemplate<IControl>
{ {
[Content]
public TemplateContent Content { get; set; } public TemplateContent Content { get; set; }
public IControl Build() => Content.Load();
object ITemplate.Build() => Build();
} }
} }

2
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -43,7 +43,7 @@ namespace Avalonia.Markup.Xaml.Templates
if (ItemsSource != null) if (ItemsSource != null)
{ {
var obs = new ExpressionObserver(item, ItemsSource.Path); var obs = new ExpressionObserver(item, ItemsSource.Path);
return new InstancedBinding(obs); return new InstancedBinding(obs, BindingMode.OneWay, BindingPriority.Style);
} }
return null; return null;

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

Loading…
Cancel
Save