Bläddra i källkod

Merge branch 'master' into readonly-struct

Steven Kirk 7 år sedan
förälder
incheckning
934c8d8de1
100 ändrade filer med 2965 tillägg och 860 borttagningar
  1. 3 0
      .ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
  2. 4 2
      .ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject
  3. 1 1
      .travis.yml
  4. 140 143
      Avalonia.sln
  5. 1 5
      appveyor.yml
  6. 15 6
      build.cake
  7. 1 1
      build.ps1
  8. 1 1
      build/JetBrains.dotMemoryUnit.props
  9. 5 5
      readme.md
  10. 1 0
      samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
  11. 6 0
      samples/ControlCatalog/ControlCatalog.csproj
  12. 2 1
      samples/ControlCatalog/MainView.xaml
  13. 46 0
      samples/ControlCatalog/Pages/DatePickerPage.xaml
  14. 36 0
      samples/ControlCatalog/Pages/DatePickerPage.xaml.cs
  15. 2 2
      samples/Previewer/MainWindow.xaml.cs
  16. 2 2
      samples/RemoteTest/Program.cs
  17. 10 2
      samples/VirtualizationTest/MainWindow.xaml
  18. 18 0
      samples/VirtualizationTest/ViewModels/MainWindowViewModel.cs
  19. 1 1
      src/Avalonia.Base/AvaloniaObject.cs
  20. 2 2
      src/Avalonia.Base/PriorityBindingEntry.cs
  21. 1 1
      src/Avalonia.Base/Threading/AvaloniaScheduler.cs
  22. 2 2
      src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
  23. 2 2
      src/Avalonia.Base/Threading/Dispatcher.cs
  24. 2 2
      src/Avalonia.Base/Threading/IDispatcher.cs
  25. 209 0
      src/Avalonia.Base/Utilities/Ref.cs
  26. 22 16
      src/Avalonia.Controls/Border.cs
  27. 1188 0
      src/Avalonia.Controls/Calendar/DatePicker.cs
  28. 1 1
      src/Avalonia.Controls/Carousel.cs
  29. 3 3
      src/Avalonia.Controls/ColumnDefinition.cs
  30. 135 0
      src/Avalonia.Controls/DataValidationErrors.cs
  31. 1 1
      src/Avalonia.Controls/DropDown.cs
  32. 76 51
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  33. 10 2
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  34. 27 0
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  35. 61 56
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  36. 1 1
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  37. 16 4
      src/Avalonia.Controls/Primitives/AdornerLayer.cs
  38. 1 1
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  39. 10 0
      src/Avalonia.Controls/Primitives/ILogicalScrollable.cs
  40. 1 0
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  41. 2 1
      src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs
  42. 1 1
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  43. 5 3
      src/Avalonia.Controls/Primitives/Thumb.cs
  44. 1 0
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  45. 1 1
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  46. 5 5
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  47. 3 3
      src/Avalonia.Controls/RowDefinition.cs
  48. 71 26
      src/Avalonia.Controls/ScrollViewer.cs
  49. 2 2
      src/Avalonia.Controls/Shapes/Line.cs
  50. 1 1
      src/Avalonia.Controls/Shapes/Path.cs
  51. 22 7
      src/Avalonia.Controls/Shapes/Shape.cs
  52. 71 98
      src/Avalonia.Controls/TextBox.cs
  53. 1 1
      src/Avalonia.Controls/TreeView.cs
  54. 0 2
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  55. 27 22
      src/Avalonia.Controls/Window.cs
  56. 1 1
      src/Avalonia.Controls/WindowIcon.cs
  57. 1 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  58. 46 12
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  59. 1 1
      src/Avalonia.Diagnostics/Views/ControlDetailsView.cs
  60. 8 8
      src/Avalonia.HtmlRenderer/HtmlControl.cs
  61. 9 9
      src/Avalonia.Input/InputElement.cs
  62. 3 3
      src/Avalonia.Input/KeyBinding.cs
  63. 10 7
      src/Avalonia.Input/Navigation/TabNavigation.cs
  64. 1 1
      src/Avalonia.Layout/LayoutManager.cs
  65. 10 2
      src/Avalonia.Remote.Protocol/DesignMessages.cs
  66. 1 1
      src/Avalonia.Remote.Protocol/TcpTransportBase.cs
  67. 38 0
      src/Avalonia.Themes.Default/DataValidationErrors.xaml
  68. 126 0
      src/Avalonia.Themes.Default/DatePicker.xaml
  69. 2 0
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  70. 6 1
      src/Avalonia.Themes.Default/ListBox.xaml
  71. 3 2
      src/Avalonia.Themes.Default/ScrollViewer.xaml
  72. 17 36
      src/Avalonia.Themes.Default/TextBox.xaml
  73. 1 1
      src/Avalonia.Themes.Default/TreeView.xaml
  74. 19 12
      src/Avalonia.Visuals/Media/EllipseGeometry.cs
  75. 109 18
      src/Avalonia.Visuals/Media/Geometry.cs
  76. 2 1
      src/Avalonia.Visuals/Media/GeometryDrawing.cs
  77. 1 1
      src/Avalonia.Visuals/Media/ImageBrush.cs
  78. 27 14
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  79. 4 2
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  80. 11 12
      src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs
  81. 3 2
      src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs
  82. 28 38
      src/Avalonia.Visuals/Media/LineGeometry.cs
  83. 1 1
      src/Avalonia.Visuals/Media/MatrixTransform.cs
  84. 21 47
      src/Avalonia.Visuals/Media/PathGeometry.cs
  85. 27 46
      src/Avalonia.Visuals/Media/PolylineGeometry.cs
  86. 11 11
      src/Avalonia.Visuals/Media/RectangleGeometry.cs
  87. 1 1
      src/Avalonia.Visuals/Media/RotateTransform.cs
  88. 16 4
      src/Avalonia.Visuals/Media/StreamGeometry.cs
  89. 2 2
      src/Avalonia.Visuals/Media/TranslateTransform.cs
  90. 1 1
      src/Avalonia.Visuals/Media/VisualBrush.cs
  91. 2 1
      src/Avalonia.Visuals/Platform/IBitmapImpl.cs
  92. 3 2
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  93. 6 10
      src/Avalonia.Visuals/Platform/IGeometryImpl.cs
  94. 24 0
      src/Avalonia.Visuals/Platform/ITransformedGeometryImpl.cs
  95. 36 26
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  96. 3 6
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  97. 5 4
      src/Avalonia.Visuals/Rendering/RenderLayer.cs
  98. 4 0
      src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs
  99. 31 22
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  100. 4 0
      src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

+ 3 - 0
.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject

@@ -1,5 +1,8 @@
 <ProjectConfiguration>
   <Settings>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\TestFiles\Direct2D1\**.*</Value>
+    </AdditionalFilesToIncludeForProject>
     <DefaultTestTimeout>3000</DefaultTestTimeout>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
   </Settings>

+ 4 - 2
.ncrunch/Avalonia.Skia.RenderTests.v3.ncrunchproject

@@ -1,7 +1,9 @@
 <ProjectConfiguration>
   <Settings>
-    <DefaultTestTimeout>1000</DefaultTestTimeout>
-    <IgnoreThisComponentCompletely>True</IgnoreThisComponentCompletely>
+    <AdditionalFilesToIncludeForProject>
+      <Value>..\TestFiles\Skia\**.*</Value>
+    </AdditionalFilesToIncludeForProject>
+    <DefaultTestTimeout>3000</DefaultTestTimeout>
     <PreviouslyBuiltSuccessfully>True</PreviouslyBuiltSuccessfully>
   </Settings>
 </ProjectConfiguration>

+ 1 - 1
.travis.yml

@@ -11,7 +11,7 @@ mono:
   - 5.2.0
 dotnet: 2.0.0
 script:
-  - ./build.sh --target "Travis" --platform "Mono" --configuration "Release"
+  - ./build.sh --target "Travis" --platform "NetCoreOnly" --configuration "Release"
 notifications:
   email: false
   webhooks:

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 140 - 143
Avalonia.sln


+ 1 - 5
appveyor.yml

@@ -1,14 +1,10 @@
 os: Visual Studio 2017
-platform:
-- Any CPU
 skip_branch_with_pr: true
 configuration:
 - Release
 environment:
   DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
   DOTNET_CLI_TELEMETRY_OPTOUT: 1
-  NUGET_API_KEY:
-    secure: Xv89dlP2MSBZKhl1nrWSxqcDgCXB0HRhOd4SWQ+jRJ7QoLxQel5mLTipXM++J3G5
   NUGET_API_URL: https://www.nuget.org/api/v2/package
   MYGET_API_KEY:
     secure: OtVfyN3ErqQrDTnWH2HDfJDlCiu/i4/X4wFmK3ZXXP7HmCiXYPSbTjMPwwdOxRaK
@@ -22,7 +18,7 @@ install:
 before_build:
 - git submodule update --init
 build_script:
-- ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration"
+- ps: .\build.ps1 -Target "AppVeyor" -Configuration "$env:configuration"
 
 test: off
 artifacts:

+ 15 - 6
build.cake

@@ -124,7 +124,16 @@ Task("Restore-NuGet-Packages")
 
 void DotNetCoreBuild()
 {
-    DotNetCoreBuild("samples\\ControlCatalog.NetCore");
+    var settings = new DotNetCoreBuildSettings 
+    {
+        Configuration = parameters.Configuration,
+        MSBuildSettings = new DotNetCoreMSBuildSettings(),
+    };
+
+    settings.MSBuildSettings.SetConfiguration(parameters.Configuration);
+    settings.MSBuildSettings.WithProperty("Platform", "\"" + parameters.Platform + "\"");
+
+    DotNetCoreBuild(parameters.MSBuildSolution, settings);
 }
 
 Task("Build")
@@ -156,8 +165,6 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
         project = System.IO.Path.Combine(project, System.IO.Path.GetFileName(project)+".csproj");
     Information("Running tests from " + project);
     var frameworks = new List<string>(){"netcoreapp2.0"};
-    if(parameters.IsRunningOnWindows)
-        frameworks.Add("net47");
     foreach(var fw in frameworks)
     {
         if(!fw.StartsWith("netcoreapp") && coreOnly)
@@ -167,7 +174,9 @@ void RunCoreTest(string project, Parameters parameters, bool coreOnly = false)
         DotNetCoreTest(project,
             new DotNetCoreTestSettings {
                 Configuration = parameters.Configuration,
-                Framework = fw
+                Framework = fw,
+                NoBuild = true,
+                NoRestore = true
             });
     }
 }
@@ -197,8 +206,8 @@ Task("Run-Render-Tests")
     .IsDependentOn("Build")
     .WithCriteria(() => !parameters.SkipTests && parameters.IsRunningOnWindows)
     .Does(() => {
-        RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
-        RunCoreTest("./tests/Avalonia.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
+        RunCoreTest("./tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj", parameters, true);
+        RunCoreTest("./tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj", parameters, true);
     });
 
 Task("Run-Designer-Unit-Tests")

+ 1 - 1
build.ps1

@@ -46,7 +46,7 @@ http://cakebuild.net
 Param(
     [string]$Script = "build.cake",
     [string]$Target = "Default",
-    [ValidateSet("Any CPU", "x86", "x64", "Mono", "iPhone")]
+    [ValidateSet("Any CPU", "x86", "x64", "NetCoreOnly", "iPhone")]
     [string]$Platform = "Any CPU",
     [ValidateSet("Release", "Debug")]
     [string]$Configuration = "Release",

+ 1 - 1
build/JetBrains.dotMemoryUnit.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="JetBrains.dotMemoryUnit" Version="2.1.20150828.125449" />
+    <PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
   </ItemGroup>
 </Project>

+ 5 - 5
readme.md

@@ -10,7 +10,7 @@
 
 Avalonia is a WPF-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of OSs: Windows (.NET Framework, .NET Core), Linux (GTK), MacOS, Android and iOS.
 
-<b>Avalonia is now in alpha.</b> This means that framework is now at a stage where you can have a play and hopefully create simple applications. There's still a lot missing, and you *will* find bugs, and the API *will* change, but this represents the first time where we've made it somewhat easy to have a play and experiment with the framework.
+**Avalonia is currently in beta** which means that the framework is generally usable for writing applications, but there may be some bugs and breaking changes as we continue development.
 
 | Control catalog | Desktop platforms | Mobile platforms |
 |---|---|---|
@@ -35,16 +35,16 @@ https://ci.appveyor.com/project/AvaloniaUI/Avalonia/branch/master/artifacts
 
 ## Documentation
 
-As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/tutorial/gettingstarted) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
+As mentioned above, Avalonia is still in alpha and as such there's not much documentation yet. You can take a look at the [getting started page](http://avaloniaui.net/guides/quickstart) for an overview of how to get started but probably the best thing to do for now is to already know a little bit about WPF/Silverlight/UWP/XAML and ask questions in our [Gitter room](https://gitter.im/AvaloniaUI/Avalonia).
 
-There's also a high-level [architecture document](http://avaloniaui.net/spec/architecture) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
+There's also a high-level [architecture document](http://avaloniaui.net/architecture/project-structure) that is currently a little bit out of date, and I've also started writing blog posts on Avalonia at http://grokys.github.io/.
 
 Contributions are always welcome!
 
 ## Building and Using
 
-See the [build instructions here](http://avaloniaui.net/guidelines/build).
+See the [build instructions here](http://avaloniaui.net/contributing/build).
 
 ## Contributing
 
-Please read the [contribution guidelines](http://avaloniaui.net/guidelines/contributing) before submitting a pull request.
+Please read the [contribution guidelines](http://avaloniaui.net/contributing/contributing) before submitting a pull request.

+ 1 - 0
samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj

@@ -6,6 +6,7 @@
   </PropertyGroup>
 
   <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
     <ProjectReference Include="..\..\src\Avalonia.DotNetCoreRuntime\Avalonia.DotNetCoreRuntime.csproj" />
     <ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />
     <ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" />

+ 6 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -60,6 +60,9 @@
     <EmbeddedResource Include="Pages\DropDownPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
+    <EmbeddedResource Include="Pages\DatePickerPage.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
     <EmbeddedResource Include="Pages\ExpanderPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
@@ -128,6 +131,9 @@
     <Compile Include="Pages\DropDownPage.xaml.cs">
       <DependentUpon>DropDownPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Pages\DatePickerPage.xaml.cs">
+      <DependentUpon>DatePickerPage.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Pages\ExpanderPage.xaml.cs">
       <DependentUpon>ExpanderPage.xaml</DependentUpon>
     </Compile>

+ 2 - 1
samples/ControlCatalog/MainView.xaml

@@ -7,11 +7,12 @@
     </TabControl.Transition>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></TabItem>
-    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem>
+    <TabItem Header="Calendar"><pages:CalendarPage/></TabItem> 
     <TabItem Header="Canvas"><pages:CanvasPage/></TabItem>
     <TabItem Header="Carousel"><pages:CarouselPage/></TabItem>
     <TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
     <TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
+    <TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
     <TabItem Header="DropDown"><pages:DropDownPage/></TabItem>
     <TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
     <TabItem Header="Image"><pages:ImagePage/></TabItem>

+ 46 - 0
samples/ControlCatalog/Pages/DatePickerPage.xaml

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

+ 36 - 0
samples/ControlCatalog/Pages/DatePickerPage.xaml.cs

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

+ 2 - 2
samples/Previewer/MainWindow.xaml.cs

@@ -39,7 +39,7 @@ namespace Previewer
             }));
             new BsonTcpTransport().Listen(IPAddress.Loopback, 25000, t =>
             {
-                Dispatcher.UIThread.InvokeAsync(() =>
+                Dispatcher.UIThread.Post(() =>
                 {
                     if (_connection != null)
                     {
@@ -61,7 +61,7 @@ namespace Previewer
 
         private void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
         {
-            Dispatcher.UIThread.InvokeAsync(() =>
+            Dispatcher.UIThread.Post(() =>
             {
                 if (transport != _connection)
                     return;

+ 2 - 2
samples/RemoteTest/Program.cs

@@ -25,7 +25,7 @@ namespace RemoteTest
             var transport = new BsonTcpTransport();
             transport.Listen(IPAddress.Loopback, port, sc =>
             {
-                Dispatcher.UIThread.InvokeAsync(() =>
+                Dispatcher.UIThread.Post(() =>
                 {
                     new RemoteServer(sc).Content = new MainView();
                 });
@@ -34,7 +34,7 @@ namespace RemoteTest
             var cts = new CancellationTokenSource();
             transport.Connect(IPAddress.Loopback, port).ContinueWith(t =>
             {
-                Dispatcher.UIThread.InvokeAsync(() =>
+                Dispatcher.UIThread.Post(() =>
                 {
                     var window = new Window()
                     {

+ 10 - 2
samples/VirtualizationTest/MainWindow.xaml

@@ -21,6 +21,12 @@
             <TextBox Watermark="Viewport"
                      UseFloatingWatermark="True"
                      Text="{Binding #listBox.Scroll.Viewport, Mode=OneWay}"/>
+            <TextBlock>Horiz. ScrollBar</TextBlock>
+            <DropDown Items="{Binding ScrollBarVisibilities}"
+                      SelectedItem="{Binding HorizontalScrollBarVisibility}"/>
+            <TextBlock>Vert. ScrollBar</TextBlock>
+            <DropDown Items="{Binding ScrollBarVisibilities}"
+                      SelectedItem="{Binding VerticalScrollBarVisibility}"/>
             <TextBox Watermark="Item to Create"
                      UseFloatingWatermark="True"
                      Text="{Binding NewItemString}"/>
@@ -35,7 +41,9 @@
                  Items="{Binding Items}" 
                  SelectedItems="{Binding SelectedItems}"
                  SelectionMode="Multiple"
-                 VirtualizationMode="{Binding VirtualizationMode}">
+                 VirtualizationMode="{Binding VirtualizationMode}"
+                 ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"
+                 ScrollViewer.VerticalScrollBarVisibility="{Binding VerticalScrollBarVisibility, Mode=TwoWay}">
             <ListBox.ItemsPanel>
                 <ItemsPanelTemplate>
                     <VirtualizingStackPanel Orientation="{Binding Orientation}"/>
@@ -43,7 +51,7 @@
             </ListBox.ItemsPanel>
             <ListBox.ItemTemplate>
                 <DataTemplate>
-                    <TextBlock Text="{Binding Header}"/>
+                    <TextBlock Text="{Binding Header}" TextWrapping="Wrap"/>
                 </DataTemplate>
             </ListBox.ItemTemplate>
         </ListBox>

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

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Collections;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
 using ReactiveUI;
 
 namespace VirtualizationTest.ViewModels
@@ -17,6 +18,8 @@ namespace VirtualizationTest.ViewModels
         private int _newItemIndex;
         private IReactiveList<ItemViewModel> _items;
         private string _prefix = "Item";
+        private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
+        private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
         private Orientation _orientation = Orientation.Vertical;
         private ItemVirtualizationMode _virtualizationMode = ItemVirtualizationMode.Simple;
 
@@ -64,6 +67,21 @@ namespace VirtualizationTest.ViewModels
         public IEnumerable<Orientation> Orientations =>
             Enum.GetValues(typeof(Orientation)).Cast<Orientation>();
 
+        public ScrollBarVisibility HorizontalScrollBarVisibility
+        {
+            get { return _horizontalScrollBarVisibility; }
+            set { this.RaiseAndSetIfChanged(ref _horizontalScrollBarVisibility, value); }
+        }
+
+        public ScrollBarVisibility VerticalScrollBarVisibility
+        {
+            get { return _verticalScrollBarVisibility; }
+            set { this.RaiseAndSetIfChanged(ref _verticalScrollBarVisibility, value); }
+        }
+
+        public IEnumerable<ScrollBarVisibility> ScrollBarVisibilities =>
+            Enum.GetValues(typeof(ScrollBarVisibility)).Cast<ScrollBarVisibility>();
+
         public ItemVirtualizationMode VirtualizationMode
         {
             get { return _virtualizationMode; }

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

@@ -774,7 +774,7 @@ namespace Avalonia
             }
             else
             {
-                Dispatcher.UIThread.InvokeAsync(Set);
+                Dispatcher.UIThread.Post(Set);
             }
         }
 

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

@@ -123,7 +123,7 @@ namespace Avalonia
             }
             else
             {
-                Dispatcher.UIThread.InvokeAsync(Signal);
+                Dispatcher.UIThread.Post(Signal);
             }
         }
 
@@ -135,7 +135,7 @@ namespace Avalonia
             }
             else
             {
-                Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this));
+                Dispatcher.UIThread.Post(() => _owner.Completed(this));
             }
         }
     }

+ 1 - 1
src/Avalonia.Base/Threading/AvaloniaScheduler.cs

@@ -33,7 +33,7 @@ namespace Avalonia.Threading
                 if (!Dispatcher.UIThread.CheckAccess())
                 {
                     var cancellation = new CancellationDisposable();
-                    Dispatcher.UIThread.InvokeAsync(() =>
+                    Dispatcher.UIThread.Post(() =>
                     {
                         if (!cancellation.Token.IsCancellationRequested)
                         {

+ 2 - 2
src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs

@@ -36,7 +36,7 @@ namespace Avalonia.Threading
         /// <inheritdoc/>
         public override void Post(SendOrPostCallback d, object state)
         {
-           Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send);
+           Dispatcher.UIThread.Post(() => d(state), DispatcherPriority.Send);
         }
 
         /// <inheritdoc/>
@@ -45,7 +45,7 @@ namespace Avalonia.Threading
             if (Dispatcher.UIThread.CheckAccess())
                 d(state);
             else
-                Dispatcher.UIThread.InvokeTaskAsync(() => d(state), DispatcherPriority.Send).Wait();
+                Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
         }
     }
 }

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

@@ -79,13 +79,13 @@ namespace Avalonia.Threading
         public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
 
         /// <inheritdoc/>
-        public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+        public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
             return _jobRunner?.InvokeAsync(action, priority);
         }
 
         /// <inheritdoc/>
-        public void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
+        public void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {
             _jobRunner?.Post(action, priority);
         }

+ 2 - 2
src/Avalonia.Base/Threading/IDispatcher.cs

@@ -25,7 +25,7 @@ namespace Avalonia.Threading
         /// <param name="action">The method.</param>
         /// <param name="priority">The priority with which to invoke the method.</param>
         /// <returns>A task that can be used to track the method's execution.</returns>
-        void InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
+        void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
 
         /// <summary>
         /// Post action that will be invoked on main thread
@@ -34,6 +34,6 @@ namespace Avalonia.Threading
         /// <param name="priority">The priority with which to invoke the method.</param>
         // TODO: The naming of this method is confusing: the Async suffix usually means return a task.
         // Remove this and rename InvokeTaskAsync as InvokeAsync. See #816.
-        Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
+        Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
     }
 }

+ 209 - 0
src/Avalonia.Base/Utilities/Ref.cs

@@ -0,0 +1,209 @@
+using System;
+using System.Runtime.ConstrainedExecution;
+using System.Threading;
+
+namespace Avalonia.Utilities
+{
+    /// <summary>
+    /// A ref-counted wrapper for a disposable object.
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public interface IRef<out T> : IDisposable where T : class
+    {
+        /// <summary>
+        /// The item that is being ref-counted.
+        /// </summary>
+        T Item { get; }
+
+        /// <summary>
+        /// Create another reference to this object and increment the refcount.
+        /// </summary>
+        /// <returns>A new reference to this object.</returns>
+        IRef<T> Clone();
+
+        /// <summary>
+        /// Create another reference to the same object, but cast the object to a different type.
+        /// </summary>
+        /// <typeparam name="TResult">The type of the new reference.</typeparam>
+        /// <returns>A reference to the value as the new type but sharing the refcount.</returns>
+        IRef<TResult> CloneAs<TResult>() where TResult : class;
+
+
+        /// <summary>
+        /// The current refcount of the object tracked in this reference. For debugging/unit test use only.
+        /// </summary>
+        int RefCount { get; }
+    }
+
+    
+
+    public static class RefCountable
+    {
+        /// <summary>
+        /// Create a reference counted object wrapping the given item.
+        /// </summary>
+        /// <typeparam name="T">The type of item.</typeparam>
+        /// <param name="item">The item to refcount.</param>
+        /// <returns>The refcounted reference to the item.</returns>
+        public static IRef<T> Create<T>(T item) where T : class, IDisposable
+        {
+            return new Ref<T>(item, new RefCounter(item));
+        }
+        
+        /// <summary>
+        /// Create an non-owning non-clonable reference to an item.
+        /// </summary>
+        /// <typeparam name="T">The type of item.</typeparam>
+        /// <param name="item">The item.</param>
+        /// <returns>A temporary reference that cannot be cloned that doesn't own the element.</returns>
+        public static IRef<T> CreateUnownedNotClonable<T>(T item) where T : class
+            => new TempRef<T>(item);
+
+        class TempRef<T> : IRef<T> where T : class
+        {
+            public void Dispose()
+            {
+                
+            }
+
+            public TempRef(T item)
+            {
+                Item = item;
+            }
+            
+            public T Item { get; }
+            public IRef<T> Clone() => throw new NotSupportedException();
+
+            public IRef<TResult> CloneAs<TResult>() where TResult : class
+                => throw new NotSupportedException();
+
+            public int RefCount => 1;
+        }
+        
+        class RefCounter
+        {
+            private IDisposable _item;
+            private volatile int _refs;
+
+            public RefCounter(IDisposable item)
+            {
+                _item = item;
+                _refs = 1;
+            }
+
+            public void AddRef()
+            {
+                var old = _refs;
+                while (true)
+                {
+                    if (old == 0)
+                    {
+                        throw new ObjectDisposedException("Cannot add a reference to a nonreferenced item");
+                    }
+                    var current = Interlocked.CompareExchange(ref _refs, old + 1, old);
+                    if (current == old)
+                    {
+                        break;
+                    }
+                    old = current;
+                }
+            }
+
+            public void Release()
+            {
+                var old = _refs;
+                while (true)
+                {
+                    var current = Interlocked.CompareExchange(ref _refs, old - 1, old);
+
+                    if (current == old)
+                    {
+                        if (old == 1)
+                        {
+                            _item.Dispose();
+                            _item = null;
+                        }
+                        break;
+                    }
+                    old = current;
+                }
+            }
+
+            internal int RefCount => _refs;
+        }
+
+        class Ref<T> : CriticalFinalizerObject, IRef<T> where T : class
+        {
+            private T _item;
+            private RefCounter _counter;
+            private object _lock = new object();
+
+            public Ref(T item, RefCounter counter)
+            {
+                _item = item;
+                _counter = counter;
+            }
+
+            public void Dispose()
+            {
+                lock (_lock)
+                {
+                    if (_item != null)
+                    {
+                        _counter.Release();
+                        _item = null;
+                    }
+                    GC.SuppressFinalize(this);
+                }
+            }
+
+            ~Ref()
+            {
+                _counter?.Release();
+            }
+
+            public T Item
+            {
+                get
+                {
+                    lock (_lock)
+                    {
+                        return _item;
+                    }
+                }
+            }
+
+            public IRef<T> Clone()
+            {
+                lock (_lock)
+                {
+                    if (_item != null)
+                    {
+                        var newRef = new Ref<T>(_item, _counter);
+                        _counter.AddRef();
+                        return newRef;
+                    }
+                    throw new ObjectDisposedException("Ref<" + typeof(T) + ">");
+                }
+            }
+
+            public IRef<TResult> CloneAs<TResult>() where TResult : class
+            {
+                lock (_lock)
+                {
+                    if (_item != null)
+                    {
+                        var castRef = new Ref<TResult>((TResult)(object)_item, _counter);
+                        Interlocked.MemoryBarrier();
+                        _counter.AddRef();
+                        return castRef;
+                    }
+                    throw new ObjectDisposedException("Ref<" + typeof(T) + ">");
+                }
+            }
+
+            public int RefCount => _counter.RefCount;
+        }
+    }
+
+}

+ 22 - 16
src/Avalonia.Controls/Border.cs

@@ -108,18 +108,7 @@ namespace Avalonia.Controls
         /// <returns>The desired size of the control.</returns>
         protected override Size MeasureOverride(Size availableSize)
         {
-            var child = Child;
-            var padding = Padding + new Thickness(BorderThickness);
-
-            if (child != null)
-            {
-                child.Measure(availableSize.Deflate(padding));
-                return child.DesiredSize.Inflate(padding);
-            }
-            else
-            {
-                return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
-            }
+            return MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
         }
 
         /// <summary>
@@ -129,15 +118,32 @@ namespace Avalonia.Controls
         /// <returns>The space taken.</returns>
         protected override Size ArrangeOverride(Size finalSize)
         {
-            var child = Child;
-
-            if (child != null)
+            if (Child != null)
             {
                 var padding = Padding + new Thickness(BorderThickness);
-                child.Arrange(new Rect(finalSize).Deflate(padding));
+                Child.Arrange(new Rect(finalSize).Deflate(padding));
             }
 
             return finalSize;
         }
+
+        internal static Size MeasureOverrideImpl(
+            Size availableSize,
+            IControl child,
+            Thickness padding,
+            double borderThickness)
+        {
+            padding += new Thickness(borderThickness);
+
+            if (child != null)
+            {
+                child.Measure(availableSize.Deflate(padding));
+                return child.DesiredSize.Inflate(padding);
+            }
+            else
+            {
+                return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
+            }
+        }
     }
 }

+ 1188 - 0
src/Avalonia.Controls/Calendar/DatePicker.cs

@@ -0,0 +1,1188 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using Avalonia.Controls.Primitives;
+using Avalonia.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using System;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Provides data for the
+    /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+    /// event.
+    /// </summary>
+    public class DatePickerDateValidationErrorEventArgs : EventArgs
+    {
+        private bool _throwException;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
+        /// class.
+        /// </summary>
+        /// <param name="exception">
+        /// The initial exception from the
+        /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+        /// event.
+        /// </param>
+        /// <param name="text">
+        /// The text that caused the
+        /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+        /// event.
+        /// </param>
+        public DatePickerDateValidationErrorEventArgs(Exception exception, string text)
+        {
+            this.Text = text;
+            this.Exception = exception;
+        }
+
+        /// <summary>
+        /// Gets the initial exception associated with the
+        /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+        /// event.
+        /// </summary>
+        /// <value>
+        /// The exception associated with the validation failure.
+        /// </value>
+        public Exception Exception { get; private set; }
+
+        /// <summary>
+        /// Gets the text that caused the
+        /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+        /// event.
+        /// </summary>
+        /// <value>
+        /// The text that caused the validation failure.
+        /// </value>
+        public string Text { get; private set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether
+        /// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
+        /// should be thrown.
+        /// </summary>
+        /// <value>
+        /// True if the exception should be thrown; otherwise, false.
+        /// </value>
+        /// <exception cref="T:System.ArgumentException">
+        /// If set to true and
+        /// <see cref="P:Avalonia.Controls.DatePickerDateValidationErrorEventArgs.Exception" />
+        /// is null.
+        /// </exception>
+        public bool ThrowException
+        {
+            get { return this._throwException; }
+            set
+            {
+                if (value && this.Exception == null)
+                {
+                    throw new ArgumentException("Cannot Throw Null Exception");
+                }
+                this._throwException = value;
+            }
+        }
+    }
+
+    /// <summary>
+    /// Specifies date formats for a
+    /// <see cref="T:Avalonia.Controls.DatePicker" />.
+    /// </summary>
+    public enum DatePickerFormat
+    {
+        /// <summary>
+        /// Specifies that the date should be displayed using unabbreviated days
+        /// of the week and month names.
+        /// </summary>
+        Long = 0,
+
+        /// <summary>
+        /// Specifies that the date should be displayed using abbreviated days
+        /// of the week and month names.
+        /// </summary>
+        Short = 1,
+
+        /// <summary>
+        /// Specifies that the date should be displayed using a custom format string.
+        /// </summary>
+        Custom = 2
+    }
+
+    public class DatePicker : TemplatedControl
+    {
+        private const string ElementTextBox = "PART_TextBox";
+        private const string ElementButton = "PART_Button";
+        private const string ElementPopup = "PART_Popup";
+        private const string ElementCalendar = "PART_Calendar";
+
+        private Calendar _calendar;
+        private string _defaultText;
+        private Button _dropDownButton;
+        //private Canvas _outsideCanvas;
+        //private Canvas _outsidePopupCanvas;
+        private Popup _popUp;
+        private TextBox _textBox;
+        private IDisposable _textBoxTextChangedSubscription;
+        private IDisposable _buttonPointerPressedSubscription;
+
+        private DateTime? _onOpenSelectedDate;
+        private bool _settingSelectedDate;
+
+        private DateTime _displayDate;
+        private DateTime? _displayDateStart;
+        private DateTime? _displayDateEnd;
+        private bool _isDropDownOpen;
+        private DateTime? _selectedDate;
+        private string _text;
+        private bool _suspendTextChangeHandler = false;
+        private bool _isPopupClosing = false;
+        private bool _ignoreButtonClick = false;
+
+        /// <summary>
+        /// Gets a collection of dates that are marked as not selectable.
+        /// </summary>
+        /// <value>
+        /// A collection of dates that cannot be selected. The default value is
+        /// an empty collection.
+        /// </value>
+        public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
+
+        public static readonly DirectProperty<DatePicker, DateTime> DisplayDateProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTime>(
+                nameof(DisplayDate),
+                o => o.DisplayDate,
+                (o, v) => o.DisplayDate = v);
+        public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateStartProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
+                nameof(DisplayDateStart),
+                o => o.DisplayDateStart,
+                (o, v) => o.DisplayDateStart = v);
+        public static readonly DirectProperty<DatePicker, DateTime?> DisplayDateEndProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
+                nameof(DisplayDateEnd),
+                o => o.DisplayDateEnd,
+                (o, v) => o.DisplayDateEnd = v);
+        public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
+            AvaloniaProperty.Register<DatePicker, DayOfWeek>(nameof(FirstDayOfWeek));
+
+        public static readonly DirectProperty<DatePicker, bool> IsDropDownOpenProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, bool>(
+                nameof(IsDropDownOpen),
+                o => o.IsDropDownOpen,
+                (o, v) => o.IsDropDownOpen = v);
+
+        public static readonly StyledProperty<bool> IsTodayHighlightedProperty =
+            AvaloniaProperty.Register<DatePicker, bool>(nameof(IsTodayHighlighted));
+        public static readonly DirectProperty<DatePicker, DateTime?> SelectedDateProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTime?>(
+                nameof(SelectedDate),
+                o => o.SelectedDate,
+                (o, v) => o.SelectedDate = v);
+
+        public static readonly StyledProperty<DatePickerFormat> SelectedDateFormatProperty =
+            AvaloniaProperty.Register<DatePicker, DatePickerFormat>(
+                nameof(SelectedDateFormat),
+                defaultValue: DatePickerFormat.Short,
+                validate: ValidateSelectedDateFormat);
+
+        public static readonly StyledProperty<string> CustomDateFormatStringProperty =
+            AvaloniaProperty.Register<DatePicker, string>(
+                nameof(CustomDateFormatString),
+                defaultValue: "d",
+                validate: ValidateDateFormatString);
+
+        public static readonly DirectProperty<DatePicker, string> TextProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, string>(
+                nameof(Text),
+                o => o.Text,
+                (o, v) => o.Text = v);
+        public static readonly StyledProperty<string> WatermarkProperty =
+            TextBox.WatermarkProperty.AddOwner<DatePicker>();
+        public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
+            TextBox.UseFloatingWatermarkProperty.AddOwner<DatePicker>();
+
+
+        /// <summary>
+        /// Gets or sets the date to display.
+        /// </summary>
+        /// <value>
+        /// The date to display. The default 
+        /// <see cref="P:System.DateTime.Today" />.
+        /// </value>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        /// The specified date is not in the range defined by
+        /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
+        /// and
+        /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />.
+        /// </exception>
+        public DateTime DisplayDate
+        {
+            get { return _displayDate; }
+            set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
+        }
+        
+        /// <summary>
+        /// Gets or sets the first date to be displayed.
+        /// </summary>
+        /// <value>The first date to display.</value>
+        public DateTime? DisplayDateStart
+        {
+            get { return _displayDateStart; }
+            set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the last date to be displayed.
+        /// </summary>
+        /// <value>The last date to display.</value>
+        public DateTime? DisplayDateEnd
+        {
+            get { return _displayDateEnd; }
+            set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the day that is considered the beginning of the week.
+        /// </summary>
+        /// <value>
+        /// A <see cref="T:System.DayOfWeek" /> representing the beginning of
+        /// the week. The default is <see cref="F:System.DayOfWeek.Sunday" />.
+        /// </value>
+        public DayOfWeek FirstDayOfWeek
+        {
+            get { return GetValue(FirstDayOfWeekProperty); }
+            set { SetValue(FirstDayOfWeekProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the drop-down
+        /// <see cref="T:Avalonia.Controls.Calendar" /> is open or closed.
+        /// </summary>
+        /// <value>
+        /// True if the <see cref="T:Avalonia.Controls.Calendar" /> is
+        /// open; otherwise, false. The default is false.
+        /// </value>
+        public bool IsDropDownOpen
+        {
+            get { return _isDropDownOpen; }
+            set { SetAndRaise(IsDropDownOpenProperty, ref _isDropDownOpen, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the current date will be
+        /// highlighted.
+        /// </summary>
+        /// <value>
+        /// True if the current date is highlighted; otherwise, false. The
+        /// default is true.
+        /// </value>
+        public bool IsTodayHighlighted
+        {
+            get { return GetValue(IsTodayHighlightedProperty); }
+            set { SetValue(IsTodayHighlightedProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the currently selected date.
+        /// </summary>
+        /// <value>
+        /// The date currently selected. The default is null.
+        /// </value>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        /// The specified date is not in the range defined by
+        /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateStart" />
+        /// and
+        /// <see cref="P:Avalonia.Controls.DatePicker.DisplayDateEnd" />,
+        /// or the specified date is in the
+        /// <see cref="P:Avalonia.Controls.DatePicker.BlackoutDates" />
+        /// collection.
+        /// </exception>
+        public DateTime? SelectedDate
+        {
+            get { return _selectedDate; }
+            set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the format that is used to display the selected date.
+        /// </summary>
+        /// <value>
+        /// The format that is used to display the selected date. The default is
+        /// <see cref="F:Avalonia.Controls.DatePickerFormat.Short" />.
+        /// </value>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        /// An specified format is not valid.
+        /// </exception>
+        public DatePickerFormat SelectedDateFormat
+        {
+            get { return GetValue(SelectedDateFormatProperty); }
+            set { SetValue(SelectedDateFormatProperty, value); }
+        }
+
+        public string CustomDateFormatString
+        {
+            get { return GetValue(CustomDateFormatStringProperty); }
+            set { SetValue(CustomDateFormatStringProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets the text that is displayed by the
+        /// <see cref="T:Avalonia.Controls.DatePicker" />.
+        /// </summary>
+        /// <value>
+        /// The text displayed by the
+        /// <see cref="T:Avalonia.Controls.DatePicker" />.
+        /// </value>
+        /// <exception cref="T:System.FormatException">
+        /// The text entered cannot be parsed to a valid date, and the exception
+        /// is not suppressed.
+        /// </exception>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        /// The text entered parses to a date that is not selectable.
+        /// </exception>
+        public string Text
+        {
+            get { return _text; }
+            set { SetAndRaise(TextProperty, ref _text, value); }
+        }
+
+        public string Watermark
+        {
+            get { return GetValue(WatermarkProperty); }
+            set { SetValue(WatermarkProperty, value); }
+        }
+        public bool UseFloatingWatermark
+        {
+            get { return GetValue(UseFloatingWatermarkProperty); }
+            set { SetValue(UseFloatingWatermarkProperty, value); }
+        }
+
+        /// <summary>
+        /// Occurs when the drop-down
+        /// <see cref="T:Avalonia.Controls.Calendar" /> is closed.
+        /// </summary>
+        public event EventHandler CalendarClosed;
+
+        /// <summary>
+        /// Occurs when the drop-down
+        /// <see cref="T:Avalonia.Controls.Calendar" /> is opened.
+        /// </summary>
+        public event EventHandler CalendarOpened;
+
+        /// <summary>
+        /// Occurs when <see cref="P:Avalonia.Controls.DatePicker.Text" />
+        /// is assigned a value that cannot be interpreted as a date.
+        /// </summary>
+        public event EventHandler<DatePickerDateValidationErrorEventArgs> DateValidationError;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:Avalonia.Controls.DatePicker.SelectedDate" />
+        /// property is changed.
+        /// </summary>
+        public event EventHandler<SelectionChangedEventArgs> SelectedDateChanged;
+
+        static DatePicker()
+        {
+            FocusableProperty.OverrideDefaultValue<DatePicker>(true);
+
+            DisplayDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateChanged);
+            DisplayDateStartProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateStartChanged);
+            DisplayDateEndProperty.Changed.AddClassHandler<DatePicker>(x => x.OnDisplayDateEndChanged);
+            IsDropDownOpenProperty.Changed.AddClassHandler<DatePicker>(x => x.OnIsDropDownOpenChanged);
+            SelectedDateProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateChanged);
+            SelectedDateFormatProperty.Changed.AddClassHandler<DatePicker>(x => x.OnSelectedDateFormatChanged);
+            CustomDateFormatStringProperty.Changed.AddClassHandler<DatePicker>(x => x.OnCustomDateFormatStringChanged);
+            TextProperty.Changed.AddClassHandler<DatePicker>(x => x.OnTextChanged);
+        }
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.DatePicker" /> class.
+        /// </summary>
+        public DatePicker()
+        {
+            FirstDayOfWeek = DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek;
+            _defaultText = string.Empty;
+            DisplayDate = DateTime.Today;
+        }
+
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            if (_calendar != null)
+            {
+                _calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp;
+                _calendar.DisplayDateChanged -= Calendar_DisplayDateChanged;
+                _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
+                _calendar.PointerPressed -= Calendar_PointerPressed;
+                _calendar.KeyDown -= Calendar_KeyDown;
+            }
+            _calendar = e.NameScope.Find<Calendar>(ElementCalendar);
+            if (_calendar != null)
+            {
+                _calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+                _calendar.SelectedDate = SelectedDate;
+                SetCalendarDisplayDate(DisplayDate);
+                SetCalendarDisplayDateStart(DisplayDateStart);
+                SetCalendarDisplayDateEnd(DisplayDateEnd);
+
+                _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
+                _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
+                _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
+                _calendar.PointerPressed += Calendar_PointerPressed;
+                _calendar.KeyDown += Calendar_KeyDown;
+                //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged);
+                //_calendar.IsTabStop = true;
+
+                var currentBlackoutDays = BlackoutDates;
+                BlackoutDates = _calendar.BlackoutDates;
+                if(currentBlackoutDays != null)
+                {
+                    foreach (var range in currentBlackoutDays)
+                    {
+                        BlackoutDates.Add(range);
+                    }
+                }
+            }
+
+            if (_popUp != null)
+            {
+                _popUp.Child = null;
+                _popUp.Closed -= PopUp_Closed;
+            }
+            _popUp = e.NameScope.Find<Popup>(ElementPopup);
+            if(_popUp != null)
+            {
+                _popUp.Closed += PopUp_Closed;
+                if (IsDropDownOpen)
+                {
+                    OpenDropDown();
+                }
+            }
+
+            if(_dropDownButton != null)
+            {
+                _dropDownButton.Click -= DropDownButton_Click;
+                _buttonPointerPressedSubscription?.Dispose();
+            }
+            _dropDownButton = e.NameScope.Find<Button>(ElementButton);
+            if(_dropDownButton != null)
+            {
+                _dropDownButton.Click += DropDownButton_Click;
+                _buttonPointerPressedSubscription =
+                    _dropDownButton.AddHandler(PointerPressedEvent, DropDownButton_PointerPressed, handledEventsToo: true);
+            }
+
+            if (_textBox != null)
+            {
+                _textBox.KeyDown -= TextBox_KeyDown;
+                _textBox.GotFocus -= TextBox_GotFocus;
+                _textBoxTextChangedSubscription?.Dispose();
+            }
+            _textBox = e.NameScope.Find<TextBox>(ElementTextBox);
+
+            if(!SelectedDate.HasValue)
+            {
+                SetWaterMarkText();
+            }
+
+            if(_textBox != null)
+            {
+                _textBox.KeyDown += TextBox_KeyDown;
+                _textBox.GotFocus += TextBox_GotFocus;
+                _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged());
+
+                if(SelectedDate.HasValue)
+                {
+                    _textBox.Text = DateTimeToString(SelectedDate.Value);
+                }
+                else if(!String.IsNullOrEmpty(_defaultText))
+                {
+                    _textBox.Text = _defaultText;
+                    SetSelectedDate();
+                }
+            }
+
+            base.OnTemplateApplied(e);
+        }
+
+        protected override void UpdateDataValidation(AvaloniaProperty property, BindingNotification status)
+        {
+            if (property == SelectedDateProperty)
+            {
+                DataValidationErrors.SetError(this, status.Error);
+            }
+        }
+
+        protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
+        {
+            base.OnPointerWheelChanged(e);
+            if (!e.Handled && SelectedDate.HasValue && _calendar != null)
+            {
+                DateTime selectedDate = this.SelectedDate.Value;
+                DateTime? newDate = DateTimeHelper.AddDays(selectedDate, e.Delta.Y > 0 ? -1 : 1);
+                if (newDate.HasValue && Calendar.IsValidDateSelection(_calendar, newDate.Value))
+                {
+                    SelectedDate = newDate;
+                    e.Handled = true;
+                }
+            }
+        }
+        protected override void OnGotFocus(GotFocusEventArgs e)
+        {
+            base.OnGotFocus(e);
+            if(IsEnabled && _textBox != null && e.NavigationMethod == NavigationMethod.Tab)
+            {
+                _textBox.Focus();
+                var text = _textBox.Text;
+                if(!string.IsNullOrEmpty(text))
+                {
+                    _textBox.SelectionStart = 0;
+                    _textBox.SelectionEnd = text.Length;
+                }
+            }
+        }
+        protected override void OnLostFocus(RoutedEventArgs e)
+        {
+            base.OnLostFocus(e);
+
+            SetSelectedDate();
+        }
+        
+        private void SetCalendarDisplayDate(DateTime value)
+        {
+            if (DateTimeHelper.CompareYearMonth(_calendar.DisplayDate, value) != 0)
+            {
+                _calendar.DisplayDate = DisplayDate;
+                if (DateTime.Compare(_calendar.DisplayDate, DisplayDate) != 0)
+                {
+                    DisplayDate = _calendar.DisplayDate;
+                }
+            }
+        }
+        private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_calendar != null)
+            {
+                var value = (DateTime)e.NewValue;
+                SetCalendarDisplayDate(value);
+            }
+        }
+        private void SetCalendarDisplayDateStart(DateTime? value)
+        {
+            _calendar.DisplayDateStart = value;
+            if (_calendar.DisplayDateStart.HasValue && DisplayDateStart.HasValue && DateTime.Compare(_calendar.DisplayDateStart.Value, DisplayDateStart.Value) != 0)
+            {
+                DisplayDateStart = _calendar.DisplayDateStart;
+            }
+        }
+        private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_calendar != null)
+            {
+                var value = (DateTime?)e.NewValue;
+                SetCalendarDisplayDateStart(value);
+            }
+        }
+        private void SetCalendarDisplayDateEnd(DateTime? value)
+        {
+            _calendar.DisplayDateEnd = value;
+            if (_calendar.DisplayDateEnd.HasValue && DisplayDateEnd.HasValue && DateTime.Compare(_calendar.DisplayDateEnd.Value, DisplayDateEnd.Value) != 0)
+            {
+                DisplayDateEnd = _calendar.DisplayDateEnd;
+            }
+
+        }
+        private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (_calendar != null)
+            {
+                var value = (DateTime?)e.NewValue;
+                SetCalendarDisplayDateEnd(value);
+            }
+        }
+        private void OnIsDropDownOpenChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var oldValue = (bool)e.OldValue;
+            var value = (bool)e.NewValue;
+
+            if (_popUp != null && _popUp.Child != null)
+            {
+                if (value != oldValue)
+                {
+                    if (_calendar.DisplayMode != CalendarMode.Month)
+                    {
+                        _calendar.DisplayMode = CalendarMode.Month;
+                    }
+
+                    if (value)
+                    {
+                        OpenDropDown();
+                    }
+                    else
+                    {
+                        _popUp.IsOpen = false;
+                        OnCalendarClosed(new RoutedEventArgs());
+                    }
+                }
+            }
+        }
+        private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var addedDate = (DateTime?)e.NewValue;
+            var removedDate = (DateTime?)e.OldValue;
+
+            if (_calendar != null && addedDate != _calendar.SelectedDate)
+            {
+                _calendar.SelectedDate = addedDate;
+            }
+
+            if (SelectedDate != null)
+            {
+                DateTime day = SelectedDate.Value;
+
+                // When the SelectedDateProperty change is done from
+                // OnTextPropertyChanged method, two-way binding breaks if
+                // BeginInvoke is not used:
+                Threading.Dispatcher.UIThread.InvokeAsync(() =>
+                {
+                    _settingSelectedDate = true;
+                    Text = DateTimeToString(day);
+                    _settingSelectedDate = false;
+                    OnDateSelected(addedDate, removedDate);
+                });
+
+                // When DatePickerDisplayDateFlag is TRUE, the SelectedDate
+                // change is coming from the Calendar UI itself, so, we
+                // shouldn't change the DisplayDate since it will automatically
+                // be changed by the Calendar
+                if ((day.Month != DisplayDate.Month || day.Year != DisplayDate.Year) && (_calendar == null || !_calendar.DatePickerDisplayDateFlag))
+                {
+                    DisplayDate = day;
+                }
+                if(_calendar != null)
+                    _calendar.DatePickerDisplayDateFlag = false;
+            }
+            else
+            {
+                _settingSelectedDate = true;
+                SetWaterMarkText();
+                _settingSelectedDate = false;
+                OnDateSelected(addedDate, removedDate);
+            }
+        }
+        private void OnDateFormatChanged()
+        {
+            if (_textBox != null)
+            {
+                if (SelectedDate.HasValue)
+                {
+                    Text = DateTimeToString(SelectedDate.Value);
+                }
+                else if (string.IsNullOrEmpty(_textBox.Text))
+                {
+                    SetWaterMarkText();
+                }
+                else
+                {
+                    DateTime? date = ParseText(_textBox.Text);
+
+                    if (date != null)
+                    {
+                        string s = DateTimeToString((DateTime)date);
+                        Text = s;
+                    }
+                }
+            }
+        }
+        private void OnSelectedDateFormatChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            OnDateFormatChanged();
+        }
+        private void OnCustomDateFormatStringChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if(SelectedDateFormat == DatePickerFormat.Custom)
+            {
+                OnDateFormatChanged();
+            }
+        }
+        private void OnTextChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var oldValue = (string)e.OldValue;
+            var value = (string)e.NewValue;
+
+            if (!_suspendTextChangeHandler)
+            {
+                if (value != null)
+                {
+                    if (_textBox != null)
+                    {
+                        _textBox.Text = value;
+                    }
+                    else
+                    {
+                        _defaultText = value;
+                    }
+                    if (!_settingSelectedDate)
+                    {
+                        SetSelectedDate();
+                    }
+                }
+                else
+                {
+                    if (!_settingSelectedDate)
+                    {
+                        _settingSelectedDate = true;
+                        SelectedDate = null;
+                        _settingSelectedDate = false;
+                    }
+                }
+            }
+            else
+            {
+                SetWaterMarkText();
+            }
+        }
+
+        /// <summary>
+        /// Raises the
+        /// <see cref="E:Avalonia.Controls.DatePicker.DateValidationError" />
+        /// event.
+        /// </summary>
+        /// <param name="e">
+        /// A
+        /// <see cref="T:Avalonia.Controls.DatePickerDateValidationErrorEventArgs" />
+        /// that contains the event data.
+        /// </param>
+        protected virtual void OnDateValidationError(DatePickerDateValidationErrorEventArgs e)
+        {
+            DateValidationError?.Invoke(this, e);
+        }
+        private void OnDateSelected(DateTime? addedDate, DateTime? removedDate)
+        {
+            EventHandler<SelectionChangedEventArgs> handler = this.SelectedDateChanged;
+            if (null != handler)
+            {
+                Collection<DateTime> addedItems = new Collection<DateTime>();
+                Collection<DateTime> removedItems = new Collection<DateTime>();
+
+                if (addedDate.HasValue)
+                {
+                    addedItems.Add(addedDate.Value);
+                }
+
+                if (removedDate.HasValue)
+                {
+                    removedItems.Add(removedDate.Value);
+                }
+
+                handler(this, new SelectionChangedEventArgs(SelectingItemsControl.SelectionChangedEvent, addedItems, removedItems));
+            }
+        }
+        private void OnCalendarClosed(EventArgs e)
+        {
+            CalendarClosed?.Invoke(this, e);
+        }
+        private void OnCalendarOpened(EventArgs e)
+        {
+            CalendarOpened?.Invoke(this, e);
+        }
+
+        private void Calendar_DayButtonMouseUp(object sender, PointerReleasedEventArgs e)
+        {
+            Focus();
+            IsDropDownOpen = false;
+        }      
+        private void Calendar_DisplayDateChanged(object sender, CalendarDateChangedEventArgs e)
+        {
+            if (e.AddedDate != this.DisplayDate)
+            {
+                SetValue(DisplayDateProperty, (DateTime) e.AddedDate);
+            }
+        }
+        private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
+        {
+            Debug.Assert(e.AddedItems.Count < 2, "There should be less than 2 AddedItems!");
+
+            if (e.AddedItems.Count > 0 && SelectedDate.HasValue && DateTime.Compare((DateTime)e.AddedItems[0], SelectedDate.Value) != 0)
+            {
+                SelectedDate = (DateTime?)e.AddedItems[0];
+            }
+            else
+            {
+                if (e.AddedItems.Count == 0)
+                {
+                    SelectedDate = null;
+                    return;
+                }
+
+                if (!SelectedDate.HasValue)
+                {
+                    if (e.AddedItems.Count > 0)
+                    {
+                        SelectedDate = (DateTime?)e.AddedItems[0];
+                    }
+                }
+            }
+        }
+        private void Calendar_PointerPressed(object sender, PointerPressedEventArgs e)
+        {
+            if (e.MouseButton == MouseButton.Left)
+            {
+                e.Handled = true;
+            }
+        }
+        private void Calendar_KeyDown(object sender, KeyEventArgs e)
+        {
+            Calendar c = sender as Calendar;
+            Debug.Assert(c != null, "The Calendar should not be null!");
+
+            if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month)
+            {
+                Focus();
+                IsDropDownOpen = false;
+
+                if (e.Key == Key.Escape)
+                {
+                    SelectedDate = _onOpenSelectedDate;
+                }
+            }
+        }
+        private void TextBox_GotFocus(object sender, RoutedEventArgs e)
+        {
+            IsDropDownOpen = false;
+        }
+        private void TextBox_KeyDown(object sender, KeyEventArgs e)
+        {
+            if (!e.Handled)
+            {
+                e.Handled = ProcessDatePickerKey(e);
+            }
+        }
+        private void TextBox_TextChanged()
+        {
+            if (_textBox != null)
+            {
+                _suspendTextChangeHandler = true;
+                Text = _textBox.Text;
+                _suspendTextChangeHandler = false;
+            }
+        }
+        private void DropDownButton_PointerPressed(object sender, PointerPressedEventArgs e)
+        {
+            _ignoreButtonClick = _isPopupClosing;
+        }
+        private void DropDownButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (!_ignoreButtonClick)
+            {
+                HandlePopUp();
+            }
+            else
+            {
+                _ignoreButtonClick = false;
+            }
+        }
+        private void PopUp_Closed(object sender, EventArgs e)
+        {
+            IsDropDownOpen = false;
+
+            if(!_isPopupClosing)
+            {
+                _isPopupClosing = true;
+                Threading.Dispatcher.UIThread.InvokeAsync(() => _isPopupClosing = false);
+            }
+        }
+
+        private void HandlePopUp()
+        {
+            if (IsDropDownOpen)
+            {
+                Focus();
+                IsDropDownOpen = false;
+            }
+            else
+            {
+                ProcessTextBox();
+            }
+        }
+        private void OpenDropDown()
+        {
+            if (_calendar != null)
+            {
+                _calendar.Focus();
+                OpenPopUp();
+                _calendar.ResetStates();
+                OnCalendarOpened(new RoutedEventArgs());
+            }
+        }
+        private void OpenPopUp()
+        {
+            _onOpenSelectedDate = SelectedDate;
+            _popUp.IsOpen = true;
+        }
+
+        /// <summary>
+        /// Input text is parsed in the correct format and changed into a
+        /// DateTime object.  If the text can not be parsed TextParseError Event
+        /// is thrown.
+        /// </summary>
+        /// <param name="text">Inherited code: Requires comment.</param>
+        /// <returns>
+        /// IT SHOULD RETURN NULL IF THE STRING IS NOT VALID, RETURN THE
+        /// DATETIME VALUE IF IT IS VALID.
+        /// </returns>
+        private DateTime? ParseText(string text)
+        {
+            DateTime newSelectedDate;
+
+            // TryParse is not used in order to be able to pass the exception to
+            // the TextParseError event
+            try
+            {
+                newSelectedDate = DateTime.Parse(text, DateTimeHelper.GetCurrentDateFormat());
+
+                if (Calendar.IsValidDateSelection(this._calendar, newSelectedDate))
+                {
+                    return newSelectedDate;
+                }
+                else
+                {
+                    var dateValidationError = new DatePickerDateValidationErrorEventArgs(new ArgumentOutOfRangeException("text", "SelectedDate value is not valid."), text);
+                    OnDateValidationError(dateValidationError);
+
+                    if (dateValidationError.ThrowException)
+                    {
+                        throw dateValidationError.Exception;
+                    }
+                }
+            }
+            catch (FormatException ex)
+            {
+                DatePickerDateValidationErrorEventArgs textParseError = new DatePickerDateValidationErrorEventArgs(ex, text);
+                OnDateValidationError(textParseError);
+
+                if (textParseError.ThrowException)
+                {
+                    throw textParseError.Exception;
+                }
+            }
+            return null;
+        }
+        private string DateTimeToString(DateTime d)
+        {
+            DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
+
+            switch (SelectedDateFormat)
+            {
+                case DatePickerFormat.Short:
+                    return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.ShortDatePattern, dtfi));
+                case DatePickerFormat.Long:
+                    return string.Format(CultureInfo.CurrentCulture, d.ToString(dtfi.LongDatePattern, dtfi));
+                case DatePickerFormat.Custom:
+                    return string.Format(CultureInfo.CurrentCulture, d.ToString(CustomDateFormatString, dtfi));
+            }
+            return null;
+        }
+        private bool ProcessDatePickerKey(KeyEventArgs e)
+        {
+            
+            switch (e.Key)
+            {
+                case Key.Enter:
+                    {
+                        SetSelectedDate();
+                        return true;
+                    }
+                case Key.Down:
+                    { 
+                        if ((e.Modifiers & InputModifiers.Control) == InputModifiers.Control)
+                        {
+                            HandlePopUp();
+                            return true;
+                        }
+                        break;
+                    }
+            }
+            return false;
+        }
+        private void ProcessTextBox()
+        {
+            SetSelectedDate();
+            IsDropDownOpen = true;
+            _calendar.Focus();
+        }
+        private void SetSelectedDate()
+        {
+            if (_textBox != null)
+            {
+                if (!string.IsNullOrEmpty(_textBox.Text))
+                {
+                    string s = _textBox.Text;
+
+                    if (SelectedDate != null)
+                    {
+                        // If the string value of the SelectedDate and the
+                        // TextBox string value are equal, we do not parse the
+                        // string again if we do an extra parse, we lose data in
+                        // M/d/yy format.
+                        // ex: SelectedDate = DateTime(1008,12,19) but when
+                        // "12/19/08" is parsed it is interpreted as
+                        // DateTime(2008,12,19)
+                        string selectedDate = DateTimeToString(SelectedDate.Value);
+                        if (selectedDate == s)
+                        {
+                            return;
+                        }
+                    }
+                    DateTime? d = SetTextBoxValue(s);
+                    if (!SelectedDate.Equals(d))
+                    {
+                        SelectedDate = d;
+                    }
+                }
+                else
+                {
+                    if (SelectedDate != null)
+                    {
+                        SelectedDate = null;
+                    }
+                }
+            }
+            else
+            {
+                DateTime? d = SetTextBoxValue(_defaultText);
+                if (!SelectedDate.Equals(d))
+                {
+                    SelectedDate = d;
+                }
+            }
+        }
+        private DateTime? SetTextBoxValue(string s)
+        {
+            if (string.IsNullOrEmpty(s))
+            {
+                SetValue(TextProperty, s);
+                return SelectedDate;
+            }
+            else
+            {
+                DateTime? d = ParseText(s);
+                if (d != null)
+                {
+                    SetValue(TextProperty, s);
+                    return d;
+                }
+                else
+                {
+                    // If parse error: TextBox should have the latest valid
+                    // selecteddate value:
+                    if (SelectedDate != null)
+                    {
+                        string newtext = this.DateTimeToString(SelectedDate.Value);
+                        SetValue(TextProperty, newtext);
+                        return SelectedDate;
+                    }
+                    else
+                    {
+                        SetWaterMarkText();
+                        return null;
+                    }
+                }
+            }
+        }
+        private void SetWaterMarkText()
+        {
+            if (_textBox != null)
+            {
+                if (string.IsNullOrEmpty(Watermark) && !UseFloatingWatermark)
+                {
+                    DateTimeFormatInfo dtfi = DateTimeHelper.GetCurrentDateFormat();
+                    Text = string.Empty;
+                    _defaultText = string.Empty;
+                    var watermarkFormat = "<{0}>";
+                    string watermarkText;
+
+                    switch (SelectedDateFormat)
+                    {
+                        case DatePickerFormat.Long:
+                            {
+                                watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.LongDatePattern.ToString());
+                                break;
+                            }
+                        case DatePickerFormat.Short:
+                        default:
+                            {
+                                watermarkText = string.Format(CultureInfo.CurrentCulture, watermarkFormat, dtfi.ShortDatePattern.ToString());
+                                break;
+                            }
+                    }
+                    _textBox.Watermark = watermarkText;
+                }
+                else
+                {
+                    _textBox.ClearValue(TextBox.WatermarkProperty);
+                }
+            }
+        }
+
+        private static bool IsValidSelectedDateFormat(DatePickerFormat value)
+        {
+            return value == DatePickerFormat.Long
+                || value == DatePickerFormat.Short
+                || value == DatePickerFormat.Custom;
+        }
+        private static DatePickerFormat ValidateSelectedDateFormat(DatePicker dp, DatePickerFormat format)
+        {
+            if(IsValidSelectedDateFormat(format))
+            {
+                return format;
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException(nameof(format), "DatePickerFormat value is not valid.");
+            }
+        }
+        private static string ValidateDateFormatString(DatePicker dp, string formatString)
+        {
+            if(string.IsNullOrWhiteSpace(formatString))
+            {
+                throw new ArgumentException("DateFormatString value is not valid.", nameof(formatString));
+            }
+            else
+            {
+                return formatString;
+            }
+        }
+        private static DateTime DiscardDayTime(DateTime d)
+        {
+            int year = d.Year;
+            int month = d.Month;
+            DateTime newD = new DateTime(year, month, 1, 0, 0, 0);
+            return newD;
+        }
+        private static DateTime? DiscardTime(DateTime? d)
+        {
+            if (d == null)
+            {
+                return null;
+            }
+            else
+            {
+                DateTime discarded = (DateTime) d;
+                int year = discarded.Year;
+                int month = discarded.Month;
+                int day = discarded.Day;
+                DateTime newD = new DateTime(year, month, day, 0, 0, 0);
+                return newD;
+            }
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Controls/Carousel.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Transition"/> property.
         /// </summary>
         public static readonly StyledProperty<IPageTransition> TransitionProperty =
-            AvaloniaProperty.Register<Carousel, IPageTransition>("Transition");
+            AvaloniaProperty.Register<Carousel, IPageTransition>(nameof(Transition));
 
         /// <summary>
         /// The default value of <see cref="ItemsControl.ItemsPanelProperty"/> for 

+ 3 - 3
src/Avalonia.Controls/ColumnDefinition.cs

@@ -12,19 +12,19 @@ namespace Avalonia.Controls
         /// Defines the <see cref="MaxWidth"/> property.
         /// </summary>
         public static readonly StyledProperty<double> MaxWidthProperty =
-            AvaloniaProperty.Register<ColumnDefinition, double>("MaxWidth", double.PositiveInfinity);
+            AvaloniaProperty.Register<ColumnDefinition, double>(nameof(MaxWidth), double.PositiveInfinity);
 
         /// <summary>
         /// Defines the <see cref="MinWidth"/> property.
         /// </summary>
         public static readonly StyledProperty<double> MinWidthProperty =
-            AvaloniaProperty.Register<ColumnDefinition, double>("MinWidth");
+            AvaloniaProperty.Register<ColumnDefinition, double>(nameof(MinWidth));
 
         /// <summary>
         /// Defines the <see cref="Width"/> property.
         /// </summary>
         public static readonly StyledProperty<GridLength> WidthProperty =
-            AvaloniaProperty.Register<ColumnDefinition, GridLength>("Width", new GridLength(1, GridUnitType.Star));
+            AvaloniaProperty.Register<ColumnDefinition, GridLength>(nameof(Width), new GridLength(1, GridUnitType.Star));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ColumnDefinition"/> class.

+ 135 - 0
src/Avalonia.Controls/DataValidationErrors.cs

@@ -0,0 +1,135 @@
+// 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 System.Reactive.Linq;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// A control which displays an error notifier when there is a DataValidationError. 
+    /// Provides attached properties to track errors on a control
+    /// </summary>
+    /// <remarks>
+    /// You will probably only want to create instances inside of control templates.
+    /// </remarks>
+    public class DataValidationErrors : ContentControl
+    {
+        /// <summary>
+        /// Defines the DataValidationErrors.Errors attached property.
+        /// </summary>
+        public static readonly AttachedProperty<IEnumerable<Exception>> ErrorsProperty =
+            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, IEnumerable<Exception>>("Errors");
+
+        /// <summary>
+        /// Defines the DataValidationErrors.HasErrors attached property.
+        /// </summary>
+        public static readonly AttachedProperty<bool> HasErrorsProperty =
+            AvaloniaProperty.RegisterAttached<DataValidationErrors, Control, bool>("HasErrors");
+
+        public static readonly StyledProperty<IDataTemplate> ErrorTemplateProperty =
+            AvaloniaProperty.Register<DataValidationErrors, IDataTemplate>(nameof(ErrorTemplate));
+
+
+        private Control _owner;
+
+        public static readonly DirectProperty<DataValidationErrors, Control> OwnerProperty =
+            AvaloniaProperty.RegisterDirect<DataValidationErrors, Control>(
+                nameof(Owner),
+                o => o.Owner,
+                (o, v) => o.Owner = v);
+
+        public Control Owner
+        {
+            get { return _owner; }
+            set { SetAndRaise(OwnerProperty, ref _owner, value); }
+        }
+
+        /// <summary>
+        /// Initializes static members of the <see cref="DataValidationErrors"/> class.
+        /// </summary>
+        static DataValidationErrors()
+        {
+            ErrorsProperty.Changed.Subscribe(ErrorsChanged);
+            HasErrorsProperty.Changed.Subscribe(HasErrorsChanged);
+            TemplatedParentProperty.Changed.AddClassHandler<DataValidationErrors>(x => x.OnTemplatedParentChange);
+        }
+
+        private void OnTemplatedParentChange(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (Owner == null)
+            {
+                Owner = (e.NewValue as Control);
+            }
+        }
+
+        public IDataTemplate ErrorTemplate
+        {
+            get { return GetValue(ErrorTemplateProperty); }
+            set { SetValue(ErrorTemplateProperty, value); }
+        }
+
+        private static void ErrorsChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var control = (Control)e.Sender;
+            var errors = (IEnumerable<Exception>)e.NewValue;
+
+            var hasErrors = false;
+            if (errors != null && errors.Any())
+                hasErrors = true;
+
+            control.SetValue(HasErrorsProperty, hasErrors);
+        }
+        private static void HasErrorsChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var control = (Control)e.Sender;
+            var classes = (IPseudoClasses)control.Classes;
+            classes.Set(":error", (bool)e.NewValue);
+        }
+
+        public static IEnumerable<Exception> GetErrors(Control control)
+        {
+            return control.GetValue(ErrorsProperty);
+        }
+        public static void SetErrors(Control control, IEnumerable<Exception> errors)
+        {
+            control.SetValue(ErrorsProperty, errors);
+        }
+        public static void SetError(Control control, Exception error)
+        {
+            SetErrors(control, UnpackException(error));
+        }
+        public static void ClearErrors(Control control)
+        {
+            SetErrors(control, null);
+        }
+        public static bool GetHasErrors(Control control)
+        {
+            return control.GetValue(HasErrorsProperty);
+        }
+
+        private static IEnumerable<Exception> UnpackException(Exception exception)
+        {
+            if (exception != null)
+            {
+                var aggregate = exception as AggregateException;
+                var exceptions = aggregate == null ?
+                    (IEnumerable<Exception>)new[] { exception } :
+                    aggregate.InnerExceptions;
+                var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
+
+                if (filtered.Count > 0)
+                {
+                    return filtered;
+                }
+            }
+
+            return null;
+        }
+    }
+}

+ 1 - 1
src/Avalonia.Controls/DropDown.cs

@@ -38,7 +38,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="SelectionBoxItem"/> property.
         /// </summary>
         public static readonly DirectProperty<DropDown, object> SelectionBoxItemProperty =
-            AvaloniaProperty.RegisterDirect<DropDown, object>("SelectionBoxItem", o => o.SelectionBoxItem);
+            AvaloniaProperty.RegisterDirect<DropDown, object>(nameof(SelectionBoxItem), o => o.SelectionBoxItem);
 
         private bool _isDropDownOpen;
         private Popup _popup;

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

@@ -2,14 +2,12 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Reactive.Linq;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Metadata;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls.Presenters
 {
@@ -340,94 +338,121 @@ namespace Avalonia.Controls.Presenters
         /// <inheritdoc/>
         protected override Size MeasureOverride(Size availableSize)
         {
-            var child = Child;
-            var padding = Padding + new Thickness(BorderThickness);
+            return Border.MeasureOverrideImpl(availableSize, Child, Padding, BorderThickness);
+        }
+
+        /// <inheritdoc/>
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            return ArrangeOverrideImpl(finalSize, new Vector());
+        }
+
+        /// <summary>
+        /// Called when the <see cref="Content"/> property changes.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            _createdChild = false;
 
-            if (child != null)
+            if (((ILogical)this).IsAttachedToLogicalTree)
             {
-                child.Measure(availableSize.Deflate(padding));
-                return child.DesiredSize.Inflate(padding);
+                UpdateChild();
             }
-            else
+            else if (Child != null)
             {
-                return new Size(padding.Left + padding.Right, padding.Bottom + padding.Top);
+                VisualChildren.Remove(Child);
+                LogicalChildren.Remove(Child);
+                Child = null;
+                _dataTemplate = null;
             }
+
+            InvalidateMeasure();
         }
 
-        /// <inheritdoc/>
-        protected override Size ArrangeOverride(Size finalSize)
+        internal Size ArrangeOverrideImpl(Size finalSize, Vector offset)
         {
-            var child = Child;
-
-            if (child != null)
+            if (Child != null)
             {
-                var padding = Padding + new Thickness(BorderThickness);
-                var sizeMinusPadding = finalSize.Deflate(padding);
-                var size = sizeMinusPadding;
-                var horizontalAlignment = HorizontalContentAlignment;
-                var verticalAlignment = VerticalContentAlignment;
-                var originX = padding.Left;
-                var originY = padding.Top;
-
-                if (horizontalAlignment != HorizontalAlignment.Stretch)
+                var padding = Padding;
+                var borderThickness = BorderThickness;
+                var horizontalContentAlignment = HorizontalContentAlignment;
+                var verticalContentAlignment = VerticalContentAlignment;
+                var useLayoutRounding = UseLayoutRounding;
+                var availableSizeMinusMargins = new Size(
+                    Math.Max(0, finalSize.Width - padding.Left - padding.Right - borderThickness),
+                    Math.Max(0, finalSize.Height - padding.Top - padding.Bottom - borderThickness));
+                var size = availableSizeMinusMargins;
+                var scale = GetLayoutScale();
+                var originX = offset.X + padding.Left + borderThickness;
+                var originY = offset.Y + padding.Top + borderThickness;
+
+                if (horizontalContentAlignment != HorizontalAlignment.Stretch)
                 {
-                    size = size.WithWidth(child.DesiredSize.Width);
+                    size = size.WithWidth(Math.Min(size.Width, DesiredSize.Width - padding.Left - padding.Right));
                 }
 
-                if (verticalAlignment != VerticalAlignment.Stretch)
+                if (verticalContentAlignment != VerticalAlignment.Stretch)
                 {
-                    size = size.WithHeight(child.DesiredSize.Height);
+                    size = size.WithHeight(Math.Min(size.Height, DesiredSize.Height - padding.Top - padding.Bottom));
                 }
 
-                switch (horizontalAlignment)
+                size = LayoutHelper.ApplyLayoutConstraints(Child, size);
+
+                if (useLayoutRounding)
+                {
+                    size = new Size(
+                        Math.Ceiling(size.Width * scale) / scale,
+                        Math.Ceiling(size.Height * scale) / scale);
+                    availableSizeMinusMargins = new Size(
+                        Math.Ceiling(availableSizeMinusMargins.Width * scale) / scale,
+                        Math.Ceiling(availableSizeMinusMargins.Height * scale) / scale);
+                }
+
+                switch (horizontalContentAlignment)
                 {
-                    case HorizontalAlignment.Stretch:
                     case HorizontalAlignment.Center:
-                        originX += (sizeMinusPadding.Width - size.Width) / 2;
+                    case HorizontalAlignment.Stretch:
+                        originX += (availableSizeMinusMargins.Width - size.Width) / 2;
                         break;
                     case HorizontalAlignment.Right:
-                        originX = size.Width - child.DesiredSize.Width;
+                        originX += availableSizeMinusMargins.Width - size.Width;
                         break;
                 }
 
-                switch (verticalAlignment)
+                switch (verticalContentAlignment)
                 {
-                    case VerticalAlignment.Stretch:
                     case VerticalAlignment.Center:
-                        originY += (sizeMinusPadding.Height - size.Height) / 2;
+                    case VerticalAlignment.Stretch:
+                        originY += (availableSizeMinusMargins.Height - size.Height) / 2;
                         break;
                     case VerticalAlignment.Bottom:
-                        originY = size.Height - child.DesiredSize.Height;
+                        originY += availableSizeMinusMargins.Height - size.Height;
                         break;
                 }
 
-                child.Arrange(new Rect(originX, originY, size.Width, size.Height));
+                if (useLayoutRounding)
+                {
+                    originX = Math.Floor(originX * scale) / scale;
+                    originY = Math.Floor(originY * scale) / scale;
+                }
+
+                Child.Arrange(new Rect(originX, originY, size.Width, size.Height));
             }
 
             return finalSize;
         }
 
-        /// <summary>
-        /// Called when the <see cref="Content"/> property changes.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
+        private double GetLayoutScale()
         {
-            _createdChild = false;
+            var result = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0;
 
-            if (((ILogical)this).IsAttachedToLogicalTree)
+            if (result == 0 || double.IsNaN(result) || double.IsInfinity(result))
             {
-                UpdateChild();
-            }
-            else if (Child != null)
-            {
-                VisualChildren.Remove(Child);
-                LogicalChildren.Remove(Child);
-                Child = null;
-                _dataTemplate = null;
+                throw new Exception($"Invalid LayoutScaling returned from {VisualRoot.GetType()}");
             }
 
-            InvalidateMeasure();
+            return result;
         }
 
         private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)

+ 10 - 2
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections;
 using System.Collections.Specialized;
 using System.Linq;
+using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Utils;
 using Avalonia.Input;
 using Avalonia.Layout;
@@ -97,6 +98,7 @@ namespace Avalonia.Controls.Presenters
         /// <inheritdoc/>
         public override Size MeasureOverride(Size availableSize)
         {
+            var scrollable = (ILogicalScrollable)Owner;
             var visualRoot = Owner.GetVisualRoot();
             var maxAvailableSize = (visualRoot as WindowBase)?.PlatformImpl?.MaxClientSize
                  ?? (visualRoot as TopLevel)?.ClientSize;
@@ -115,7 +117,10 @@ namespace Avalonia.Controls.Presenters
                     }
                 }
 
-                availableSize = availableSize.WithWidth(double.PositiveInfinity);
+                if (scrollable.CanHorizontallyScroll)
+                {
+                    availableSize = availableSize.WithWidth(double.PositiveInfinity);
+                }
             }
             else
             {
@@ -127,7 +132,10 @@ namespace Avalonia.Controls.Presenters
                     }
                 }
 
-                availableSize = availableSize.WithHeight(double.PositiveInfinity);
+                if (scrollable.CanVerticallyScroll)
+                {
+                    availableSize = availableSize.WithHeight(double.PositiveInfinity);
+                }
             }
 
             Owner.Panel.Measure(availableSize);

+ 27 - 0
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@@ -23,6 +23,8 @@ namespace Avalonia.Controls.Presenters
                 defaultValue: ItemVirtualizationMode.None);
 
         private ItemVirtualizer _virtualizer;
+        private bool _canHorizontallyScroll;
+        private bool _canVerticallyScroll;
 
         /// <summary>
         /// Initializes static members of the <see cref="ItemsPresenter"/> class.
@@ -46,6 +48,31 @@ namespace Avalonia.Controls.Presenters
             set { SetValue(VirtualizationModeProperty, value); }
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        bool ILogicalScrollable.CanHorizontallyScroll
+        {
+            get { return _canHorizontallyScroll; }
+            set
+            {
+                _canHorizontallyScroll = value;
+                InvalidateMeasure();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        bool ILogicalScrollable.CanVerticallyScroll
+        {
+            get { return _canVerticallyScroll; }
+            set
+            {
+                _canVerticallyScroll = value;
+                InvalidateMeasure();
+            }
+        }
         /// <inheritdoc/>
         bool ILogicalScrollable.IsLogicalScrollEnabled
         {

+ 61 - 56
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@@ -17,6 +17,24 @@ namespace Avalonia.Controls.Presenters
     /// </summary>
     public class ScrollContentPresenter : ContentPresenter, IPresenter, IScrollable
     {
+        /// <summary>
+        /// Defines the <see cref="CanHorizontallyScroll"/> property.
+        /// </summary>
+        public static readonly DirectProperty<ScrollContentPresenter, bool> CanHorizontallyScrollProperty =
+            AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
+                nameof(CanHorizontallyScroll),
+                o => o.CanHorizontallyScroll,
+                (o, v) => o.CanHorizontallyScroll = v);
+
+        /// <summary>
+        /// Defines the <see cref="CanVerticallyScroll"/> property.
+        /// </summary>
+        public static readonly DirectProperty<ScrollContentPresenter, bool> CanVerticallyScrollProperty =
+            AvaloniaProperty.RegisterDirect<ScrollContentPresenter, bool>(
+                nameof(CanVerticallyScroll),
+                o => o.CanVerticallyScroll,
+                (o, v) => o.CanVerticallyScroll = v);
+
         /// <summary>
         /// Defines the <see cref="Extent"/> property.
         /// </summary>
@@ -41,14 +59,9 @@ namespace Avalonia.Controls.Presenters
                 o => o.Viewport,
                 (o, v) => o.Viewport = v);
 
-        /// <summary>
-        /// Defines the <see cref="CanScrollHorizontally"/> property.
-        /// </summary>
-        public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
-            ScrollViewer.CanScrollHorizontallyProperty.AddOwner<ScrollContentPresenter>();
-
+        private bool _canHorizontallyScroll;
+        private bool _canVerticallyScroll;
         private Size _extent;
-        private Size _measuredExtent;
         private Vector _offset;
         private IDisposable _logicalScrollSubscription;
         private Size _viewport;
@@ -73,6 +86,24 @@ namespace Avalonia.Controls.Presenters
             this.GetObservable(ChildProperty).Subscribe(UpdateScrollableSubscription);
         }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        public bool CanHorizontallyScroll
+        {
+            get { return _canHorizontallyScroll; }
+            set { SetAndRaise(CanHorizontallyScrollProperty, ref _canHorizontallyScroll, value); }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        public bool CanVerticallyScroll
+        {
+            get { return _canVerticallyScroll; }
+            set { SetAndRaise(CanVerticallyScrollProperty, ref _canVerticallyScroll, value); }
+        }
+
         /// <summary>
         /// Gets the extent of the scrollable content.
         /// </summary>
@@ -100,11 +131,6 @@ namespace Avalonia.Controls.Presenters
             private set { SetAndRaise(ViewportProperty, ref _viewport, value); }
         }
 
-        /// <summary>
-        /// Gets a value indicating whether the content can be scrolled horizontally.
-        /// </summary>
-        public bool CanScrollHorizontally => GetValue(CanScrollHorizontallyProperty);
-
         /// <summary>
         /// Attempts to bring a portion of the target visual into view by scrolling the content.
         /// </summary>
@@ -172,60 +198,34 @@ namespace Avalonia.Controls.Presenters
         /// <inheritdoc/>
         protected override Size MeasureOverride(Size availableSize)
         {
-            var child = Child;
-
-            if (child != null)
+            if (_logicalScrollSubscription != null || Child == null)
             {
-                var measureSize = availableSize;
-
-                if (_logicalScrollSubscription == null)
-                {
-                    measureSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
+                return base.MeasureOverride(availableSize);
+            }
 
-                    if (!CanScrollHorizontally)
-                    {
-                        measureSize = measureSize.WithWidth(availableSize.Width);
-                    }
-                }
+            var constraint = new Size(
+                CanHorizontallyScroll ? double.PositiveInfinity : availableSize.Width,
+                CanVerticallyScroll ? double.PositiveInfinity : availableSize.Height);
 
-                child.Measure(measureSize);
-                var size = child.DesiredSize;
-                _measuredExtent = size;
-                return size.Constrain(availableSize);
-            }
-            else
-            {
-                return Extent = new Size();
-            }
+            Child.Measure(constraint);
+            return Child.DesiredSize.Constrain(availableSize);
         }
 
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
         {
-            var child = this.GetVisualChildren().SingleOrDefault() as ILayoutable;
-            var logicalScroll = _logicalScrollSubscription != null;
-
-            if (!logicalScroll)
-            {
-                Viewport = finalSize;
-                Extent = _measuredExtent;
-
-                if (child != null)
-                {
-                    var size = new Size(
-                    Math.Max(finalSize.Width, child.DesiredSize.Width),
-                    Math.Max(finalSize.Height, child.DesiredSize.Height));
-                    child.Arrange(new Rect((Point)(-Offset), size));
-                    return finalSize;
-                }
-            }
-            else if (child != null)
+            if (_logicalScrollSubscription != null || Child == null)
             {
-                child.Arrange(new Rect(finalSize));
-                return finalSize;
+                return base.ArrangeOverride(finalSize);
             }
 
-            return new Size();
+            var size = new Size(
+                CanHorizontallyScroll ? Math.Max(Child.DesiredSize.Width, finalSize.Width) : finalSize.Width,
+                CanVerticallyScroll ? Math.Max(Child.DesiredSize.Height, finalSize.Height) : finalSize.Height);
+            ArrangeOverrideImpl(size, -Offset);
+            Viewport = finalSize;
+            Extent = Child.Bounds.Size;
+            return finalSize;
         }
 
         /// <inheritdoc/>
@@ -289,7 +289,12 @@ namespace Avalonia.Controls.Presenters
                 if (scrollable.IsLogicalScrollEnabled == true)
                 {
                     _logicalScrollSubscription = new CompositeDisposable(
-                        this.GetObservable(OffsetProperty).Skip(1).Subscribe(x => scrollable.Offset = x),
+                        this.GetObservable(CanHorizontallyScrollProperty)
+                            .Subscribe(x => scrollable.CanHorizontallyScroll = x),
+                        this.GetObservable(CanVerticallyScrollProperty)
+                            .Subscribe(x => scrollable.CanVerticallyScroll = x),
+                        this.GetObservable(OffsetProperty)
+                            .Skip(1).Subscribe(x => scrollable.Offset = x),
                         Disposable.Create(() => scrollable.InvalidateScroll = null));
                     UpdateFromScrollable(scrollable);
                 }

+ 1 - 1
src/Avalonia.Controls/Presenters/TextPresenter.cs

@@ -191,7 +191,7 @@ namespace Avalonia.Controls.Presenters
                     // The measure is currently invalid so there's no point trying to bring the 
                     // current char into view until a measure has been carried out as the scroll
                     // viewer extents may not be up-to-date.
-                    Dispatcher.UIThread.InvokeAsync(
+                    Dispatcher.UIThread.Post(
                         () =>
                         {
                             var rect = FormattedText.HitTestTextPosition(caretIndex);

+ 16 - 4
src/Avalonia.Controls/Primitives/AdornerLayer.cs

@@ -18,8 +18,6 @@ namespace Avalonia.Controls.Primitives
         private static readonly AttachedProperty<AdornedElementInfo> s_adornedElementInfoProperty =
             AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo>("AdornedElementInfo");
 
-        private readonly BoundsTracker _tracker = new BoundsTracker();
-
         static AdornerLayer()
         {
             AdornedElementProperty.Changed.Subscribe(AdornedElementChanged);
@@ -55,12 +53,13 @@ namespace Avalonia.Controls.Primitives
 
             foreach (var child in Children)
             {
-                var info = (AdornedElementInfo)child.GetValue(s_adornedElementInfoProperty);
+                var info = child.GetValue(s_adornedElementInfoProperty);
 
                 if (info != null && info.Bounds.HasValue)
                 {
                     child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform);
                     child.RenderTransformOrigin = new RelativePoint(new Point(0,0), RelativeUnit.Absolute);
+                    UpdateClip(child, info.Bounds.Value);
                     child.Arrange(info.Bounds.Value.Bounds);
                 }
                 else
@@ -80,6 +79,19 @@ namespace Avalonia.Controls.Primitives
             layer?.UpdateAdornedElement(adorner, adorned);
         }
 
+        private void UpdateClip(IControl control, TransformedBounds bounds)
+        {
+            var clip = control.Clip as RectangleGeometry;
+
+            if (clip == null)
+            {
+                clip = new RectangleGeometry { Transform = new MatrixTransform() };
+                control.Clip = clip;
+            }
+
+            clip.Rect = bounds.Clip.TransformToAABB(-bounds.Transform);
+        }
+
         private void ChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
         {
             switch (e.Action)
@@ -118,7 +130,7 @@ namespace Avalonia.Controls.Primitives
                     adorner.SetValue(s_adornedElementInfoProperty, info);
                 }
 
-                info.Subscription = _tracker.Track(adorned).Subscribe(x =>
+                info.Subscription = adorned.GetObservable(TransformedBoundsProperty).Subscribe(x =>
                 {
                     info.Bounds = x;
                     InvalidateArrange();

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

@@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="Header"/> property.
         /// </summary>
         public static readonly StyledProperty<object> HeaderProperty =
-            AvaloniaProperty.Register<ContentControl, object>("Header");
+            AvaloniaProperty.Register<ContentControl, object>(nameof(Header));
 
         /// <summary>
         /// Gets or sets the header content.

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

@@ -19,6 +19,16 @@ namespace Avalonia.Controls.Primitives
     /// </remarks>
     public interface ILogicalScrollable : IScrollable
     {
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        bool CanHorizontallyScroll { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the content can be scrolled horizontally.
+        /// </summary>
+        bool CanVerticallyScroll { get; set; }
+
         /// <summary>
         /// Gets a value indicating whether logical scrolling is enabled on the control.
         /// </summary>

+ 1 - 0
src/Avalonia.Controls/Primitives/ScrollBar.cs

@@ -99,6 +99,7 @@ namespace Avalonia.Controls.Primitives
                 case ScrollBarVisibility.Visible:
                     return true;
 
+                case ScrollBarVisibility.Disabled:
                 case ScrollBarVisibility.Hidden:
                     return false;
 

+ 2 - 1
src/Avalonia.Controls/Primitives/ScrollBarVisibility.cs

@@ -5,8 +5,9 @@ namespace Avalonia.Controls.Primitives
 {
     public enum ScrollBarVisibility
     {
+        Disabled,
         Auto,
-        Visible,
         Hidden,
+        Visible,
     }
 }

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

@@ -75,7 +75,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="Template"/> property.
         /// </summary>
         public static readonly StyledProperty<IControlTemplate> TemplateProperty =
-            AvaloniaProperty.Register<TemplatedControl, IControlTemplate>("Template");
+            AvaloniaProperty.Register<TemplatedControl, IControlTemplate>(nameof(Template));
 
         /// <summary>
         /// Defines the IsTemplateFocusTarget attached property.

+ 5 - 3
src/Avalonia.Controls/Primitives/Thumb.cs

@@ -11,13 +11,13 @@ namespace Avalonia.Controls.Primitives
     public class Thumb : TemplatedControl
     {
         public static readonly RoutedEvent<VectorEventArgs> DragStartedEvent =
-            RoutedEvent.Register<Thumb, VectorEventArgs>("DragStarted", RoutingStrategies.Bubble);
+            RoutedEvent.Register<Thumb, VectorEventArgs>(nameof(DragStarted), RoutingStrategies.Bubble);
 
         public static readonly RoutedEvent<VectorEventArgs> DragDeltaEvent =
-            RoutedEvent.Register<Thumb, VectorEventArgs>("DragDelta", RoutingStrategies.Bubble);
+            RoutedEvent.Register<Thumb, VectorEventArgs>(nameof(DragDelta), RoutingStrategies.Bubble);
 
         public static readonly RoutedEvent<VectorEventArgs> DragCompletedEvent =
-            RoutedEvent.Register<Thumb, VectorEventArgs>("DragCompleted", RoutingStrategies.Bubble);
+            RoutedEvent.Register<Thumb, VectorEventArgs>(nameof(DragCompleted), RoutingStrategies.Bubble);
 
         private Point? _lastPoint;
 
@@ -75,6 +75,7 @@ namespace Avalonia.Controls.Primitives
         protected override void OnPointerPressed(PointerPressedEventArgs e)
         {
             e.Device.Capture(this);
+            e.Handled = true;
             _lastPoint = e.GetPosition(this);
 
             var ev = new VectorEventArgs
@@ -91,6 +92,7 @@ namespace Avalonia.Controls.Primitives
             if (_lastPoint.HasValue)
             {
                 e.Device.Capture(null);
+                e.Handled = true;
                 _lastPoint = null;
 
                 var ev = new VectorEventArgs

+ 1 - 0
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -14,6 +14,7 @@ namespace Avalonia.Controls.Primitives
                 nameof(IsChecked),
                 o => o.IsChecked,
                 (o, v) => o.IsChecked = v,
+                unsetValue: false,
                 defaultBindingMode: BindingMode.TwoWay);
 
         public static readonly StyledProperty<bool> IsThreeStateProperty =

+ 1 - 1
src/Avalonia.Controls/Remote/RemoteWidget.cs

@@ -18,7 +18,7 @@ namespace Avalonia.Controls.Remote
         public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
         {
             _connection = connection;
-            _connection.OnMessage += (t, msg) => Dispatcher.UIThread.InvokeAsync(() => OnMessage(msg));
+            _connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg));
             _connection.Send(new ClientSupportedPixelFormatsMessage
             {
                 Formats = new[]

+ 5 - 5
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@@ -46,16 +46,16 @@ namespace Avalonia.Controls.Remote.Server
                     {
                         _lastReceivedFrame = lastFrame.SequenceId;
                     }
-                    Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
+                    Dispatcher.UIThread.Post(RenderIfNeeded);
                 }
                 if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
                 {
                     lock (_lock)
                         _supportedFormats = supportedFormats.Formats;
-                    Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
+                    Dispatcher.UIThread.Post(RenderIfNeeded);
                 }
                 if (obj is MeasureViewportMessage measure)
-                    Dispatcher.UIThread.InvokeAsync(() =>
+                    Dispatcher.UIThread.Post(() =>
                     {
                         var m = Measure(new Size(measure.Width, measure.Height));
                         _transport.Send(new MeasureViewportMessage
@@ -69,7 +69,7 @@ namespace Avalonia.Controls.Remote.Server
                     lock (_lock)
                     {
                         if (_pendingAllocation == null)
-                            Dispatcher.UIThread.InvokeAsync(() =>
+                            Dispatcher.UIThread.Post(() =>
                             {
                                 ClientViewportAllocatedMessage allocation;
                                 lock (_lock)
@@ -168,7 +168,7 @@ namespace Avalonia.Controls.Remote.Server
         public override void Invalidate(Rect rect)
         {
             _invalidated = true;
-            Dispatcher.UIThread.InvokeAsync(RenderIfNeeded);
+            Dispatcher.UIThread.Post(RenderIfNeeded);
         }
 
         public override IMouseDevice MouseDevice { get; } = new MouseDevice();

+ 3 - 3
src/Avalonia.Controls/RowDefinition.cs

@@ -12,19 +12,19 @@ namespace Avalonia.Controls
         /// Defines the <see cref="MaxHeight"/> property.
         /// </summary>
         public static readonly StyledProperty<double> MaxHeightProperty =
-            AvaloniaProperty.Register<RowDefinition, double>("MaxHeight", double.PositiveInfinity);
+            AvaloniaProperty.Register<RowDefinition, double>(nameof(MaxHeight), double.PositiveInfinity);
 
         /// <summary>
         /// Defines the <see cref="MinHeight"/> property.
         /// </summary>
         public static readonly StyledProperty<double> MinHeightProperty =
-            AvaloniaProperty.Register<RowDefinition, double>("MinHeight");
+            AvaloniaProperty.Register<RowDefinition, double>(nameof(MinHeight));
 
         /// <summary>
         /// Defines the <see cref="Height"/> property.
         /// </summary>
         public static readonly StyledProperty<GridLength> HeightProperty =
-            AvaloniaProperty.Register<RowDefinition, GridLength>("Height", new GridLength(1, GridUnitType.Star));
+            AvaloniaProperty.Register<RowDefinition, GridLength>(nameof(Height), new GridLength(1, GridUnitType.Star));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="RowDefinition"/> class.

+ 71 - 26
src/Avalonia.Controls/ScrollViewer.cs

@@ -2,8 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Linq;
-using System.Reactive.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 
@@ -15,16 +13,34 @@ namespace Avalonia.Controls
     public class ScrollViewer : ContentControl, IScrollable
     {
         /// <summary>
-        /// Defines the <see cref="CanScrollHorizontally"/> property.
+        /// Defines the <see cref="CanHorizontallyScroll"/> property.
         /// </summary>
-        public static readonly StyledProperty<bool> CanScrollHorizontallyProperty =
-            AvaloniaProperty.Register<ScrollViewer, bool>(nameof(CanScrollHorizontally), true);
+        /// <remarks>
+        /// There is no public C# accessor for this property as it is intended to be bound to by a 
+        /// <see cref="ScrollContentPresenter"/> in the control's template.
+        /// </remarks>
+        public static readonly DirectProperty<ScrollViewer, bool> CanHorizontallyScrollProperty =
+            AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
+                nameof(CanHorizontallyScroll),
+                o => o.CanHorizontallyScroll);
+
+        /// <summary>
+        /// Defines the <see cref="CanVerticallyScroll"/> property.
+        /// </summary>
+        /// <remarks>
+        /// There is no public C# accessor for this property as it is intended to be bound to by a 
+        /// <see cref="ScrollContentPresenter"/> in the control's template.
+        /// </remarks>
+        public static readonly DirectProperty<ScrollViewer, bool> CanVerticallyScrollProperty =
+            AvaloniaProperty.RegisterDirect<ScrollViewer, bool>(
+                nameof(CanVerticallyScroll),
+                o => o.CanVerticallyScroll);
 
         /// <summary>
         /// Defines the <see cref="Extent"/> property.
         /// </summary>
         public static readonly DirectProperty<ScrollViewer, Size> ExtentProperty =
-            AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent), 
+            AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Extent),
                 o => o.Extent,
                 (o, v) => o.Extent = v);
 
@@ -41,7 +57,7 @@ namespace Avalonia.Controls
         /// Defines the <see cref="Viewport"/> property.
         /// </summary>
         public static readonly DirectProperty<ScrollViewer, Size> ViewportProperty =
-            AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport), 
+            AvaloniaProperty.RegisterDirect<ScrollViewer, Size>(nameof(Viewport),
                 o => o.Viewport,
                 (o, v) => o.Viewport = v);
 
@@ -85,14 +101,10 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="HorizontalScrollBarVisibility"/> property.
         /// </summary>
-        /// <remarks>
-        /// There is no public C# accessor for this property as it is intended to be bound to by a 
-        /// <see cref="ScrollContentPresenter"/> in the control's template.
-        /// </remarks>
         public static readonly AttachedProperty<ScrollBarVisibility> HorizontalScrollBarVisibilityProperty =
             AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
                 nameof(HorizontalScrollBarVisibility),
-                ScrollBarVisibility.Auto);
+                ScrollBarVisibility.Hidden);
 
         /// <summary>
         /// Defines the VerticalScrollBarMaximum property.
@@ -136,7 +148,7 @@ namespace Avalonia.Controls
         /// </summary>
         public static readonly AttachedProperty<ScrollBarVisibility> VerticalScrollBarVisibilityProperty =
             AvaloniaProperty.RegisterAttached<ScrollViewer, Control, ScrollBarVisibility>(
-                nameof(VerticalScrollBarVisibility), 
+                nameof(VerticalScrollBarVisibility),
                 ScrollBarVisibility.Auto);
 
         private Size _extent;
@@ -150,6 +162,8 @@ namespace Avalonia.Controls
         {
             AffectsValidation(ExtentProperty, OffsetProperty);
             AffectsValidation(ViewportProperty, OffsetProperty);
+            HorizontalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
+            VerticalScrollBarVisibilityProperty.Changed.AddClassHandler<ScrollViewer>(x => x.ScrollBarVisibilityChanged);
         }
 
         /// <summary>
@@ -218,15 +232,6 @@ namespace Avalonia.Controls
             }
         }
 
-        /// <summary>
-        /// Gets a value indicating whether the content can be scrolled horizontally.
-        /// </summary>
-        public bool CanScrollHorizontally
-        {
-            get { return GetValue(CanScrollHorizontallyProperty); }
-            set { SetValue(CanScrollHorizontallyProperty, value); }
-        }
-
         /// <summary>
         /// Gets or sets the horizontal scrollbar visibility.
         /// </summary>
@@ -245,6 +250,22 @@ namespace Avalonia.Controls
             set { SetValue(VerticalScrollBarVisibilityProperty, value); }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether the viewer can scroll horizontally.
+        /// </summary>
+        protected bool CanHorizontallyScroll
+        {
+            get { return HorizontalScrollBarVisibility != ScrollBarVisibility.Disabled; }
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether the viewer can scroll vertically.
+        /// </summary>
+        protected bool CanVerticallyScroll
+        {
+            get { return VerticalScrollBarVisibility != ScrollBarVisibility.Disabled; }
+        }
+
         /// <summary>
         /// Gets the maximum horizontal scrollbar value.
         /// </summary>
@@ -316,7 +337,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="control">The control to read the value from.</param>
         /// <returns>The value of the property.</returns>
-        public ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control)
+        public static ScrollBarVisibility GetHorizontalScrollBarVisibility(Control control)
         {
             return control.GetValue(HorizontalScrollBarVisibilityProperty);
         }
@@ -326,7 +347,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="control">The control to set the value on.</param>
         /// <param name="value">The value of the property.</param>
-        public void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value)
+        public static void SetHorizontalScrollBarVisibility(Control control, ScrollBarVisibility value)
         {
             control.SetValue(HorizontalScrollBarVisibilityProperty, value);
         }
@@ -336,7 +357,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="control">The control to read the value from.</param>
         /// <returns>The value of the property.</returns>
-        public ScrollBarVisibility GetVerticalScrollBarVisibility(Control control)
+        public static ScrollBarVisibility GetVerticalScrollBarVisibility(Control control)
         {
             return control.GetValue(VerticalScrollBarVisibilityProperty);
         }
@@ -346,7 +367,7 @@ namespace Avalonia.Controls
         /// </summary>
         /// <param name="control">The control to set the value on.</param>
         /// <param name="value">The value of the property.</param>
-        public void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value)
+        public static void SetVerticalScrollBarVisibility(Control control, ScrollBarVisibility value)
         {
             control.SetValue(VerticalScrollBarVisibilityProperty, value);
         }
@@ -385,6 +406,30 @@ namespace Avalonia.Controls
             }
         }
 
+        private void ScrollBarVisibilityChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var wasEnabled = !ScrollBarVisibility.Disabled.Equals(e.OldValue);
+            var isEnabled = !ScrollBarVisibility.Disabled.Equals(e.NewValue);
+
+            if (wasEnabled != isEnabled)
+            {
+                if (e.Property == HorizontalScrollBarVisibilityProperty)
+                {
+                    RaisePropertyChanged(
+                        CanHorizontallyScrollProperty,
+                        wasEnabled,
+                        isEnabled);
+                }
+                else if (e.Property == VerticalScrollBarVisibilityProperty)
+                {
+                    RaisePropertyChanged(
+                        CanVerticallyScrollProperty,
+                        wasEnabled,
+                        isEnabled);
+                }
+            }
+        }
+
         private void CalculatedPropertiesChanged()
         {
             // Pass old values of 0 here because we don't have the old values at this point,

+ 2 - 2
src/Avalonia.Controls/Shapes/Line.cs

@@ -8,10 +8,10 @@ namespace Avalonia.Controls.Shapes
     public class Line : Shape
     {
         public static readonly StyledProperty<Point> StartPointProperty =
-            AvaloniaProperty.Register<Line, Point>("StartPoint");
+            AvaloniaProperty.Register<Line, Point>(nameof(StartPoint));
 
         public static readonly StyledProperty<Point> EndPointProperty =
-            AvaloniaProperty.Register<Line, Point>("EndPoint");
+            AvaloniaProperty.Register<Line, Point>(nameof(EndPoint));
 
         static Line()
         {

+ 1 - 1
src/Avalonia.Controls/Shapes/Path.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Controls.Shapes
     public class Path : Shape
     {
         public static readonly StyledProperty<Geometry> DataProperty =
-            AvaloniaProperty.Register<Path, Geometry>("Data");
+            AvaloniaProperty.Register<Path, Geometry>(nameof(Data));
 
         static Path()
         {

+ 22 - 7
src/Avalonia.Controls/Shapes/Shape.cs

@@ -12,19 +12,19 @@ namespace Avalonia.Controls.Shapes
     public abstract class Shape : Control
     {
         public static readonly StyledProperty<IBrush> FillProperty =
-            AvaloniaProperty.Register<Shape, IBrush>("Fill");
+            AvaloniaProperty.Register<Shape, IBrush>(nameof(Fill));
 
         public static readonly StyledProperty<Stretch> StretchProperty =
-            AvaloniaProperty.Register<Shape, Stretch>("Stretch");
+            AvaloniaProperty.Register<Shape, Stretch>(nameof(Stretch));
 
         public static readonly StyledProperty<IBrush> StrokeProperty =
-            AvaloniaProperty.Register<Shape, IBrush>("Stroke");
+            AvaloniaProperty.Register<Shape, IBrush>(nameof(Stroke));
 
         public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
             AvaloniaProperty.Register<Shape, AvaloniaList<double>>("StrokeDashArray");
 
         public static readonly StyledProperty<double> StrokeThicknessProperty =
-            AvaloniaProperty.Register<Shape, double>("StrokeThickness");
+            AvaloniaProperty.Register<Shape, double>(nameof(StrokeThickness));
 
         private Matrix _transform = Matrix.Identity;
         private Geometry _definingGeometry;
@@ -61,12 +61,26 @@ namespace Avalonia.Controls.Shapes
         {
             get
             {
-                if (_renderedGeometry == null)
+                if (_renderedGeometry == null && DefiningGeometry != null)
                 {
-                    if (DefiningGeometry != null)
+                    if (_transform == Matrix.Identity)
+                    {
+                        _renderedGeometry = DefiningGeometry;
+                    }
+                    else
                     {
                         _renderedGeometry = DefiningGeometry.Clone();
-                        _renderedGeometry.Transform = new MatrixTransform(_transform);
+
+                        if (_renderedGeometry.Transform == null ||
+                            _renderedGeometry.Transform.Value == Matrix.Identity)
+                        {
+                            _renderedGeometry.Transform = new MatrixTransform(_transform);
+                        }
+                        else
+                        {
+                            _renderedGeometry.Transform = new MatrixTransform(
+                                _renderedGeometry.Transform.Value * _transform);
+                        }
                     }
                 }
 
@@ -193,6 +207,7 @@ namespace Avalonia.Controls.Shapes
 
             return finalSize;
         }
+
         private Size CalculateShapeSizeAndSetTransform(Size availableSize)
         {
             // This should probably use GetRenderBounds(strokeThickness) but then the calculations

+ 71 - 98
src/Avalonia.Controls/TextBox.cs

@@ -8,7 +8,6 @@ using System.Linq;
 using System.Reactive.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
-using Avalonia.Controls.Templates;
 using Avalonia.Controls.Utils;
 using Avalonia.Input;
 using Avalonia.Interactivity;
@@ -21,25 +20,17 @@ namespace Avalonia.Controls
     public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
     {
         public static readonly StyledProperty<bool> AcceptsReturnProperty =
-            AvaloniaProperty.Register<TextBox, bool>("AcceptsReturn");
+            AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
 
         public static readonly StyledProperty<bool> AcceptsTabProperty =
-            AvaloniaProperty.Register<TextBox, bool>("AcceptsTab");
-
-        public static readonly DirectProperty<TextBox, bool> CanScrollHorizontallyProperty =
-            AvaloniaProperty.RegisterDirect<TextBox, bool>("CanScrollHorizontally", o => o.CanScrollHorizontally);
+            AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsTab));
 
         public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
             AvaloniaProperty.RegisterDirect<TextBox, int>(
                 nameof(CaretIndex),
                 o => o.CaretIndex,
                 (o, v) => o.CaretIndex = v);
-
-        public static readonly DirectProperty<TextBox, IEnumerable<Exception>> DataValidationErrorsProperty =
-            AvaloniaProperty.RegisterDirect<TextBox, IEnumerable<Exception>>(
-                nameof(DataValidationErrors),
-                o => o.DataValidationErrors);
-
+        
         public static readonly StyledProperty<bool> IsReadOnlyProperty =
             AvaloniaProperty.Register<TextBox, bool>(nameof(IsReadOnly));
 
@@ -69,10 +60,10 @@ namespace Avalonia.Controls
             TextBlock.TextWrappingProperty.AddOwner<TextBox>();
 
         public static readonly StyledProperty<string> WatermarkProperty =
-            AvaloniaProperty.Register<TextBox, string>("Watermark");
+            AvaloniaProperty.Register<TextBox, string>(nameof(Watermark));
 
         public static readonly StyledProperty<bool> UseFloatingWatermarkProperty =
-            AvaloniaProperty.Register<TextBox, bool>("UseFloatingWatermark");
+            AvaloniaProperty.Register<TextBox, bool>(nameof(UseFloatingWatermark));
 
         struct UndoRedoState : IEquatable<UndoRedoState>
         {
@@ -92,11 +83,9 @@ namespace Avalonia.Controls
         private int _caretIndex;
         private int _selectionStart;
         private int _selectionEnd;
-        private bool _canScrollHorizontally;
         private TextPresenter _presenter;
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
         private bool _ignoreTextChanges;
-        private IEnumerable<Exception> _dataValidationErrors;
         private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
 
         static TextBox()
@@ -106,13 +95,22 @@ namespace Avalonia.Controls
 
         public TextBox()
         {
-            this.GetObservable(TextWrappingProperty)
-                .Select(x => x == TextWrapping.NoWrap)
-                .Subscribe(x => CanScrollHorizontally = x);
-
-            var horizontalScrollBarVisibility = this.GetObservable(AcceptsReturnProperty)
-                .Select(x => x ? ScrollBarVisibility.Auto : ScrollBarVisibility.Hidden);
-
+            var horizontalScrollBarVisibility = Observable.CombineLatest(
+                this.GetObservable(AcceptsReturnProperty),
+                this.GetObservable(TextWrappingProperty),
+                (acceptsReturn, wrapping) =>
+                {
+                    if (acceptsReturn)
+                    {
+                        return wrapping == TextWrapping.NoWrap ?
+                            ScrollBarVisibility.Auto :
+                            ScrollBarVisibility.Disabled;
+                    }
+                    else
+                    {
+                        return ScrollBarVisibility.Hidden;
+                    }
+                });
             Bind(
                 ScrollViewer.HorizontalScrollBarVisibilityProperty,
                 horizontalScrollBarVisibility,
@@ -132,12 +130,6 @@ namespace Avalonia.Controls
             set { SetValue(AcceptsTabProperty, value); }
         }
 
-        public bool CanScrollHorizontally
-        {
-            get { return _canScrollHorizontally; }
-            private set { SetAndRaise(CanScrollHorizontallyProperty, ref _canScrollHorizontally, value); }
-        }
-
         public int CaretIndex
         {
             get
@@ -154,13 +146,7 @@ namespace Avalonia.Controls
                     _undoRedoHelper.UpdateLastState();
             }
         }
-
-        public IEnumerable<Exception> DataValidationErrors
-        {
-            get { return _dataValidationErrors; }
-            private set { SetAndRaise(DataValidationErrorsProperty, ref _dataValidationErrors, value); }
-        }
-
+        
         public bool IsReadOnly
         {
             get { return GetValue(IsReadOnlyProperty); }
@@ -337,7 +323,7 @@ namespace Avalonia.Controls
             string text = Text ?? string.Empty;
             int caretIndex = CaretIndex;
             bool movement = false;
-            bool handled = true;
+            bool handled = false;
             var modifiers = e.Modifiers;
 
             switch (e.Key)
@@ -346,13 +332,14 @@ namespace Avalonia.Controls
                     if (modifiers == InputModifiers.Control)
                     {
                         SelectAll();
+                        handled = true;
                     }
-
                     break;
                 case Key.C:
                     if (modifiers == InputModifiers.Control)
                     {
                         Copy();
+                        handled = true;
                     }
                     break;
 
@@ -361,6 +348,7 @@ namespace Avalonia.Controls
                     {
                         Copy();
                         DeleteSelection();
+                        handled = true;
                     }
                     break;
 
@@ -368,19 +356,24 @@ namespace Avalonia.Controls
                     if (modifiers == InputModifiers.Control)
                     {
                         Paste();
+                        handled = true;
                     }
 
                     break;
 
                 case Key.Z:
                     if (modifiers == InputModifiers.Control)
+                    {
                         _undoRedoHelper.Undo();
-
+                        handled = true;
+                    }
                     break;
                 case Key.Y:
                     if (modifiers == InputModifiers.Control)
+                    {
                         _undoRedoHelper.Redo();
-
+                        handled = true;
+                    }
                     break;
                 case Key.Left:
                     MoveHorizontal(-1, modifiers);
@@ -393,13 +386,11 @@ namespace Avalonia.Controls
                     break;
 
                 case Key.Up:
-                    MoveVertical(-1, modifiers);
-                    movement = true;
+                    movement = MoveVertical(-1, modifiers);
                     break;
 
                 case Key.Down:
-                    MoveVertical(1, modifiers);
-                    movement = true;
+                    movement = MoveVertical(1, modifiers);
                     break;
 
                 case Key.Home:
@@ -435,7 +426,7 @@ namespace Avalonia.Controls
                         CaretIndex -= removedCharacters;
                         SelectionStart = SelectionEnd = CaretIndex;
                     }
-
+                    handled = true;
                     break;
 
                 case Key.Delete:
@@ -459,13 +450,14 @@ namespace Avalonia.Controls
 
                         SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
                     }
-
+                    handled = true;
                     break;
 
                 case Key.Enter:
                     if (AcceptsReturn)
                     {
                         HandleTextInput("\r\n");
+                        handled = true;
                     }
 
                     break;
@@ -474,11 +466,11 @@ namespace Avalonia.Controls
                     if (AcceptsTab)
                     {
                         HandleTextInput("\t");
+                        handled = true;
                     }
                     else
                     {
                         base.OnKeyDown(e);
-                        handled = false;
                     }
 
                     break;
@@ -497,7 +489,7 @@ namespace Avalonia.Controls
                 SelectionStart = SelectionEnd = CaretIndex;
             }
 
-            if (handled)
+            if (handled || movement)
             {
                 e.Handled = true;
             }
@@ -505,37 +497,34 @@ namespace Avalonia.Controls
 
         protected override void OnPointerPressed(PointerPressedEventArgs e)
         {
-            if (e.Source == _presenter)
-            {
-                var point = e.GetPosition(_presenter);
-                var index = CaretIndex = _presenter.GetCaretIndex(point);
-                var text = Text;
+            var point = e.GetPosition(_presenter);
+            var index = CaretIndex = _presenter.GetCaretIndex(point);
+            var text = Text;
 
-                if (text != null)
+            if (text != null)
+            {
+                switch (e.ClickCount)
                 {
-                    switch (e.ClickCount)
-                    {
-                        case 1:
-                            SelectionStart = SelectionEnd = index;
-                            break;
-                        case 2:
-                            if (!StringUtils.IsStartOfWord(text, index))
-                            {
-                                SelectionStart = StringUtils.PreviousWord(text, index);
-                            }
+                    case 1:
+                        SelectionStart = SelectionEnd = index;
+                        break;
+                    case 2:
+                        if (!StringUtils.IsStartOfWord(text, index))
+                        {
+                            SelectionStart = StringUtils.PreviousWord(text, index);
+                        }
 
-                            SelectionEnd = StringUtils.NextWord(text, index);
-                            break;
-                        case 3:
-                            SelectionStart = 0;
-                            SelectionEnd = text.Length;
-                            break;
-                    }
+                        SelectionEnd = StringUtils.NextWord(text, index);
+                        break;
+                    case 3:
+                        SelectionStart = 0;
+                        SelectionEnd = text.Length;
+                        break;
                 }
-
-                e.Device.Capture(_presenter);
-                e.Handled = true;
             }
+
+            e.Device.Capture(_presenter);
+            e.Handled = true;
         }
 
         protected override void OnPointerMoved(PointerEventArgs e)
@@ -559,31 +548,10 @@ namespace Avalonia.Controls
         {
             if (property == TextProperty)
             {
-                var classes = (IPseudoClasses)Classes;
-                DataValidationErrors = UnpackException(status.Error);
-                classes.Set(":error", DataValidationErrors != null);
+                DataValidationErrors.SetError(this, status.Error);
             }
         }
-
-        private static IEnumerable<Exception> UnpackException(Exception exception)
-        {
-            if (exception != null)
-            {
-                var aggregate = exception as AggregateException;
-                var exceptions = aggregate == null ?
-                    (IEnumerable<Exception>)new[] { exception } :
-                    aggregate.InnerExceptions;
-                var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
-
-                if (filtered.Count > 0)
-                {
-                    return filtered;
-                }
-            }
-
-            return null;
-        }
-
+        
         private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text?.Length ?? 0);
 
         private int CoerceCaretIndex(int value, int length)
@@ -674,7 +642,7 @@ namespace Avalonia.Controls
             }
         }
 
-        private void MoveVertical(int count, InputModifiers modifiers)
+        private bool MoveVertical(int count, InputModifiers modifiers)
         {
             var formattedText = _presenter.FormattedText;
             var lines = formattedText.GetLines().ToList();
@@ -689,6 +657,11 @@ namespace Avalonia.Controls
                 var point = new Point(rect.X, y + (count * (line.Height / 2)));
                 var hit = formattedText.HitTestPoint(point);
                 CaretIndex = hit.TextPosition + (hit.IsTrailing ? 1 : 0);
+                return true;
+            }
+            else
+            {
+                return false;
             }
         }
 

+ 1 - 1
src/Avalonia.Controls/TreeView.cs

@@ -250,7 +250,7 @@ namespace Avalonia.Controls
 
                         if (AutoScrollToSelectedItem)
                         {
-                            Dispatcher.UIThread.InvokeAsync(container.ContainerControl.BringIntoView);
+                            Dispatcher.UIThread.Post(container.ContainerControl.BringIntoView);
                         }
 
                         break;

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

@@ -3,11 +3,9 @@
 
 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
 {

+ 27 - 22
src/Avalonia.Controls/Window.cs

@@ -19,27 +19,28 @@ namespace Avalonia.Controls
     /// <summary>
     /// Determines how a <see cref="Window"/> will size itself to fit its content.
     /// </summary>
+    [Flags]
     public enum SizeToContent
     {
         /// <summary>
         /// The window will not automatically size itself to fit its content.
         /// </summary>
-        Manual,
+        Manual = 0,
 
         /// <summary>
         /// The window will size itself horizontally to fit its content.
         /// </summary>
-        Width,
+        Width = 1,
 
         /// <summary>
         /// The window will size itself vertically to fit its content.
         /// </summary>
-        Height,
+        Height = 2,
 
         /// <summary>
         /// The window will size itself horizontally and vertically to fit its content.
         /// </summary>
-        WidthAndHeight,
+        WidthAndHeight = 3,
     }
 
     /// <summary>
@@ -373,28 +374,32 @@ namespace Avalonia.Controls
         protected override Size MeasureOverride(Size availableSize)
         {
             var sizeToContent = SizeToContent;
-            var size = ClientSize;
-            var desired = base.MeasureOverride(availableSize.Constrain(_maxPlatformClientSize));
+            var clientSize = ClientSize;
+            Size constraint = clientSize;
 
-            switch (sizeToContent)
+            if ((sizeToContent & SizeToContent.Width) != 0)
             {
-                case SizeToContent.Width:
-                    size = new Size(desired.Width, ClientSize.Height);
-                    break;
-                case SizeToContent.Height:
-                    size = new Size(ClientSize.Width, desired.Height);
-                    break;
-                case SizeToContent.WidthAndHeight:
-                    size = new Size(desired.Width, desired.Height);
-                    break;
-                case SizeToContent.Manual:
-                    size = ClientSize;
-                    break;
-                default:
-                    throw new InvalidOperationException("Invalid value for SizeToContent.");
+                constraint = constraint.WithWidth(double.PositiveInfinity);
             }
 
-            return size;
+            if ((sizeToContent & SizeToContent.Height) != 0)
+            {
+                constraint = constraint.WithHeight(double.PositiveInfinity);
+            }
+
+            var result = base.MeasureOverride(constraint);
+
+            if ((sizeToContent & SizeToContent.Width) == 0)
+            {
+                result = result.WithWidth(clientSize.Width);
+            }
+
+            if ((sizeToContent & SizeToContent.Height) == 0)
+            {
+                result = result.WithHeight(clientSize.Height);
+            }
+
+            return result;
         }
 
         protected override void HandleClosed()

+ 1 - 1
src/Avalonia.Controls/WindowIcon.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls
     {
         public WindowIcon(IBitmap bitmap)
         {
-            PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformIconLoader>().LoadIcon(bitmap.PlatformImpl);
+            PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformIconLoader>().LoadIcon(bitmap.PlatformImpl.Item);
         }
 
         public WindowIcon(string fileName)

+ 1 - 1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@@ -49,7 +49,7 @@ namespace Avalonia.DesignerSupport.Remote
             // In previewer mode we completely ignore client-side viewport size
             if (obj is ClientViewportAllocatedMessage alloc)
             {
-                Dispatcher.UIThread.InvokeAsync(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY)));
+                Dispatcher.UIThread.Post(() => SetDpi(new Vector(alloc.DpiX, alloc.DpiY)));
                 return;
             }
             base.OnMessage(transport, obj);

+ 46 - 12
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.Net;
 using System.Reflection;
 using Avalonia.Controls;
@@ -22,6 +23,15 @@ namespace Avalonia.DesignerSupport.Remote
         {
             public string AppPath { get; set; }
             public Uri Transport { get; set; }
+            public string Method { get; set; } = Methods.AvaloniaRemote;
+            public string SessionId { get; set; } = Guid.NewGuid().ToString();
+        }
+
+        static class Methods
+        {
+            public const string AvaloniaRemote = "avalonia-remote";
+            public const string Win32 = "win32";
+
         }
 
         static Exception Die(string error)
@@ -35,11 +45,13 @@ namespace Avalonia.DesignerSupport.Remote
             return new Exception("APPEXIT");
         }
 
+        static void Log(string message) => Console.WriteLine(message);
+
         static Exception PrintUsage()
         {
-            Console.Error.WriteLine("Usage: --transport transport_spec app");
+            Console.Error.WriteLine("Usage: --transport transport_spec --session-id sid --method method app");
             Console.Error.WriteLine();
-            Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ MyApp.exe");
+            Console.Error.WriteLine("Example: --transport tcp-bson://127.0.0.1:30243/ --session-id 123 --method avalonia-remote MyApp.exe");
             Console.Error.Flush();
             return Die(null);
         }
@@ -59,6 +71,10 @@ namespace Avalonia.DesignerSupport.Remote
                     }
                     else if (arg == "--transport")
                         next = a => rv.Transport = new Uri(a, UriKind.Absolute);
+                    else if (arg == "--method")
+                        next = a => rv.Method = a;
+                    else if (arg == "--session-id")
+                        next = a => rv.SessionId = a;
                     else if (rv.AppPath == null)
                         rv.AppPath = arg;
                     else
@@ -84,18 +100,22 @@ namespace Avalonia.DesignerSupport.Remote
             PrintUsage();
             return null;
         }
-
+        
         interface IAppInitializer
         {
-            Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj);
+            Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, CommandLineArgs args, object obj);
         }
         
         class AppInitializer<T> : IAppInitializer where T : AppBuilderBase<T>, new()
         {
-            public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport, object obj)
+            public Application GetConfiguredApp(IAvaloniaRemoteTransportConnection transport,
+                CommandLineArgs args, object obj)
             {
                 var builder = (AppBuilderBase<T>) obj;
-                builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport));
+                if (args.Method == Methods.AvaloniaRemote)
+                    builder.UseWindowingSubsystem(() => PreviewerWindowingPlatform.Initialize(transport));
+                if (args.Method == Methods.Win32)
+                    builder.UseWindowingSubsystem("Avalonia.Win32");
                 builder.SetupWithoutStarting();
                 return builder.Instance;
             }
@@ -120,13 +140,17 @@ namespace Avalonia.DesignerSupport.Remote
                 BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
             if (builderMethod == null)
                 throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
-
+            Design.IsDesignMode = true;
+            Log($"Obtaining AppBuilder instance from {builderMethod.DeclaringType.FullName}.{builderMethod.Name}");
             var appBuilder = builderMethod.Invoke(null, null);
+            Log($"Initializing application in design mode");
             var initializer =(IAppInitializer)Activator.CreateInstance(typeof(AppInitializer<>).MakeGenericType(appBuilder.GetType()));
-            var app = initializer.GetConfiguredApp(transport, appBuilder);
+            var app = initializer.GetConfiguredApp(transport, args, appBuilder);
             s_transport = transport;
             transport.OnMessage += OnTransportMessage;
             transport.OnException += (t, e) => Die(e.ToString());
+            Log("Sending StartDesignerSessionMessage");
+            transport.Send(new StartDesignerSessionMessage {SessionId = args.SessionId});
             app.Run(new NeverClose());
         }
 
@@ -139,8 +163,9 @@ namespace Avalonia.DesignerSupport.Remote
                 s_viewportAllocatedMessage
             };
         }
-        
-        private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.InvokeAsync(() =>
+
+        private static Window s_currentWindow;
+        private static void OnTransportMessage(IAvaloniaRemoteTransportConnection transport, object obj) => Dispatcher.UIThread.Post(() =>
         {
             if (obj is ClientSupportedPixelFormatsMessage formats)
             {
@@ -156,8 +181,17 @@ namespace Avalonia.DesignerSupport.Remote
             {
                 try
                 {
-                    DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath);
-                    s_transport.Send(new UpdateXamlResultMessage());
+                    s_currentWindow?.Close();
+                }
+                catch
+                {
+                    //Ignore
+                }
+                s_currentWindow = null;
+                try
+                {
+                    s_currentWindow = DesignWindowLoader.LoadDesignerWindow(xaml.Xaml, xaml.AssemblyPath);
+                    s_transport.Send(new UpdateXamlResultMessage(){Handle = s_currentWindow.PlatformImpl?.Handle?.Handle.ToString()});
                 }
                 catch (Exception e)
                 {

+ 1 - 1
src/Avalonia.Diagnostics/Views/ControlDetailsView.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.Views
     internal class ControlDetailsView : UserControl
     {
         private static readonly StyledProperty<ControlDetailsViewModel> ViewModelProperty =
-            AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>("ViewModel");
+            AvaloniaProperty.Register<ControlDetailsView, ControlDetailsViewModel>(nameof(ViewModel));
         private SimpleGrid _grid;
 
         public ControlDetailsView()

+ 8 - 8
src/Avalonia.HtmlRenderer/HtmlControl.cs

@@ -74,29 +74,29 @@ namespace Avalonia.Controls.Html
         protected Point _lastScrollOffset;
 
         public static readonly AvaloniaProperty AvoidImagesLateLoadingProperty = 
-            PropertyHelper.Register<HtmlControl, bool>("AvoidImagesLateLoading", false, OnAvaloniaProperty_valueChanged);
+            PropertyHelper.Register<HtmlControl, bool>(nameof(AvoidImagesLateLoading), false, OnAvaloniaProperty_valueChanged);
         public static readonly AvaloniaProperty IsSelectionEnabledProperty =
-            PropertyHelper.Register<HtmlControl, bool>("IsSelectionEnabled", true, OnAvaloniaProperty_valueChanged);
+            PropertyHelper.Register<HtmlControl, bool>(nameof(IsSelectionEnabled), true, OnAvaloniaProperty_valueChanged);
         public static readonly AvaloniaProperty IsContextMenuEnabledProperty =
-            PropertyHelper.Register<HtmlControl, bool>("IsContextMenuEnabled", true, OnAvaloniaProperty_valueChanged);
+            PropertyHelper.Register<HtmlControl, bool>(nameof(IsContextMenuEnabled), true, OnAvaloniaProperty_valueChanged);
 
         public static readonly AvaloniaProperty BaseStylesheetProperty =
-            PropertyHelper.Register<HtmlControl, string>("BaseStylesheet", null, OnAvaloniaProperty_valueChanged);
+            PropertyHelper.Register<HtmlControl, string>(nameof(BaseStylesheet), null, OnAvaloniaProperty_valueChanged);
 
         public static readonly AvaloniaProperty TextProperty =
-            PropertyHelper.Register<HtmlControl, string>("Text", null, OnAvaloniaProperty_valueChanged);
+            PropertyHelper.Register<HtmlControl, string>(nameof(Text), null, OnAvaloniaProperty_valueChanged);
 
         public static readonly StyledProperty<IBrush> BackgroundProperty =
             Border.BackgroundProperty.AddOwner<HtmlControl>();
 
         public static readonly AvaloniaProperty BorderThicknessProperty =
-            AvaloniaProperty.Register<HtmlControl, Thickness>("BorderThickness", new Thickness(0));
+            AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(BorderThickness), new Thickness(0));
 
         public static readonly AvaloniaProperty BorderBrushProperty =
-    AvaloniaProperty.Register<HtmlControl, IBrush>("BorderBrush");
+    AvaloniaProperty.Register<HtmlControl, IBrush>(nameof(BorderBrush));
 
         public static readonly AvaloniaProperty PaddingProperty =
-            AvaloniaProperty.Register<HtmlControl, Thickness>("Padding", new Thickness(0));
+            AvaloniaProperty.Register<HtmlControl, Thickness>(nameof(Padding), new Thickness(0));
 
         public static readonly RoutedEvent LoadCompleteEvent =
             RoutedEvent.Register<RoutedEventArgs>("LoadComplete",  RoutingStrategies.Bubble, typeof(HtmlControl));

+ 9 - 9
src/Avalonia.Input/InputElement.cs

@@ -31,43 +31,43 @@ namespace Avalonia.Input
         /// Defines the <see cref="IsEnabledCore"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> IsEnabledCoreProperty =
-            AvaloniaProperty.Register<InputElement, bool>("IsEnabledCore", true);
+            AvaloniaProperty.Register<InputElement, bool>(nameof(IsEnabledCore), true);
 
         /// <summary>
         /// Gets or sets associated mouse cursor.
         /// </summary>
         public static readonly StyledProperty<Cursor> CursorProperty =
-            AvaloniaProperty.Register<InputElement, Cursor>("Cursor", null, true);
+            AvaloniaProperty.Register<InputElement, Cursor>(nameof(Cursor), null, true);
 
         /// <summary>
         /// Defines the <see cref="IsFocused"/> property.
         /// </summary>
         public static readonly DirectProperty<InputElement, bool> IsFocusedProperty =
-            AvaloniaProperty.RegisterDirect<InputElement, bool>("IsFocused", o => o.IsFocused);
+            AvaloniaProperty.RegisterDirect<InputElement, bool>(nameof(IsFocused), o => o.IsFocused);
 
         /// <summary>
         /// Defines the <see cref="IsHitTestVisible"/> property.
         /// </summary>
         public static readonly StyledProperty<bool> IsHitTestVisibleProperty =
-            AvaloniaProperty.Register<InputElement, bool>("IsHitTestVisible", true);
+            AvaloniaProperty.Register<InputElement, bool>(nameof(IsHitTestVisible), true);
 
         /// <summary>
         /// Defines the <see cref="IsPointerOver"/> property.
         /// </summary>
         public static readonly DirectProperty<InputElement, bool> IsPointerOverProperty =
-            AvaloniaProperty.RegisterDirect<InputElement, bool>("IsPointerOver", o => o.IsPointerOver);
+            AvaloniaProperty.RegisterDirect<InputElement, bool>(nameof(IsPointerOver), o => o.IsPointerOver);
 
         /// <summary>
         /// Defines the <see cref="GotFocus"/> event.
         /// </summary>
         public static readonly RoutedEvent<GotFocusEventArgs> GotFocusEvent =
-            RoutedEvent.Register<InputElement, GotFocusEventArgs>("GotFocus", RoutingStrategies.Bubble);
+            RoutedEvent.Register<InputElement, GotFocusEventArgs>(nameof(GotFocus), RoutingStrategies.Bubble);
 
         /// <summary>
         /// Defines the <see cref="LostFocus"/> event.
         /// </summary>
         public static readonly RoutedEvent<RoutedEventArgs> LostFocusEvent =
-            RoutedEvent.Register<InputElement, RoutedEventArgs>("LostFocus", RoutingStrategies.Bubble);
+            RoutedEvent.Register<InputElement, RoutedEventArgs>(nameof(LostFocus), RoutingStrategies.Bubble);
 
         /// <summary>
         /// Defines the <see cref="KeyDown"/> event.
@@ -97,13 +97,13 @@ namespace Avalonia.Input
         /// Defines the <see cref="PointerEnter"/> event.
         /// </summary>
         public static readonly RoutedEvent<PointerEventArgs> PointerEnterEvent =
-            RoutedEvent.Register<InputElement, PointerEventArgs>("PointerEnter", RoutingStrategies.Direct);
+            RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerEnter), RoutingStrategies.Direct);
 
         /// <summary>
         /// Defines the <see cref="PointerLeave"/> event.
         /// </summary>
         public static readonly RoutedEvent<PointerEventArgs> PointerLeaveEvent =
-            RoutedEvent.Register<InputElement, PointerEventArgs>("PointerLeave", RoutingStrategies.Direct);
+            RoutedEvent.Register<InputElement, PointerEventArgs>(nameof(PointerLeave), RoutingStrategies.Direct);
 
         /// <summary>
         /// Defines the <see cref="PointerMoved"/> event.

+ 3 - 3
src/Avalonia.Input/KeyBinding.cs

@@ -10,7 +10,7 @@ namespace Avalonia.Input
     public class KeyBinding : AvaloniaObject
     {
         public static readonly StyledProperty<ICommand> CommandProperty =
-            AvaloniaProperty.Register<KeyBinding, ICommand>("Command");
+            AvaloniaProperty.Register<KeyBinding, ICommand>(nameof(Command));
 
         public ICommand Command
         {
@@ -19,7 +19,7 @@ namespace Avalonia.Input
         }
 
         public static readonly StyledProperty<object> CommandParameterProperty =
-            AvaloniaProperty.Register<KeyBinding, object>("CommandParameter");
+            AvaloniaProperty.Register<KeyBinding, object>(nameof(CommandParameter));
 
         public object CommandParameter
         {
@@ -28,7 +28,7 @@ namespace Avalonia.Input
         }
 
         public static readonly StyledProperty<KeyGesture> GestureProperty =
-            AvaloniaProperty.Register<KeyBinding, KeyGesture>("Gesture");
+            AvaloniaProperty.Register<KeyBinding, KeyGesture>(nameof(Gesture));
 
         public KeyGesture Gesture
         {

+ 10 - 7
src/Avalonia.Input/Navigation/TabNavigation.cs

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

+ 1 - 1
src/Avalonia.Layout/LayoutManager.cs

@@ -203,7 +203,7 @@ namespace Avalonia.Layout
         {
             if (!_queued && !_running)
             {
-                Dispatcher.UIThread.InvokeAsync(ExecuteLayoutPass, DispatcherPriority.Layout);
+                Dispatcher.UIThread.Post(ExecuteLayoutPass, DispatcherPriority.Layout);
                 _queued = true;
             }
         }

+ 10 - 2
src/Avalonia.Remote.Protocol/DesignMessages.cs

@@ -1,4 +1,6 @@
-namespace Avalonia.Remote.Protocol.Designer
+using System;
+
+namespace Avalonia.Remote.Protocol.Designer
 {
     [AvaloniaRemoteMessageGuid("9AEC9A2E-6315-4066-B4BA-E9A9EFD0F8CC")]
     public class UpdateXamlMessage
@@ -11,7 +13,13 @@
     public class UpdateXamlResultMessage
     {
         public string Error { get; set; }
+        public string Handle { get; set; }
+    }
+
+    [AvaloniaRemoteMessageGuid("854887CF-2694-4EB6-B499-7461B6FB96C7")]
+    public class StartDesignerSessionMessage
+    {
+        public string SessionId { get; set; }
     }
-    
     
 }

+ 1 - 1
src/Avalonia.Remote.Protocol/TcpTransportBase.cs

@@ -72,7 +72,7 @@ namespace Avalonia.Remote.Protocol
         {
             var c = new TcpClient();
             await c.ConnectAsync(address, port);
-            return CreateTransport(_resolver, c.GetStream(), c.Dispose);
+            return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose);
         }
     }
 }

+ 38 - 0
src/Avalonia.Themes.Default/DataValidationErrors.xaml

@@ -0,0 +1,38 @@
+<Style xmlns="https://github.com/avaloniaui" 
+       Selector="DataValidationErrors">
+  <Setter Property="Template">
+    <ControlTemplate>
+      <DockPanel LastChildFill="True">
+        <ContentControl DockPanel.Dock="Right"
+                        ContentTemplate="{TemplateBinding ErrorTemplate}"
+                        DataContext="{TemplateBinding Owner}"
+                        Content="{Binding (DataValidationErrors.Errors)}"
+                        IsVisible="{Binding (DataValidationErrors.HasErrors)}"/>
+        <ContentPresenter Name="PART_ContentPresenter"
+                          Background="{TemplateBinding Background}"
+                          BorderBrush="{TemplateBinding BorderBrush}"
+                          BorderThickness="{TemplateBinding BorderThickness}"
+                          ContentTemplate="{TemplateBinding ContentTemplate}"
+                          Content="{TemplateBinding Content}"
+                          Padding="{TemplateBinding Padding}"/>
+      </DockPanel>
+    </ControlTemplate>
+  </Setter>
+  <Setter Property="ErrorTemplate">
+    <DataTemplate>
+      <Canvas Width="14" Height="14" Margin="4 0 1 0" 
+              Background="#00FFFFFF">
+        <Canvas.Styles>
+          <Style Selector="ToolTip">
+            <Setter Property="Background" Value="{DynamicResource ErrorBrushLight}"/>
+            <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
+          </Style>
+        </Canvas.Styles>
+        <ToolTip.Tip>
+          <ItemsControl Items="{Binding}" MemberSelector="Message"/>
+        </ToolTip.Tip>
+        <Path Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2" Stroke="{DynamicResource ErrorBrush}" StrokeThickness="2"/>
+      </Canvas>
+    </DataTemplate>
+  </Setter>
+</Style>

+ 126 - 0
src/Avalonia.Themes.Default/DatePicker.xaml

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

+ 2 - 0
src/Avalonia.Themes.Default/DefaultTheme.xaml

@@ -1,6 +1,7 @@
 <Styles xmlns="https://github.com/avaloniaui">
   <!-- Define ToolTip first so its styles can be overriden by other controls (e.g. TextBox) -->
   <StyleInclude Source="resm:Avalonia.Themes.Default.ToolTip.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.DataValidationErrors.xaml?assembly=Avalonia.Themes.Default"/>
 
   <StyleInclude Source="resm:Avalonia.Themes.Default.FocusAdorner.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Button.xaml?assembly=Avalonia.Themes.Default"/>
@@ -40,4 +41,5 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarDayButton.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarItem.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Calendar.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.DatePicker.xaml?assembly=Avalonia.Themes.Default"/>
 </Styles>

+ 6 - 1
src/Avalonia.Themes.Default/ListBox.xaml

@@ -3,11 +3,16 @@
   <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
   <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
   <Setter Property="Padding" Value="4"/>
+  <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
+  <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
   <Setter Property="Template">
     <ControlTemplate>
       <Border BorderBrush="{TemplateBinding BorderBrush}"
               BorderThickness="{TemplateBinding BorderThickness}">
-        <ScrollViewer Name="PART_ScrollViewer" Background="{TemplateBinding Background}">
+        <ScrollViewer Name="PART_ScrollViewer"
+                      Background="{TemplateBinding Background}"
+                      HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
+                      VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
           <ItemsPresenter Name="PART_ItemsPresenter"
                           Items="{TemplateBinding Items}"
                           ItemsPanel="{TemplateBinding ItemsPanel}"

+ 3 - 2
src/Avalonia.Themes.Default/ScrollViewer.xaml

@@ -6,12 +6,13 @@
       <Grid ColumnDefinitions="*,Auto" RowDefinitions="*,Auto">
         <ScrollContentPresenter Name="PART_ContentPresenter"
                                 Background="{TemplateBinding Background}"
+                                CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
+                                CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
                                 Content="{TemplateBinding Content}"
                                 Extent="{TemplateBinding Path=Extent, Mode=TwoWay}"
                                 Margin="{TemplateBinding Padding}"
                                 Offset="{TemplateBinding Path=Offset, Mode=TwoWay}"
-                                Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"
-                                CanScrollHorizontally="{TemplateBinding CanScrollHorizontally}"/>
+                                Viewport="{TemplateBinding Path=Viewport, Mode=TwoWay}"/>
         <ScrollBar Name="horizontalScrollBar"
                    Orientation="Horizontal"
                    Maximum="{TemplateBinding HorizontalScrollBarMaximum}"

+ 17 - 36
src/Avalonia.Themes.Default/TextBox.xaml

@@ -28,35 +28,26 @@
               </TextBlock.IsVisible>
             </TextBlock>
 
-            <DockPanel LastChildFill="True">
-              <Canvas Name="error" DockPanel.Dock="Right" Width="14" Height="14" Margin="4 0 1 0">
-                <ToolTip.Tip>
-                  <ItemsControl Items="{TemplateBinding DataValidationErrors}" MemberSelector="Message"/>
-                </ToolTip.Tip>
-                <Path Data="M14,7 A7,7 0 0,0 0,7 M0,7 A7,7 0 1,0 14,7 M7,3l0,5 M7,9l0,2" Stroke="{DynamicResource ErrorBrush}" StrokeThickness="2"/>
-              </Canvas>
-              
-              <ScrollViewer CanScrollHorizontally="{TemplateBinding CanScrollHorizontally}"
-                            HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
+            <DataValidationErrors>
+              <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding (ScrollViewer.HorizontalScrollBarVisibility)}"
                             VerticalScrollBarVisibility="{TemplateBinding (ScrollViewer.VerticalScrollBarVisibility)}">
                 
-              <Panel>
-                <TextBlock Name="watermark"
-                           Opacity="0.5"
-                           Text="{TemplateBinding Watermark}"
-                           IsVisible="{TemplateBinding Path=Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
-                <TextPresenter Name="PART_TextPresenter"
-                               Text="{TemplateBinding Text, Mode=TwoWay}"
-                               CaretIndex="{TemplateBinding CaretIndex}"
-                               SelectionStart="{TemplateBinding SelectionStart}"
-                               SelectionEnd="{TemplateBinding SelectionEnd}"
-                               TextAlignment="{TemplateBinding TextAlignment}"
-                               TextWrapping="{TemplateBinding TextWrapping}"/>
-              </Panel>
-            </ScrollViewer>
+                <Panel>
+                  <TextBlock Name="watermark"
+                             Opacity="0.5"
+                             Text="{TemplateBinding Watermark}"
+                             IsVisible="{TemplateBinding Path=Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
+                  <TextPresenter Name="PART_TextPresenter"
+                                 Text="{TemplateBinding Text, Mode=TwoWay}"
+                                 CaretIndex="{TemplateBinding CaretIndex}"
+                                 SelectionStart="{TemplateBinding SelectionStart}"
+                                 SelectionEnd="{TemplateBinding SelectionEnd}"
+                                 TextAlignment="{TemplateBinding TextAlignment}"
+                                 TextWrapping="{TemplateBinding TextWrapping}"/>
+                </Panel>
+              </ScrollViewer>
+            </DataValidationErrors>
           </DockPanel>
-
-        </DockPanel>
         </Border>
       </ControlTemplate>
     </Setter>
@@ -70,14 +61,4 @@
   <Style Selector="TextBox:error /template/ Border#border">
     <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
   </Style>
-  <Style Selector="TextBox /template/ Canvas#error">
-    <Setter Property="IsVisible" Value="False"/>
-  </Style>
-  <Style Selector="TextBox:error /template/ Canvas#error">
-    <Setter Property="IsVisible" Value="True"/>
-  </Style>
-  <Style Selector="TextBox /template/ ToolTip">
-    <Setter Property="Background" Value="{DynamicResource ErrorBrushLight}"/>
-    <Setter Property="BorderBrush" Value="{DynamicResource ErrorBrush}"/>
-  </Style>
 </Styles>

+ 1 - 1
src/Avalonia.Themes.Default/TreeView.xaml

@@ -7,7 +7,7 @@
     <ControlTemplate>
       <Border BorderBrush="{TemplateBinding BorderBrush}"
               BorderThickness="{TemplateBinding BorderThickness}">
-        <ScrollViewer CanScrollHorizontally="True" Background="{TemplateBinding Background}">
+        <ScrollViewer Background="{TemplateBinding Background}">
           <ItemsPresenter Name="PART_ItemsPresenter"
                           Items="{TemplateBinding Items}"
                           ItemsPanel="{TemplateBinding ItemsPanel}"

+ 19 - 12
src/Avalonia.Visuals/Media/EllipseGeometry.cs

@@ -17,15 +17,9 @@ namespace Avalonia.Media
         public static readonly StyledProperty<Rect> RectProperty =
             AvaloniaProperty.Register<EllipseGeometry, Rect>(nameof(Rect));
 
-        public Rect Rect
-        {
-            get => GetValue(RectProperty);
-            set => SetValue(RectProperty, value);
-        }
-
         static EllipseGeometry()
         {
-            RectProperty.Changed.AddClassHandler<EllipseGeometry>(x => x.RectChanged);
+            AffectsGeometry(RectProperty);
         }
 
         /// <summary>
@@ -33,8 +27,6 @@ namespace Avalonia.Media
         /// </summary>
         public EllipseGeometry()
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.CreateStreamGeometry();
         }
 
         /// <summary>
@@ -46,17 +38,30 @@ namespace Avalonia.Media
             Rect = rect;
         }
 
+        /// <summary>
+        /// Gets or sets a rect that defines the bounds of the ellipse.
+        /// </summary>
+        public Rect Rect
+        {
+            get => GetValue(RectProperty);
+            set => SetValue(RectProperty, value);
+        }
+
         /// <inheritdoc/>
         public override Geometry Clone()
         {
             return new EllipseGeometry(Rect);
         }
 
-        private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+        /// <inheritdoc/>
+        protected override IGeometryImpl CreateDefiningGeometry()
         {
-            var rect = (Rect)e.NewValue;
-            using (var ctx = ((IStreamGeometryImpl)PlatformImpl).Open())
+            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            var geometry = factory.CreateStreamGeometry();
+
+            using (var ctx = geometry.Open())
             {
+                var rect = Rect;
                 double controlPointRatio = (Math.Sqrt(2) - 1) * 4 / 3;
                 var center = rect.Center;
                 var radius = new Vector(rect.Width / 2, rect.Height / 2);
@@ -80,6 +85,8 @@ namespace Avalonia.Media
                 ctx.CubicBezierTo(new Point(x0, y1), new Point(x1, y0), new Point(x2, y0));
                 ctx.EndFigure(true);
             }
+
+            return geometry;
         }
     }
 }

+ 109 - 18
src/Avalonia.Visuals/Media/Geometry.cs

@@ -15,28 +15,49 @@ namespace Avalonia.Media
         /// Defines the <see cref="Transform"/> property.
         /// </summary>
         public static readonly StyledProperty<Transform> TransformProperty =
-            AvaloniaProperty.Register<Geometry, Transform>("Transform");
+            AvaloniaProperty.Register<Geometry, Transform>(nameof(Transform));
+
+        private bool _isDirty = true;
+        private IGeometryImpl _platformImpl;
 
-        /// <summary>
-        /// Initializes static members of the <see cref="Geometry"/> class.
-        /// </summary>
         static Geometry()
         {
             TransformProperty.Changed.AddClassHandler<Geometry>(x => x.TransformChanged);
         }
 
+        /// <summary>
+        /// Raised when the geometry changes.
+        /// </summary>
+        public event EventHandler Changed;
+
         /// <summary>
         /// Gets the geometry's bounding rectangle.
         /// </summary>
-        public Rect Bounds => PlatformImpl.Bounds;
+        public Rect Bounds => PlatformImpl?.Bounds ?? Rect.Empty;
 
         /// <summary>
         /// Gets the platform-specific implementation of the geometry.
         /// </summary>
-        public virtual IGeometryImpl PlatformImpl
+        public IGeometryImpl PlatformImpl
         {
-            get;
-            protected set;
+            get
+            {
+                if (_isDirty)
+                {
+                    var geometry = CreateDefiningGeometry();
+                    var transform = Transform;
+
+                    if (geometry != null && transform != null && transform.Value != Matrix.Identity)
+                    {
+                        geometry = geometry.WithTransform(transform.Value);
+                    }
+
+                    _platformImpl = geometry;
+                    _isDirty = false;
+                }
+
+                return _platformImpl;
+            }
         }
 
         /// <summary>
@@ -55,14 +76,11 @@ namespace Avalonia.Media
         public abstract Geometry Clone();
 
         /// <summary>
-        /// Gets the geometry's bounding rectangle with the specified stroke thickness.
+        /// Gets the geometry's bounding rectangle with the specified pen.
         /// </summary>
-        /// <param name="strokeThickness">The stroke thickness.</param>
+        /// <param name="pen">The stroke thickness.</param>
         /// <returns>The bounding rectangle.</returns>
-        public Rect GetRenderBounds(double strokeThickness)
-        {
-            return PlatformImpl.GetRenderBounds(strokeThickness);
-        }
+        public Rect GetRenderBounds(Pen pen) => PlatformImpl?.GetRenderBounds(pen) ?? Rect.Empty;
 
         /// <summary>
         /// Indicates whether the geometry's fill contains the specified point.
@@ -71,7 +89,7 @@ namespace Avalonia.Media
         /// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
         public bool FillContains(Point point)
         {
-            return PlatformImpl.FillContains(point);
+            return PlatformImpl?.FillContains(point) == true;
         }
 
         /// <summary>
@@ -82,13 +100,86 @@ namespace Avalonia.Media
         /// <returns><c>true</c> if the geometry contains the point; otherwise, <c>false</c>.</returns>
         public bool StrokeContains(Pen pen, Point point)
         {
-            return PlatformImpl.StrokeContains(pen, point);
+            return PlatformImpl?.StrokeContains(pen, point) == true;
+        }
+
+        /// <summary>
+        /// Marks a property as affecting the geometry's <see cref="PlatformImpl"/>.
+        /// </summary>
+        /// <param name="properties">The properties.</param>
+        /// <remarks>
+        /// After a call to this method in a control's static constructor, any change to the
+        /// property will cause <see cref="InvalidateGeometry"/> to be called on the element.
+        /// </remarks>
+        protected static void AffectsGeometry(params AvaloniaProperty[] properties)
+        {
+            foreach (var property in properties)
+            {
+                property.Changed.Subscribe(AffectsGeometryInvalidate);
+            }
+        }
+
+        /// <summary>
+        /// Creates the platform implementation of the geometry, without the transform applied.
+        /// </summary>
+        /// <returns></returns>
+        protected abstract IGeometryImpl CreateDefiningGeometry();
+
+        /// <summary>
+        /// Invalidates the platform implementation of the geometry.
+        /// </summary>
+        protected void InvalidateGeometry()
+        {
+            _isDirty = true;
+            _platformImpl = null;
+            Changed?.Invoke(this, EventArgs.Empty);
         }
 
         private void TransformChanged(AvaloniaPropertyChangedEventArgs e)
         {
-            var transform = (Transform)e.NewValue;
-            PlatformImpl = PlatformImpl.WithTransform(transform.Value);
+            var oldValue = (Transform)e.OldValue;
+            var newValue = (Transform)e.NewValue;
+
+            if (oldValue != null)
+            {
+                oldValue.Changed -= TransformChanged;
+            }
+
+            if (newValue != null)
+            {
+                newValue.Changed += TransformChanged;
+            }
+
+            TransformChanged(newValue, EventArgs.Empty);
+        }
+
+        private void TransformChanged(object sender, EventArgs e)
+        {
+            var transform = ((Transform)sender)?.Value;
+
+            if (_platformImpl is ITransformedGeometryImpl t)
+            {
+                if (transform == null || transform == Matrix.Identity)
+                {
+                    _platformImpl = t.SourceGeometry;
+                }
+                else if (transform != t.Transform)
+                {
+                    _platformImpl = t.SourceGeometry.WithTransform(transform.Value);
+                }
+            }
+            else if (_platformImpl != null && transform != null && transform != Matrix.Identity)
+            {
+                _platformImpl = PlatformImpl.WithTransform(transform.Value);
+            }
+
+            Changed?.Invoke(this, EventArgs.Empty);
+        }
+
+        private static void AffectsGeometryInvalidate(AvaloniaPropertyChangedEventArgs e)
+        {
+            var control = e.Sender as Geometry;
+            control?.InvalidateGeometry();
         }
     }
 }

+ 2 - 1
src/Avalonia.Visuals/Media/GeometryDrawing.cs

@@ -37,7 +37,8 @@
         public override Rect GetBounds()
         {
             // adding the Pen's stroke thickness here could yield wrong results due to transforms
-            return Geometry?.GetRenderBounds(0) ?? new Rect();
+            var pen = new Pen(Brushes.Black, 0);
+            return Geometry?.GetRenderBounds(pen) ?? new Rect();
         }
     }
 }

+ 1 - 1
src/Avalonia.Visuals/Media/ImageBrush.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Visual"/> property.
         /// </summary>
         public static readonly StyledProperty<IBitmap> SourceProperty =
-            AvaloniaProperty.Register<ImageBrush, IBitmap>("Source");
+            AvaloniaProperty.Register<ImageBrush, IBitmap>(nameof(Source));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ImageBrush"/> class.

+ 27 - 14
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@@ -4,6 +4,7 @@
 using System;
 using System.IO;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media.Imaging
 {
@@ -19,7 +20,7 @@ namespace Avalonia.Media.Imaging
         public Bitmap(string fileName)
         {
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.LoadBitmap(fileName);
+            PlatformImpl = RefCountable.Create(factory.LoadBitmap(fileName));
         }
 
         /// <summary>
@@ -29,18 +30,33 @@ namespace Avalonia.Media.Imaging
         public Bitmap(Stream stream)
         {
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.LoadBitmap(stream);
+            PlatformImpl = RefCountable.Create(factory.LoadBitmap(stream));
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Bitmap"/> class.
         /// </summary>
         /// <param name="impl">A platform-specific bitmap implementation.</param>
+        public Bitmap(IRef<IBitmapImpl> impl)
+        {
+            PlatformImpl = impl.Clone();
+        }
+        
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Bitmap"/> class.
+        /// </summary>
+        /// <param name="impl">A platform-specific bitmap implementation. Bitmap class takes the ownership.</param>
         protected Bitmap(IBitmapImpl impl)
         {
-            PlatformImpl = impl;
+            PlatformImpl = RefCountable.Create(impl);
         }
-
+        
+        /// <inheritdoc/>
+        public virtual void Dispose()
+        {
+            PlatformImpl.Dispose();
+        }
+        
         /// <summary>
         /// Initializes a new instance of the <see cref="Bitmap"/> class.
         /// </summary>
@@ -51,27 +67,24 @@ namespace Avalonia.Media.Imaging
         /// <param name="stride">Bytes per row</param>
         public Bitmap(PixelFormat format, IntPtr data, int width, int height, int stride)
         {
-            PlatformImpl = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
-                .LoadBitmap(format, data, width, height, stride);
+            PlatformImpl = RefCountable.Create(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()
+                .LoadBitmap(format, data, width, height, stride));
         }
 
         /// <summary>
         /// Gets the width of the bitmap, in pixels.
         /// </summary>
-        public int PixelWidth => PlatformImpl.PixelWidth;
+        public int PixelWidth => PlatformImpl.Item.PixelWidth;
 
         /// <summary>
         /// Gets the height of the bitmap, in pixels.
         /// </summary>
-        public int PixelHeight => PlatformImpl.PixelHeight;
+        public int PixelHeight => PlatformImpl.Item.PixelHeight;
 
         /// <summary>
         /// Gets the platform-specific bitmap implementation.
         /// </summary>
-        public IBitmapImpl PlatformImpl
-        {
-            get;
-        }
+        public IRef<IBitmapImpl> PlatformImpl { get; }
 
         /// <summary>
         /// Saves the bitmap to a file.
@@ -79,12 +92,12 @@ namespace Avalonia.Media.Imaging
         /// <param name="fileName">The filename.</param>
         public void Save(string fileName)
         {
-            PlatformImpl.Save(fileName);
+            PlatformImpl.Item.Save(fileName);
         }
 
         public void Save(Stream stream)
         {
-            PlatformImpl.Save(stream);
+            PlatformImpl.Item.Save(stream);
         }
     }
 }

+ 4 - 2
src/Avalonia.Visuals/Media/Imaging/IBitmap.cs

@@ -1,15 +1,17 @@
 // 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.IO;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media.Imaging
 {
     /// <summary>
     /// Represents a bitmap image.
     /// </summary>
-    public interface IBitmap
+    public interface IBitmap : IDisposable
     {
         /// <summary>
         /// Gets the width of the bitmap, in pixels.
@@ -24,7 +26,7 @@ namespace Avalonia.Media.Imaging
         /// <summary>
         /// Gets the platform-specific bitmap implementation.
         /// </summary>
-        IBitmapImpl PlatformImpl { get; }
+        IRef<IBitmapImpl> PlatformImpl { get; }
 
         /// <summary>
         /// Saves the bitmap to a file.

+ 11 - 12
src/Avalonia.Visuals/Media/Imaging/RenderTargetBitmap.cs

@@ -2,8 +2,10 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
+using System.Runtime.CompilerServices;
 using Avalonia.Platform;
 using Avalonia.Rendering;
+using Avalonia.Utilities;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Media.Imaging
@@ -21,22 +23,19 @@ namespace Avalonia.Media.Imaging
         /// <param name="dpiX">The horizontal DPI of the bitmap.</param>
         /// <param name="dpiY">The vertical DPI of the bitmap.</param>
         public RenderTargetBitmap(int pixelWidth, int pixelHeight, double dpiX = 96, double dpiY = 96)
-            : base(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY))
+           : this(RefCountable.Create(CreateImpl(pixelWidth, pixelHeight, dpiX, dpiY)))
         {
         }
 
-        /// <summary>
-        /// Gets the platform-specific bitmap implementation.
-        /// </summary>
-        public new IRenderTargetBitmapImpl PlatformImpl => (IRenderTargetBitmapImpl)base.PlatformImpl;
+        private RenderTargetBitmap(IRef<IRenderTargetBitmapImpl> impl) : base(impl)
+        {
+            PlatformImpl = impl;
+        }
 
         /// <summary>
-        /// Disposes of the bitmap.
+        /// Gets the platform-specific bitmap implementation.
         /// </summary>
-        public void Dispose()
-        {
-            PlatformImpl.Dispose();
-        }
+        public new IRef<IRenderTargetBitmapImpl> PlatformImpl { get; }
 
         /// <summary>
         /// Renders a visual to the <see cref="RenderTargetBitmap"/>.
@@ -52,13 +51,13 @@ namespace Avalonia.Media.Imaging
         /// <param name="dpiX">The horizontal DPI of the bitmap.</param>
         /// <param name="dpiY">The vertical DPI of the bitmap.</param>
         /// <returns>The platform-specific implementation.</returns>
-        private static IBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY)
+        private static IRenderTargetBitmapImpl CreateImpl(int width, int height, double dpiX, double dpiY)
         {
             IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
             return factory.CreateRenderTargetBitmap(width, height, dpiX, dpiY);
         }
 
         /// <inheritdoc/>
-        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.CreateDrawingContext(vbr);
+        public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer vbr) => PlatformImpl.Item.CreateDrawingContext(vbr);
     }
 }

+ 3 - 2
src/Avalonia.Visuals/Media/Imaging/WritableBitmap.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 
 namespace Avalonia.Media.Imaging
 {
@@ -16,7 +17,7 @@ namespace Avalonia.Media.Imaging
             : base(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateWritableBitmap(width, height, format))
         {
         }
-
-        public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl).Lock();
+        
+        public ILockedFramebuffer Lock() => ((IWritableBitmapImpl) PlatformImpl.Item).Lock();
     }
 }

+ 28 - 38
src/Avalonia.Visuals/Media/LineGeometry.cs

@@ -16,29 +16,15 @@ namespace Avalonia.Media
         public static readonly StyledProperty<Point> StartPointProperty =
             AvaloniaProperty.Register<LineGeometry, Point>(nameof(StartPoint));
 
-        public Point StartPoint
-        {
-            get => GetValue(StartPointProperty);
-            set => SetValue(StartPointProperty, value);
-        }
-
         /// <summary>
         /// Defines the <see cref="EndPoint"/> property.
         /// </summary>
         public static readonly StyledProperty<Point> EndPointProperty =
             AvaloniaProperty.Register<LineGeometry, Point>(nameof(EndPoint));
-        private bool _isDirty = true;
-
-        public Point EndPoint
-        {
-            get => GetValue(EndPointProperty);
-            set => SetValue(EndPointProperty, value);
-        }
 
         static LineGeometry()
         {
-            StartPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
-            EndPointProperty.Changed.AddClassHandler<LineGeometry>(x => x.PointsChanged);
+            AffectsGeometry(StartPointProperty, EndPointProperty);
         }
 
         /// <summary>
@@ -46,8 +32,6 @@ namespace Avalonia.Media
         /// </summary>
         public LineGeometry()
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.CreateStreamGeometry();
         }
 
         /// <summary>
@@ -61,38 +45,44 @@ namespace Avalonia.Media
             EndPoint = endPoint;
         }
 
-        public override IGeometryImpl PlatformImpl
+        /// <summary>
+        /// Gets or sets the start point of the line.
+        /// </summary>
+        public Point StartPoint
         {
-            get
-            {
-                PrepareIfNeeded();
-                return base.PlatformImpl;
-            }
-            protected set => base.PlatformImpl = value;
+            get => GetValue(StartPointProperty);
+            set => SetValue(StartPointProperty, value);
         }
 
-        public void PrepareIfNeeded()
+        /// <summary>
+        /// Gets or sets the end point of the line.
+        /// </summary>
+        public Point EndPoint
         {
-            if (_isDirty)
-            {
-                _isDirty = false;
-
-                using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
-                {
-                    context.BeginFigure(StartPoint, false);
-                    context.LineTo(EndPoint);
-                    context.EndFigure(false);
-                }
-            }
+            get => GetValue(EndPointProperty);
+            set => SetValue(EndPointProperty, value);
         }
 
         /// <inheritdoc/>
         public override Geometry Clone()
         {
-            PrepareIfNeeded();
             return new LineGeometry(StartPoint, EndPoint);
         }
 
-        private void PointsChanged(AvaloniaPropertyChangedEventArgs e) => _isDirty = true;
+        /// <inheritdoc/>
+        protected override IGeometryImpl CreateDefiningGeometry()
+        {
+            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            var geometry = factory.CreateStreamGeometry();
+
+            using (var context = geometry.Open())
+            {
+                context.BeginFigure(StartPoint, false);
+                context.LineTo(EndPoint);
+                context.EndFigure(false);
+            }
+
+            return geometry;
+        }
     }
 }

+ 1 - 1
src/Avalonia.Visuals/Media/MatrixTransform.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Matrix"/> property.
         /// </summary>
         public static readonly StyledProperty<Matrix> MatrixProperty =
-            AvaloniaProperty.Register<MatrixTransform, Matrix>("Matrix", Matrix.Identity);
+            AvaloniaProperty.Register<MatrixTransform, Matrix>(nameof(Matrix), Matrix.Identity);
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MatrixTransform"/> class.

+ 21 - 47
src/Avalonia.Visuals/Media/PathGeometry.cs

@@ -1,10 +1,10 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using Avalonia.Collections;
 using Avalonia.Metadata;
 using Avalonia.Platform;
-using System;
 
 namespace Avalonia.Media
 {
@@ -22,12 +22,14 @@ namespace Avalonia.Media
         public static readonly StyledProperty<FillRule> FillRuleProperty =
                                  AvaloniaProperty.Register<PathGeometry, FillRule>(nameof(FillRule));
 
+        private PathFigures _figures;
+        private IDisposable _figuresObserver;
+        private IDisposable _figuresPropertiesObserver;
+
         static PathGeometry()
         {
-            FiguresProperty.Changed.Subscribe(onNext: v =>
-            {
-                (v.Sender as PathGeometry)?.OnFiguresChanged(v.OldValue as PathFigures, v.NewValue as PathFigures);
-            });
+            FiguresProperty.Changed.AddClassHandler<PathGeometry>((s, e) => 
+                s.OnFiguresChanged(e.NewValue as PathFigures));
         }
 
         /// <summary>
@@ -63,61 +65,33 @@ namespace Avalonia.Media
             set { SetValue(FillRuleProperty, value); }
         }
 
-        public override IGeometryImpl PlatformImpl
+        protected override IGeometryImpl CreateDefiningGeometry()
         {
-            get
-            {
-                PrepareIfNeeded();
-                return base.PlatformImpl;
-            }
+            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            var geometry = factory.CreateStreamGeometry();
 
-            protected set
+            using (var ctx = new StreamGeometryContext(geometry.Open()))
             {
-                base.PlatformImpl = value;
-            }
-        }
-
-        public override Geometry Clone()
-        {
-            PrepareIfNeeded();
-
-            return base.Clone();
-        }
-
-        public void PrepareIfNeeded()
-        {
-            if (_isDirty)
-            {
-                _isDirty = false;
-
-                using (var ctx = Open())
+                ctx.SetFillRule(FillRule);
+                foreach (var f in Figures)
                 {
-                    ctx.SetFillRule(FillRule);
-                    foreach (var f in Figures)
-                    {
-                        f.ApplyTo(ctx);
-                    }
+                    f.ApplyTo(ctx);
                 }
             }
-        }
 
-        internal void NotifyChanged()
-        {
-            _isDirty = true;
+            return geometry;
         }
 
-        private PathFigures _figures;
-        private IDisposable _figuresObserver = null;
-        private IDisposable _figuresPropertiesObserver = null;
-        private bool _isDirty = true;
-
-        private void OnFiguresChanged(PathFigures oldValue, PathFigures newValue)
+        private void OnFiguresChanged(PathFigures figures)
         {
             _figuresObserver?.Dispose();
             _figuresPropertiesObserver?.Dispose();
 
-            _figuresObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
-            _figuresPropertiesObserver = newValue?.TrackItemPropertyChanged(t => NotifyChanged());
+            _figuresObserver = figures?.ForEachItem(
+                _ => InvalidateGeometry(),
+                _ => InvalidateGeometry(),
+                () => InvalidateGeometry());
+            _figuresPropertiesObserver = figures?.TrackItemPropertyChanged(_ => InvalidateGeometry());
         }
     }
 }

+ 27 - 46
src/Avalonia.Visuals/Media/PolylineGeometry.cs

@@ -27,14 +27,12 @@ namespace Avalonia.Media
             AvaloniaProperty.Register<PolylineGeometry, bool>(nameof(IsFilled));
 
         private Points _points;
-        private bool _isDirty = true;
         private IDisposable _pointsObserver;
 
         static PolylineGeometry()
         {
-            PointsProperty.Changed.AddClassHandler<PolylineGeometry>((s, e) =>
-                s.OnPointsChanged(e.OldValue as Points, e.NewValue as Points));
-            IsFilledProperty.Changed.AddClassHandler<PolylineGeometry>((s, _) => s.NotifyChanged());
+            AffectsGeometry(IsFilledProperty);
+            PointsProperty.Changed.AddClassHandler<PolylineGeometry>((s, e) => s.OnPointsChanged(e.NewValue as Points));
         }
 
         /// <summary>
@@ -42,9 +40,6 @@ namespace Avalonia.Media
         /// </summary>
         public PolylineGeometry()
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.CreateStreamGeometry();
-
             Points = new Points();
         }
 
@@ -57,29 +52,6 @@ namespace Avalonia.Media
             IsFilled = isFilled;
         }
 
-        public void PrepareIfNeeded()
-        {
-            if (_isDirty)
-            {
-                _isDirty = false;
-
-                using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
-                {
-                    var points = Points;
-                    var isFilled = IsFilled;
-                    if (points.Count > 0)
-                    {
-                        context.BeginFigure(points[0], isFilled);
-                        for (int i = 1; i < points.Count; i++)
-                        {
-                            context.LineTo(points[i]);
-                        }
-                        context.EndFigure(isFilled);
-                    }
-                }
-            }
-        }
-
         /// <summary>
         /// Gets or sets the figures.
         /// </summary>
@@ -99,33 +71,42 @@ namespace Avalonia.Media
             set => SetValue(IsFilledProperty, value);
         }
 
-        public override IGeometryImpl PlatformImpl
-        {
-            get
-            {
-                PrepareIfNeeded();
-                return base.PlatformImpl;
-            }
-            protected set => base.PlatformImpl = value;
-        }
-
         /// <inheritdoc/>
         public override Geometry Clone()
         {
-            PrepareIfNeeded();
             return new PolylineGeometry(Points, IsFilled);
         }
 
-        private void OnPointsChanged(Points oldValue, Points newValue)
+        protected override IGeometryImpl CreateDefiningGeometry()
         {
-            _pointsObserver?.Dispose();
+            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            var geometry = factory.CreateStreamGeometry();
+
+            using (var context = geometry.Open())
+            {
+                var points = Points;
+                var isFilled = IsFilled;
+                if (points.Count > 0)
+                {
+                    context.BeginFigure(points[0], isFilled);
+                    for (int i = 1; i < points.Count; i++)
+                    {
+                        context.LineTo(points[i]);
+                    }
+                    context.EndFigure(isFilled);
+                }
+            }
 
-            _pointsObserver = newValue?.ForEachItem(f => NotifyChanged(), f => NotifyChanged(), () => NotifyChanged());
+            return geometry;
         }
 
-        internal void NotifyChanged()
+        private void OnPointsChanged(Points newValue)
         {
-            _isDirty = true;
+            _pointsObserver?.Dispose();
+            _pointsObserver = newValue?.ForEachItem(
+                _ => InvalidateGeometry(),
+                _ => InvalidateGeometry(),
+                InvalidateGeometry);
         }
     }
 }

+ 11 - 11
src/Avalonia.Visuals/Media/RectangleGeometry.cs

@@ -24,7 +24,7 @@ namespace Avalonia.Media
 
         static RectangleGeometry()
         {
-            RectProperty.Changed.AddClassHandler<RectangleGeometry>(x => x.RectChanged);
+            AffectsGeometry(RectProperty);
         }
 
         /// <summary>
@@ -32,36 +32,36 @@ namespace Avalonia.Media
         /// </summary>
         public RectangleGeometry()
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.CreateStreamGeometry();
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="RectangleGeometry"/> class.
         /// </summary>
         /// <param name="rect">The rectangle bounds.</param>
-        public RectangleGeometry(Rect rect) : this()
+        public RectangleGeometry(Rect rect)
         {
             Rect = rect;
         }
 
         /// <inheritdoc/>
-        public override Geometry Clone()
-        {
-            return new RectangleGeometry(Rect);
-        }
+        public override Geometry Clone() => new RectangleGeometry(Rect);
 
-        private void RectChanged(AvaloniaPropertyChangedEventArgs e)
+        protected override IGeometryImpl CreateDefiningGeometry()
         {
-            var rect = (Rect)e.NewValue;
-            using (var context = ((IStreamGeometryImpl)PlatformImpl).Open())
+            var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+            var geometry = factory.CreateStreamGeometry();
+
+            using (var context = geometry.Open())
             {
+                var rect = Rect;
                 context.BeginFigure(rect.TopLeft, true);
                 context.LineTo(rect.TopRight);
                 context.LineTo(rect.BottomRight);
                 context.LineTo(rect.BottomLeft);
                 context.EndFigure(true);
             }
+
+            return geometry;
         }
     }
 }

+ 1 - 1
src/Avalonia.Visuals/Media/RotateTransform.cs

@@ -15,7 +15,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Angle"/> property.
         /// </summary>
         public static readonly StyledProperty<double> AngleProperty =
-            AvaloniaProperty.Register<RotateTransform, double>("Angle");
+            AvaloniaProperty.Register<RotateTransform, double>(nameof(Angle));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="RotateTransform"/> class.

+ 16 - 4
src/Avalonia.Visuals/Media/StreamGeometry.cs

@@ -10,22 +10,22 @@ namespace Avalonia.Media
     /// </summary>
     public class StreamGeometry : Geometry
     {
+        IStreamGeometryImpl _impl;
+
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometry"/> class.
         /// </summary>
         public StreamGeometry()
         {
-            IPlatformRenderInterface factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
-            PlatformImpl = factory.CreateStreamGeometry();
         }
 
         /// <summary>
         /// Initializes a new instance of the <see cref="StreamGeometry"/> class.
         /// </summary>
         /// <param name="impl">The platform-specific implementation.</param>
-        private StreamGeometry(IGeometryImpl impl)
+        private StreamGeometry(IStreamGeometryImpl impl)
         {
-            PlatformImpl = impl;
+            _impl = impl;
         }
 
         /// <summary>
@@ -61,5 +61,17 @@ namespace Avalonia.Media
         {
             return new StreamGeometryContext(((IStreamGeometryImpl)PlatformImpl).Open());
         }
+
+        /// <inheritdoc/>
+        protected override IGeometryImpl CreateDefiningGeometry()
+        {
+            if (_impl == null)
+            {
+                var factory = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
+                _impl = factory.CreateStreamGeometry();
+            }
+
+            return _impl;
+        }
     }
 }

+ 2 - 2
src/Avalonia.Visuals/Media/TranslateTransform.cs

@@ -15,13 +15,13 @@ namespace Avalonia.Media
         /// Defines the <see cref="X"/> property.
         /// </summary>
         public static readonly StyledProperty<double> XProperty =
-            AvaloniaProperty.Register<TranslateTransform, double>("X");
+            AvaloniaProperty.Register<TranslateTransform, double>(nameof(X));
 
         /// <summary>
         /// Defines the <see cref="Y"/> property.
         /// </summary>
         public static readonly StyledProperty<double> YProperty =
-            AvaloniaProperty.Register<TranslateTransform, double>("Y");
+            AvaloniaProperty.Register<TranslateTransform, double>(nameof(Y));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="TranslateTransform"/> class.

+ 1 - 1
src/Avalonia.Visuals/Media/VisualBrush.cs

@@ -14,7 +14,7 @@ namespace Avalonia.Media
         /// Defines the <see cref="Visual"/> property.
         /// </summary>
         public static readonly StyledProperty<IVisual> VisualProperty =
-            AvaloniaProperty.Register<VisualBrush, IVisual>("Visual");
+            AvaloniaProperty.Register<VisualBrush, IVisual>(nameof(Visual));
 
         /// <summary>
         /// Initializes a new instance of the <see cref="VisualBrush"/> class.

+ 2 - 1
src/Avalonia.Visuals/Platform/IBitmapImpl.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using System.IO;
 
 namespace Avalonia.Platform
@@ -8,7 +9,7 @@ namespace Avalonia.Platform
     /// <summary>
     /// Defines the platform-specific interface for a <see cref="Avalonia.Media.Imaging.Bitmap"/>.
     /// </summary>
-    public interface IBitmapImpl
+    public interface IBitmapImpl : IDisposable
     {
         /// <summary>
         /// Gets the width of the bitmap, in pixels.

+ 3 - 2
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Media;
+using Avalonia.Utilities;
 
 namespace Avalonia.Platform
 {
@@ -29,7 +30,7 @@ namespace Avalonia.Platform
         /// <param name="opacity">The opacity to draw with.</param>
         /// <param name="sourceRect">The rect in the image to draw.</param>
         /// <param name="destRect">The rect in the output to draw to.</param>
-        void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect);
+        void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect);
 
         /// <summary>
         /// Draws a bitmap image.
@@ -38,7 +39,7 @@ namespace Avalonia.Platform
         /// <param name="opacityMask">The opacity mask to draw with.</param>
         /// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
         /// <param name="destRect">The rect in the output to draw to.</param>
-        void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
+        void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
 
         /// <summary>
         /// Draws a line.

+ 6 - 10
src/Avalonia.Visuals/Platform/IGeometryImpl.cs

@@ -1,12 +1,13 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System;
 using Avalonia.Media;
 
 namespace Avalonia.Platform
 {
     /// <summary>
-    /// Defines the platform-specific interface for <see cref="Avalonia.Media.Geometry"/>.
+    /// Defines the platform-specific interface for a <see cref="Geometry"/>.
     /// </summary>
     public interface IGeometryImpl
     {
@@ -16,16 +17,11 @@ namespace Avalonia.Platform
         Rect Bounds { get; }
 
         /// <summary>
-        /// Gets the transform to applied to the geometry.
+        /// Gets the geometry's bounding rectangle with the specified pen.
         /// </summary>
-        Matrix Transform { get; }
-
-        /// <summary>
-        /// Gets the geometry's bounding rectangle with the specified stroke thickness.
-        /// </summary>
-        /// <param name="strokeThickness">The stroke thickness.</param>
+        /// <param name="pen">The pen to use. May be null.</param>
         /// <returns>The bounding rectangle.</returns>
-        Rect GetRenderBounds(double strokeThickness);
+        Rect GetRenderBounds(Pen pen);
 
         /// <summary>
         /// Indicates whether the geometry's fill contains the specified point.
@@ -54,6 +50,6 @@ namespace Avalonia.Platform
         /// </summary>
         /// <param name="transform">The transform.</param>
         /// <returns>The cloned geometry.</returns>
-        IGeometryImpl WithTransform(Matrix transform);
+        ITransformedGeometryImpl WithTransform(Matrix transform);
     }
 }

+ 24 - 0
src/Avalonia.Visuals/Platform/ITransformedGeometryImpl.cs

@@ -0,0 +1,24 @@
+using System;
+
+namespace Avalonia.Platform
+{
+    /// <summary>
+    /// Represents a geometry with a transform applied.
+    /// </summary>
+    /// <remarks>
+    /// An <see cref="ITransformedGeometryImpl"/> transforms a geometry without transforming its
+    /// stroke thickness.
+    /// </remarks>
+    public interface ITransformedGeometryImpl : IGeometryImpl
+    {
+        /// <summary>
+        /// Gets the source geometry that the <see cref="Transform"/> is applied to.
+        /// </summary>
+        IGeometryImpl SourceGeometry { get; }
+
+        /// <summary>
+        /// Gets the applied transform.
+        /// </summary>
+        Matrix Transform { get; }
+    }
+}

+ 36 - 26
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@@ -12,6 +12,7 @@ using System.IO;
 using Avalonia.Media.Immutable;
 using System.Threading;
 using System.Linq;
+using Avalonia.Utilities;
 
 namespace Avalonia.Rendering
 {
@@ -27,14 +28,14 @@ namespace Avalonia.Rendering
         private readonly ISceneBuilder _sceneBuilder;
 
         private bool _running;
-        private Scene _scene;
+        private volatile IRef<Scene> _scene;
         private DirtyVisuals _dirty;
-        private IRenderTargetBitmapImpl _overlay;
+        private IRef<IRenderTargetBitmapImpl> _overlay;
         private bool _updateQueued;
         private object _rendering = new object();
         private int _lastSceneId = -1;
         private DisplayDirtyRects _dirtyRectsDisplay = new DisplayDirtyRects();
-        private IDrawOperation _currentDraw;
+        private IRef<IDrawOperation> _currentDraw;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="DeferredRenderer"/> class.
@@ -111,7 +112,12 @@ namespace Avalonia.Rendering
         /// <summary>
         /// Disposes of the renderer and detaches from the render loop.
         /// </summary>
-        public void Dispose() => Stop();
+        public void Dispose()
+        {
+            var scene = Interlocked.Exchange(ref _scene, null);
+            scene?.Dispose();
+            Stop();
+        }
 
         /// <inheritdoc/>
         public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter)
@@ -122,7 +128,7 @@ namespace Avalonia.Rendering
                 UpdateScene();
             }
 
-            return _scene?.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>();
+            return _scene?.Item.HitTest(p, root, filter) ?? Enumerable.Empty<IVisual>();
         }
 
         /// <inheritdoc/>
@@ -158,13 +164,13 @@ namespace Avalonia.Rendering
         /// <inheritdoc/>
         Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)
         {
-            return (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
+            return (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual]?.Size ?? Size.Empty;
         }
 
         /// <inheritdoc/>
         void IVisualBrushRenderer.RenderVisualBrush(IDrawingContextImpl context, IVisualBrush brush)
         {
-            var childScene = (_currentDraw as BrushDrawOperation)?.ChildScenes?[brush.Visual];
+            var childScene = (_currentDraw.Item as BrushDrawOperation)?.ChildScenes?[brush.Visual];
 
             if (childScene != null)
             {
@@ -174,7 +180,7 @@ namespace Avalonia.Rendering
 
         internal void UnitTestUpdateScene() => UpdateScene();
 
-        internal void UnitTestRender() => Render(_scene);
+        internal void UnitTestRender() => Render(_scene.Item);
 
         private void Render(Scene scene)
         {
@@ -252,7 +258,7 @@ namespace Avalonia.Rendering
                     foreach (var operation in node.DrawOperations)
                     {
                         _currentDraw = operation;
-                        operation.Render(context);
+                        operation.Item.Render(context);
                         _currentDraw = null;
                     }
 
@@ -277,7 +283,7 @@ namespace Avalonia.Rendering
 
                     if (node != null)
                     {
-                        using (var context = renderTarget.CreateDrawingContext(this))
+                        using (var context = renderTarget.Item.CreateDrawingContext(this))
                         {
                             foreach (var rect in layer.Dirty)
                             {
@@ -304,7 +310,7 @@ namespace Avalonia.Rendering
             {
                 var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
 
-                using (var context = overlay.CreateDrawingContext(this))
+                using (var context = overlay.Item.CreateDrawingContext(this))
                 {
                     context.Clear(Colors.Transparent);
                     RenderDirtyRects(context);
@@ -333,7 +339,7 @@ namespace Avalonia.Rendering
             foreach (var layer in scene.Layers)
             {
                 var bitmap = Layers[layer.LayerRoot].Bitmap;
-                var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
+                var sourceRect = new Rect(0, 0, bitmap.Item.PixelWidth, bitmap.Item.PixelHeight);
 
                 if (layer.GeometryClip != null)
                 {
@@ -357,7 +363,7 @@ namespace Avalonia.Rendering
 
             if (_overlay != null)
             {
-                var sourceRect = new Rect(0, 0, _overlay.PixelWidth, _overlay.PixelHeight);
+                var sourceRect = new Rect(0, 0, _overlay.Item.PixelWidth, _overlay.Item.PixelHeight);
                 context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
             }
 
@@ -375,7 +381,8 @@ namespace Avalonia.Rendering
             {
                 if (_root.IsVisible)
                 {
-                    var scene = _scene?.Clone() ?? new Scene(_root);
+                    var sceneRef = RefCountable.Create(_scene?.Item.CloneScene() ?? new Scene(_root));
+                    var scene = sceneRef.Item;
 
                     if (_dirty == null)
                     {
@@ -390,14 +397,16 @@ namespace Avalonia.Rendering
                         }
                     }
 
-                    Interlocked.Exchange(ref _scene, scene);
+                    var oldScene = Interlocked.Exchange(ref _scene, sceneRef);
+                    oldScene?.Dispose();
 
                     _dirty.Clear();
                     (_root as IRenderRoot)?.Invalidate(new Rect(scene.Size));
                 }
                 else
                 {
-                    Interlocked.Exchange(ref _scene, null);
+                    var oldScene = Interlocked.Exchange(ref _scene, null);
+                    oldScene?.Dispose();
                 }
             }
             finally
@@ -415,12 +424,13 @@ namespace Avalonia.Rendering
                     if (!_updateQueued && (_dirty == null || _dirty.Count > 0))
                     {
                         _updateQueued = true;
-                        _dispatcher.InvokeAsync(UpdateScene, DispatcherPriority.Render);
+                        _dispatcher.Post(UpdateScene, DispatcherPriority.Render);
+                    }
+                    
+                    using (var scene = _scene?.Clone())
+                    {
+                        Render(scene?.Item);
                     }
-
-                    Scene scene = null;
-                    Interlocked.Exchange(ref scene, _scene);
-                    Render(scene);
                 }
                 catch { }
                 finally
@@ -430,7 +440,7 @@ namespace Avalonia.Rendering
             }
         }
 
-        private IRenderTargetBitmapImpl GetOverlay(
+        private IRef<IRenderTargetBitmapImpl> GetOverlay(
             IDrawingContextImpl parentContext,
             Size size,
             double scaling)
@@ -438,11 +448,11 @@ namespace Avalonia.Rendering
             var pixelSize = size * scaling;
 
             if (_overlay == null ||
-                _overlay.PixelWidth != pixelSize.Width ||
-                _overlay.PixelHeight != pixelSize.Height)
+                _overlay.Item.PixelWidth != pixelSize.Width ||
+                _overlay.Item.PixelHeight != pixelSize.Height)
             {
                 _overlay?.Dispose();
-                _overlay = parentContext.CreateLayer(size);
+                _overlay = RefCountable.Create(parentContext.CreateLayer(size));
             }
 
             return _overlay;
@@ -455,7 +465,7 @@ namespace Avalonia.Rendering
             foreach (var layer in Layers)
             {
                 var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
-                layer.Bitmap.Save(fileName);
+                layer.Bitmap.Item.Save(fileName);
             }
         }
     }

+ 3 - 6
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@@ -169,7 +169,7 @@ namespace Avalonia.Rendering
         {
             foreach (var e in visual.GetSelfAndVisualDescendants())
             {
-                BoundsTracker.SetTransformedBounds((Visual)visual, null);
+                visual.TransformedBounds = null;
             }
         }
 
@@ -197,7 +197,7 @@ namespace Avalonia.Rendering
 
             if (filter?.Invoke(visual) != false)
             {
-                bool containsPoint = BoundsTracker.GetTransformedBounds((Visual)visual)?.Contains(p) == true;
+                bool containsPoint = visual.TransformedBounds?.Contains(p) == true;
 
                 if ((containsPoint || !visual.ClipToBounds) && visual.VisualChildren.Count > 0)
                 {
@@ -257,10 +257,7 @@ namespace Avalonia.Rendering
                         new TransformedBounds(bounds, new Rect(), context.CurrentContainerTransform);
 #pragma warning restore 0618
 
-                    if (visual is Visual)
-                    {
-                        BoundsTracker.SetTransformedBounds((Visual)visual, transformed);
-                    }
+                    visual.TransformedBounds = transformed;
 
                     foreach (var child in visual.VisualChildren.OrderBy(x => x, ZIndexComparer.Instance))
                     {

+ 5 - 4
src/Avalonia.Visuals/Rendering/RenderLayer.cs

@@ -1,6 +1,7 @@
 using System;
 using Avalonia.Media;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering
@@ -16,13 +17,13 @@ namespace Avalonia.Rendering
             IVisual layerRoot)
         {
             _drawingContext = drawingContext;
-            Bitmap = drawingContext.CreateLayer(size);
+            Bitmap = RefCountable.Create(drawingContext.CreateLayer(size));
             Size = size;
             Scaling = scaling;
             LayerRoot = layerRoot;
         }
 
-        public IRenderTargetBitmapImpl Bitmap { get; private set; }
+        public IRef<IRenderTargetBitmapImpl> Bitmap { get; private set; }
         public double Scaling { get; private set; }
         public Size Size { get; private set; }
         public IVisual LayerRoot { get; }
@@ -31,9 +32,9 @@ namespace Avalonia.Rendering
         {
             if (Size != size || Scaling != scaling)
             {
-                var resized = _drawingContext.CreateLayer(size);
+                var resized = RefCountable.Create(_drawingContext.CreateLayer(size));
 
-                using (var context = resized.CreateDrawingContext(null))
+                using (var context = resized.Item.CreateDrawingContext(null))
                 {
                     context.Clear(Colors.Transparent);
                     context.DrawImage(Bitmap, 1, new Rect(Size), new Rect(Size));

+ 4 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/ClipNode.cs

@@ -60,5 +60,9 @@ namespace Avalonia.Rendering.SceneGraph
                 context.PopClip();
             }
         }
+
+        public void Dispose()
+        {
+        }
     }
 }

+ 31 - 22
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using Avalonia.Media;
 using Avalonia.Platform;
+using Avalonia.Utilities;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Rendering.SceneGraph
@@ -80,7 +81,7 @@ namespace Avalonia.Rendering.SceneGraph
         /// <inheritdoc/>
         public void Dispose()
         {
-            // Nothing to do here as we allocate no unmanaged resources.
+            // Nothing to do here since we allocate no unmanaged resources.
         }
 
         /// <summary>
@@ -102,7 +103,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<GeometryNode>();
 
-            if (next == null || !next.Equals(Transform, brush, pen, geometry))
+            if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
             {
                 Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
             }
@@ -113,11 +114,11 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public void DrawImage(IBitmapImpl source, double opacity, Rect sourceRect, Rect destRect)
+        public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
         {
             var next = NextDrawAs<ImageNode>();
 
-            if (next == null || !next.Equals(Transform, source, opacity, sourceRect, destRect))
+            if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect))
             {
                 Add(new ImageNode(Transform, source, opacity, sourceRect, destRect));
             }
@@ -128,7 +129,7 @@ namespace Avalonia.Rendering.SceneGraph
         }
 
         /// <inheritdoc/>
-        public void DrawImage(IBitmapImpl source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
+        public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
         {
             // This method is currently only used to composite layers so shouldn't be called here.
             throw new NotSupportedException();
@@ -139,7 +140,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<LineNode>();
 
-            if (next == null || !next.Equals(Transform, pen, p1, p2))
+            if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
             {
                 Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
             }
@@ -154,7 +155,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<RectangleNode>();
 
-            if (next == null || !next.Equals(Transform, null, pen, rect, cornerRadius))
+            if (next == null || !next.Item.Equals(Transform, null, pen, rect, cornerRadius))
             {
                 Add(new RectangleNode(Transform, null, pen, rect, cornerRadius, CreateChildScene(pen.Brush)));
             }
@@ -169,7 +170,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<TextNode>();
 
-            if (next == null || !next.Equals(Transform, foreground, origin, text))
+            if (next == null || !next.Item.Equals(Transform, foreground, origin, text))
             {
                 Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
             }
@@ -184,7 +185,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<RectangleNode>();
 
-            if (next == null || !next.Equals(Transform, brush, null, rect, cornerRadius))
+            if (next == null || !next.Item.Equals(Transform, brush, null, rect, cornerRadius))
             {
                 Add(new RectangleNode(Transform, brush, null, rect, cornerRadius, CreateChildScene(brush)));
             }
@@ -204,7 +205,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<ClipNode>();
 
-            if (next == null || !next.Equals(null))
+            if (next == null || !next.Item.Equals(null))
             {
                 Add(new ClipNode());
             }
@@ -219,9 +220,9 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<GeometryClipNode>();
 
-            if (next == null || !next.Equals(null))
+            if (next == null || !next.Item.Equals(null))
             {
-                Add(new GeometryClipNode());
+                Add((new GeometryClipNode()));
             }
             else
             {
@@ -234,7 +235,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<OpacityNode>();
 
-            if (next == null || !next.Equals(null))
+            if (next == null || !next.Item.Equals(null))
             {
                 Add(new OpacityNode());
             }
@@ -249,7 +250,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<OpacityMaskNode>();
 
-            if (next == null || !next.Equals(null, null))
+            if (next == null || !next.Item.Equals(null, null))
             {
                 Add(new OpacityMaskNode());
             }
@@ -264,7 +265,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<ClipNode>();
 
-            if (next == null || !next.Equals(clip))
+            if (next == null || !next.Item.Equals(clip))
             {
                 Add(new ClipNode(clip));
             }
@@ -279,7 +280,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<GeometryClipNode>();
 
-            if (next == null || !next.Equals(clip))
+            if (next == null || !next.Item.Equals(clip))
             {
                 Add(new GeometryClipNode(clip));
             }
@@ -294,7 +295,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<OpacityNode>();
 
-            if (next == null || !next.Equals(opacity))
+            if (next == null || !next.Item.Equals(opacity))
             {
                 Add(new OpacityNode(opacity));
             }
@@ -309,7 +310,7 @@ namespace Avalonia.Rendering.SceneGraph
         {
             var next = NextDrawAs<OpacityMaskNode>();
 
-            if (next == null || !next.Equals(mask, bounds))
+            if (next == null || !next.Item.Equals(mask, bounds))
             {
                 Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
             }
@@ -341,7 +342,7 @@ namespace Avalonia.Rendering.SceneGraph
 
                 foreach (var operation in Owner._node.DrawOperations)
                 {
-                    dirty.Add(operation.Bounds);
+                    dirty.Add(operation.Item.Bounds);
                 }
 
                 Owner._node = Node;
@@ -355,7 +356,15 @@ namespace Avalonia.Rendering.SceneGraph
             public int DrawOperationIndex { get; }
         }
 
-        private void Add(IDrawOperation  node)
+        private void Add(IDrawOperation node)
+        {
+            using (var refCounted = RefCountable.Create(node))
+            {
+                Add(refCounted); 
+            }
+        }
+
+        private void Add(IRef<IDrawOperation> node)
         {
             if (_drawOperationindex < _node.DrawOperations.Count)
             {
@@ -369,9 +378,9 @@ namespace Avalonia.Rendering.SceneGraph
             ++_drawOperationindex;
         }
 
-        private T NextDrawAs<T>() where T : class, IDrawOperation
+        private IRef<T> NextDrawAs<T>() where T : class, IDrawOperation
         {
-            return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as T : null;
+            return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef<T> : null;
         }
 
         private IDictionary<IVisual, Scene> CreateChildScene(IBrush brush)

+ 4 - 0
src/Avalonia.Visuals/Rendering/SceneGraph/DrawOperation.cs

@@ -22,5 +22,9 @@ namespace Avalonia.Rendering.SceneGraph
         public abstract bool HitTest(Point p);
 
         public abstract void Render(IDrawingContextImpl context);
+
+        public virtual void Dispose()
+        {
+        }
     }
 }

Vissa filer visades inte eftersom för många filer har ändrats