Browse Source

Merge branch 'master' into pr/553

Conflicts:
tests/Avalonia.RenderTests/Avalonia.RenderTests.projitems
Steven Kirk 9 years ago
parent
commit
169ffca591
100 changed files with 3170 additions and 682 deletions
  1. 39 1
      Avalonia.sln
  2. 1 1
      nuget/template/Avalonia.Skia.Desktop.nuspec
  3. 1 1
      readme.md
  4. 1 2
      samples/BindingTest/App.xaml.cs
  5. 1 2
      samples/ControlCatalog.Desktop/Program.cs
  6. 1 0
      samples/ControlCatalog/Pages/MenuPage.xaml
  7. 1 2
      samples/TestApplication/Program.cs
  8. 6 0
      samples/VirtualizationTest/App.config
  9. 6 0
      samples/VirtualizationTest/App.xaml
  10. 16 0
      samples/VirtualizationTest/App.xaml.cs
  11. 51 0
      samples/VirtualizationTest/MainWindow.xaml
  12. 25 0
      samples/VirtualizationTest/MainWindow.xaml.cs
  13. 34 0
      samples/VirtualizationTest/Program.cs
  14. 36 0
      samples/VirtualizationTest/Properties/AssemblyInfo.cs
  15. 22 0
      samples/VirtualizationTest/ViewModels/ItemViewModel.cs
  16. 141 0
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  17. 180 0
      samples/VirtualizationTest/VirtualizationTest.csproj
  18. 26 0
      samples/VirtualizationTest/VirtualizationTest.v2.ncrunchproject
  19. 10 0
      samples/VirtualizationTest/packages.config
  20. 1 2
      samples/XamlTestApplication/Program.cs
  21. 34 21
      samples/XamlTestApplicationPcl/TestScrollable.cs
  22. 2 0
      samples/XamlTestApplicationPcl/Views/MainWindow.cs
  23. 1 0
      samples/XamlTestApplicationPcl/Views/MainWindow.xaml
  24. 0 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  25. 4 4
      src/Android/Avalonia.Android/Avalonia.Android.v2.ncrunchproject
  26. 2 2
      src/Avalonia.Base/AvaloniaObject.cs
  27. 51 0
      src/Avalonia.Base/Collections/AvaloniaList.cs
  28. 44 2
      src/Avalonia.Controls/AppBuilder.cs
  29. 0 51
      src/Avalonia.Controls/Application.cs
  30. 12 6
      src/Avalonia.Controls/Avalonia.Controls.csproj
  31. 1 1
      src/Avalonia.Controls/Canvas.cs
  32. 15 2
      src/Avalonia.Controls/Control.cs
  33. 21 1
      src/Avalonia.Controls/Design.cs
  34. 24 14
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  35. 4 7
      src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs
  36. 107 79
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  37. 32 2
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  38. 5 5
      src/Avalonia.Controls/Generators/ItemContainerInfo.cs
  39. 27 0
      src/Avalonia.Controls/Generators/MenuItemContainerGenerator.cs
  40. 3 3
      src/Avalonia.Controls/Generators/TreeContainerIndex.cs
  41. 9 4
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  42. 29 0
      src/Avalonia.Controls/IScrollable.cs
  43. 22 0
      src/Avalonia.Controls/ISetInheritanceParent.cs
  44. 22 0
      src/Avalonia.Controls/IVirtualizingController.cs
  45. 67 0
      src/Avalonia.Controls/IVirtualizingPanel.cs
  46. 21 0
      src/Avalonia.Controls/ItemVirtualizationMode.cs
  47. 23 1
      src/Avalonia.Controls/ItemsControl.cs
  48. 1 1
      src/Avalonia.Controls/LayoutTransformControl.cs
  49. 54 3
      src/Avalonia.Controls/ListBox.cs
  50. 7 0
      src/Avalonia.Controls/Menu.cs
  51. 7 0
      src/Avalonia.Controls/MenuItem.cs
  52. 1 16
      src/Avalonia.Controls/Mixins/SelectableMixin.cs
  53. 17 17
      src/Avalonia.Controls/Panel.cs
  54. 4 8
      src/Avalonia.Controls/Presenters/CarouselPresenter.cs
  55. 76 20
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  56. 2 0
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  57. 198 0
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  58. 173 0
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  59. 504 0
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  60. 81 97
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  61. 44 10
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  62. 82 38
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  63. 1 1
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  64. 69 0
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  65. 0 54
      src/Avalonia.Controls/Primitives/IScrollable.cs
  66. 43 4
      src/Avalonia.Controls/Primitives/Popup.cs
  67. 25 0
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  68. 4 0
      src/Avalonia.Controls/Primitives/TabStrip.cs
  69. 2 2
      src/Avalonia.Controls/ScrollViewer.cs
  70. 38 13
      src/Avalonia.Controls/StackPanel.cs
  71. 0 48
      src/Avalonia.Controls/Templates/DataTemplateExtensions.cs
  72. 34 5
      src/Avalonia.Controls/Templates/FuncDataTemplate.cs
  73. 9 4
      src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs
  74. 3 0
      src/Avalonia.Controls/Templates/FuncTemplate`1.cs
  75. 6 0
      src/Avalonia.Controls/Templates/IDataTemplate.cs
  76. 4 2
      src/Avalonia.Controls/Templates/ITemplate`1.cs
  77. 12 7
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  78. 229 0
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  79. 10 1
      src/Avalonia.Controls/Window.cs
  80. 9 9
      src/Avalonia.Controls/WrapPanel.cs
  81. 44 8
      src/Avalonia.DesignerSupport/DesignerApi.cs
  82. 53 11
      src/Avalonia.DesignerSupport/DesignerAssist.cs
  83. 2 0
      src/Avalonia.Diagnostics/ViewLocator.cs
  84. 2 2
      src/Avalonia.Input/Avalonia.Input.csproj
  85. 1 1
      src/Avalonia.Input/IKeyboardNavigationHandler.cs
  86. 1 1
      src/Avalonia.Input/INavigableContainer.cs
  87. 5 6
      src/Avalonia.Input/InputExtensions.cs
  88. 23 11
      src/Avalonia.Input/KeyboardNavigationHandler.cs
  89. 13 14
      src/Avalonia.Input/Navigation/DirectionalNavigation.cs
  90. 13 13
      src/Avalonia.Input/Navigation/TabNavigation.cs
  91. 12 2
      src/Avalonia.Input/NavigationDirection.cs
  92. 26 9
      src/Avalonia.Layout/LayoutManager.cs
  93. 2 1
      src/Avalonia.SceneGraph/Media/Imaging/Bitmap.cs
  94. 13 3
      src/Avalonia.SceneGraph/Rect.cs
  95. 2 2
      src/Avalonia.SceneGraph/Rendering/RendererMixin.cs
  96. 6 6
      src/Avalonia.SceneGraph/Visual.cs
  97. 2 2
      src/Avalonia.SceneGraph/VisualTree/IVisual.cs
  98. 10 12
      src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs
  99. 10 1
      src/Avalonia.Styling/Avalonia.Styling.csproj
  100. 0 0
      src/Avalonia.Styling/Controls/INameScope.cs

+ 39 - 1
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
@@ -158,6 +161,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DesignerSupport.Te
 EndProject
 EndProject
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.RenderTests.shproj", "{48840EDD-24BF-495D-911E-2EB12AE75D3B}"
 Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Avalonia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.RenderTests.shproj", "{48840EDD-24BF-495D-911E-2EB12AE75D3B}"
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VirtualizationTest", "samples\VirtualizationTest\VirtualizationTest.csproj", "{FBCAF3D0-2808-4934-8E96-3F607594517B}"
+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
@@ -1909,6 +1914,38 @@ Global
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.ActiveCfg = Release|Any CPU
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.ActiveCfg = Release|Any CPU
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.Build.0 = Release|Any CPU
 		{F1381F98-4D24-409A-A6C5-1C5B1E08BB08}.Release|x86.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Ad-Hoc|x86.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|Any CPU.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|iPhone.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|iPhone.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|x86.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.AppStore|x86.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|iPhone.Build.0 = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Debug|x86.Build.0 = Debug|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|iPhone.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|iPhone.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|x86.ActiveCfg = Release|Any CPU
+		{FBCAF3D0-2808-4934-8E96-3F607594517B}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE
@@ -1960,5 +1997,6 @@ Global
 		{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}
 		{48840EDD-24BF-495D-911E-2EB12AE75D3B} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
+		{FBCAF3D0-2808-4934-8E96-3F607594517B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
 	EndGlobalSection
 	EndGlobalSection
 EndGlobal
 EndGlobal

+ 1 - 1
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>

+ 1 - 1
readme.md

@@ -45,7 +45,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/.

+ 1 - 2
samples/BindingTest/App.xaml.cs

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

+ 1 - 2
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()
-                .UseDirect2D1()
+                .UsePlatformDetect()
                 .Start<MainWindow>();
                 .Start<MainWindow>();
         }
         }
 
 

+ 1 - 0
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"/>

+ 1 - 2
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()
-                .UseDirect2D1()
+                .UsePlatformDetect()
                 .SetupWithoutStarting();
                 .SetupWithoutStarting();
 
 
             app.Run();
             app.Run();

+ 6 - 0
samples/VirtualizationTest/App.config

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
+    </startup>
+</configuration>

+ 6 - 0
samples/VirtualizationTest/App.xaml

@@ -0,0 +1,6 @@
+<Application xmlns="https://github.com/avaloniaui">
+  <Application.Styles>
+    <StyleInclude Source="resm:Avalonia.Themes.Default.DefaultTheme.xaml?assembly=Avalonia.Themes.Default"/>
+    <StyleInclude Source="resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"/>
+  </Application.Styles>
+</Application>

+ 16 - 0
samples/VirtualizationTest/App.xaml.cs

@@ -0,0 +1,16 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia;
+using Avalonia.Markup.Xaml;
+
+namespace VirtualizationTest
+{
+    public class App : Application
+    {
+        public override void Initialize()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 51 - 0
samples/VirtualizationTest/MainWindow.xaml

@@ -0,0 +1,51 @@
+<Window xmlns="https://github.com/avaloniaui"
+        Title="Avalonia Virtualization Test">
+    <DockPanel LastChildFill="True" Margin="16">
+        <StackPanel DockPanel.Dock="Right" 
+                    Margin="16 0 0 0" 
+                    MinWidth="150"
+                    Gap="4">
+            <DropDown Items="{Binding VirtualizationModes}"
+                      SelectedItem="{Binding VirtualizationMode}"/>
+            <DropDown Items="{Binding Orientations}"
+                      SelectedItem="{Binding Orientation}"/>
+            <TextBox Watermark="Item Count"
+                     UseFloatingWatermark="True"
+                     Text="{Binding ItemCount}"/>
+            <TextBox Watermark="Extent"
+                     UseFloatingWatermark="True"
+                     Text="{Binding #listBox.Scroll.Extent, Mode=OneWay}"/>
+            <TextBox Watermark="Offset"
+                     UseFloatingWatermark="True"
+                     Text="{Binding #listBox.Scroll.Offset, Mode=OneWay}"/>
+            <TextBox Watermark="Viewport"
+                     UseFloatingWatermark="True"
+                     Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
+            <TextBox Watermark="Item to Create"
+                     UseFloatingWatermark="True"
+                     Text="{Binding NewItemString}"/>
+            <Button Command="{Binding AddItemCommand}">Add Item</Button>
+            <Button Command="{Binding RemoveItemCommand}">Remove Item</Button>
+            <Button Command="{Binding RecreateCommand}">Recreate</Button>
+            <Button Command="{Binding SelectFirstCommand}">Select First</Button>
+            <Button Command="{Binding SelectLastCommand}">Select Last</Button>
+        </StackPanel>
+
+        <ListBox Name="listBox" 
+                 Items="{Binding Items}" 
+                 SelectedItems="{Binding SelectedItems}"
+                 SelectionMode="Multiple"
+                 VirtualizationMode="{Binding VirtualizationMode}">
+            <ListBox.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <VirtualizingStackPanel Orientation="{Binding Orientation}"/>
+                </ItemsPanelTemplate>
+            </ListBox.ItemsPanel>
+            <ListBox.ItemTemplate>
+                <DataTemplate>
+                    <TextBlock Text="{Binding Header}"/>
+                </DataTemplate>
+            </ListBox.ItemTemplate>
+        </ListBox>
+    </DockPanel>
+</Window>

+ 25 - 0
samples/VirtualizationTest/MainWindow.xaml.cs

@@ -0,0 +1,25 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using VirtualizationTest.ViewModels;
+
+namespace VirtualizationTest
+{
+    public class MainWindow : Window
+    {
+        public MainWindow()
+        {
+            this.InitializeComponent();
+            this.AttachDevTools();
+            DataContext = new MainWindowViewModel();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 34 - 0
samples/VirtualizationTest/Program.cs

@@ -0,0 +1,34 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Logging.Serilog;
+using Serilog;
+
+namespace VirtualizationTest
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+            InitializeLogging();
+
+            AppBuilder.Configure<App>()
+               .UseWin32()
+               .UseDirect2D1()
+               .Start<MainWindow>();
+        }
+
+        private static void InitializeLogging()
+        {
+#if DEBUG
+            SerilogLogger.Initialize(new LoggerConfiguration()
+                .MinimumLevel.Warning()
+                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
+                .CreateLogger());
+#endif
+        }
+    }
+}

+ 36 - 0
samples/VirtualizationTest/Properties/AssemblyInfo.cs

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

+ 22 - 0
samples/VirtualizationTest/ViewModels/ItemViewModel.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.
+
+using System;
+using ReactiveUI;
+
+namespace VirtualizationTest.ViewModels
+{
+    internal class ItemViewModel : ReactiveObject
+    {
+        private string _prefix;
+        private int _index;
+
+        public ItemViewModel(int index, string prefix = "Item")
+        {
+            _prefix = prefix;
+            _index = index;
+        }
+
+        public string Header => $"{_prefix} {_index}";
+    }
+}

+ 141 - 0
samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs

@@ -0,0 +1,141 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using ReactiveUI;
+
+namespace VirtualizationTest.ViewModels
+{
+    internal class MainWindowViewModel : ReactiveObject
+    {
+        private int _itemCount = 200;
+        private string _newItemString = "New Item";
+        private int _newItemIndex;
+        private IReactiveList<ItemViewModel> _items;
+        private string _prefix = "Item";
+        private Orientation _orientation;
+        private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
+
+        public MainWindowViewModel()
+        {
+            this.WhenAnyValue(x => x.ItemCount).Subscribe(ResizeItems);
+            RecreateCommand = ReactiveCommand.Create();
+            RecreateCommand.Subscribe(_ => Recreate());
+
+            AddItemCommand = ReactiveCommand.Create();
+            AddItemCommand.Subscribe(_ => AddItem());
+
+            RemoveItemCommand = ReactiveCommand.Create();
+            RemoveItemCommand.Subscribe(_ => Remove());
+
+            SelectFirstCommand = ReactiveCommand.Create();
+            SelectFirstCommand.Subscribe(_ => SelectItem(0));
+
+            SelectLastCommand = ReactiveCommand.Create();
+            SelectLastCommand.Subscribe(_ => SelectItem(Items.Count - 1));
+        }
+
+        public string NewItemString
+        {
+            get { return _newItemString; }
+            set { this.RaiseAndSetIfChanged(ref _newItemString, value); }
+        }
+
+        public int ItemCount
+        {
+            get { return _itemCount; }
+            set { this.RaiseAndSetIfChanged(ref _itemCount, value); }
+        }
+
+        public AvaloniaList<ItemViewModel> SelectedItems { get; } 
+            = new AvaloniaList<ItemViewModel>();
+
+        public IReactiveList<ItemViewModel> Items
+        {
+            get { return _items; }
+            private set { this.RaiseAndSetIfChanged(ref _items, value); }
+        }
+
+        public Orientation Orientation
+        {
+            get { return _orientation; }
+            set { this.RaiseAndSetIfChanged(ref _orientation, value); }
+        }
+
+        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> SelectFirstCommand { get; private set; }
+        public ReactiveCommand<object> SelectLastCommand { get; private set; }
+
+        private void ResizeItems(int count)
+        {
+            if (Items == null)
+            {
+                var items = Enumerable.Range(0, count)
+                    .Select(x => new ItemViewModel(x));
+                Items = new ReactiveList<ItemViewModel>(items);
+            }
+            else if (count > Items.Count)
+            {
+                var items = Enumerable.Range(Items.Count, count - Items.Count)
+                    .Select(x => new ItemViewModel(x));
+                Items.AddRange(items);
+            }
+            else if (count < Items.Count)
+            {
+                Items.RemoveRange(count, Items.Count - count);
+            }
+        }
+
+        private void AddItem()
+        {
+            var index = Items.Count;
+
+            if (SelectedItems.Count > 0)
+            {
+                index = Items.IndexOf(SelectedItems[0]);
+            }
+
+            Items.Insert(index, new ItemViewModel(_newItemIndex++, NewItemString));
+        }
+
+        private void Remove()
+        {
+            if (SelectedItems.Count > 0)
+            {
+                Items.RemoveAll(SelectedItems);
+            }
+        }
+
+        private void Recreate()
+        {
+            _prefix = _prefix == "Item" ? "Recreated" : "Item";
+            var items = Enumerable.Range(0, _itemCount)
+                .Select(x => new ItemViewModel(x, _prefix));
+            Items = new ReactiveList<ItemViewModel>(items);
+        }
+
+        private void SelectItem(int index)
+        {
+            SelectedItems.Clear();
+            SelectedItems.Add(Items[index]);
+        }
+    }
+}

+ 180 - 0
samples/VirtualizationTest/VirtualizationTest.csproj

@@ -0,0 +1,180 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.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')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{FBCAF3D0-2808-4934-8E96-3F607594517B}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>VirtualizationTest</RootNamespace>
+    <AssemblyName>VirtualizationTest</AssemblyName>
+    <TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup>
+    <StartupObject />
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Serilog, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Serilog.FullNetFx, Version=1.5.0.0, Culture=neutral, PublicKeyToken=24c2f752a8e58a10, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Serilog.1.5.14\lib\net45\Serilog.FullNetFx.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="Splat, Version=1.6.2.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Splat.1.6.2\lib\Net45\Splat.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Reactive.Core, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Core.2.2.5\lib\net45\System.Reactive.Core.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Interfaces, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Interfaces.2.2.5\lib\net45\System.Reactive.Interfaces.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.Linq, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-Linq.2.2.5\lib\net45\System.Reactive.Linq.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Reactive.PlatformServices, Version=2.2.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+      <HintPath>..\..\packages\Rx-PlatformServices.2.2.5\lib\net45\System.Reactive.PlatformServices.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xml" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="ViewModels\ItemViewModel.cs" />
+    <Compile Include="ViewModels\MainWindowViewModel.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj">
+      <Project>{d211e587-d8bc-45b9-95a4-f297c8fa5200}</Project>
+      <Name>Avalonia.Animation</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj">
+      <Project>{b09b78d8-9b26-48b0-9149-d64a2f120f3f}</Project>
+      <Name>Avalonia.Base</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
+      <Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
+      <Name>Avalonia.Controls</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj">
+      <Project>{799a7bb5-3c2c-48b6-85a7-406a12c420da}</Project>
+      <Name>Avalonia.DesignerSupport</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj">
+      <Project>{7062ae20-5dcc-4442-9645-8195bdece63e}</Project>
+      <Name>Avalonia.Diagnostics</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Input\Avalonia.Input.csproj">
+      <Project>{62024b2d-53eb-4638-b26b-85eeaa54866e}</Project>
+      <Name>Avalonia.Input</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Interactivity\Avalonia.Interactivity.csproj">
+      <Project>{6b0ed19d-a08b-461c-a9d9-a9ee40b0c06b}</Project>
+      <Name>Avalonia.Interactivity</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Layout\Avalonia.Layout.csproj">
+      <Project>{42472427-4774-4c81-8aff-9f27b8e31721}</Project>
+      <Name>Avalonia.Layout</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Logging.Serilog\Avalonia.Logging.Serilog.csproj">
+      <Project>{B61B66A3-B82D-4875-8001-89D3394FE0C9}</Project>
+      <Name>Avalonia.Logging.Serilog</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj">
+      <Project>{6417b24e-49c2-4985-8db2-3ab9d898ec91}</Project>
+      <Name>Avalonia.ReactiveUI</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.SceneGraph\Avalonia.SceneGraph.csproj">
+      <Project>{eb582467-6abb-43a1-b052-e981ba910e3a}</Project>
+      <Name>Avalonia.SceneGraph</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj">
+      <Project>{f1baa01a-f176-4c6a-b39d-5b40bb1b148f}</Project>
+      <Name>Avalonia.Styling</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj">
+      <Project>{3e10a5fa-e8da-48b1-ad44-6a5b6cb7750f}</Project>
+      <Name>Avalonia.Themes.Default</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj">
+      <Project>{3e53a01a-b331-47f3-b828-4a5717e77a24}</Project>
+      <Name>Avalonia.Markup.Xaml</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj">
+      <Project>{6417e941-21bc-467b-a771-0de389353ce6}</Project>
+      <Name>Avalonia.Markup</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Direct2D1\Avalonia.Direct2D1.csproj">
+      <Project>{3e908f67-5543-4879-a1dc-08eace79b3cd}</Project>
+      <Name>Avalonia.Direct2D1</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj">
+      <Project>{811a76cf-1cf6-440f-963b-bbe31bd72a82}</Project>
+      <Name>Avalonia.Win32</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="App.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="MainWindow.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>

+ 26 - 0
samples/VirtualizationTest/VirtualizationTest.v2.ncrunchproject

@@ -0,0 +1,26 @@
+<ProjectConfiguration>
+  <AutoDetectNugetBuildDependencies>true</AutoDetectNugetBuildDependencies>
+  <BuildPriority>1000</BuildPriority>
+  <CopyReferencedAssembliesToWorkspace>false</CopyReferencedAssembliesToWorkspace>
+  <ConsiderInconclusiveTestsAsPassing>false</ConsiderInconclusiveTestsAsPassing>
+  <PreloadReferencedAssemblies>false</PreloadReferencedAssemblies>
+  <AllowDynamicCodeContractChecking>true</AllowDynamicCodeContractChecking>
+  <AllowStaticCodeContractChecking>false</AllowStaticCodeContractChecking>
+  <AllowCodeAnalysis>false</AllowCodeAnalysis>
+  <IgnoreThisComponentCompletely>true</IgnoreThisComponentCompletely>
+  <RunPreBuildEvents>false</RunPreBuildEvents>
+  <RunPostBuildEvents>false</RunPostBuildEvents>
+  <PreviouslyBuiltSuccessfully>true</PreviouslyBuiltSuccessfully>
+  <InstrumentAssembly>true</InstrumentAssembly>
+  <PreventSigningOfAssembly>false</PreventSigningOfAssembly>
+  <AnalyseExecutionTimes>true</AnalyseExecutionTimes>
+  <DetectStackOverflow>true</DetectStackOverflow>
+  <IncludeStaticReferencesInWorkspace>true</IncludeStaticReferencesInWorkspace>
+  <DefaultTestTimeout>60000</DefaultTestTimeout>
+  <UseBuildConfiguration></UseBuildConfiguration>
+  <UseBuildPlatform></UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
+  <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
+  <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
+  <BuildProcessArchitecture>x86</BuildProcessArchitecture>
+</ProjectConfiguration>

+ 10 - 0
samples/VirtualizationTest/packages.config

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="Rx-Core" version="2.2.5" targetFramework="net452" />
+  <package id="Rx-Interfaces" version="2.2.5" targetFramework="net452" />
+  <package id="Rx-Linq" version="2.2.5" targetFramework="net452" />
+  <package id="Rx-Main" version="2.2.5" targetFramework="net452" />
+  <package id="Rx-PlatformServices" version="2.2.5" targetFramework="net452" />
+  <package id="Serilog" version="1.5.14" targetFramework="net452" />
+  <package id="Splat" version="1.6.2" targetFramework="net452" />
+</packages>

+ 1 - 2
samples/XamlTestApplication/Program.cs

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

+ 34 - 21
samples/XamlTestApplicationPcl/TestScrollable.cs

@@ -2,11 +2,13 @@ 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;
 
 
 namespace XamlTestApplication
 namespace XamlTestApplication
 {
 {
-    public class TestScrollable : Control, IScrollable
+    public class TestScrollable : Control, ILogicalScrollable
     {
     {
         private int itemCount = 100;
         private int itemCount = 100;
         private Size _extent;
         private Size _extent;
@@ -14,6 +16,7 @@ namespace XamlTestApplication
         private Size _viewport;
         private Size _viewport;
         private Size _lineSize;
         private Size _lineSize;
 
 
+        public bool IsLogicalScrollEnabled => true;
         public Action InvalidateScroll { get; set; }
         public Action InvalidateScroll { get; set; }
 
 
         Size IScrollable.Extent
         Size IScrollable.Extent
@@ -53,6 +56,36 @@ namespace XamlTestApplication
             }
             }
         }
         }
 
 
+        public override void Render(DrawingContext context)
+        {
+            var y = 0.0;
+
+            for (var i = (int)_offset.Y; i < itemCount; ++i)
+            {
+                using (var line = new FormattedText(
+                    "Item " + (i + 1),
+                    TextBlock.GetFontFamily(this),
+                    TextBlock.GetFontSize(this),
+                    TextBlock.GetFontStyle(this),
+                    TextAlignment.Left,
+                    TextBlock.GetFontWeight(this)))
+                {
+                    context.DrawText(Brushes.Black, new Point(-_offset.X, y), line);
+                    y += _lineSize.Height;
+                }
+            }
+        }
+
+        public bool BringIntoView(IControl target, Rect targetRect)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IControl GetControlInDirection(NavigationDirection direction, IControl from)
+        {
+            throw new NotImplementedException();
+        }
+
         protected override Size MeasureOverride(Size availableSize)
         protected override Size MeasureOverride(Size availableSize)
         {
         {
             using (var line = new FormattedText(
             using (var line = new FormattedText(
@@ -76,25 +109,5 @@ namespace XamlTestApplication
             InvalidateScroll?.Invoke();
             InvalidateScroll?.Invoke();
             return finalSize;
             return finalSize;
         }
         }
-
-        public override void Render(DrawingContext context)
-        {
-            var y = 0.0;
-
-            for (var i = (int)_offset.Y; i < itemCount; ++i)
-            {
-                using (var line = new FormattedText(
-                    "Item " + (i + 1),
-                    TextBlock.GetFontFamily(this),
-                    TextBlock.GetFontSize(this),
-                    TextBlock.GetFontStyle(this),
-                    TextAlignment.Left,
-                    TextBlock.GetFontWeight(this)))
-                {
-                    context.DrawText(Brushes.Black, new Point(-_offset.X, y), line);
-                    y += _lineSize.Height;
-                }
-            }
-        }
     }
     }
 }
 }

+ 2 - 0
samples/XamlTestApplicationPcl/Views/MainWindow.cs

@@ -1,6 +1,8 @@
 // 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.Collections.Generic;
+using System.Linq;
 using Avalonia;
 using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls;
 using Avalonia.Diagnostics;
 using Avalonia.Diagnostics;

+ 1 - 0
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@@ -24,6 +24,7 @@
       <TabControl.Transition>
       <TabControl.Transition>
         <PageSlide Duration="0.25" />
         <PageSlide Duration="0.25" />
       </TabControl.Transition>
       </TabControl.Transition>
+     
       <TabItem Header="Buttons">
       <TabItem Header="Buttons">
         <ScrollViewer CanScrollHorizontally="False">
         <ScrollViewer CanScrollHorizontally="False">
           <StackPanel Margin="10" Gap="4">
           <StackPanel Margin="10" Gap="4">

+ 0 - 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;
 
 

+ 4 - 4
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 />
-  <UseBuildPlatform />
-  <ProxyProcessPath />
+  <UseBuildConfiguration></UseBuildConfiguration>
+  <UseBuildPlatform></UseBuildPlatform>
+  <ProxyProcessPath></ProxyProcessPath>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <UseCPUArchitecture>AutoDetect</UseCPUArchitecture>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <MSTestThreadApartmentState>STA</MSTestThreadApartmentState>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>
   <BuildProcessArchitecture>x86</BuildProcessArchitecture>

+ 2 - 2
src/Avalonia.Base/AvaloniaObject.cs

@@ -26,7 +26,7 @@ namespace Avalonia
         /// <summary>
         /// <summary>
         /// The parent object that inherited values are inherited from.
         /// The parent object that inherited values are inherited from.
         /// </summary>
         /// </summary>
-        private AvaloniaObject _inheritanceParent;
+        private IAvaloniaObject _inheritanceParent;
 
 
         /// <summary>
         /// <summary>
         /// The set values/bindings on this object.
         /// The set values/bindings on this object.
@@ -120,7 +120,7 @@ namespace Avalonia
         /// <value>
         /// <value>
         /// The inheritance parent.
         /// The inheritance parent.
         /// </value>
         /// </value>
-        protected AvaloniaObject InheritanceParent
+        protected IAvaloniaObject InheritanceParent
         {
         {
             get
             get
             {
             {

+ 51 - 0
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -319,6 +319,57 @@ namespace Avalonia.Collections
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Moves an item to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The index of the item to move.</param>
+        /// <param name="newIndex">The index to move the item to.</param>
+        public void Move(int oldIndex, int newIndex)
+        {
+            var item = this[oldIndex];
+            _inner.RemoveAt(oldIndex);
+            _inner.Insert(newIndex, item);
+
+            if (_collectionChanged != null)
+            {
+                var e = new NotifyCollectionChangedEventArgs(
+                    NotifyCollectionChangedAction.Move,
+                    item,
+                    newIndex,
+                    oldIndex);
+                _collectionChanged(this, e);
+            }
+        }
+
+        /// <summary>
+        /// Moves multiple items to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The first index of the items to move.</param>
+        /// <param name="count">The number of items to move.</param>
+        /// <param name="newIndex">The index to move the items to.</param>
+        public void MoveRange(int oldIndex, int count, int newIndex)
+        {
+            var items = _inner.GetRange(oldIndex, count);
+            _inner.RemoveRange(oldIndex, count);
+
+            if (newIndex > oldIndex)
+            {
+                newIndex -= count;
+            }
+
+            _inner.InsertRange(newIndex, items);
+
+            if (_collectionChanged != null)
+            {
+                var e = new NotifyCollectionChangedEventArgs(
+                    NotifyCollectionChangedAction.Move,
+                    items,
+                    newIndex,
+                    oldIndex);
+                _collectionChanged(this, e);
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Removes an item from the collection.
         /// Removes an item from the collection.
         /// </summary>
         /// </summary>

+ 44 - 2
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>

+ 0 - 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");
-        }
     }
     }
 }
 }

+ 12 - 6
src/Avalonia.Controls/Avalonia.Controls.csproj

@@ -50,26 +50,31 @@
     <Compile Include="Design.cs" />
     <Compile Include="Design.cs" />
     <Compile Include="DockPanel.cs" />
     <Compile Include="DockPanel.cs" />
     <Compile Include="Expander.cs" />
     <Compile Include="Expander.cs" />
-    <Compile Include="Generators\ItemContainer.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="INameScope.cs" />
+    <Compile Include="IScrollable.cs" />
     <Compile Include="IPseudoClasses.cs" />
     <Compile Include="IPseudoClasses.cs" />
     <Compile Include="DropDownItem.cs" />
     <Compile Include="DropDownItem.cs" />
+    <Compile Include="ISetInheritanceParent.cs" />
+    <Compile Include="ItemVirtualizationMode.cs" />
+    <Compile Include="IVirtualizingController.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" />
     <Compile Include="Presenters\IContentPresenterHost.cs" />
     <Compile Include="Presenters\IContentPresenterHost.cs" />
     <Compile Include="Presenters\IItemsPresenterHost.cs" />
     <Compile Include="Presenters\IItemsPresenterHost.cs" />
     <Compile Include="Presenters\ItemsPresenterBase.cs" />
     <Compile Include="Presenters\ItemsPresenterBase.cs" />
+    <Compile Include="Presenters\ItemVirtualizerNone.cs" />
+    <Compile Include="Presenters\ItemVirtualizerSimple.cs" />
+    <Compile Include="Presenters\ItemVirtualizer.cs" />
     <Compile Include="Primitives\HeaderedSelectingControl.cs" />
     <Compile Include="Primitives\HeaderedSelectingControl.cs" />
-    <Compile Include="Primitives\IScrollable.cs" />
+    <Compile Include="Primitives\ILogicalScrollable.cs" />
     <Compile Include="Primitives\TabStripItem.cs" />
     <Compile Include="Primitives\TabStripItem.cs" />
     <Compile Include="Primitives\TemplateAppliedEventArgs.cs" />
     <Compile Include="Primitives\TemplateAppliedEventArgs.cs" />
     <Compile Include="SelectionChangedEventArgs.cs" />
     <Compile Include="SelectionChangedEventArgs.cs" />
@@ -170,6 +175,7 @@
     <Compile Include="TopLevel.cs" />
     <Compile Include="TopLevel.cs" />
     <Compile Include="Primitives\PopupRoot.cs" />
     <Compile Include="Primitives\PopupRoot.cs" />
     <Compile Include="Utils\UndoRedoHelper.cs" />
     <Compile Include="Utils\UndoRedoHelper.cs" />
+    <Compile Include="VirtualizingStackPanel.cs" />
     <Compile Include="WindowState.cs" />
     <Compile Include="WindowState.cs" />
     <Compile Include="Window.cs" />
     <Compile Include="Window.cs" />
     <Compile Include="RowDefinition.cs" />
     <Compile Include="RowDefinition.cs" />

+ 1 - 1
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;

+ 15 - 2
src/Avalonia.Controls/Control.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Controls
     /// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
     /// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
     /// - Implements <see cref="ILogical"/> to form part of a logical tree.
     /// - Implements <see cref="ILogical"/> to form part of a logical tree.
     /// </remarks>
     /// </remarks>
-    public class Control : InputElement, IControl, INamed, ISetLogicalParent, ISupportInitialize
+    public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="DataContext"/> property.
         /// Defines the <see cref="DataContext"/> property.
@@ -435,7 +435,11 @@ namespace Avalonia.Controls
                     OnDetachedFromLogicalTreeCore(e);
                     OnDetachedFromLogicalTreeCore(e);
                 }
                 }
 
 
-                InheritanceParent = parent as AvaloniaObject;
+                if (InheritanceParent == null || parent == null)
+                {
+                    InheritanceParent = parent as AvaloniaObject;
+                }
+
                 _parent = (IControl)parent;
                 _parent = (IControl)parent;
 
 
                 if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
                 if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
@@ -455,6 +459,15 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        /// <summary>
+        /// Sets the control's inheritance parent.
+        /// </summary>
+        /// <param name="parent">The parent.</param>
+        void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
+        {
+            InheritanceParent = parent;
+        }
+
         /// <summary>
         /// <summary>
         /// Adds a pseudo-class to be set when a property is true.
         /// Adds a pseudo-class to be set when a property is true.
         /// </summary>
         /// </summary>

+ 21 - 1
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))

+ 24 - 14
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@@ -2,7 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Templates;
 
 
@@ -16,7 +15,7 @@ namespace Avalonia.Controls.Generators
         /// <summary>
         /// <summary>
         /// Gets the currently realized containers.
         /// Gets the currently realized containers.
         /// </summary>
         /// </summary>
-        IEnumerable<ItemContainer> Containers { get; }
+        IEnumerable<ItemContainerInfo> Containers { get; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the data template used to display the items in the control.
         /// Gets or sets the data template used to display the items in the control.
@@ -34,28 +33,33 @@ namespace Avalonia.Controls.Generators
         event EventHandler<ItemContainerEventArgs> Dematerialized;
         event EventHandler<ItemContainerEventArgs> Dematerialized;
 
 
         /// <summary>
         /// <summary>
-        /// Creates container controls for a collection of items.
+        /// Event raised whenever containers are recycled.
         /// </summary>
         /// </summary>
-        /// <param name="startingIndex">
-        /// The index of the first item of the data in the containing collection.
+        event EventHandler<ItemContainerEventArgs> Recycled;
+
+        /// <summary>
+        /// Creates a container control for an item.
+        /// </summary>
+        /// <param name="index">
+        /// The index of the item of data in the control's items.
         /// </param>
         /// </param>
-        /// <param name="items">The items.</param>
+        /// <param name="item">The item.</param>
         /// <param name="selector">An optional member selector.</param>
         /// <param name="selector">An optional member selector.</param>
         /// <returns>The created controls.</returns>
         /// <returns>The created controls.</returns>
-        IEnumerable<ItemContainer> Materialize(
-            int startingIndex,
-            IEnumerable items,
+        ItemContainerInfo Materialize(
+            int index,
+            object item,
             IMemberSelector selector);
             IMemberSelector selector);
 
 
         /// <summary>
         /// <summary>
         /// Removes a set of created containers.
         /// Removes a set of created containers.
         /// </summary>
         /// </summary>
         /// <param name="startingIndex">
         /// <param name="startingIndex">
-        /// The index of the first item of the data in the containing collection.
+        /// The index of the first item in the control's items.
         /// </param>
         /// </param>
         /// <param name="count">The the number of items to remove.</param>
         /// <param name="count">The the number of items to remove.</param>
         /// <returns>The removed containers.</returns>
         /// <returns>The removed containers.</returns>
-        IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count);
+        IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count);
 
 
         /// <summary>
         /// <summary>
         /// Inserts space for newly inserted containers in the index.
         /// Inserts space for newly inserted containers in the index.
@@ -69,17 +73,23 @@ namespace Avalonia.Controls.Generators
         /// the gap.
         /// the gap.
         /// </summary>
         /// </summary>
         /// <param name="startingIndex">
         /// <param name="startingIndex">
-        /// The index of the first item of the data in the containing collection.
+        /// The index of the first item in the control's items.
         /// </param>
         /// </param>
         /// <param name="count">The the number of items to remove.</param>
         /// <param name="count">The the number of items to remove.</param>
         /// <returns>The removed containers.</returns>
         /// <returns>The removed containers.</returns>
-        IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count);
+        IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count);
+
+        bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector);
 
 
         /// <summary>
         /// <summary>
         /// Clears all created containers and returns the removed controls.
         /// Clears all created containers and returns the removed controls.
         /// </summary>
         /// </summary>
         /// <returns>The removed controls.</returns>
         /// <returns>The removed controls.</returns>
-        IEnumerable<ItemContainer> Clear();
+        IEnumerable<ItemContainerInfo> Clear();
 
 
         /// <summary>
         /// <summary>
         /// Gets the container control representing the item with the specified index.
         /// Gets the container control representing the item with the specified index.

+ 4 - 7
src/Avalonia.Controls/Generators/ItemContainerEventArgs.cs

@@ -15,13 +15,10 @@ namespace Avalonia.Controls.Generators
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemContainerEventArgs"/> class.
         /// Initializes a new instance of the <see cref="ItemContainerEventArgs"/> class.
         /// </summary>
         /// </summary>
-        /// <param name="startingIndex">The index of the first container in the source items.</param>
         /// <param name="container">The container.</param>
         /// <param name="container">The container.</param>
-        public ItemContainerEventArgs(
-            int startingIndex,
-            ItemContainer container)
+        public ItemContainerEventArgs(ItemContainerInfo container)
         {
         {
-            StartingIndex = startingIndex;
+            StartingIndex = container.Index;
             Containers = new[] { container };
             Containers = new[] { container };
         }
         }
 
 
@@ -32,7 +29,7 @@ namespace Avalonia.Controls.Generators
         /// <param name="containers">The containers.</param>
         /// <param name="containers">The containers.</param>
         public ItemContainerEventArgs(
         public ItemContainerEventArgs(
             int startingIndex, 
             int startingIndex, 
-            IList<ItemContainer> containers)
+            IList<ItemContainerInfo> containers)
         {
         {
             StartingIndex = startingIndex;
             StartingIndex = startingIndex;
             Containers = containers;
             Containers = containers;
@@ -41,7 +38,7 @@ namespace Avalonia.Controls.Generators
         /// <summary>
         /// <summary>
         /// Gets the containers.
         /// Gets the containers.
         /// </summary>
         /// </summary>
-        public IList<ItemContainer> Containers { get; }
+        public IList<ItemContainerInfo> Containers { get; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the index of the first container in the source items.
         /// Gets the index of the first container in the source items.

+ 107 - 79
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@@ -2,11 +2,11 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using System.Reactive.Subjects;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 
 
 namespace Avalonia.Controls.Generators
 namespace Avalonia.Controls.Generators
 {
 {
@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Generators
     /// </summary>
     /// </summary>
     public class ItemContainerGenerator : IItemContainerGenerator
     public class ItemContainerGenerator : IItemContainerGenerator
     {
     {
-        private List<ItemContainer> _containers = new List<ItemContainer>();
+        private Dictionary<int, ItemContainerInfo> _containers = new Dictionary<int, ItemContainerInfo>();
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
         /// Initializes a new instance of the <see cref="ItemContainerGenerator"/> class.
@@ -29,7 +29,7 @@ namespace Avalonia.Controls.Generators
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public IEnumerable<ItemContainer> Containers => _containers.Where(x => x != null);
+        public IEnumerable<ItemContainerInfo> Containers => _containers.Values;
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public event EventHandler<ItemContainerEventArgs> Materialized;
         public event EventHandler<ItemContainerEventArgs> Materialized;
@@ -37,6 +37,9 @@ namespace Avalonia.Controls.Generators
         /// <inheritdoc/>
         /// <inheritdoc/>
         public event EventHandler<ItemContainerEventArgs> Dematerialized;
         public event EventHandler<ItemContainerEventArgs> Dematerialized;
 
 
+        /// <inheritdoc/>
+        public event EventHandler<ItemContainerEventArgs> Recycled;
+
         /// <summary>
         /// <summary>
         /// Gets or sets the data template used to display the items in the control.
         /// Gets or sets the data template used to display the items in the control.
         /// </summary>
         /// </summary>
@@ -48,41 +51,29 @@ namespace Avalonia.Controls.Generators
         public IControl Owner { get; }
         public IControl Owner { get; }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public IEnumerable<ItemContainer> Materialize(
-            int startingIndex,
-            IEnumerable items,
+        public ItemContainerInfo Materialize(
+            int index,
+            object item,
             IMemberSelector selector)
             IMemberSelector selector)
         {
         {
-            Contract.Requires<ArgumentNullException>(items != null);
+            var i = selector != null ? selector.Select(item) : item;
+            var container = new ItemContainerInfo(CreateContainer(i), item, index);
 
 
-            int index = startingIndex;
-            var result = new List<ItemContainer>();
+            _containers.Add(container.Index, container);
+            Materialized?.Invoke(this, new ItemContainerEventArgs(container));
 
 
-            foreach (var item in items)
-            {
-                var i = selector != null ? selector.Select(item) : item;
-                var container = new ItemContainer(CreateContainer(i), item, index++);
-                result.Add(container);
-            }
-
-            AddContainers(result);
-            Materialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
-
-            return result.Where(x => x != null).ToList();
+            return container;
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public virtual IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
+        public virtual IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
         {
         {
-            var result = new List<ItemContainer>();
+            var result = new List<ItemContainerInfo>();
 
 
             for (int i = startingIndex; i < startingIndex + count; ++i)
             for (int i = startingIndex; i < startingIndex + count; ++i)
             {
             {
-                if (i < _containers.Count)
-                {
-                    result.Add(_containers[i]);
-                    _containers[i] = null;
-                }
+                result.Add(_containers[i]);
+                _containers.Remove(i);
             }
             }
 
 
             Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
             Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
@@ -93,18 +84,47 @@ namespace Avalonia.Controls.Generators
         /// <inheritdoc/>
         /// <inheritdoc/>
         public virtual void InsertSpace(int index, int count)
         public virtual void InsertSpace(int index, int count)
         {
         {
-            _containers.InsertRange(index, Enumerable.Repeat<ItemContainer>(null, count));
+            if (count > 0)
+            {
+                var toMove = _containers.Where(x => x.Key >= index).ToList();
+
+                foreach (var i in toMove)
+                {
+                    _containers.Remove(i.Key);
+                    i.Value.Index += count;
+                    _containers[i.Value.Index] = i.Value;
+                }
+            }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public virtual IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
+        public virtual IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
         {
         {
-            List<ItemContainer> result = new List<ItemContainer>();
+            var result = new List<ItemContainerInfo>();
 
 
-            if (startingIndex < _containers.Count)
+            if (count > 0)
             {
             {
-                result.AddRange(_containers.GetRange(startingIndex, count));
-                _containers.RemoveRange(startingIndex, count);
+                for (var i = startingIndex; i < startingIndex + count; ++i)
+                {
+                    ItemContainerInfo found;
+
+                    if (_containers.TryGetValue(i, out found))
+                    {
+                        result.Add(found);
+                    }
+
+                    _containers.Remove(i);
+                }
+
+                var toMove = _containers.Where(x => x.Key >= startingIndex).ToList();
+
+                foreach (var i in toMove)
+                {
+                    _containers.Remove(i.Key);
+                    i.Value.Index -= count;
+                    _containers.Add(i.Value.Index, i.Value);
+                }
+
                 Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
                 Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
             }
             }
 
 
@@ -112,10 +132,20 @@ namespace Avalonia.Controls.Generators
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        public virtual IEnumerable<ItemContainer> Clear()
+        public virtual bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector)
+        {
+            return false;
+        }
+
+        /// <inheritdoc/>
+        public virtual IEnumerable<ItemContainerInfo> Clear()
         {
         {
-            var result = _containers.Where(x => x != null).ToList();
-            _containers = new List<ItemContainer>();
+            var result = Containers.ToList();
+            _containers.Clear();
 
 
             if (result.Count > 0)
             if (result.Count > 0)
             {
             {
@@ -128,27 +158,20 @@ namespace Avalonia.Controls.Generators
         /// <inheritdoc/>
         /// <inheritdoc/>
         public IControl ContainerFromIndex(int index)
         public IControl ContainerFromIndex(int index)
         {
         {
-            if (index < _containers.Count)
-            {
-                return _containers[index]?.ContainerControl;
-            }
-
-            return null;
+            ItemContainerInfo result;
+            _containers.TryGetValue(index, out result);
+            return result?.ContainerControl;
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public int IndexFromContainer(IControl container)
         public int IndexFromContainer(IControl container)
         {
         {
-            var index = 0;
-
             foreach (var i in _containers)
             foreach (var i in _containers)
             {
             {
-                if (i?.ContainerControl == container)
+                if (i.Value.ContainerControl == container)
                 {
                 {
-                    return index;
+                    return i.Key;
                 }
                 }
-
-                ++index;
             }
             }
 
 
             return -1;
             return -1;
@@ -161,44 +184,40 @@ namespace Avalonia.Controls.Generators
         /// <returns>The created container control.</returns>
         /// <returns>The created container control.</returns>
         protected virtual IControl CreateContainer(object item)
         protected virtual IControl CreateContainer(object item)
         {
         {
-            var result = Owner.MaterializeDataTemplate(item, ItemTemplate);
+            var result = item as IControl;
 
 
-            if (result != null && !(item is IControl))
+            if (result == null)
             {
             {
-                result.DataContext = item;
+                result = new ContentPresenter();
+                result.SetValue(ContentPresenter.ContentProperty, item, BindingPriority.Style);
+
+                if (ItemTemplate != null)
+                {
+                    result.SetValue(
+                        ContentPresenter.ContentTemplateProperty,
+                        ItemTemplate,
+                        BindingPriority.TemplatedParent);
+                }
             }
             }
 
 
             return result;
             return result;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Adds a collection of containers to the index.
+        /// Moves a container.
         /// </summary>
         /// </summary>
-        /// <param name="containers">The containers.</param>
-        protected void AddContainers(IList<ItemContainer> containers)
+        /// <param name="oldIndex">The old index.</param>
+        /// <param name="newIndex">The new index.</param>
+        /// <param name="item">The new item.</param>
+        /// <returns>The container info.</returns>
+        protected ItemContainerInfo MoveContainer(int oldIndex, int newIndex, object item)
         {
         {
-            Contract.Requires<ArgumentNullException>(containers != null);
-
-            foreach (var c in containers)
-            {
-                while (_containers.Count < c.Index)
-                {
-                    _containers.Add(null);
-                }
-
-                if (_containers.Count == c.Index)
-                {
-                    _containers.Add(c);
-                }
-                else if (_containers[c.Index] == null)
-                {
-                    _containers[c.Index] = c;
-                }
-                else
-                {
-                    throw new InvalidOperationException("Container already created.");
-                }
-            }
+            var container = _containers[oldIndex];
+            container.Index = newIndex;
+            container.Item = item;
+            _containers.Remove(oldIndex);
+            _containers.Add(newIndex, container);
+            return container;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -207,9 +226,18 @@ namespace Avalonia.Controls.Generators
         /// <param name="index">The first index.</param>
         /// <param name="index">The first index.</param>
         /// <param name="count">The number of elements in the range.</param>
         /// <param name="count">The number of elements in the range.</param>
         /// <returns>The containers.</returns>
         /// <returns>The containers.</returns>
-        protected IEnumerable<ItemContainer> GetContainerRange(int index, int count)
+        protected IEnumerable<ItemContainerInfo> GetContainerRange(int index, int count)
+        {
+            return _containers.Where(x => x.Key >= index && x.Key <= index + count).Select(x => x.Value);
+        }
+
+        /// <summary>
+        /// Raises the <see cref="Recycled"/> event.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected void RaiseRecycled(ItemContainerEventArgs e)
         {
         {
-            return _containers.GetRange(index, count);
+            Recycled?.Invoke(this, e);
         }
         }
     }
     }
 }
 }

+ 32 - 2
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))
                 {
                 {
@@ -75,5 +76,34 @@ namespace Avalonia.Controls.Generators
                 return result;
                 return result;
             }
             }
         }
         }
+
+        /// <inheritdoc/>
+        public override bool TryRecycle(
+            int oldIndex,
+            int newIndex,
+            object item,
+            IMemberSelector selector)
+        {
+            var container = ContainerFromIndex(oldIndex);
+
+            if (container == null)
+            {
+                throw new IndexOutOfRangeException("Could not recycle container: not materialized.");
+            }
+
+            var i = selector != null ? selector.Select(item) : item;
+
+            container.SetValue(ContentProperty, i);
+
+            if (!(item is IControl))
+            {
+                container.DataContext = i;
+            }
+
+            var info = MoveContainer(oldIndex, newIndex, i);
+            RaiseRecycled(new ItemContainerEventArgs(info));
+
+            return true;
+        }
     }
     }
 }
 }

+ 5 - 5
src/Avalonia.Controls/Generators/ItemContainer.cs → src/Avalonia.Controls/Generators/ItemContainerInfo.cs

@@ -7,17 +7,17 @@ namespace Avalonia.Controls.Generators
     /// Holds information about an item container generated by an 
     /// Holds information about an item container generated by an 
     /// <see cref="IItemContainerGenerator"/>.
     /// <see cref="IItemContainerGenerator"/>.
     /// </summary>
     /// </summary>
-    public class ItemContainer
+    public class ItemContainerInfo
     {
     {
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="ItemContainer"/> class.
+        /// Initializes a new instance of the <see cref="ItemContainerInfo"/> class.
         /// </summary>
         /// </summary>
         /// <param name="container">The container control.</param>
         /// <param name="container">The container control.</param>
         /// <param name="item">The item that the container represents.</param>
         /// <param name="item">The item that the container represents.</param>
         /// <param name="index">
         /// <param name="index">
         /// The index of the item in the <see cref="ItemsControl.Items"/> collection.
         /// The index of the item in the <see cref="ItemsControl.Items"/> collection.
         /// </param>
         /// </param>
-        public ItemContainer(IControl container, object item, int index)
+        public ItemContainerInfo(IControl container, object item, int index)
         {
         {
             ContainerControl = container;
             ContainerControl = container;
             Item = item;
             Item = item;
@@ -35,11 +35,11 @@ namespace Avalonia.Controls.Generators
         /// <summary>
         /// <summary>
         /// Gets the item that the container represents.
         /// Gets the item that the container represents.
         /// </summary>
         /// </summary>
-        public object Item { get; }
+        public object Item { get; internal set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the index of the item in the <see cref="ItemsControl.Items"/> collection.
         /// Gets the index of the item in the <see cref="ItemsControl.Items"/> collection.
         /// </summary>
         /// </summary>
-        public int Index { get; }
+        public int Index { get; internal set; }
     }
     }
 }
 }

+ 27 - 0
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);
+        }
+    }
+}

+ 3 - 3
src/Avalonia.Controls/Generators/TreeContainerIndex.cs

@@ -47,7 +47,7 @@ namespace Avalonia.Controls.Generators
 
 
             Materialized?.Invoke(
             Materialized?.Invoke(
                 this, 
                 this, 
-                new ItemContainerEventArgs(0, new ItemContainer(container, item, 0)));
+                new ItemContainerEventArgs(new ItemContainerInfo(container, item, 0)));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -62,14 +62,14 @@ namespace Avalonia.Controls.Generators
 
 
             Dematerialized?.Invoke(
             Dematerialized?.Invoke(
                 this, 
                 this, 
-                new ItemContainerEventArgs(0, new ItemContainer(container, item, 0)));
+                new ItemContainerEventArgs(new ItemContainerInfo(container, item, 0)));
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Removes a set of containers from the index.
         /// Removes a set of containers from the index.
         /// </summary>
         /// </summary>
         /// <param name="containers">The item containers.</param>
         /// <param name="containers">The item containers.</param>
-        public void Remove(IEnumerable<ItemContainer> containers)
+        public void Remove(IEnumerable<ItemContainerInfo> containers)
         {
         {
             foreach (var container in containers)
             foreach (var container in containers)
             {
             {

+ 9 - 4
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);
 
 
@@ -99,25 +99,30 @@ namespace Avalonia.Controls.Generators
             }
             }
         }
         }
 
 
-        public override IEnumerable<ItemContainer> Clear()
+        public override IEnumerable<ItemContainerInfo> Clear()
         {
         {
             var items = base.Clear();
             var items = base.Clear();
             Index.Remove(items);
             Index.Remove(items);
             return items;
             return items;
         }
         }
 
 
-        public override IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
+        public override IEnumerable<ItemContainerInfo> Dematerialize(int startingIndex, int count)
         {
         {
             Index.Remove(GetContainerRange(startingIndex, count));
             Index.Remove(GetContainerRange(startingIndex, count));
             return base.Dematerialize(startingIndex, count);
             return base.Dematerialize(startingIndex, count);
         }
         }
 
 
-        public override IEnumerable<ItemContainer> RemoveRange(int startingIndex, int count)
+        public override IEnumerable<ItemContainerInfo> RemoveRange(int startingIndex, int count)
         {
         {
             Index.Remove(GetContainerRange(startingIndex, count));
             Index.Remove(GetContainerRange(startingIndex, count));
             return base.RemoveRange(startingIndex, count);
             return base.RemoveRange(startingIndex, count);
         }
         }
 
 
+        public override bool TryRecycle(int oldIndex, int newIndex, object item, IMemberSelector selector)
+        {
+            return false;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the data template for the specified item.
         /// Gets the data template for the specified item.
         /// </summary>
         /// </summary>

+ 29 - 0
src/Avalonia.Controls/IScrollable.cs

@@ -0,0 +1,29 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// Interface implemented by scrollable controls.
+    /// </summary>
+    public interface IScrollable
+    {
+        /// <summary>
+        /// Gets the extent of the scrollable content, in logical units
+        /// </summary>
+        Size Extent { get; }
+
+        /// <summary>
+        /// Gets or sets the current scroll offset, in logical units.
+        /// </summary>
+        Vector Offset { get; set; }
+
+        /// <summary>
+        /// Gets the size of the viewport, in logical units.
+        /// </summary>
+        Size Viewport { get; }
+    }
+}

+ 22 - 0
src/Avalonia.Controls/ISetInheritanceParent.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>
+    /// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
+    /// </summary>
+    /// <remarks>
+    /// You should not usually need to use this interface - it is for advanced scenarios only.
+    /// Additionally, <see cref="ISetLogicalParent"/> also sets the inheritance parent; this
+    /// interface is only needed where the logical and inheritance parents differ.
+    /// </remarks>
+    public interface ISetInheritanceParent
+    {
+        /// <summary>
+        /// Sets the control's inheritance parent.
+        /// </summary>
+        /// <param name="parent">The parent.</param>
+        void SetParent(IAvaloniaObject parent);
+    }
+}

+ 22 - 0
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();
+    }
+}

+ 67 - 0
src/Avalonia.Controls/IVirtualizingPanel.cs

@@ -0,0 +1,67 @@
+// 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>
+    /// A panel that can be used to virtualize items.
+    /// </summary>
+    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>
+        /// Gets a value indicating whether the panel is full.
+        /// </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; }
+
+        /// <summary>
+        /// Gets the number of items that can be removed while keeping the panel full.
+        /// </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; }
+
+        /// <summary>
+        /// Gets the direction of scroll.
+        /// </summary>
+        Orientation ScrollDirection { get; }
+
+        /// <summary>
+        /// Gets the average size of the materialized items in the direction of scroll.
+        /// </summary>
+        double AverageItemSize { get; }
+
+        /// <summary>
+        /// Gets or sets a size in pixels by which the content is overflowing the panel, in the
+        /// direction of scroll.
+        /// </summary>
+        /// <remarks>
+        /// This may be non-zero even when <see cref="OverflowCount"/> is zero if the last item
+        /// overflows the panel bounds.
+        /// </remarks>
+        double PixelOverflow { get; }
+
+        /// <summary>
+        /// Gets or sets the current pixel offset of the items in the direction of scroll.
+        /// </summary>
+        double PixelOffset { get; set; }
+    }
+}

+ 21 - 0
src/Avalonia.Controls/ItemVirtualizationMode.cs

@@ -0,0 +1,21 @@
+// 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>
+    /// Describes the item virtualization method to use for a list.
+    /// </summary>
+    public enum ItemVirtualizationMode
+    {
+        /// <summary>
+        /// Do not virtualize items.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// Virtualize items without smooth scrolling.
+        /// </summary>
+        Simple,
+    }
+}

+ 23 - 1
src/Avalonia.Controls/ItemsControl.cs

@@ -25,7 +25,6 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// The default value for the <see cref="ItemsPanel"/> property.
         /// The default value for the <see cref="ItemsPanel"/> property.
         /// </summary>
         /// </summary>
-        [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "Needs to be before or a NullReferenceException is thrown.")]
         private static readonly FuncTemplate<IPanel> DefaultPanel =
         private static readonly FuncTemplate<IPanel> DefaultPanel =
             new FuncTemplate<IPanel>(() => new StackPanel());
             new FuncTemplate<IPanel>(() => new StackPanel());
 
 
@@ -90,6 +89,7 @@ namespace Avalonia.Controls
                         _itemContainerGenerator.ItemTemplate = ItemTemplate;
                         _itemContainerGenerator.ItemTemplate = ItemTemplate;
                         _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
                         _itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
                         _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
                         _itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
+                        _itemContainerGenerator.Recycled += (_, e) => OnContainersRecycled(e);
                     }
                     }
                 }
                 }
 
 
@@ -265,6 +265,28 @@ namespace Avalonia.Controls
             LogicalChildren.RemoveAll(toRemove);
             LogicalChildren.RemoveAll(toRemove);
         }
         }
 
 
+        /// <summary>
+        /// Called when containers are recycled for the <see cref="ItemsControl"/> by its
+        /// <see cref="ItemContainerGenerator"/>.
+        /// </summary>
+        /// <param name="e">The details of the containers.</param>
+        protected virtual void OnContainersRecycled(ItemContainerEventArgs e)
+        {
+            var toRemove = new List<ILogical>();
+
+            foreach (var container in e.Containers)
+            {
+                // If the item is its own container, then it will be removed from the logical tree
+                // when it is removed from the Items collection.
+                if (container?.ContainerControl != container?.Item)
+                {
+                    toRemove.Add(container.ContainerControl);
+                }
+            }
+
+            LogicalChildren.RemoveAll(toRemove);
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override void OnTemplateChanged(AvaloniaPropertyChangedEventArgs e)
         protected override void OnTemplateChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {

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

+ 54 - 3
src/Avalonia.Controls/ListBox.cs

@@ -2,12 +2,11 @@
 // 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.Collections;
 using System.Collections;
-using System.Collections.Generic;
-using Avalonia.Collections;
 using Avalonia.Controls.Generators;
 using Avalonia.Controls.Generators;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
 using Avalonia.Input;
 using Avalonia.Input;
-using Avalonia.Interactivity;
 
 
 namespace Avalonia.Controls
 namespace Avalonia.Controls
 {
 {
@@ -16,6 +15,18 @@ namespace Avalonia.Controls
     /// </summary>
     /// </summary>
     public class ListBox : SelectingItemsControl
     public class ListBox : SelectingItemsControl
     {
     {
+        /// <summary>
+        /// The default value for the <see cref="ItemsControl.ItemsPanel"/> property.
+        /// </summary>
+        private static readonly FuncTemplate<IPanel> DefaultPanel =
+            new FuncTemplate<IPanel>(() => new VirtualizingStackPanel());
+
+        /// <summary>
+        /// Defines the <see cref="Scroll"/> property.
+        /// </summary>
+        public static readonly DirectProperty<ListBox, IScrollable> ScrollProperty =
+            AvaloniaProperty.RegisterDirect<ListBox, IScrollable>(nameof(Scroll), o => o.Scroll);
+
         /// <summary>
         /// <summary>
         /// Defines the <see cref="SelectedItems"/> property.
         /// Defines the <see cref="SelectedItems"/> property.
         /// </summary>
         /// </summary>
@@ -28,6 +39,31 @@ namespace Avalonia.Controls
         public static readonly new AvaloniaProperty<SelectionMode> SelectionModeProperty = 
         public static readonly new AvaloniaProperty<SelectionMode> SelectionModeProperty = 
             SelectingItemsControl.SelectionModeProperty;
             SelectingItemsControl.SelectionModeProperty;
 
 
+        /// <summary>
+        /// Defines the <see cref="VirtualizationMode"/> property.
+        /// </summary>
+        public static readonly AvaloniaProperty<ItemVirtualizationMode> VirtualizationModeProperty =
+            ItemsPresenter.VirtualizationModeProperty.AddOwner<ListBox>();
+
+        private IScrollable _scroll;
+
+        /// <summary>
+        /// Initializes static members of the <see cref="ItemsControl"/> class.
+        /// </summary>
+        static ListBox()
+        {
+            ItemsPanelProperty.OverrideDefaultValue<ListBox>(DefaultPanel);
+        }
+
+        /// <summary>
+        /// Gets the scroll information for the <see cref="ListBox"/>.
+        /// </summary>
+        public IScrollable Scroll
+        {
+            get { return _scroll; }
+            private set { SetAndRaise(ScrollProperty, ref _scroll, value); }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         public new IList SelectedItems => base.SelectedItems;
         public new IList SelectedItems => base.SelectedItems;
 
 
@@ -38,6 +74,15 @@ namespace Avalonia.Controls
             set { base.SelectionMode = value; }
             set { base.SelectionMode = value; }
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets the virtualization mode for the items.
+        /// </summary>
+        public ItemVirtualizationMode VirtualizationMode
+        {
+            get { return GetValue(VirtualizationModeProperty); }
+            set { SetValue(VirtualizationModeProperty, value); }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         protected override IItemContainerGenerator CreateItemContainerGenerator()
         {
         {
@@ -75,5 +120,11 @@ namespace Avalonia.Controls
                     (e.InputModifiers & InputModifiers.Control) != 0);
                     (e.InputModifiers & InputModifiers.Control) != 0);
             }
             }
         }
         }
+
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+            Scroll = e.NameScope.Find<IScrollable>("PART_ScrollViewer");
+        }
     }
     }
 }
 }

+ 7 - 0
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 - 0
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>

+ 1 - 16
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;
-
-                    if ((bool)x.NewValue)
-                    {
-                        ((IPseudoClasses)sender.Classes).Add(":selected");
-
-                        if (((IVisual)sender).IsAttachedToVisualTree && 
-                            itemsControl?.AutoScrollToSelectedItem == true)
-                        {
-                            sender.BringIntoView();
-                        }
-                    }
-                    else
-                    {
-                        ((IPseudoClasses)sender.Classes).Remove(":selected");
-                    }
+                    ((IPseudoClasses)sender.Classes).Set(":selected", (bool)x.NewValue);
 
 
                     sender.RaiseEvent(new RoutedEventArgs
                     sender.RaiseEvent(new RoutedEventArgs
                     {
                     {

+ 17 - 17
src/Avalonia.Controls/Panel.cs

@@ -79,12 +79,28 @@ namespace Avalonia.Controls
             set { SetValue(BackgroundProperty, value); }
             set { SetValue(BackgroundProperty, value); }
         }
         }
 
 
+        /// <summary>
+        /// Renders the visual to a <see cref="DrawingContext"/>.
+        /// </summary>
+        /// <param name="context">The drawing context.</param>
+        public override void Render(DrawingContext context)
+        {
+            var background = Background;
+            if (background != null)
+            {
+                var renderSize = Bounds.Size;
+                context.FillRectangle(background, new Rect(renderSize));
+            }
+
+            base.Render(context);
+        }
+
         /// <summary>
         /// <summary>
         /// Called when the <see cref="Children"/> collection changes.
         /// Called when the <see cref="Children"/> collection changes.
         /// </summary>
         /// </summary>
         /// <param name="sender">The event sender.</param>
         /// <param name="sender">The event sender.</param>
         /// <param name="e">The event args.</param>
         /// <param name="e">The event args.</param>
-        private void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+        protected virtual void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
         {
         {
             List<Control> controls;
             List<Control> controls;
 
 
@@ -122,21 +138,5 @@ namespace Avalonia.Controls
 
 
             InvalidateMeasure();
             InvalidateMeasure();
         }
         }
-
-        /// <summary>
-        /// Renders the visual to a <see cref="DrawingContext"/>.
-        /// </summary>
-        /// <param name="context">The drawing context.</param>
-        public override void Render(DrawingContext context)
-        {
-            var background = Background;
-            if (background != null)
-            {
-                var renderSize = Bounds.Size;
-                context.FillRectangle(background, new Rect(renderSize));
-            }
-
-            base.Render(context);
-        }
     }
     }
 }
 }

+ 4 - 8
src/Avalonia.Controls/Presenters/CarouselPresenter.cs

@@ -95,9 +95,8 @@ namespace Avalonia.Controls.Presenters
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected override void CreatePanel()
+        protected override void PanelCreated(IPanel panel)
         {
         {
-            base.CreatePanel();
             var task = MoveToPage(-1, SelectedIndex);
             var task = MoveToPage(-1, SelectedIndex);
         }
         }
 
 
@@ -175,12 +174,9 @@ namespace Avalonia.Controls.Presenters
             if (container == null)
             if (container == null)
             {
             {
                 var item = Items.Cast<object>().ElementAt(index);
                 var item = Items.Cast<object>().ElementAt(index);
-                var materialized = ItemContainerGenerator.Materialize(
-                    index,
-                    new[] { item },
-                    MemberSelector);
-                container = materialized.First().ContainerControl;
-                Panel.Children.Add(container);
+                var materialized = ItemContainerGenerator.Materialize(index, item, MemberSelector);
+                Panel.Children.Add(materialized.ContainerControl);
+                container = materialized.ContainerControl;
             }
             }
 
 
             return container;
             return container;

+ 76 - 20
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -80,6 +80,7 @@ namespace Avalonia.Controls.Presenters
 
 
         private IControl _child;
         private IControl _child;
         private bool _createdChild;
         private bool _createdChild;
+        private IDataTemplate _dataTemplate;
 
 
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="ContentPresenter"/> class.
         /// Initializes static members of the <see cref="ContentPresenter"/> class.
@@ -95,10 +96,6 @@ namespace Avalonia.Controls.Presenters
         /// </summary>
         /// </summary>
         public ContentPresenter()
         public ContentPresenter()
         {
         {
-            var dataContext = this.GetObservable(ContentProperty)
-                .Select(x => x is IControl ? AvaloniaProperty.UnsetValue : x);
-
-            Bind(Control.DataContextProperty, dataContext);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -200,6 +197,13 @@ namespace Avalonia.Controls.Presenters
             }
             }
         }
         }
 
 
+        /// <inheritdoc/>
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+            _dataTemplate = null;
+        }
+
         /// <summary>
         /// <summary>
         /// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
         /// Updates the <see cref="Child"/> control based on the control's <see cref="Content"/>.
         /// </summary>
         /// </summary>
@@ -213,34 +217,86 @@ namespace Avalonia.Controls.Presenters
         /// </remarks>
         /// </remarks>
         public void UpdateChild()
         public void UpdateChild()
         {
         {
-            var old = Child;
             var content = Content;
             var content = Content;
-            var result = this.MaterializeDataTemplate(content, ContentTemplate);
+            var oldChild = Child;
+            var newChild = content as IControl;
+
+            if (content != null && newChild == null)
+            {
+                // We have content and it isn't a control, so first try to recycle the existing
+                // child control to display the new data by querying if the template that created
+                // the child can recycle items and that it also matches the new data.
+                if (oldChild != null && 
+                    _dataTemplate != null &&
+                    _dataTemplate.SupportsRecycling && 
+                    _dataTemplate.Match(content))
+                {
+                    newChild = oldChild;
+                }
+                else
+                {
+                    // We couldn't recycle an existing control so find a data template for the data
+                    // and use it to create a control.
+                    _dataTemplate = this.FindDataTemplate(content, ContentTemplate) ?? FuncDataTemplate.Default;
+                    newChild = _dataTemplate.Build(content);
+
+                    // Try to give the new control its own name scope.
+                    var controlResult = newChild as Control;
+
+                    if (controlResult != null)
+                    {
+                        NameScope.SetNameScope(controlResult, new NameScope());
+                    }
+                }
+            }
+            else
+            {
+                _dataTemplate = null;
+            }
+
+            // Remove the old child if we're not recycling it.
+            if (oldChild != null && newChild != oldChild)
+            {
+                VisualChildren.Remove(oldChild);
+            }
 
 
-            if (old != null)
+            // Set the DataContext if the data isn't a control.
+            if (!(content is IControl))
             {
             {
-                VisualChildren.Remove(old);
+                DataContext = content;
             }
             }
 
 
-            if (result != null)
+            // Update the Child.
+            if (newChild == null)
+            {
+                Child = null;
+            }
+            else if (newChild != oldChild)
             {
             {
-                if (!(content is IControl))
+                ((ISetInheritanceParent)newChild).SetParent(this);
+
+                Child = newChild;
+
+                if (oldChild?.Parent == this)
                 {
                 {
-                    result.DataContext = content;
+                    LogicalChildren.Remove(oldChild);
                 }
                 }
 
 
-                Child = result;
-
-                if (result.Parent == null)
+                if (newChild.Parent == null)
                 {
                 {
-                    ((ISetLogicalParent)result).SetParent((ILogical)this.TemplatedParent ?? this);
+                    var templatedLogicalParent = TemplatedParent as ILogical;
+
+                    if (templatedLogicalParent != null)
+                    {
+                        ((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
+                    }
+                    else
+                    {
+                        LogicalChildren.Add(newChild);
+                    }
                 }
                 }
 
 
-                VisualChildren.Add(result);
-            }
-            else
-            {
-                Child = null;
+                VisualChildren.Add(newChild);
             }
             }
 
 
             _createdChild = true;
             _createdChild = true;

+ 2 - 0
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);
     }
     }
 }
 }

+ 198 - 0
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@@ -0,0 +1,198 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Utils;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Presenters
+{
+    /// <summary>
+    /// Base class for classes which handle virtualization for an <see cref="ItemsPresenter"/>.
+    /// </summary>
+    internal abstract class ItemVirtualizer : IVirtualizingController, IDisposable
+    {
+        private bool disposedValue;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ItemVirtualizer"/> class.
+        /// </summary>
+        /// <param name="owner"></param>
+        public ItemVirtualizer(ItemsPresenter owner)
+        {
+            Owner = owner;
+            Items = owner.Items;
+            ItemCount = owner.Items.Count();
+        }
+
+        /// <summary>
+        /// Gets the <see cref="ItemsPresenter"/> which owns the virtualizer.
+        /// </summary>
+        public ItemsPresenter Owner { get; }
+
+        /// <summary>
+        /// Gets the <see cref="IVirtualizingPanel"/> which will host the items.
+        /// </summary>
+        public IVirtualizingPanel VirtualizingPanel => Owner.Panel as IVirtualizingPanel;
+
+        /// <summary>
+        /// Gets the items to display.
+        /// </summary>
+        public IEnumerable Items { get; private set; }
+
+        /// <summary>
+        /// Gets the number of items in <see cref="Items"/>.
+        /// </summary>
+        public int ItemCount { get; private set; }
+
+        /// <summary>
+        /// Gets or sets the index of the first item displayed in the panel.
+        /// </summary>
+        public int FirstIndex { get; protected set; }
+
+        /// <summary>
+        /// Gets or sets the index of the first item beyond those displayed in the panel.
+        /// </summary>
+        public int NextIndex { get; protected set; }
+
+        /// <summary>
+        /// Gets a value indicating whether the items should be scroll horizontally or vertically.
+        /// </summary>
+        public bool Vertical => VirtualizingPanel.ScrollDirection == Orientation.Vertical;
+
+        /// <summary>
+        /// Gets a value indicating whether logical scrolling is enabled.
+        /// </summary>
+        public abstract bool IsLogicalScrollEnabled { get; }
+
+        /// <summary>
+        /// Gets the value of the scroll extent.
+        /// </summary>
+        public abstract double ExtentValue { get; }
+
+        /// <summary>
+        /// Gets or sets the value of the current scroll offset.
+        /// </summary>
+        public abstract double OffsetValue { get; set; }
+
+        /// <summary>
+        /// Gets the value of the scrollable viewport.
+        /// </summary>
+        public abstract double ViewportValue { get; }
+
+        /// <summary>
+        /// Gets the <see cref="ExtentValue"/> as a <see cref="Size"/>.
+        /// </summary>
+        public Size Extent => Vertical ? new Size(0, ExtentValue) : new Size(ExtentValue, 0);
+
+        /// <summary>
+        /// Gets the <see cref="ViewportValue"/> as a <see cref="Size"/>.
+        /// </summary>
+        public Size Viewport => Vertical ? new Size(0, ViewportValue) : new Size(ViewportValue, 0);
+
+        /// <summary>
+        /// Gets or sets the <see cref="OffsetValue"/> as a <see cref="Vector"/>.
+        /// </summary>
+        public Vector Offset
+        {
+            get
+            {
+                return Vertical ? new Vector(0, OffsetValue) : new Vector(OffsetValue, 0);
+            }
+
+            set
+            {
+                OffsetValue = Vertical ? value.Y : value.X;
+            }
+        }
+        
+        /// <summary>
+        /// Creates an <see cref="ItemVirtualizer"/> based on an item presenter's 
+        /// <see cref="ItemVirtualizationMode"/>.
+        /// </summary>
+        /// <param name="owner">The items presenter.</param>
+        /// <returns>An <see cref="ItemVirtualizer"/>.</returns>
+        public static ItemVirtualizer Create(ItemsPresenter owner)
+        {
+            var virtualizingPanel = owner.Panel as IVirtualizingPanel;
+            var scrollable = (ILogicalScrollable)owner;
+            ItemVirtualizer result = null;
+
+            if (virtualizingPanel != null && scrollable.InvalidateScroll != null)
+            {
+                switch (owner.VirtualizationMode)
+                {
+                    case ItemVirtualizationMode.Simple:
+                        result = new ItemVirtualizerSimple(owner);
+                        break;
+                }
+            }
+
+            if (result == null)
+            {
+                result = new ItemVirtualizerNone(owner);
+            }
+
+            if (virtualizingPanel != null)
+            {
+                virtualizingPanel.Controller = result;
+            }
+
+            return result;
+        }
+
+        /// <inheritdoc/>
+        public virtual void UpdateControls()
+        {
+        }
+
+        /// <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>
+        public virtual IControl GetControlInDirection(NavigationDirection direction, IControl from)
+        {
+            return null;
+        }
+
+        /// <summary>
+        /// Called when the items for the presenter change, either because 
+        /// <see cref="ItemsPresenterBase.Items"/> has been set, the items collection has been
+        /// modified, or the panel has been created.
+        /// </summary>
+        /// <param name="items">The items.</param>
+        /// <param name="e">A description of the change.</param>
+        public virtual void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
+        {
+            Items = items;
+            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();
+    }
+}

+ 173 - 0
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@@ -0,0 +1,173 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using Avalonia.Controls.Generators;
+using Avalonia.Controls.Utils;
+
+namespace Avalonia.Controls.Presenters
+{
+    /// <summary>
+    /// Represents an item virtualizer for an <see cref="ItemsPresenter"/> that doesn't actually
+    /// virtualize items - it just creates a container for every item.
+    /// </summary>
+    internal class ItemVirtualizerNone : ItemVirtualizer
+    {
+        public ItemVirtualizerNone(ItemsPresenter owner)
+            : base(owner)
+        {
+            if (Items != null && owner.Panel != null)
+            {
+                AddContainers(0, Items);
+            }
+        }
+
+        /// <inheritdoc/>
+        public override bool IsLogicalScrollEnabled => false;
+
+        /// <summary>
+        /// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
+        /// false.
+        /// </summary>
+        public override double ExtentValue
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        /// <summary>
+        /// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
+        /// false.
+        /// </summary>
+        public override double OffsetValue
+        {
+            get { throw new NotSupportedException(); }
+            set { throw new NotSupportedException(); }
+        }
+
+        /// <summary>
+        /// This property should never be accessed because <see cref="IsLogicalScrollEnabled"/> is
+        /// false.
+        /// </summary>
+        public override double ViewportValue
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        /// <inheritdoc/>
+        public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
+        {
+            base.ItemsChanged(items, e);
+
+            var generator = Owner.ItemContainerGenerator;
+            var panel = Owner.Panel;
+
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    if (e.NewStartingIndex + e.NewItems.Count < Items.Count())
+                    {
+                        generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
+                    }
+
+                    AddContainers(e.NewStartingIndex, e.NewItems);
+                    break;
+
+                case NotifyCollectionChangedAction.Remove:
+                    RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
+                    var containers = AddContainers(e.NewStartingIndex, e.NewItems);
+
+                    var i = e.NewStartingIndex;
+
+                    foreach (var container in containers)
+                    {
+                        panel.Children[i++] = container.ContainerControl;
+                    }
+
+                    break;
+
+                case NotifyCollectionChangedAction.Move:
+                    // TODO: Handle move in a more efficient manner. At the moment we just
+                    // drop through to Reset to recreate all the containers.
+
+                case NotifyCollectionChangedAction.Reset:
+                    RemoveContainers(generator.Clear());
+
+                    if (Items != null)
+                    {
+                        AddContainers(0, Items);
+                    }
+
+                    break;
+            }
+
+            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)
+        {
+            var generator = Owner.ItemContainerGenerator;
+            var result = new List<ItemContainerInfo>();
+            var panel = Owner.Panel;
+
+            foreach (var item in items)
+            {
+                var i = generator.Materialize(index++, item, Owner.MemberSelector);
+
+                if (i.ContainerControl != null)
+                {
+                    if (i.Index < panel.Children.Count)
+                    {
+                        // TODO: This will insert at the wrong place when there are null items.
+                        panel.Children.Insert(i.Index, i.ContainerControl);
+                    }
+                    else
+                    {
+                        panel.Children.Add(i.ContainerControl);
+                    }
+                }
+
+                result.Add(i);
+            }
+
+            return result;
+        }
+
+        private void RemoveContainers(IEnumerable<ItemContainerInfo> items)
+        {
+            var panel = Owner.Panel;
+
+            foreach (var i in items)
+            {
+                if (i.ContainerControl != null)
+                {
+                    panel.Children.Remove(i.ContainerControl);
+                }
+            }
+        }
+    }
+}

+ 504 - 0
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -0,0 +1,504 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Linq;
+using Avalonia.Controls.Utils;
+using Avalonia.Input;
+using Avalonia.Layout;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls.Presenters
+{
+    /// <summary>
+    /// Handles virtualization in an <see cref="ItemsPresenter"/> for
+    /// <see cref="ItemVirtualizationMode.Simple"/>.
+    /// </summary>
+    internal class ItemVirtualizerSimple : ItemVirtualizer
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ItemVirtualizerSimple"/> class.
+        /// </summary>
+        /// <param name="owner"></param>
+        public ItemVirtualizerSimple(ItemsPresenter owner)
+            : base(owner)
+        {
+            // Don't need to add children here as UpdateControls should be called by the panel
+            // measure/arrange.
+        }
+
+        /// <inheritdoc/>
+        public override bool IsLogicalScrollEnabled => true;
+
+        /// <inheritdoc/>
+        public override double ExtentValue => ItemCount;
+
+        /// <inheritdoc/>
+        public override double OffsetValue
+        {
+            get
+            {
+                var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0;
+                return FirstIndex + offset;
+            }
+
+            set
+            {
+                var panel = VirtualizingPanel;
+                var offset = VirtualizingPanel.PixelOffset > 0 ? 1 : 0;
+                var delta = (int)(value - (FirstIndex + offset));
+
+                if (delta != 0)
+                {
+                    var newLastIndex = (NextIndex - 1) + delta;
+
+                    if (newLastIndex < ItemCount)
+                    {
+                        if (panel.PixelOffset > 0)
+                        {
+                            panel.PixelOffset = 0;
+                            delta += 1;
+                        }
+
+                        if (delta != 0)
+                        {
+                            RecycleContainersForMove(delta);
+                        }
+                    }
+                    else
+                    {
+                        // 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;
+                        RecycleContainersForMove(firstIndex - FirstIndex);
+
+                        panel.PixelOffset = VirtualizingPanel.ScrollDirection == Orientation.Vertical ?
+                            panel.Children[0].Bounds.Height :
+                            panel.Children[0].Bounds.Width;
+                    }
+                }
+            }
+        }
+
+        /// <inheritdoc/>
+        public override double ViewportValue
+        {
+            get
+            {
+                // If we can't fit the last item in the panel fully, subtract 1 from the viewport.
+                var overflow = VirtualizingPanel.PixelOverflow > 0 ? 1 : 0;
+                return VirtualizingPanel.Children.Count - overflow;
+            }
+        }
+
+        /// <inheritdoc/>
+        public override void UpdateControls()
+        {
+            CreateAndRemoveContainers();
+            InvalidateScroll();
+        }
+
+        /// <inheritdoc/>
+        public override void ItemsChanged(IEnumerable items, NotifyCollectionChangedEventArgs e)
+        {
+            base.ItemsChanged(items, e);
+
+            var panel = VirtualizingPanel;
+
+            if (items != null)
+            {
+                switch (e.Action)
+                {
+                    case NotifyCollectionChangedAction.Add:
+                        CreateAndRemoveContainers();
+
+                        if (e.NewStartingIndex >= FirstIndex &&
+                            e.NewStartingIndex + e.NewItems.Count <= NextIndex)
+                        {
+                            RecycleContainers();
+                        }
+
+                        break;
+
+                    case NotifyCollectionChangedAction.Remove:
+                        if (e.OldStartingIndex >= FirstIndex &&
+                            e.OldStartingIndex + e.OldItems.Count <= NextIndex)
+                        {
+                            RecycleContainersOnRemove();
+                        }
+
+                        break;
+
+                    case NotifyCollectionChangedAction.Move:
+                    case NotifyCollectionChangedAction.Replace:
+                        RecycleContainers();
+                        break;
+
+                    case NotifyCollectionChangedAction.Reset:
+                        RecycleContainersOnRemove();
+                        CreateAndRemoveContainers();
+                        break;
+                }
+            }
+            else
+            {
+                Owner.ItemContainerGenerator.Clear();
+                VirtualizingPanel.Children.Clear();
+                FirstIndex = NextIndex = 0;
+            }
+
+            // 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>
+        /// Creates and removes containers such that we have at most enough containers to fill
+        /// the panel.
+        /// </summary>
+        private void CreateAndRemoveContainers()
+        {
+            var generator = Owner.ItemContainerGenerator;
+            var panel = VirtualizingPanel;
+
+            if (!panel.IsFull && Items != null)
+            {
+                var memberSelector = Owner.MemberSelector;
+                var index = NextIndex;
+                var step = 1;
+
+                while (!panel.IsFull)
+                {
+                    if (index >= ItemCount)
+                    {
+                        // We can fit more containers in the panel, but we're at the end of the
+                        // items. If we're scrolled to the top (FirstIndex == 0), then there are
+                        // no more items to create. Otherwise, go backwards adding containers to
+                        // the beginning of the panel.
+                        if (FirstIndex == 0)
+                        {
+                            break;
+                        }
+                        else
+                        {
+                            index = FirstIndex - 1;
+                            step = -1;
+                        }
+                    }
+
+                    var materialized = generator.Materialize(index, Items.ElementAt(index), memberSelector);
+
+                    if (step == 1)
+                    {
+                        panel.Children.Add(materialized.ContainerControl);
+                    }
+                    else
+                    {
+                        panel.Children.Insert(0, materialized.ContainerControl);
+                    }
+
+                    index += step;
+                }
+
+                if (step == 1)
+                {
+                    NextIndex = index;
+                }
+                else
+                {
+                    NextIndex = ItemCount;
+                    FirstIndex = index + 1;
+                }
+            }
+
+            if (panel.OverflowCount > 0)
+            {
+                RemoveContainers(panel.OverflowCount);
+            }
+        }
+
+        /// <summary>
+        /// Updates the containers in the panel to make sure they are displaying the correct item
+        /// based on <see cref="ItemVirtualizer.FirstIndex"/>.
+        /// </summary>
+        /// <remarks>
+        /// This method requires that <see cref="ItemVirtualizer.FirstIndex"/> + the number of
+        /// materialized containers is not more than <see cref="ItemVirtualizer.ItemCount"/>.
+        /// </remarks>
+        private void RecycleContainers()
+        {
+            var panel = VirtualizingPanel;
+            var generator = Owner.ItemContainerGenerator;
+            var selector = Owner.MemberSelector;
+            var containers = generator.Containers.ToList();
+            var itemIndex = FirstIndex;
+
+            foreach (var container in containers)
+            {
+                var item = Items.ElementAt(itemIndex);
+
+                if (!object.Equals(container.Item, item))
+                {
+                    if (!generator.TryRecycle(itemIndex, itemIndex, item, selector))
+                    {
+                        throw new NotImplementedException();
+                    }
+                }
+
+                ++itemIndex;
+            }
+        }
+
+        /// <summary>
+        /// Recycles containers when a move occurs.
+        /// </summary>
+        /// <param name="delta">The delta of the move.</param>
+        /// <remarks>
+        /// If the move is less than a page, then this method moves the containers for the items
+        /// that are still visible to the correct place, and recyles and moves the others. For
+        /// example: if there are 20 items and 10 containers visible and the user scrolls 5
+        /// items down, then the bottom 5 containers will be moved to the top and the top 5 will
+        /// be moved to the bottom and recycled to display the newly visible item. Updates 
+        /// <see cref="ItemVirtualizer.FirstIndex"/> and <see cref="ItemVirtualizer.NextIndex"/>
+        /// with their new values.
+        /// </remarks>
+        private void RecycleContainersForMove(int delta)
+        {
+            var panel = VirtualizingPanel;
+            var generator = Owner.ItemContainerGenerator;
+            var selector = Owner.MemberSelector;
+            var sign = delta < 0 ? -1 : 1;
+            var count = Math.Min(Math.Abs(delta), panel.Children.Count);
+            var move = count < panel.Children.Count;
+            var first = delta < 0 && move ? panel.Children.Count + delta : 0;
+            var containers = panel.Children.GetRange(first, count).ToList();
+
+            for (var i = 0; i < count; ++i)
+            {
+                var oldItemIndex = FirstIndex + first + i;
+                var newItemIndex = oldItemIndex + delta + ((panel.Children.Count - count) * sign);
+
+                var item = Items.ElementAt(newItemIndex);
+
+                if (!generator.TryRecycle(oldItemIndex, newItemIndex, item, selector))
+                {
+                    throw new NotImplementedException();
+                }
+            }
+
+            if (move)
+            {
+                if (delta > 0)
+                {
+                    panel.Children.MoveRange(first, count, panel.Children.Count);
+                }
+                else
+                {
+                    panel.Children.MoveRange(first, count, 0);
+                }
+            }
+
+            FirstIndex += delta;
+            NextIndex += delta;
+        }
+
+        /// <summary>
+        /// Recycles containers due to items being removed.
+        /// </summary>
+        private void RecycleContainersOnRemove()
+        {
+            var panel = VirtualizingPanel;
+
+            if (NextIndex <= ItemCount)
+            {
+                // Items have been removed but FirstIndex..NextIndex is still a valid range in the
+                // items, so just recycle the containers to adapt to the new state.
+                RecycleContainers();
+            }
+            else
+            {
+                // Items have been removed and now the range FirstIndex..NextIndex goes out of 
+                // the item bounds. Remove any excess containers, try to scroll up and then recycle
+                // the containers to make sure they point to the correct item.
+                var newFirstIndex = Math.Max(0, FirstIndex - (NextIndex - ItemCount));
+                var delta = newFirstIndex - FirstIndex;
+                var newNextIndex = NextIndex + delta;
+
+                if (newNextIndex > ItemCount)
+                {
+                    RemoveContainers(newNextIndex - ItemCount);
+                }
+
+                if (delta != 0)
+                {
+                    RecycleContainersForMove(delta);
+                }
+
+                RecycleContainers();
+            }
+        }
+
+        /// <summary>
+        /// Removes the specified number of containers from the end of the panel and updates
+        /// <see cref="ItemVirtualizer.NextIndex"/>.
+        /// </summary>
+        /// <param name="count">The number of containers to remove.</param>
+        private void RemoveContainers(int count)
+        {
+            var index = VirtualizingPanel.Children.Count - count;
+
+            VirtualizingPanel.Children.RemoveRange(index, count);
+            Owner.ItemContainerGenerator.Dematerialize(FirstIndex + index, 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);
+        }
+    }
+}

+ 81 - 97
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -1,19 +1,29 @@
 // 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.Collections.Generic;
+using System;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
-using Avalonia.Controls.Generators;
-using Avalonia.Controls.Utils;
+using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Input;
+using static Avalonia.Utilities.MathUtilities;
 
 
 namespace Avalonia.Controls.Presenters
 namespace Avalonia.Controls.Presenters
 {
 {
     /// <summary>
     /// <summary>
     /// Displays items inside an <see cref="ItemsControl"/>.
     /// Displays items inside an <see cref="ItemsControl"/>.
     /// </summary>
     /// </summary>
-    public class ItemsPresenter : ItemsPresenterBase
+    public class ItemsPresenter : ItemsPresenterBase, ILogicalScrollable
     {
     {
+        /// <summary>
+        /// Defines the <see cref="VirtualizationMode"/> property.
+        /// </summary>
+        public static readonly StyledProperty<ItemVirtualizationMode> VirtualizationModeProperty =
+            AvaloniaProperty.Register<ItemsPresenter, ItemVirtualizationMode>(
+                nameof(VirtualizationMode),
+                defaultValue: ItemVirtualizationMode.Simple);
+
+        private ItemVirtualizer _virtualizer;
+
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="ItemsPresenter"/> class.
         /// Initializes static members of the <see cref="ItemsPresenter"/> class.
         /// </summary>
         /// </summary>
@@ -22,127 +32,101 @@ 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);
         }
         }
 
 
-        /// <inheritdoc/>
-        protected override void CreatePanel()
+        /// <summary>
+        /// Gets or sets the virtualization mode for the items.
+        /// </summary>
+        public ItemVirtualizationMode VirtualizationMode
         {
         {
-            base.CreatePanel();
-
-            if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
-            {
-                KeyboardNavigation.SetDirectionalNavigation(
-                    (InputElement)Panel,
-                    KeyboardNavigationMode.Contained);
-            }
-
-            KeyboardNavigation.SetTabNavigation(
-                (InputElement)Panel,
-                KeyboardNavigation.GetTabNavigation(this));
+            get { return GetValue(VirtualizationModeProperty); }
+            set { SetValue(VirtualizationModeProperty, value); }
         }
         }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
-        protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
+        bool ILogicalScrollable.IsLogicalScrollEnabled
         {
         {
-            var generator = ItemContainerGenerator;
+            get { return _virtualizer?.IsLogicalScrollEnabled ?? false; }
+        }
 
 
-            // TODO: Handle Move and Replace etc.
-            switch (e.Action)
-            {
-                case NotifyCollectionChangedAction.Add:
-                    if (e.NewStartingIndex + e.NewItems.Count < Items.Count())
-                    {
-                        generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
-                    }
+        /// <inheritdoc/>
+        Size IScrollable.Extent => _virtualizer.Extent;
 
 
-                    AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
-                    break;
+        /// <inheritdoc/>
+        Vector IScrollable.Offset
+        {
+            get { return _virtualizer.Offset; }
+            set { _virtualizer.Offset = CoerceOffset(value); }
+        }
+
+        /// <inheritdoc/>
+        Size IScrollable.Viewport => _virtualizer.Viewport;
 
 
-                case NotifyCollectionChangedAction.Remove:
-                    RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
-                    break;
+        /// <inheritdoc/>
+        Action ILogicalScrollable.InvalidateScroll { get; set; }
 
 
-                case NotifyCollectionChangedAction.Replace:
-                    RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
-                    var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
-                    AddContainers(containers);
+        /// <inheritdoc/>
+        Size ILogicalScrollable.ScrollSize => new Size(1, 1);
 
 
-                    var i = e.NewStartingIndex;
+        /// <inheritdoc/>
+        Size ILogicalScrollable.PageScrollSize => new Size(0, 1);
 
 
-                    foreach (var container in containers)
-                    {
-                        Panel.Children[i++] = container.ContainerControl;
-                    }
+        /// <inheritdoc/>
+        bool ILogicalScrollable.BringIntoView(IControl target, Rect targetRect)
+        {
+            return false;
+        }
 
 
-                    break;
+        /// <inheritdoc/>
+        IControl ILogicalScrollable.GetControlInDirection(NavigationDirection direction, IControl from)
+        {
+            return _virtualizer?.GetControlInDirection(direction, from);
+        }
 
 
-                case NotifyCollectionChangedAction.Move:
-                // TODO: Implement Move in a more efficient manner.
-                case NotifyCollectionChangedAction.Reset:
-                    RemoveContainers(generator.Clear());
+        public override void ScrollIntoView(object item)
+        {
+            _virtualizer?.ScrollIntoView(item);
+        }
 
 
-                    if (Items != null)
-                    {
-                        AddContainers(generator.Materialize(0, Items, MemberSelector));
-                    }
+        /// <inheritdoc/>
+        protected override void PanelCreated(IPanel panel)
+        {
+            _virtualizer = ItemVirtualizer.Create(this);
+            ((ILogicalScrollable)this).InvalidateScroll?.Invoke();
 
 
-                    break;
+            if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
+            {
+                KeyboardNavigation.SetDirectionalNavigation(
+                    (InputElement)Panel,
+                    KeyboardNavigationMode.Contained);
             }
             }
 
 
-            InvalidateMeasure();
+            KeyboardNavigation.SetTabNavigation(
+                (InputElement)Panel,
+                KeyboardNavigation.GetTabNavigation(this));
         }
         }
 
 
-        private void AddContainersToPanel(IEnumerable<ItemContainer> items)
+        protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
         {
         {
-            foreach (var i in items)
-            {
-                if (i.ContainerControl != null)
-                {
-                    if (i.Index < this.Panel.Children.Count)
-                    {
-                        // HACK: This will insert at the wrong place when there are null items,
-                        // but all of this will need to be rewritten when we implement 
-                        // virtualization so hope no-one notices until then :)
-                        this.Panel.Children.Insert(i.Index, i.ContainerControl);
-                    }
-                    else
-                    {
-                        this.Panel.Children.Add(i.ContainerControl);
-                    }
-                }
-            }
+            _virtualizer?.ItemsChanged(Items, e);
         }
         }
 
 
-        private void AddContainers(IEnumerable<ItemContainer> items)
+        private Vector CoerceOffset(Vector value)
         {
         {
-            foreach (var i in items)
-            {
-                if (i.ContainerControl != null)
-                {
-                    if (i.Index < this.Panel.Children.Count)
-                    {
-                        // HACK: This will insert at the wrong place when there are null items,
-                        // but all of this will need to be rewritten when we implement 
-                        // virtualization so hope no-one notices until then :)
-                        this.Panel.Children.Insert(i.Index, i.ContainerControl);
-                    }
-                    else
-                    {
-                        this.Panel.Children.Add(i.ContainerControl);
-                    }
-                }
-            }
+            var scrollable = (ILogicalScrollable)this;
+            var maxX = Math.Max(scrollable.Extent.Width - scrollable.Viewport.Width, 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));
         }
         }
 
 
-        private void RemoveContainers(IEnumerable<ItemContainer> items)
+        private void VirtualizationModeChanged(AvaloniaPropertyChangedEventArgs e)
         {
         {
-            foreach (var i in items)
-            {
-                if (i.ContainerControl != null)
-                {
-                    this.Panel.Children.Remove(i.ContainerControl);
-                }
-            }
+            _virtualizer?.Dispose();
+            _virtualizer = ItemVirtualizer.Create(this);
+            ((ILogicalScrollable)this).InvalidateScroll?.Invoke();
         }
         }
     }
     }
 }
 }

+ 44 - 10
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@@ -101,8 +101,7 @@ namespace Avalonia.Controls.Presenters
             {
             {
                 if (_generator == null)
                 if (_generator == null)
                 {
                 {
-                    var i = TemplatedParent as ItemsControl;
-                    _generator = (i?.ItemContainerGenerator) ?? new ItemContainerGenerator(this);
+                    _generator = CreateItemContainerGenerator();
                 }
                 }
 
 
                 return _generator;
                 return _generator;
@@ -164,6 +163,31 @@ namespace Avalonia.Controls.Presenters
             }
             }
         }
         }
 
 
+        /// <inheritdoc/>
+        public virtual void ScrollIntoView(object item)
+        {
+        }
+
+        /// <summary>
+        /// Creates the <see cref="ItemContainerGenerator"/> for the control.
+        /// </summary>
+        /// <returns>
+        /// An <see cref="IItemContainerGenerator"/> or null.
+        /// </returns>
+        protected virtual IItemContainerGenerator CreateItemContainerGenerator()
+        {
+            var i = TemplatedParent as ItemsControl;
+            var result = i?.ItemContainerGenerator;
+
+            if (result == null)
+            {
+                result = new ItemContainerGenerator(this);
+                result.ItemTemplate = ItemTemplate;
+            }
+
+            return result;
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override Size MeasureOverride(Size availableSize)
         protected override Size MeasureOverride(Size availableSize)
         {
         {
@@ -178,11 +202,26 @@ namespace Avalonia.Controls.Presenters
             return finalSize;
             return finalSize;
         }
         }
 
 
+        /// <summary>
+        /// Called when the <see cref="Panel"/> is created.
+        /// </summary>
+        /// <param name="panel">The panel.</param>
+        protected virtual void PanelCreated(IPanel panel)
+        {
+        }
+
+        /// <summary>
+        /// Called when the items for the presenter change, either because <see cref="Items"/>
+        /// has been set, the items collection has been modified, or the panel has been created.
+        /// </summary>
+        /// <param name="e">A description of the change.</param>
+        protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e);
+
         /// <summary>
         /// <summary>
         /// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first
         /// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first
         /// time.
         /// time.
         /// </summary>
         /// </summary>
-        protected virtual void CreatePanel()
+        private void CreatePanel()
         {
         {
             Panel = ItemsPanel.Build();
             Panel = ItemsPanel.Build();
             Panel.SetValue(TemplatedParentProperty, TemplatedParent);
             Panel.SetValue(TemplatedParentProperty, TemplatedParent);
@@ -201,16 +240,11 @@ namespace Avalonia.Controls.Presenters
                 incc.CollectionChanged += ItemsCollectionChanged;
                 incc.CollectionChanged += ItemsCollectionChanged;
             }
             }
 
 
+            PanelCreated(Panel);
+
             ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
             ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
         }
         }
 
 
-        /// <summary>
-        /// Called when the items for the presenter change, either because <see cref="Items"/>
-        /// has been set, or the items collection has been modified.
-        /// </summary>
-        /// <param name="e">A description of the change.</param>
-        protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e);
-
         /// <summary>
         /// <summary>
         /// Called when the <see cref="Items"/> collection changes.
         /// Called when the <see cref="Items"/> collection changes.
         /// </summary>
         /// </summary>

+ 82 - 38
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Controls.Presenters
     /// <summary>
     /// <summary>
     /// Presents a scrolling view of content inside a <see cref="ScrollViewer"/>.
     /// Presents a scrolling view of content inside a <see cref="ScrollViewer"/>.
     /// </summary>
     /// </summary>
-    public class ScrollContentPresenter : ContentPresenter, IPresenter
+    public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="Extent"/> property.
         /// Defines the <see cref="Extent"/> property.
@@ -50,7 +50,7 @@ namespace Avalonia.Controls.Presenters
         private Size _extent;
         private Size _extent;
         private Size _measuredExtent;
         private Size _measuredExtent;
         private Vector _offset;
         private Vector _offset;
-        private IDisposable _scrollableSubscription;
+        private IDisposable _logicalScrollSubscription;
         private Size _viewport;
         private Size _viewport;
 
 
         /// <summary>
         /// <summary>
@@ -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);
         }
         }
 
 
@@ -69,7 +70,7 @@ namespace Avalonia.Controls.Presenters
         {
         {
             AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
             AddHandler(RequestBringIntoViewEvent, BringIntoViewRequested);
 
 
-            this.GetObservable(ChildProperty).Subscribe(ChildChanged);
+            this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -117,6 +118,14 @@ namespace Avalonia.Controls.Presenters
                 return false;
                 return false;
             }
             }
 
 
+            var scrollable = Child as ILogicalScrollable;
+            var control = target as IControl;
+
+            if (scrollable?.IsLogicalScrollEnabled == true && control != null)
+            {
+                return scrollable.BringIntoView(control, targetRect);
+            }
+
             var transform = target.TransformToVisual(Child);
             var transform = target.TransformToVisual(Child);
 
 
             if (transform == null)
             if (transform == null)
@@ -169,7 +178,7 @@ namespace Avalonia.Controls.Presenters
             {
             {
                 var measureSize = availableSize;
                 var measureSize = availableSize;
 
 
-                if (_scrollableSubscription == null)
+                if (_logicalScrollSubscription == null)
                 {
                 {
                     measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
                     measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
 
 
@@ -194,21 +203,25 @@ namespace Avalonia.Controls.Presenters
         protected override Size ArrangeOverride(Size finalSize)
         protected override Size ArrangeOverride(Size finalSize)
         {
         {
             var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
             var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
-            var offset = default(Vector);
+            var logicalScroll = _logicalScrollSubscription != null;
 
 
-            if (_scrollableSubscription == null)
+            if (!logicalScroll)
             {
             {
                 Viewport = finalSize;
                 Viewport = finalSize;
                 Extent = _measuredExtent;
                 Extent = _measuredExtent;
-                offset = Offset;
-            }
 
 
-            if (child != null)
-            {
-                var size = new Size(
+                if (child != null)
+                {
+                    var size = new Size(
                     Math.Max(finalSize.Width, child.DesiredSize.Width),
                     Math.Max(finalSize.Width, child.DesiredSize.Width),
                     Math.Max(finalSize.Height, child.DesiredSize.Height));
                     Math.Max(finalSize.Height, child.DesiredSize.Height));
-                child.Arrange(new Rect((Point)(-offset), size));
+                    child.Arrange(new Rect((Point)(-Offset), size));
+                    return finalSize;
+                }
+            }
+            else if (child != null)
+            {
+                child.Arrange(new Rect(finalSize));
                 return finalSize;
                 return finalSize;
             }
             }
 
 
@@ -218,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 IScrollable;
+                var scrollable = Child as ILogicalScrollable;
+                bool isLogical = scrollable?.IsLogicalScrollEnabled == true;
+
+                double x = Offset.X;
+                double y = Offset.Y;
 
 
-                if (scrollable != null)
-                {                    
-                    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);
-                    y = Math.Max(y, 0);
-                    y = Math.Min(y, Extent.Height - Viewport.Height);
-                    Offset = new Vector(Offset.X, y);
-                    e.Handled = true;
+                    double width = isLogical ? scrollable.ScrollSize.Width : 50;
+                    x += -e.Delta.X * width;
+                    x = Math.Max(x, 0);
+                    x = Math.Min(x, Extent.Width - Viewport.Width);
                 }
                 }
+
+                Offset = new Vector(x, y);
+                e.Handled = true;
             }
             }
         }
         }
 
 
@@ -246,28 +265,53 @@ namespace Avalonia.Controls.Presenters
             e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
             e.Handled = BringDescendentIntoView(e.TargetObject, e.TargetRect);
         }
         }
 
 
-        private void ChildChanged(IControl child)
+        private void ChildChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            UpdateScrollableSubscription((IControl)e.NewValue);
+
+            if (e.OldValue != null)
+            {
+                Offset = default(Vector);
+            }
+        }
+
+        private void UpdateScrollableSubscription(IControl child)
         {
         {
-            var scrollable = child as IScrollable;
+            var scrollable = child as ILogicalScrollable;
 
 
-            _scrollableSubscription?.Dispose();
-            _scrollableSubscription = null;
+            _logicalScrollSubscription?.Dispose();
+            _logicalScrollSubscription = null;
 
 
             if (scrollable != null)
             if (scrollable != null)
             {
             {
                 scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
                 scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
-                _scrollableSubscription = new CompositeDisposable(
-                    this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
-                    Disposable.Create(() => scrollable.InvalidateScroll = null));
-                UpdateFromScrollable(scrollable);
+
+                if (scrollable.IsLogicalScrollEnabled == true)
+                {
+                    _logicalScrollSubscription = new CompositeDisposable(
+                        this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
+                        Disposable.Create(() => scrollable.InvalidateScroll = null));
+                    UpdateFromScrollable(scrollable);
+                }
             }
             }
         }
         }
 
 
-        private void UpdateFromScrollable(IScrollable scrollable)
+        private void UpdateFromScrollable(ILogicalScrollable scrollable)
         {
         {
-            Viewport = scrollable.Viewport;
-            Extent = scrollable.Extent;
-            Offset = scrollable.Offset;
+            var logicalScroll = _logicalScrollSubscription != null;
+
+            if (logicalScroll != scrollable.IsLogicalScrollEnabled)
+            {
+                UpdateScrollableSubscription(Child);
+                Offset = default(Vector);
+                InvalidateMeasure();
+            }
+            else if (scrollable.IsLogicalScrollEnabled)
+            {
+                Viewport = scrollable.Viewport;
+                Extent = scrollable.Extent;
+                Offset = scrollable.Offset;
+            }
         }
         }
     }
     }
-}
+}

+ 1 - 1
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -60,7 +60,7 @@ namespace Avalonia.Controls.Primitives
                 if (info != null)
                 if (info != null)
                 {
                 {
                     child.RenderTransform = new MatrixTransform(info.Bounds.Transform);
                     child.RenderTransform = new MatrixTransform(info.Bounds.Transform);
-                    child.TransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
+                    child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
                     child.Arrange(info.Bounds.Bounds);
                     child.Arrange(info.Bounds.Bounds);
                 }
                 }
                 else
                 else

+ 69 - 0
src/Avalonia.Controls/Primitives/ILogicalScrollable.cs

@@ -0,0 +1,69 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using Avalonia.Input;
+
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// Interface implemented by controls that handle their own scrolling when placed inside a 
+    /// <see cref="ScrollViewer"/>.
+    /// </summary>
+    /// <remarks>
+    /// Controls that implement this interface, when placed inside a <see cref="ScrollViewer"/>
+    /// can override the physical scrolling behavior of the scroll viewer with logical scrolling.
+    /// Physical scrolling means that the scroll viewer is a simple viewport onto a larger canvas
+    /// whereas logical scrolling means that the scrolling is handled by the child control itself
+    /// and it can choose to do handle the scroll information as it sees fit.
+    /// </remarks>
+    public interface ILogicalScrollable : IScrollable
+    {
+        /// <summary>
+        /// Gets a value indicating whether logical scrolling is enabled on the control.
+        /// </summary>
+        bool IsLogicalScrollEnabled { get; }
+
+        /// <summary>
+        /// Gets or sets the scroll invalidation method.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// This method notifies the attached <see cref="ScrollViewer"/> of a change in 
+        /// the <see cref="IScrollable.Extent"/>, <see cref="IScrollable.Offset"/> or 
+        /// <see cref="IScrollable.Viewport"/> properties.
+        /// </para>
+        /// <para>
+        /// This property is set by the parent <see cref="ScrollViewer"/> when the 
+        /// <see cref="ILogicalScrollable"/> is placed inside it.
+        /// </para>
+        /// </remarks>
+        Action InvalidateScroll { get; set; }
+
+        /// <summary>
+        /// Gets the size to scroll by, in logical units.
+        /// </summary>
+        Size ScrollSize { get; }
+
+        /// <summary>
+        /// Gets the size to page by, in logical units.
+        /// </summary>
+        Size PageScrollSize { get; }
+
+        /// <summary>
+        /// Attempts to bring a portion of the target visual into view by scrolling the content.
+        /// </summary>
+        /// <param name="target">The target visual.</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>
+        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);
+    }
+}

+ 0 - 54
src/Avalonia.Controls/Primitives/IScrollable.cs

@@ -1,54 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-
-namespace Avalonia.Controls.Primitives
-{
-    /// <summary>
-    /// Interface implemented by controls that handle their own scrolling when placed inside a 
-    /// <see cref="ScrollViewer"/>.
-    /// </summary>
-    public interface IScrollable
-    {
-        /// <summary>
-        /// Gets or sets the scroll invalidation method.
-        /// </summary>
-        /// <remarks>
-        /// <para>
-        /// This method notifies the attached <see cref="ScrollViewer"/> of a change in 
-        /// the <see cref="Extent"/>, <see cref="Offset"/> or <see cref="Viewport"/> properties.
-        /// </para>
-        /// <para>
-        /// This property is set by the parent <see cref="ScrollViewer"/> when the 
-        /// <see cref="IScrollable"/> is placed inside it.
-        /// </para>
-        /// </remarks>
-        Action InvalidateScroll { get; set; }
-
-        /// <summary>
-        /// Gets the extent of the scrollable content, in logical units
-        /// </summary>
-        Size Extent { get; }
-
-        /// <summary>
-        /// Gets or sets the current scroll offset, in logical units.
-        /// </summary>
-        Vector Offset { get; set; }
-
-        /// <summary>
-        /// Gets the size of the viewport, in logical units.
-        /// </summary>
-        Size Viewport { get; }
-
-        /// <summary>
-        /// Gets the size to scroll by, in logical units.
-        /// </summary>
-        Size ScrollSize { get; }
-
-        /// <summary>
-        /// Gets the size to page by, in logical units.
-        /// </summary>
-        Size PageScrollSize { get; }
-    }
-}

+ 43 - 4
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");

+ 25 - 0
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>
@@ -394,6 +400,19 @@ namespace Avalonia.Controls.Primitives
             }
             }
         }
         }
 
 
+        protected override void OnContainersRecycled(ItemContainerEventArgs e)
+        {
+            foreach (var i in e.Containers)
+            {
+                if (i.ContainerControl != null && i.Item != null)
+                {
+                    MarkContainerSelected(
+                        i.ContainerControl,
+                        SelectedItems.Contains(i.Item));
+                }
+            }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override void OnDataContextChanging()
         protected override void OnDataContextChanging()
         {
         {
@@ -710,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 - 0
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 - 2
src/Avalonia.Controls/ScrollViewer.cs

@@ -12,7 +12,7 @@ namespace Avalonia.Controls
     /// <summary>
     /// <summary>
     /// A control scrolls its content if the content is bigger than the space available.
     /// A control scrolls its content if the content is bigger than the space available.
     /// </summary>
     /// </summary>
-    public class ScrollViewer : ContentControl
+    public class ScrollViewer : ContentControl, IScrollable
     {
     {
         /// <summary>
         /// <summary>
         /// Defines the <see cref="CanScrollHorizontally"/> property.
         /// Defines the <see cref="CanScrollHorizontally"/> property.
@@ -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)

+ 38 - 13
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)
@@ -181,6 +196,7 @@ namespace Avalonia.Controls
         /// <returns>The space taken.</returns>
         /// <returns>The space taken.</returns>
         protected override Size ArrangeOverride(Size finalSize)
         protected override Size ArrangeOverride(Size finalSize)
         {
         {
+            var orientation = Orientation;
             double arrangedWidth = finalSize.Width;
             double arrangedWidth = finalSize.Width;
             double arrangedHeight = finalSize.Height;
             double arrangedHeight = finalSize.Height;
             double gap = Gap;
             double gap = Gap;
@@ -199,11 +215,11 @@ namespace Avalonia.Controls
                 double childWidth = child.DesiredSize.Width;
                 double childWidth = child.DesiredSize.Width;
                 double childHeight = child.DesiredSize.Height;
                 double childHeight = child.DesiredSize.Height;
 
 
-                if (Orientation == Orientation.Vertical)
+                if (orientation == Orientation.Vertical)
                 {
                 {
                     double width = Math.Max(childWidth, arrangedWidth);
                     double width = Math.Max(childWidth, arrangedWidth);
                     Rect childFinal = new Rect(0, arrangedHeight, width, childHeight);
                     Rect childFinal = new Rect(0, arrangedHeight, width, childHeight);
-                    child.Arrange(childFinal);
+                    ArrangeChild(child, childFinal, finalSize, orientation);
                     arrangedWidth = Math.Max(arrangedWidth, childWidth);
                     arrangedWidth = Math.Max(arrangedWidth, childWidth);
                     arrangedHeight += childHeight + gap;
                     arrangedHeight += childHeight + gap;
                 }
                 }
@@ -211,13 +227,13 @@ namespace Avalonia.Controls
                 {
                 {
                     double height = Math.Max(childHeight, arrangedHeight);
                     double height = Math.Max(childHeight, arrangedHeight);
                     Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height);
                     Rect childFinal = new Rect(arrangedWidth, 0, childWidth, height);
-                    child.Arrange(childFinal);
+                    ArrangeChild(child, childFinal, finalSize, orientation);
                     arrangedWidth += childWidth + gap;
                     arrangedWidth += childWidth + gap;
                     arrangedHeight = Math.Max(arrangedHeight, childHeight);
                     arrangedHeight = Math.Max(arrangedHeight, childHeight);
                 }
                 }
             }
             }
 
 
-            if (Orientation == Orientation.Vertical)
+            if (orientation == Orientation.Vertical)
             {
             {
                 arrangedHeight = Math.Max(arrangedHeight - gap, finalSize.Height);
                 arrangedHeight = Math.Max(arrangedHeight - gap, finalSize.Height);
             }
             }
@@ -228,5 +244,14 @@ namespace Avalonia.Controls
 
 
             return new Size(arrangedWidth, arrangedHeight);
             return new Size(arrangedWidth, arrangedHeight);
         }
         }
+
+        internal virtual void ArrangeChild(
+            IControl child,
+            Rect rect,
+            Size panelSize,
+            Orientation orientation)
+        {
+            child.Arrange(rect);
+        }
     }
     }
 }
 }

+ 0 - 48
src/Avalonia.Controls/Templates/DataTemplateExtensions.cs

@@ -11,54 +11,6 @@ namespace Avalonia.Controls.Templates
     /// </summary>
     /// </summary>
     public static class DataTemplateExtensions
     public static class DataTemplateExtensions
     {
     {
-        /// <summary>
-        /// Materializes a piece of data based on a data template.
-        /// </summary>
-        /// <param name="control">The control materializing the data template.</param>
-        /// <param name="data">The data.</param>
-        /// <param name="primary">
-        /// An optional primary template that can will be tried before the
-        /// <see cref="IControl.DataTemplates"/> in the tree are searched.
-        /// </param>
-        /// <returns>The data materialized as a control.</returns>
-        public static IControl MaterializeDataTemplate(
-            this IControl control,
-            object data,
-            IDataTemplate primary = null)
-        {
-            if (data == null)
-            {
-                return null;
-            }
-            else
-            {
-                var asControl = data as IControl;
-
-                if (asControl != null)
-                {
-                    return asControl;
-                }
-                else
-                {
-                    IDataTemplate template = control.FindDataTemplate(data, primary);
-                    IControl result;
-
-                    if (template != null)
-                    {
-                        result = template.Build(data);
-                    }
-                    else
-                    {
-                        result = FuncDataTemplate.Default.Build(data);
-                    }
-
-                    NameScope.SetNameScope((Control)result, new NameScope());
-
-                    return result;
-                }
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Find a data template that matches a piece of data.
         /// Find a data template that matches a piece of data.
         /// </summary>
         /// </summary>

+ 34 - 5
src/Avalonia.Controls/Templates/FuncDataTemplate.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.Reactive.Linq;
 using System.Reflection;
 using System.Reflection;
 
 
 namespace Avalonia.Controls.Templates
 namespace Avalonia.Controls.Templates
@@ -12,10 +13,26 @@ namespace Avalonia.Controls.Templates
     public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
     public class FuncDataTemplate : FuncTemplate<object, IControl>, IDataTemplate
     {
     {
         /// <summary>
         /// <summary>
-        /// The default data template used in the case where not matching data template is found.
+        /// The default data template used in the case where no matching data template is found.
         /// </summary>
         /// </summary>
         public static readonly FuncDataTemplate Default =
         public static readonly FuncDataTemplate Default =
-           new FuncDataTemplate(typeof(object), o => (o != null) ? new TextBlock { Text = o.ToString() } : null);
+            new FuncDataTemplate<object>(
+                data =>
+                {
+                    if (data != null)
+                    {
+                        var result = new TextBlock();
+                        result.Bind(
+                            TextBlock.TextProperty,
+                            result.GetObservable(Control.DataContextProperty).Select(x => x?.ToString()));
+                        return result;
+                    }
+                    else
+                    {
+                        return null;
+                    }
+                },
+                true);
 
 
         /// <summary>
         /// <summary>
         /// The implementation of the <see cref="Match"/> method.
         /// The implementation of the <see cref="Match"/> method.
@@ -29,8 +46,12 @@ namespace Avalonia.Controls.Templates
         /// <param name="build">
         /// <param name="build">
         /// A function which when passed an object of <paramref name="type"/> returns a control.
         /// A function which when passed an object of <paramref name="type"/> returns a control.
         /// </param>
         /// </param>
-        public FuncDataTemplate(Type type, Func<object, IControl> build)
-            : this(o => IsInstance(o, type), build)
+        /// <param name="supportsRecycling">Whether the control can be recycled.</param>
+        public FuncDataTemplate(
+            Type type, 
+            Func<object, IControl> build,
+            bool supportsRecycling = false)
+            : this(o => IsInstance(o, type), build, supportsRecycling)
         {
         {
         }
         }
 
 
@@ -43,14 +64,22 @@ namespace Avalonia.Controls.Templates
         /// <param name="build">
         /// <param name="build">
         /// A function which returns a control for matching data.
         /// A function which returns a control for matching data.
         /// </param>
         /// </param>
-        public FuncDataTemplate(Func<object, bool> match, Func<object, IControl> build)
+        /// <param name="supportsRecycling">Whether the control can be recycled.</param>
+        public FuncDataTemplate(
+            Func<object, bool> match,
+            Func<object, IControl> build,
+            bool supportsRecycling = false)
             : base(build)
             : base(build)
         {
         {
             Contract.Requires<ArgumentNullException>(match != null);
             Contract.Requires<ArgumentNullException>(match != null);
 
 
             _match = match;
             _match = match;
+            SupportsRecycling = supportsRecycling;
         }
         }
 
 
+        /// <inheritdoc/>
+        public bool SupportsRecycling { get; }
+
         /// <summary>
         /// <summary>
         /// Checks to see if this data template matches the specified data.
         /// Checks to see if this data template matches the specified data.
         /// </summary>
         /// </summary>

+ 9 - 4
src/Avalonia.Controls/Templates/FuncDataTemplate`1.cs

@@ -17,8 +17,9 @@ namespace Avalonia.Controls.Templates
         /// <param name="build">
         /// <param name="build">
         /// A function which when passed an object of <typeparamref name="T"/> returns a control.
         /// A function which when passed an object of <typeparamref name="T"/> returns a control.
         /// </param>
         /// </param>
-        public FuncDataTemplate(Func<T, IControl> build)
-            : base(typeof(T), CastBuild(build))
+        /// <param name="supportsRecycling">Whether the control can be recycled.</param>
+        public FuncDataTemplate(Func<T, IControl> build, bool supportsRecycling = false)
+            : base(typeof(T), CastBuild(build), supportsRecycling)
         {
         {
         }
         }
 
 
@@ -31,8 +32,12 @@ namespace Avalonia.Controls.Templates
         /// <param name="build">
         /// <param name="build">
         /// A function which when passed an object of <typeparamref name="T"/> returns a control.
         /// A function which when passed an object of <typeparamref name="T"/> returns a control.
         /// </param>
         /// </param>
-        public FuncDataTemplate(Func<T, bool> match, Func<T, IControl> build)
-            : base(CastMatch(match), CastBuild(build))
+        /// <param name="supportsRecycling">Whether the control can be recycled.</param>
+        public FuncDataTemplate(
+            Func<T, bool> match,
+            Func<T, IControl> build,
+            bool supportsRecycling = false)
+            : base(CastMatch(match), CastBuild(build), supportsRecycling)
         {
         {
         }
         }
 
 

+ 3 - 0
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 - 0
src/Avalonia.Controls/Templates/IDataTemplate.cs

@@ -8,6 +8,12 @@ namespace Avalonia.Controls.Templates
     /// </summary>
     /// </summary>
     public interface IDataTemplate : ITemplate<object, IControl>
     public interface IDataTemplate : ITemplate<object, IControl>
     {
     {
+        /// <summary>
+        /// Gets a value indicating whether the data template supports recycling of the generated
+        /// control.
+        /// </summary>
+        bool SupportsRecycling { get; }
+
         /// <summary>
         /// <summary>
         /// Checks to see if this data template matches the specified data.
         /// Checks to see if this data template matches the specified data.
         /// </summary>
         /// </summary>

+ 4 - 2
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();
     }
     }
 }
 }

+ 12 - 7
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@@ -17,17 +17,22 @@ namespace Avalonia.Controls.Utils
 
 
         public static int Count(this IEnumerable items)
         public static int Count(this IEnumerable items)
         {
         {
-            Contract.Requires<ArgumentNullException>(items != null);
-
-            var collection = items as ICollection;
-
-            if (collection != null)
+            if (items != null)
             {
             {
-                return collection.Count;
+                var collection = items as ICollection;
+
+                if (collection != null)
+                {
+                    return collection.Count;
+                }
+                else
+                {
+                    return Enumerable.Count(items.Cast<object>());
+                }
             }
             }
             else
             else
             {
             {
-                return Enumerable.Count(items.Cast<object>());
+                return 0;
             }
             }
         }
         }
 
 

+ 229 - 0
src/Avalonia.Controls/VirtualizingStackPanel.cs

@@ -0,0 +1,229 @@
+// Copyright (c) The Avalonia Project. All rights reserved.
+// Licensed under the MIT license. See licence.md file in the project root for full license information.
+
+using System;
+using System.Collections.Specialized;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Layout;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+    public class VirtualizingStackPanel : StackPanel, IVirtualizingPanel
+    {
+        private Size _availableSpace;
+        private double _takenSpace;
+        private int _canBeRemoved;
+        private double _averageItemSize;
+        private int _averageCount;
+        private double _pixelOffset;
+
+        bool IVirtualizingPanel.IsFull
+        {
+            get
+            {
+                return Orientation == Orientation.Horizontal ?
+                    _takenSpace >= _availableSpace.Width :
+                    _takenSpace >= _availableSpace.Height;
+            }
+        }
+
+        IVirtualizingController IVirtualizingPanel.Controller { get; set; }
+        int IVirtualizingPanel.OverflowCount => _canBeRemoved;
+        Orientation IVirtualizingPanel.ScrollDirection => Orientation;
+        double IVirtualizingPanel.AverageItemSize => _averageItemSize;
+
+        double IVirtualizingPanel.PixelOverflow
+        {
+            get
+            {
+                var bounds = Orientation == Orientation.Horizontal ? 
+                    _availableSpace.Width : _availableSpace.Height;
+                return Math.Max(0, _takenSpace - bounds);
+            }
+        }
+
+        double IVirtualizingPanel.PixelOffset
+        {
+            get { return _pixelOffset; }
+
+            set
+            {
+                if (_pixelOffset != value)
+                {
+                    _pixelOffset = value;
+                    InvalidateArrange();
+                }
+            }
+        }
+
+        private IVirtualizingController Controller => ((IVirtualizingPanel)this).Controller;
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            if (availableSize != ((ILayoutable)this).PreviousMeasure)
+            {
+                // TODO: We need to put a reasonable limit on this, probably based on the max
+                // window size.
+                _availableSpace = availableSize;
+                Controller?.UpdateControls();
+            }
+
+            return base.MeasureOverride(availableSize);
+        }
+
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            _availableSpace = finalSize;
+            _canBeRemoved = 0;
+            _takenSpace = 0;
+            _averageItemSize = 0;
+            _averageCount = 0;
+            var result = base.ArrangeOverride(finalSize);
+            _takenSpace += _pixelOffset;
+            Controller?.UpdateControls();
+            return result;
+        }
+
+        protected override void ChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            base.ChildrenChanged(sender, e);
+
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    foreach (IControl control in e.NewItems)
+                    {
+                        UpdateAdd(control);
+                    }
+
+                    break;
+
+                case NotifyCollectionChangedAction.Remove:
+                    foreach (IControl control in e.OldItems)
+                    {
+                        UpdateRemove(control);
+                    }
+
+                    break;
+            }
+        }
+
+        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(
+            IControl child, 
+            Rect rect,
+            Size panelSize,
+            Orientation orientation)
+        {
+            if (orientation == Orientation.Vertical)
+            {
+                rect = new Rect(rect.X, rect.Y - _pixelOffset, rect.Width, rect.Height);
+                child.Arrange(rect);
+
+                if (rect.Y >= _availableSpace.Height)
+                {
+                    ++_canBeRemoved;
+                }
+
+                if (rect.Bottom >= _takenSpace)
+                {
+                    _takenSpace = rect.Bottom;
+                }
+
+                AddToAverageItemSize(rect.Height);
+            }
+            else
+            {
+                rect = new Rect(rect.X - _pixelOffset, rect.Y, rect.Width, rect.Height);
+                child.Arrange(rect);
+
+                if (rect.X >= _availableSpace.Width)
+                {
+                    ++_canBeRemoved;
+                }
+
+                if (rect.Right >= _takenSpace)
+                {
+                    _takenSpace = rect.Right;
+                }
+
+                AddToAverageItemSize(rect.Width);
+            }
+        }
+
+        private void UpdateAdd(IControl child)
+        {
+            var bounds = Bounds;
+            var gap = Gap;
+
+            child.Measure(_availableSpace);
+            ++_averageCount;
+
+            if (Orientation == Orientation.Vertical)
+            {
+                var height = child.DesiredSize.Height;
+                _takenSpace += height + gap;
+                AddToAverageItemSize(height);
+            }
+            else
+            {
+                var width = child.DesiredSize.Width;
+                _takenSpace += width + gap;
+                AddToAverageItemSize(width);
+            }
+        }
+
+        private void UpdateRemove(IControl child)
+        {
+            var bounds = Bounds;
+            var gap = Gap;
+
+            if (Orientation == Orientation.Vertical)
+            {
+                var height = child.DesiredSize.Height;
+                _takenSpace -= height + gap;
+                RemoveFromAverageItemSize(height);
+            }
+            else
+            {
+                var width = child.DesiredSize.Width;
+                _takenSpace -= width + gap;
+                RemoveFromAverageItemSize(width);
+            }
+
+            if (_canBeRemoved > 0)
+            {
+                --_canBeRemoved;
+            }
+        }
+
+        private void AddToAverageItemSize(double value)
+        {
+            ++_averageCount;
+            _averageItemSize += (value - _averageItemSize) / _averageCount;
+        }
+
+        private void RemoveFromAverageItemSize(double value)
+        {
+            _averageItemSize = ((_averageItemSize * _averageCount) - value) / (_averageCount - 1);
+            --_averageCount;
+        }
+    }
+}

+ 10 - 1
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;
         }
         }

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

+ 44 - 8
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>())
+        {
+            
+        }
     }
     }
 }
 }

+ 53 - 11
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)
-                .WithRenderingSubsystem(() => { })
+                .UseWindowingSubsystem("Avalonia.Win32")
+                .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();
         }
         }

+ 2 - 0
src/Avalonia.Diagnostics/ViewLocator.cs

@@ -9,6 +9,8 @@ namespace Avalonia.Diagnostics
 {
 {
     public class ViewLocator<TViewModel> : IDataTemplate
     public class ViewLocator<TViewModel> : IDataTemplate
     {
     {
+        public bool SupportsRecycling => false;
+
         public IControl Build(object data)
         public IControl Build(object data)
         {
         {
             var name = data.GetType().FullName.Replace("ViewModel", "View");
             var name = data.GetType().FullName.Replace("ViewModel", "View");

+ 2 - 2
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" />

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

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

+ 5 - 6
src/Avalonia.Input/InputExtensions.cs

@@ -24,14 +24,14 @@ namespace Avalonia.Input
         public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement element, Point p)
         public static IEnumerable<IInputElement> GetInputElementsAt(this IInputElement element, Point p)
         {
         {
             Contract.Requires<ArgumentNullException>(element != null);
             Contract.Requires<ArgumentNullException>(element != null);
-            var transformedBounds = BoundsTracker.GetTransformedBounds((Visual)element);
-            var geometry = transformedBounds.GetTransformedBoundsGeometry();
 
 
             if (element.IsVisible &&
             if (element.IsVisible &&
                 element.IsHitTestVisible &&
                 element.IsHitTestVisible &&
                 element.IsEnabledCore)
                 element.IsEnabledCore)
             {
             {
-                if (element.VisualChildren.Any())
+                bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)element).Contains(p);
+
+                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,7 @@ namespace Avalonia.Input
                     }
                     }
                 }
                 }
 
 
-                if (geometry.FillContains(p))
+                if (containsPoint)
                 {
                 {
                     yield return element;
                     yield return element;
                 }
                 }
@@ -71,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>
@@ -95,4 +94,4 @@ namespace Avalonia.Input
             }
             }
         }
         }
     }
     }
-}
+}

+ 23 - 11
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 ||
-                             direction == FocusNavigationDirection.Previous ?
+                var method = direction == NavigationDirection.Next ||
+                             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;
                 }
                 }
 
 

+ 13 - 14
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 != FocusNavigationDirection.Previous);
+                direction != NavigationDirection.Next &&
+                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 ||
-                   direction == FocusNavigationDirection.Last ||
-                   direction == FocusNavigationDirection.Right ||
-                   direction == FocusNavigationDirection.Down;
+            return direction == NavigationDirection.Next ||
+                   direction == NavigationDirection.Last ||
+                   direction == NavigationDirection.Right ||
+                   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);

+ 13 - 13
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 == FocusNavigationDirection.Previous);
+                direction == NavigationDirection.Next ||
+                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();
             }
             }

+ 12 - 2
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,
     }
     }
 }
 }

+ 26 - 9
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 Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
+        private readonly HashSet<ILayoutable> _toMeasure = new HashSet<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);
-            _toArrange.Enqueue(control);
+            _toMeasure.Add(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()

+ 2 - 1
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.

+ 13 - 3
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 &&
-                   p.Y >= _y && p.Y < _y + _height;
+            return p.X >= _x && p.X <= _x + _width &&
+                   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>

+ 2 - 2
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);
                 }
                 }
@@ -172,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);

+ 6 - 6
src/Avalonia.SceneGraph/Visual.cs

@@ -70,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 =
-            AvaloniaProperty.Register<Visual, RelativePoint>(nameof(TransformOrigin), defaultValue: RelativePoint.Center);
+        public static readonly StyledProperty<RelativePoint> RenderTransformOriginProperty =
+            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.
@@ -196,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); }
-            set { SetValue(TransformOriginProperty, value); }
+            get { return GetValue(RenderTransformOriginProperty); }
+            set { SetValue(RenderTransformOriginProperty, value); }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 2
src/Avalonia.SceneGraph/VisualTree/IVisual.cs

@@ -77,9 +77,9 @@ namespace Avalonia.VisualTree
         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.

+ 10 - 12
src/Avalonia.SceneGraph/VisualTree/TransformedBounds.cs

@@ -38,20 +38,18 @@ namespace Avalonia.VisualTree
         /// </summary>
         /// </summary>
         public Matrix Transform { get; }
         public Matrix Transform { get; }
 
 
-        public Geometry GetTransformedBoundsGeometry()
+        public bool Contains(Point point)
         {
         {
-            StreamGeometry geometry = new StreamGeometry();
-            using (var context = geometry.Open())
+            if (Transform.HasInverse)
             {
             {
-                context.SetFillRule(FillRule.EvenOdd);
-                context.BeginFigure(Bounds.TopLeft * Transform, true);
-                context.LineTo(Bounds.TopRight * Transform);
-                context.LineTo(Bounds.BottomRight * Transform);
-                context.LineTo(Bounds.BottomLeft * Transform);
-                context.LineTo(Bounds.TopLeft * Transform);
-                context.EndFigure(true);
+                Point trPoint = point * Transform.Invert();
+
+                return Bounds.Contains(trPoint);
+            }
+            else
+            {
+                return Bounds.Contains(point);
             }
             }
-            return geometry;
         }
         }
     }
     }
-}
+}

+ 10 - 1
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 - 0
src/Avalonia.Controls/INameScope.cs → src/Avalonia.Styling/Controls/INameScope.cs


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