Forráskód Böngészése

Merge branch 'master' into issues/855_test

Jeremy Koritzinsky 8 éve
szülő
commit
1dcd06df3f
100 módosított fájl, 6543 hozzáadás és 652 törlés
  1. 1 2
      Avalonia.sln
  2. 1 5
      appveyor.yml
  3. 1 1
      build/MonoMac.props
  4. 0 5
      build/Serilog.Sinks.Trace.props
  5. 3 1
      build/Serilog.props
  6. 0 5
      build/Splat.props
  7. 6 4
      packages.cake
  8. 1 12
      samples/BindingTest/App.xaml.cs
  9. 0 2
      samples/BindingTest/BindingTest.csproj
  10. 0 1
      samples/ControlCatalog.Desktop/ControlCatalog.Desktop.csproj
  11. 1 14
      samples/ControlCatalog.Desktop/Program.cs
  12. 6 0
      samples/ControlCatalog/ControlCatalog.csproj
  13. 1 0
      samples/ControlCatalog/MainView.xaml
  14. 47 0
      samples/ControlCatalog/Pages/CalendarPage.xaml
  15. 28 0
      samples/ControlCatalog/Pages/CalendarPage.xaml.cs
  16. 9 1
      samples/ControlCatalog/Pages/CarouselPage.xaml
  17. 4 1
      samples/ControlCatalog/Pages/CarouselPage.xaml.cs
  18. 19 6
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  19. 1 1
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  20. 18 6
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  21. 1 13
      samples/RenderTest/Program.cs
  22. 0 2
      samples/RenderTest/RenderTest.csproj
  23. 1 12
      samples/VirtualizationTest/Program.cs
  24. 0 2
      samples/VirtualizationTest/VirtualizationTest.csproj
  25. 0 2
      samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
  26. 0 1
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  27. 1 1
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  28. 3 2
      src/Avalonia.Base/Collections/AvaloniaList.cs
  29. 15 0
      src/Avalonia.Base/Collections/IAvaloniaList.cs
  30. 11 6
      src/Avalonia.Base/Data/IndexerBinding.cs
  31. 101 54
      src/Avalonia.Base/Data/InstancedBinding.cs
  32. 6 0
      src/Avalonia.Base/Threading/Dispatcher.cs
  33. 2132 0
      src/Avalonia.Controls/Calendar/Calendar.cs
  34. 215 0
      src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs
  35. 193 0
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  36. 79 0
      src/Avalonia.Controls/Calendar/CalendarDateRange.cs
  37. 253 0
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  38. 21 0
      src/Avalonia.Controls/Calendar/CalendarExtensions.cs
  39. 1264 0
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  40. 155 0
      src/Avalonia.Controls/Calendar/DateTimeHelper.cs
  41. 361 0
      src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
  42. 1 1
      src/Avalonia.Controls/Classes.cs
  43. 3 2
      src/Avalonia.Controls/Control.cs
  44. 32 1
      src/Avalonia.Controls/DropDown.cs
  45. 11 0
      src/Avalonia.Controls/ItemsControl.cs
  46. 6 5
      src/Avalonia.Controls/Panel.cs
  47. 1 1
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  48. 28 8
      src/Avalonia.Controls/Primitives/ToggleButton.cs
  49. 2 3
      src/Avalonia.Controls/ProgressBar.cs
  50. 5 4
      src/Avalonia.Controls/RadioButton.cs
  51. 44 2
      src/Avalonia.Controls/Shapes/Shape.cs
  52. 1 1
      src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs
  53. 12 0
      src/Avalonia.Controls/TextBox.cs
  54. 1 4
      src/Avalonia.Controls/TreeView.cs
  55. 5 3
      src/Avalonia.Controls/VirtualizingStackPanel.cs
  56. 0 1
      src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
  57. 0 53
      src/Avalonia.Diagnostics/LogManager.cs
  58. 6 0
      src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
  59. 1 0
      src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj
  60. 51 0
      src/Avalonia.Logging.Serilog/SerilogExtensions.cs
  61. 0 1
      src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj
  62. 1 2
      src/Avalonia.Remote.Protocol/BsonStreamTransport.cs
  63. 31 29
      src/Avalonia.Styling/Styling/Setter.cs
  64. 30 0
      src/Avalonia.Themes.Default/Calendar.xaml
  65. 80 0
      src/Avalonia.Themes.Default/CalendarButton.xaml
  66. 116 0
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  67. 183 0
      src/Avalonia.Themes.Default/CalendarItem.xaml
  68. 23 8
      src/Avalonia.Themes.Default/CheckBox.xaml
  69. 4 0
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  70. 14 0
      src/Avalonia.Themes.Default/RadioButton.xaml
  71. 21 4
      src/Avalonia.Visuals/Animation/PageSlide.cs
  72. 1 1
      src/Avalonia.Visuals/Media/PathMarkupParser.cs
  73. 1 1
      src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
  74. 13 0
      src/Avalonia.Visuals/Visual.cs
  75. 0 3
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  76. 39 72
      src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
  77. 7 28
      src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs
  78. 2 0
      src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
  79. 0 69
      src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs
  80. 173 5
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  81. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs
  82. 0 31
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs
  83. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
  84. 1 11
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs
  85. 0 63
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs
  86. 1 1
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github
  87. 1 1
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  88. 72 19
      src/Markup/Avalonia.Markup/ControlLocator.cs
  89. 1 9
      src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
  90. 71 17
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  91. 42 4
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  92. 22 10
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  93. 33 0
      src/OSX/Avalonia.MonoMac/RenderLoop.cs
  94. 85 4
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  95. 0 1
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  96. 1 1
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  97. 2 2
      tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs
  98. 22 0
      tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
  99. 2 2
      tests/Avalonia.Base.UnitTests/PriorityValueTests.cs
  100. 277 0
      tests/Avalonia.Controls.UnitTests/CalendarTests.cs

+ 1 - 2
Avalonia.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
 # Visual Studio 15
-VisualStudioVersion = 15.0.26730.10
+VisualStudioVersion = 15.0.27004.2008
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
 EndProject
@@ -155,7 +155,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
 		build\ReactiveUI.props = build\ReactiveUI.props
 		build\Rx.props = build\Rx.props
 		build\Serilog.props = build\Serilog.props
-		build\Serilog.Sinks.Trace.props = build\Serilog.Sinks.Trace.props
 		build\SharpDX.props = build\SharpDX.props
 		build\SkiaSharp.Desktop.props = build\SkiaSharp.Desktop.props
 		build\SkiaSharp.props = build\SkiaSharp.props

+ 1 - 5
appveyor.yml

@@ -1,4 +1,4 @@
-os: Visual Studio 2017
+os: Previous Visual Studio 2017
 platform:
 - Any CPU
 skip_branch_with_pr: true
@@ -23,10 +23,6 @@ before_build:
 - git submodule update --init
 build_script:
 - ps: .\build.ps1 -Target "AppVeyor" -Platform "$env:platform" -Configuration "$env:configuration"
-after_build:
-- "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%"
-- pip install codecov
-- codecov -f "./artifacts/coverage.xml"
 
 test: off
 artifacts:

+ 1 - 1
build/MonoMac.props

@@ -1,5 +1,5 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-    <PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
+    <PackageReference Include="MonoMac.NetStandard" Version="0.0.4" />
   </ItemGroup>
 </Project>

+ 0 - 5
build/Serilog.Sinks.Trace.props

@@ -1,5 +0,0 @@
-<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-     <PackageReference Include="Serilog.Sinks.Trace" Version="2.1.0" />
-  </ItemGroup>
-</Project>

+ 3 - 1
build/Serilog.props

@@ -1,5 +1,7 @@
 <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <ItemGroup>
-     <PackageReference Include="Serilog" Version="2.4.0" />
+    <PackageReference Include="Serilog" Version="2.5.0" />
+    <PackageReference Include="Serilog.Sinks.Trace" Version="2.1.0" />
+    <PackageReference Include="Serilog.Sinks.Debug" Version="1.0.0" />
   </ItemGroup>
 </Project>

+ 0 - 5
build/Splat.props

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

+ 6 - 4
packages.cake

@@ -107,7 +107,8 @@ public class Packages
         context.Information("Setting NuGet package dependencies versions:");
 
         var SerilogVersion = packageVersions["Serilog"].FirstOrDefault().Item1;
-        var SplatVersion = packageVersions["Splat"].FirstOrDefault().Item1;
+        var SerilogSinksDebugVersion = packageVersions["Serilog.Sinks.Debug"].FirstOrDefault().Item1;
+        var SerilogSinksTraceVersion = packageVersions["Serilog.Sinks.Trace"].FirstOrDefault().Item1;
         var SpracheVersion = packageVersions["Sprache"].FirstOrDefault().Item1;
         var SystemReactiveVersion = packageVersions["System.Reactive"].FirstOrDefault().Item1;
         var ReactiveUIVersion = packageVersions["reactiveui"].FirstOrDefault().Item1;
@@ -121,7 +122,6 @@ public class Packages
         var SharpDXDXGIVersion = packageVersions["SharpDX.DXGI"].FirstOrDefault().Item1;
 
         context.Information("Package: Serilog, version: {0}", SerilogVersion);
-        context.Information("Package: Splat, version: {0}", SplatVersion);
         context.Information("Package: Sprache, version: {0}", SpracheVersion);
         context.Information("Package: System.Reactive, version: {0}", SystemReactiveVersion);
         context.Information("Package: reactiveui, version: {0}", ReactiveUIVersion);
@@ -245,7 +245,8 @@ public class Packages
                 Dependencies = new DependencyBuilder(this)
                 {
                     new NuSpecDependency() { Id = "Serilog", Version = SerilogVersion },
-                    new NuSpecDependency() { Id = "Splat", Version = SplatVersion },
+                    new NuSpecDependency() { Id = "Serilog.Sinks.Debug", Version = SerilogSinksDebugVersion },
+                    new NuSpecDependency() { Id = "Serilog.Sinks.Trace", Version = SerilogSinksTraceVersion },
                     new NuSpecDependency() { Id = "Sprache", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "System.Reactive", Version = SystemReactiveVersion },
                     new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", Version = parameters.Version },
@@ -253,8 +254,9 @@ public class Packages
                     new NuSpecDependency() { Id = "System.Threading.ThreadPool", TargetFramework = "netcoreapp2.0", Version = "4.3.0" },
                     new NuSpecDependency() { Id = "Microsoft.Extensions.DependencyModel", TargetFramework = "netcoreapp2.0", Version = "1.1.0" },
                     new NuSpecDependency() { Id = "NETStandard.Library", TargetFramework = "netcoreapp2.0", Version = "1.6.0" },
-                    new NuSpecDependency() { Id = "Splat", TargetFramework = "netcoreapp2.0", Version = SplatVersion },
                     new NuSpecDependency() { Id = "Serilog", TargetFramework = "netcoreapp2.0", Version = SerilogVersion },
+                    new NuSpecDependency() { Id = "Serilog.Sinks.Debug", TargetFramework = "netcoreapp2.0", Version = SerilogSinksDebugVersion },
+                    new NuSpecDependency() { Id = "Serilog.Sinks.Trace", TargetFramework = "netcoreapp2.0", Version = SerilogSinksTraceVersion },
                     new NuSpecDependency() { Id = "Sprache", TargetFramework = "netcoreapp2.0", Version = SpracheVersion },
                     new NuSpecDependency() { Id = "System.Reactive", TargetFramework = "netcoreapp2.0", Version = SystemReactiveVersion },
                     new NuSpecDependency() { Id = "Avalonia.Remote.Protocol", TargetFramework = "netcoreapp2.0", Version = parameters.Version },

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

@@ -16,22 +16,11 @@ namespace BindingTest
 
         private static void Main()
         {
-            InitializeLogging();
-
             AppBuilder.Configure<App>()
                 .UsePlatformDetect()
                 .UseReactiveUI()
+                .LogToDebug()
                 .Start<MainWindow>();
         }
-
-        private static void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
     }
 }

+ 0 - 2
samples/BindingTest/BindingTest.csproj

@@ -151,8 +151,6 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
-  <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
-  <Import Project="..\..\build\Splat.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
 </Project>

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

@@ -135,5 +135,4 @@
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
   <Import Project="..\..\build\SkiaSharp.props" />
-  <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
 </Project>

+ 1 - 14
samples/ControlCatalog.Desktop/Program.cs

@@ -12,8 +12,6 @@ namespace ControlCatalog
     {
         static void Main(string[] args)
         {
-            InitializeLogging();
-
             // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
             // again.
             BuildAvaloniaApp().Start<MainWindow>();
@@ -23,18 +21,7 @@ namespace ControlCatalog
         /// This method is needed for IDE previewer infrastructure
         /// </summary>
         public static AppBuilder BuildAvaloniaApp()
-            => AppBuilder.Configure<App>().UsePlatformDetect();
-
-        // This will be made into a runtime configuration extension soon!
-        private static void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
+            => AppBuilder.Configure<App>().LogToDebug().UsePlatformDetect();
 
         private static void ConfigureAssetAssembly(AppBuilder builder)
         {

+ 6 - 0
samples/ControlCatalog/ControlCatalog.csproj

@@ -44,6 +44,9 @@
     <EmbeddedResource Include="Pages\ButtonPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
+    <EmbeddedResource Include="Pages\CalendarPage.xaml">
+      <SubType>Designer</SubType>
+    </EmbeddedResource>
     <EmbeddedResource Include="Pages\CanvasPage.xaml">
       <SubType>Designer</SubType>
     </EmbeddedResource>
@@ -107,6 +110,9 @@
     <Compile Include="Pages\ButtonPage.xaml.cs">
       <DependentUpon>ButtonPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Pages\CalendarPage.xaml.cs">
+      <DependentUpon>CalendarPage.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Pages\CanvasPage.xaml.cs">
       <DependentUpon>CanvasPage.xaml</DependentUpon>
     </Compile>

+ 1 - 0
samples/ControlCatalog/MainView.xaml

@@ -7,6 +7,7 @@
     </TabControl.Transition>
     <TabItem Header="Border"><pages:BorderPage/></TabItem>
     <TabItem Header="Button"><pages:ButtonPage/></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>

+ 47 - 0
samples/ControlCatalog/Pages/CalendarPage.xaml

@@ -0,0 +1,47 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <StackPanel Orientation="Vertical" Gap="4">
+    <TextBlock Classes="h1">Calendar</TextBlock>
+    <TextBlock Classes="h2">A calendar control for selecting dates</TextBlock>
+        
+    <StackPanel Orientation="Horizontal"
+                Margin="0,16,0,0"
+                HorizontalAlignment="Center"
+                Gap="16">
+      <StackPanel Orientation="Vertical">
+        <TextBlock Text="SelectionMode: None"/>
+        <Calendar SelectionMode="None"
+                  Margin="0,0,0,8"/>
+
+        <TextBlock Text="SelectionMode: SingleDate"/>
+        <Calendar SelectionMode="SingleDate"
+                  Margin="0,0,0,8"/>
+
+        <TextBlock Text="Disabled"/>
+        <Calendar IsEnabled="False"
+                  SelectionMode="SingleDate"/>
+      </StackPanel>
+
+      <StackPanel Orientation="Vertical">
+        <TextBlock Text="SelectionMode: SingleRange"/>
+        <Calendar SelectionMode="SingleRange"
+                  Margin="0,0,0,8"/>
+
+        <TextBlock Text="SelectionMode: MultipleRange"/>
+        <Calendar SelectionMode="MultipleRange"/>
+      </StackPanel>
+
+      <StackPanel Orientation="Vertical">
+        <TextBlock Text="DisplayDates"/>
+        <Calendar Name="DisplayDatesCalendar"
+                  SelectionMode="SingleDate"
+                  Margin="0,0,0,8"/>
+
+        <TextBlock Text="BlackoutDates"/>
+        <Calendar Name="BlackoutDatesCalendar"
+                  SelectionMode="SingleDate" />
+      </StackPanel>
+      
+    </StackPanel> 
+  </StackPanel>
+</UserControl>

+ 28 - 0
samples/ControlCatalog/Pages/CalendarPage.xaml.cs

@@ -0,0 +1,28 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using System;
+
+namespace ControlCatalog.Pages
+{
+    public class CalendarPage : UserControl
+    {
+        public CalendarPage()
+        {
+            this.InitializeComponent();
+
+            var today = DateTime.Today; 
+            var cal1 = this.FindControl<Calendar>("DisplayDatesCalendar");
+            cal1.DisplayDateStart = today.AddDays(-25);
+            cal1.DisplayDateEnd = today.AddDays(25);
+
+            var cal2 = this.FindControl<Calendar>("BlackoutDatesCalendar");
+            cal2.BlackoutDates.AddDatesInPast();
+            cal2.BlackoutDates.Add(new CalendarDateRange(today.AddDays(6)));
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 9 - 1
samples/ControlCatalog/Pages/CarouselPage.xaml

@@ -9,7 +9,7 @@
       </Button>
       <Carousel Name="carousel">
         <Carousel.Transition>
-          <PageSlide Duration="0.25"/>
+          <PageSlide Duration="0.25" Orientation="Vertical" />
         </Carousel.Transition>
         <Image Source="resm:ControlCatalog.Assets.delicate-arch-896885_640.jpg"/>
         <Image Source="resm:ControlCatalog.Assets.hirsch-899118_640.jpg"/>
@@ -28,6 +28,14 @@
         <DropDownItem>Crossfade</DropDownItem>
       </DropDown>
     </StackPanel>
+
+    <StackPanel Orientation="Horizontal" Gap="4">
+      <TextBlock VerticalAlignment="Center">Orientation</TextBlock>
+      <DropDown Name="orientation" SelectedIndex="1" VerticalAlignment="Center">
+        <DropDownItem>Horizontal</DropDownItem>
+        <DropDownItem>Vertical</DropDownItem>
+      </DropDown>
+    </StackPanel>
     
   </StackPanel>
 </UserControl>

+ 4 - 1
samples/ControlCatalog/Pages/CarouselPage.xaml.cs

@@ -11,6 +11,7 @@ namespace ControlCatalog.Pages
         private Button _left;
         private Button _right;
         private DropDown _transition;
+        private DropDown _orientation;
 
         public CarouselPage()
         {
@@ -18,6 +19,7 @@ namespace ControlCatalog.Pages
             _left.Click += (s, e) => _carousel.Previous();
             _right.Click += (s, e) => _carousel.Next();
             _transition.SelectionChanged += TransitionChanged;
+            _orientation.SelectionChanged += TransitionChanged;
         }
 
         private void InitializeComponent()
@@ -27,6 +29,7 @@ namespace ControlCatalog.Pages
             _left = this.FindControl<Button>("left");
             _right = this.FindControl<Button>("right");
             _transition = this.FindControl<DropDown>("transition");
+            _orientation = this.FindControl<DropDown>("orientation");
         }
 
         private void TransitionChanged(object sender, SelectionChangedEventArgs e)
@@ -37,7 +40,7 @@ namespace ControlCatalog.Pages
                     _carousel.Transition = null;
                     break;
                 case 1:
-                    _carousel.Transition = new PageSlide(TimeSpan.FromSeconds(0.25));
+                    _carousel.Transition = new PageSlide(TimeSpan.FromSeconds(0.25), _orientation.SelectedIndex == 0 ? PageSlide.SlideAxis.Horizontal : PageSlide.SlideAxis.Vertical);
                     break;
                 case 2:
                     _carousel.Transition = new CrossFade(TimeSpan.FromSeconds(0.25));

+ 19 - 6
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@@ -1,15 +1,28 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <StackPanel Orientation="Vertical" Gap="4">
     <TextBlock Classes="h1">CheckBox</TextBlock>
     <TextBlock Classes="h2">A check box control</TextBlock>
 
-    <StackPanel Orientation="Vertical"
+    <StackPanel Orientation="Horizontal"
                 Margin="0,16,0,0"
                 HorizontalAlignment="Center"
                 Gap="16">
-      <CheckBox>Unchecked</CheckBox>
-      <CheckBox IsChecked="True">Checked</CheckBox>
-      <CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
-    </StackPanel>    
+      <StackPanel Orientation="Vertical"
+                  Gap="16">
+        <CheckBox>Unchecked</CheckBox>
+        <CheckBox IsChecked="True">Checked</CheckBox>
+        <CheckBox IsChecked="{x:Null}">Indeterminate</CheckBox>
+        <CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
+      </StackPanel>
+      <StackPanel Orientation="Vertical"
+                  HorizontalAlignment="Center"
+                  Gap="16">
+        <CheckBox IsChecked="False" IsThreeState="True">Three State: Unchecked</CheckBox>
+        <CheckBox IsChecked="True" IsThreeState="True">Three State: Checked</CheckBox>
+        <CheckBox IsChecked="{x:Null}" IsThreeState="True">Three State: Indeterminate</CheckBox>
+        <CheckBox IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Three State: Disabled</CheckBox>
+      </StackPanel>
+    </StackPanel>
   </StackPanel>
 </UserControl>

+ 1 - 1
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@@ -36,7 +36,7 @@ namespace ControlCatalog.Pages
             };
         }
 
-        Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked ? (Window)this.VisualRoot : null;
+        Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked.Value ? (Window)this.VisualRoot : null;
 
         private void InitializeComponent()
         {

+ 18 - 6
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@@ -1,15 +1,27 @@
-<UserControl xmlns="https://github.com/avaloniaui">
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
   <StackPanel Orientation="Vertical" Gap="4">
     <TextBlock Classes="h1">RadioButton</TextBlock>
     <TextBlock Classes="h2">Allows the selection of a single option of many</TextBlock>
 
-    <StackPanel Orientation="Vertical"
+    <StackPanel Orientation="Horizontal"
                 Margin="0,16,0,0"
                 HorizontalAlignment="Center"
                 Gap="16">
-      <RadioButton IsChecked="True">Option 1</RadioButton>
-      <RadioButton>Option 2</RadioButton>
-      <RadioButton IsEnabled="False">Disabled</RadioButton>
-    </StackPanel>    
+      <StackPanel Orientation="Vertical"
+                  Gap="16">
+        <RadioButton IsChecked="True">Option 1</RadioButton>
+        <RadioButton>Option 2</RadioButton>
+        <RadioButton IsChecked="{x:Null}">Option 3</RadioButton>
+        <RadioButton IsEnabled="False">Disabled</RadioButton>
+      </StackPanel>
+      <StackPanel Orientation="Vertical"
+                  Gap="16">
+        <RadioButton IsChecked="True" IsThreeState="True">Three States: Option 1</RadioButton>
+        <RadioButton IsChecked="False" IsThreeState="True">Three States: Option 2</RadioButton>
+        <RadioButton IsChecked="{x:Null}" IsThreeState="True">Three States: Option 3</RadioButton>
+        <RadioButton IsChecked="{x:Null}" IsThreeState="True" IsEnabled="False">Disabled</RadioButton>
+      </StackPanel>
+    </StackPanel>
   </StackPanel>
 </UserControl>

+ 1 - 13
samples/RenderTest/Program.cs

@@ -12,25 +12,13 @@ namespace RenderTest
     {
         static void Main(string[] args)
         {
-            InitializeLogging();
-
             // TODO: Make this work with GTK/Skia/Cairo depending on command-line args
             // again.
             AppBuilder.Configure<App>()
                 .UsePlatformDetect()
                 .UseReactiveUI()
+                .LogToDebug()
                 .Start<MainWindow>();
         }
-
-        // This will be made into a runtime configuration extension soon!
-        private static void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
     }
 }

+ 0 - 2
samples/RenderTest/RenderTest.csproj

@@ -180,8 +180,6 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
-  <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
-  <Import Project="..\..\build\Splat.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
 </Project>

+ 1 - 12
samples/VirtualizationTest/Program.cs

@@ -13,22 +13,11 @@ namespace VirtualizationTest
     {
         static void Main(string[] args)
         {
-            InitializeLogging();
-
             AppBuilder.Configure<App>()
                .UsePlatformDetect()
                .UseReactiveUI()
+               .LogToDebug()
                .Start<MainWindow>();
         }
-
-        private static void InitializeLogging()
-        {
-#if DEBUG
-            SerilogLogger.Initialize(new LoggerConfiguration()
-                .MinimumLevel.Warning()
-                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
-                .CreateLogger());
-#endif
-        }
     }
 }

+ 0 - 2
samples/VirtualizationTest/VirtualizationTest.csproj

@@ -147,8 +147,6 @@
   </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="..\..\build\Serilog.props" />
-  <Import Project="..\..\build\Serilog.Sinks.Trace.props" />
-  <Import Project="..\..\build\Splat.props" />
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
 </Project>

+ 0 - 2
samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj

@@ -28,7 +28,5 @@
         <ProjectReference Include="..\..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" />
     </ItemGroup>
     <Import Project="..\..\..\build\Serilog.props" />
-    <Import Project="..\..\..\build\Serilog.Sinks.Trace.props" />
-    <Import Project="..\..\..\build\Splat.props" />
     <Import Project="..\..\..\build\Rx.props" />
 </Project>

+ 0 - 1
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@@ -154,7 +154,6 @@
   </ItemGroup>
   <Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
   <Import Project="..\..\..\build\Serilog.props" />
-  <Import Project="..\..\..\build\Splat.props" />
   <Import Project="..\..\..\build\Sprache.props" />
   <Import Project="..\..\..\build\Rx.props" />
 </Project>

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

@@ -310,7 +310,7 @@ namespace Avalonia
                 object anchor = null,
                 bool enableDataValidation = false)
             {
-                return new InstancedBinding(_source);
+                return InstancedBinding.OneWay(_source);
             }
         }
     }

+ 3 - 2
src/Avalonia.Base/Collections/AvaloniaList.cs

@@ -350,14 +350,15 @@ namespace Avalonia.Collections
         public void MoveRange(int oldIndex, int count, int newIndex)
         {
             var items = _inner.GetRange(oldIndex, count);
+            var modifiedNewIndex = newIndex;
             _inner.RemoveRange(oldIndex, count);
 
             if (newIndex > oldIndex)
             {
-                newIndex -= count;
+                modifiedNewIndex -= count;
             }
 
-            _inner.InsertRange(newIndex, items);
+            _inner.InsertRange(modifiedNewIndex, items);
 
             if (_collectionChanged != null)
             {

+ 15 - 0
src/Avalonia.Base/Collections/IAvaloniaList.cs

@@ -36,6 +36,21 @@ namespace Avalonia.Collections
         /// <param name="items">The items.</param>
         void InsertRange(int index, IEnumerable<T> items);
 
+        /// <summary>
+        /// Moves an item to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The index of the item to move.</param>
+        /// <param name="newIndex">The index to move the item to.</param>
+        void Move(int oldIndex, int newIndex);
+
+        /// <summary>
+        /// Moves multiple items to a new index.
+        /// </summary>
+        /// <param name="oldIndex">The first index of the items to move.</param>
+        /// <param name="count">The number of items to move.</param>
+        /// <param name="newIndex">The index to move the items to.</param>
+        void MoveRange(int oldIndex, int count, int newIndex);
+
         /// <summary>
         /// Removes multiple items from the collection.
         /// </summary>

+ 11 - 6
src/Avalonia.Base/Data/IndexerBinding.cs

@@ -31,13 +31,18 @@ namespace Avalonia.Data
                 targetProperty.GetMetadata(target.GetType()).DefaultBindingMode :
                 Mode;
 
-            if (mode == BindingMode.TwoWay)
+            switch (mode)
             {
-                return new InstancedBinding(Source.GetSubject(Property), mode);
-            }
-            else
-            {
-                return new InstancedBinding(Source.GetObservable(Property), mode);
+                case BindingMode.OneTime:
+                    return InstancedBinding.OneTime(Source.GetObservable(Property));
+                case BindingMode.OneWay:
+                    return InstancedBinding.OneWay(Source.GetObservable(Property));
+                case BindingMode.OneWayToSource:
+                    return InstancedBinding.OneWayToSource(Source.GetSubject(Property));
+                case BindingMode.TwoWay:
+                    return InstancedBinding.TwoWay(Source.GetSubject(Property));
+                default:
+                    throw new NotSupportedException("Unsupported BindingMode.");
             }
         }
     }

+ 101 - 54
src/Avalonia.Base/Data/InstancedBinding.cs

@@ -14,71 +14,35 @@ namespace Avalonia.Data
     /// property on a control's DataContext"; this class represents a binding that has been 
     /// *instanced* by calling <see cref="IBinding.Initiate(IAvaloniaObject, AvaloniaProperty, object, bool)"/>
     /// on a target object.
-    /// 
-    /// When a binding is initiated, it can return one of 3 possible sources for the binding:
-    /// - An <see cref="ISubject{Object}"/> which can be used for any type of binding.
-    /// - An <see cref="IObservable{Object}"/> which can be used for all types of bindings except
-    ///  <see cref="BindingMode.OneWayToSource"/> and <see cref="BindingMode.TwoWay"/>.
-    /// - A plain object, which can only represent a <see cref="BindingMode.OneTime"/> binding.
     /// </remarks>
     public class InstancedBinding
     {
         /// <summary>
         /// Initializes a new instance of the <see cref="InstancedBinding"/> class.
         /// </summary>
-        /// <param name="value">
-        /// The value used for the <see cref="BindingMode.OneTime"/> binding.
-        /// </param>
-        /// <param name="priority">The binding priority.</param>
-        public InstancedBinding(object value,
-            BindingPriority priority = BindingPriority.LocalValue)
-        {
-            Mode = BindingMode.OneTime;
-            Priority = priority;
-            Value = value;
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="InstancedBinding"/> class.
-        /// </summary>
-        /// <param name="observable">The observable for a one-way binding.</param>
+        /// <param name="subject">The binding source.</param>
         /// <param name="mode">The binding mode.</param>
-        /// <param name="priority">The binding priority.</param>
-        public InstancedBinding(
-            IObservable<object> observable, 
-            BindingMode mode = BindingMode.OneWay,
-            BindingPriority priority = BindingPriority.LocalValue)
+        /// <param name="priority">The priority of the binding.</param>
+        /// <remarks>
+        /// This constructor can be used to create any type of binding and as such requires an
+        /// <see cref="ISubject{Object}"/> as the binding source because this is the only binding
+        /// source which can be used for all binding modes. If you wish to create an instance with
+        /// something other than a subject, use one of the static creation methods on this class.
+        /// </remarks>
+        public InstancedBinding(ISubject<object> subject, BindingMode mode, BindingPriority priority)
         {
-            Contract.Requires<ArgumentNullException>(observable != null);
-
-            if (mode == BindingMode.OneWayToSource || mode == BindingMode.TwoWay)
-            {
-                throw new ArgumentException(
-                    "Invalid BindingResult mode: OneWayToSource and TwoWay bindings" +
-                    "require a Subject.");
-            }
+            Contract.Requires<ArgumentNullException>(subject != null);
 
             Mode = mode;
             Priority = priority;
-            Observable = observable;
+            Value = subject;
         }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="InstancedBinding"/> class.
-        /// </summary>
-        /// <param name="subject">The subject for a two-way binding.</param>
-        /// <param name="mode">The binding mode.</param>
-        /// <param name="priority">The binding priority.</param>
-        public InstancedBinding(
-            ISubject<object> subject,
-            BindingMode mode = BindingMode.OneWay,
-            BindingPriority priority = BindingPriority.LocalValue)
+        private InstancedBinding(object value, BindingMode mode, BindingPriority priority)
         {
-            Contract.Requires<ArgumentNullException>(subject != null);
-
             Mode = mode;
             Priority = priority;
-            Subject = subject;
+            Value = value;
         }
 
         /// <summary>
@@ -92,18 +56,101 @@ namespace Avalonia.Data
         public BindingPriority Priority { get; }
 
         /// <summary>
-        /// Gets the value used for a <see cref="BindingMode.OneTime"/> binding.
+        /// Gets the value or source of the binding.
         /// </summary>
         public object Value { get; }
 
         /// <summary>
-        /// Gets the observable for a one-way binding.
+        /// Gets the <see cref="Value"/> as an observable.
         /// </summary>
-        public IObservable<object> Observable { get; }
+        public IObservable<object> Observable => Value as IObservable<object>;
 
         /// <summary>
-        /// Gets the subject for a two-way binding.
+        /// Gets the <see cref="Value"/> as a subject.
         /// </summary>
-        public ISubject<object> Subject { get; }
+        public ISubject<object> Subject => Value as ISubject<object>;
+
+        /// <summary>
+        /// Creates a new one-time binding with a fixed value.
+        /// </summary>
+        /// <param name="value">The value.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public static InstancedBinding OneTime(
+            object value,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            return new InstancedBinding(value, BindingMode.OneTime, priority);
+        }
+
+        /// <summary>
+        /// Creates a new one-time binding.
+        /// </summary>
+        /// <param name="observable">The source observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public static InstancedBinding OneTime(
+            IObservable<object> observable,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            Contract.Requires<ArgumentNullException>(observable != null);
+
+            return new InstancedBinding(observable, BindingMode.OneTime, priority);
+        }
+
+        /// <summary>
+        /// Creates a new one-way binding.
+        /// </summary>
+        /// <param name="observable">The source observable.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public static InstancedBinding OneWay(
+            IObservable<object> observable,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            Contract.Requires<ArgumentNullException>(observable != null);
+
+            return new InstancedBinding(observable, BindingMode.OneWay, priority);
+        }
+
+        /// <summary>
+        /// Creates a new one-way to source binding.
+        /// </summary>
+        /// <param name="subject">The binding source.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public static InstancedBinding OneWayToSource(
+            ISubject<object> subject,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            Contract.Requires<ArgumentNullException>(subject != null);
+
+            return new InstancedBinding(subject, BindingMode.OneWayToSource, priority);
+        }
+
+        /// <summary>
+        /// Creates a new two-way binding.
+        /// </summary>
+        /// <param name="subject">The binding source.</param>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public static InstancedBinding TwoWay(
+            ISubject<object> subject,
+            BindingPriority priority = BindingPriority.LocalValue)
+        {
+            Contract.Requires<ArgumentNullException>(subject != null);
+
+            return new InstancedBinding(subject, BindingMode.TwoWay, priority);
+        }
+
+        /// <summary>
+        /// Creates a copy of the <see cref="InstancedBinding"/> with a different priority.
+        /// </summary>
+        /// <param name="priority">The priority of the binding.</param>
+        /// <returns>An <see cref="InstancedBinding"/> instance.</returns>
+        public InstancedBinding WithPriority(BindingPriority priority)
+        {
+            return new InstancedBinding(Value, Mode, priority);
+        }
     }
 }

+ 6 - 0
src/Avalonia.Base/Threading/Dispatcher.cs

@@ -72,6 +72,12 @@ namespace Avalonia.Threading
             _jobRunner?.RunJobs(null);
         }
 
+        /// <summary>
+        /// Use this method to ensure that more prioritized tasks are executed
+        /// </summary>
+        /// <param name="minimumPriority"></param>
+        public void RunJobs(DispatcherPriority minimumPriority) => _jobRunner.RunJobs(minimumPriority);
+
         /// <inheritdoc/>
         public Task InvokeTaskAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
         {

+ 2132 - 0
src/Avalonia.Controls/Calendar/Calendar.cs

@@ -0,0 +1,2132 @@
+// (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;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Specifies values for the different modes of operation of a
+    /// <see cref="T:Avalonia.Controls.Calendar" />.
+    /// </summary>
+    public enum CalendarMode
+    {
+        /// <summary>
+        /// The <see cref="T:Avalonia.Controls.Calendar" /> displays a
+        /// month at a time.
+        /// </summary>
+        Month = 0,
+
+        /// <summary>
+        /// The <see cref="T:Avalonia.Controls.Calendar" /> displays a
+        /// year at a time.
+        /// </summary>
+        Year = 1,
+
+        /// <summary>
+        /// The <see cref="T:Avalonia.Controls.Calendar" /> displays a
+        /// decade at a time.
+        /// </summary>
+        Decade = 2,
+    }
+
+    /// <summary>
+    /// Specifies values that describe the available selection modes for a
+    /// <see cref="T:Avalonia.Controls.Calendar" />.
+    /// </summary>
+    /// <remarks>
+    /// This enumeration provides the values that are used by the SelectionMode
+    /// property.
+    /// </remarks>
+    public enum CalendarSelectionMode
+    {
+        /// <summary>
+        /// Only a single date can be selected. Use the
+        /// <see cref="P:Avalonia.Controls.Calendar.SelectedDate" />
+        /// property to retrieve the selected date.
+        /// </summary>
+        SingleDate = 0,
+
+        /// <summary>
+        /// A single range of dates can be selected. Use 
+        /// <see cref="P:Avalonia.Controls.Calendar.SelectedDates" />
+        /// property to retrieve the selected dates.
+        /// </summary>
+        SingleRange = 1,
+
+        /// <summary>
+        /// Multiple non-contiguous ranges of dates can be selected. Use the
+        /// <see cref="P:Avalonia.Controls.Calendar.SelectedDates" />
+        /// property to retrieve the selected dates.
+        /// </summary>
+        MultipleRange = 2,
+
+        /// <summary>
+        /// No selections are allowed.
+        /// </summary>
+        None = 3,
+    }
+
+    /// <summary>
+    /// Provides data for the
+    /// <see cref="E:Avalonia.Controls.Calendar.DisplayDateChanged" />
+    /// event.
+    /// </summary>
+    public class CalendarDateChangedEventArgs : RoutedEventArgs
+    {
+        /// <summary>
+        /// Gets the date that was previously displayed.
+        /// </summary>
+        /// <value>
+        /// The date previously displayed.
+        /// </value>
+        public DateTime? RemovedDate { get; private set; }
+
+        /// <summary>
+        /// Gets the date to be newly displayed.
+        /// </summary>
+        /// <value>The new date to display.</value>
+        public DateTime? AddedDate { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the CalendarDateChangedEventArgs
+        /// class.
+        /// </summary>
+        /// <param name="removedDate">
+        /// The date that was previously displayed.
+        /// </param>
+        /// <param name="addedDate">The date to be newly displayed.</param>
+        internal CalendarDateChangedEventArgs(DateTime? removedDate, DateTime? addedDate)
+        {
+            RemovedDate = removedDate;
+            AddedDate = addedDate;
+        }
+    }
+
+    /// <summary>
+    /// Provides data for the
+    /// <see cref="E:Avalonia.Controls.Calendar.DisplayModeChanged" />
+    /// event.
+    /// </summary>
+    /// <QualityBand>Mature</QualityBand>
+    public class CalendarModeChangedEventArgs : RoutedEventArgs
+    {
+        /// <summary>
+        /// Gets the previous mode of the
+        /// <see cref="T:Avalonia.Controls.Calendar" />.
+        /// </summary>
+        /// <value>
+        /// A <see cref="T:Avalonia.Controls.CalendarMode" /> representing
+        /// the previous mode.
+        /// </value>
+        public CalendarMode OldMode { get; private set; }
+
+        /// <summary>
+        /// Gets the new mode of the
+        /// <see cref="T:Avalonia.Controls.Calendar" />.
+        /// </summary>
+        /// <value>
+        /// A <see cref="T:Avalonia.Controls.CalendarMode" /> 
+        /// the new mode.
+        /// </value>
+        public CalendarMode NewMode { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.CalendarModeChangedEventArgs" />
+        /// class.
+        /// </summary>
+        /// <param name="oldMode">The previous mode.</param>
+        /// <param name="newMode">The new mode.</param>
+        public CalendarModeChangedEventArgs(CalendarMode oldMode, CalendarMode newMode)
+        {
+            OldMode = oldMode;
+            NewMode = newMode;
+        }
+    }
+
+    /// <summary>
+    /// Represents a control that enables a user to select a date by using a
+    /// visual calendar display.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// A Calendar control can be used on its own, or as a drop-down part of a
+    /// DatePicker control. For more information, see DatePicker.  A Calendar
+    /// displays either the days of a month, the months of a year, or the years
+    /// of a decade, depending on the value of the DisplayMode property.  When
+    /// displaying the days of a month, the user can select a date, a range of
+    /// dates, or multiple ranges of dates.  The kinds of selections that are
+    /// allowed are controlled by the SelectionMode property.
+    /// </para>
+    /// <para>
+    /// The range of dates displayed is governed by the DisplayDateStart and
+    /// DisplayDateEnd properties.  If DisplayMode is Year or Decade, only
+    /// months or years that contain displayable dates will be displayed.
+    /// Setting the displayable range to a range that does not include the
+    /// current DisplayDate will throw an ArgumentOutOfRangeException.
+    /// </para>
+    /// <para>
+    /// The BlackoutDates property can be used to specify dates that cannot be
+    /// selected. These dates will be displayed as dimmed and disabled.
+    /// </para>
+    /// <para>
+    /// By default, Today is highlighted.  This can be disabled by setting
+    /// IsTodayHighlighted to false.
+    /// </para>
+    /// <para>
+    /// The Calendar control provides basic navigation using either the mouse or
+    /// keyboard. The following table summarizes keyboard navigation.
+    /// 
+    ///     Key Combination     DisplayMode     Action
+    ///     ARROW               Any             Change focused date, unselect
+    ///                                         all selected dates, and select
+    ///                                         new focused date.
+    ///                                         
+    ///     SHIFT+ARROW         Any             If SelectionMode is not set to
+    ///                                         SingleDate or None begin
+    ///                                         selecting a range of dates.
+    ///                                         
+    ///     CTRL+UP ARROW       Any             Switch to the next larger
+    ///                                         DisplayMode.  If DisplayMode is
+    ///                                         already Decade, no action.
+    ///                                         
+    ///     CTRL+DOWN ARROW     Any             Switch to the next smaller
+    ///                                         DisplayMode.  If DisplayMode is
+    ///                                         already Month, no action.
+    ///                                         
+    ///     SPACEBAR            Month           Select focused date.
+    ///     
+    ///     SPACEBAR            Year or Decade  Switch DisplayMode to the Month
+    ///                                         or Year represented by focused
+    ///                                         item.
+    /// </para>
+    /// <para>
+    /// XAML Usage for Classes Derived from Calendar
+    /// If you define a class that derives from Calendar, the class can be used
+    /// as an object element in XAML, and all of the inherited properties and
+    /// events that show a XAML usage in the reference for the Calendar members
+    /// can have the same XAML usage for the derived class. However, the object
+    /// element itself must have a different prefix mapping than the controls:
+    /// mapping shown in the usages, because the derived class comes from an
+    /// assembly and namespace that you create and define.  You must define your
+    /// own prefix mapping to an XML namespace to use the class as an object
+    /// element in XAML.
+    /// </para>
+    /// </remarks>
+    public class Calendar : TemplatedControl
+    {
+        internal const int RowsPerMonth = 7;
+        internal const int ColumnsPerMonth = 7;
+        internal const int RowsPerYear = 3;
+        internal const int ColumnsPerYear = 4;
+
+        private DateTime? _selectedDate;
+        private DateTime _selectedMonth;
+        private DateTime _selectedYear;
+
+        private DateTime _displayDate = DateTime.Today;
+        private DateTime? _displayDateStart = null;
+        private DateTime? _displayDateEnd = null;
+
+        private bool _isShiftPressed;
+        private bool _displayDateIsChanging = false;
+
+        internal CalendarDayButton FocusButton { get; set; }
+        internal CalendarButton FocusCalendarButton { get; set; }
+
+        internal Panel Root { get; set; }
+        internal CalendarItem MonthControl
+        {
+            get
+            {
+
+                if (Root != null && Root.Children.Count > 0)
+                {
+                    return Root.Children[0] as CalendarItem;
+                }
+                return null;
+            }
+        }
+
+        public static readonly StyledProperty<DayOfWeek> FirstDayOfWeekProperty =
+            AvaloniaProperty.Register<Calendar, DayOfWeek>(
+                    nameof(FirstDayOfWeek),
+                    defaultValue: DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek);
+        /// <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>
+        /// FirstDayOfWeekProperty property changed handler.
+        /// </summary>
+        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
+        private void OnFirstDayOfWeekChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+
+            if (IsValidFirstDayOfWeek(e.NewValue))
+            {
+                UpdateMonths();
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek");
+            }
+        }
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        /// <param name="value">Inherited code: Requires comment 1.</param>
+        /// <returns>Inherited code: Requires comment 2.</returns>
+        private static bool IsValidFirstDayOfWeek(object value)
+        {
+            DayOfWeek day = (DayOfWeek)value;
+
+            return day == DayOfWeek.Sunday
+                || day == DayOfWeek.Monday
+                || day == DayOfWeek.Tuesday
+                || day == DayOfWeek.Wednesday
+                || day == DayOfWeek.Thursday
+                || day == DayOfWeek.Friday
+                || day == DayOfWeek.Saturday;
+        }
+
+        public static readonly StyledProperty<bool> IsTodayHighlightedProperty =
+            AvaloniaProperty.Register<Calendar, bool>(
+                nameof(IsTodayHighlighted),
+                defaultValue: true);
+        /// <summary>
+        /// Gets or sets a value indicating whether the current date is
+        /// 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>
+        /// IsTodayHighlightedProperty property changed handler.
+        /// </summary>
+        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
+        private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (DisplayDate != null)
+            {
+                int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
+
+                if (i > -2 && i < 2)
+                {
+                    UpdateMonths();
+                }
+            }
+        }
+
+        public static readonly StyledProperty<IBrush> HeaderBackgroundProperty =
+            AvaloniaProperty.Register<Calendar, IBrush>(nameof(HeaderBackground));
+        public IBrush HeaderBackground
+        {
+            get { return GetValue(HeaderBackgroundProperty); }
+            set { SetValue(HeaderBackgroundProperty, value); }
+        }
+
+        public static readonly StyledProperty<CalendarMode> DisplayModeProperty =
+            AvaloniaProperty.Register<Calendar, CalendarMode>(
+                nameof(DisplayMode),
+                validate: ValidateDisplayMode);
+        /// <summary>
+        /// Gets or sets a value indicating whether the calendar is displayed in
+        /// months, years, or decades.
+        /// </summary>
+        /// <value>
+        /// A value indicating what length of time the
+        /// <see cref="T:System.Windows.Controls.Calendar" /> should display.
+        /// </value>
+        public CalendarMode DisplayMode
+        {
+            get { return GetValue(DisplayModeProperty); }
+            set { SetValue(DisplayModeProperty, value); }
+        }
+        /// <summary>
+        /// DisplayModeProperty property changed handler.
+        /// </summary>
+        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
+        private void OnDisplayModePropertyChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            CalendarMode mode = (CalendarMode)e.NewValue;
+            CalendarMode oldMode = (CalendarMode)e.OldValue;
+            CalendarItem monthControl = MonthControl;
+
+            if (monthControl != null)
+            {
+                switch (oldMode)
+                {
+                    case CalendarMode.Month:
+                        {
+                            SelectedYear = DisplayDateInternal;
+                            SelectedMonth = DisplayDateInternal;
+                            break;
+                        }
+                    case CalendarMode.Year:
+                        {
+                            DisplayDate = SelectedMonth;
+                            SelectedYear = SelectedMonth;
+                            break;
+                        }
+                    case CalendarMode.Decade:
+                        {
+                            DisplayDate = SelectedYear;
+                            SelectedMonth = SelectedYear;
+                            break;
+                        }
+                }
+
+                switch (mode)
+                {
+                    case CalendarMode.Month:
+                        {
+                            OnMonthClick();
+                            break;
+                        }
+                    case CalendarMode.Year:
+                    case CalendarMode.Decade:
+                        {
+                            OnHeaderClick();
+                            break;
+                        }
+                }
+            }
+            OnDisplayModeChanged(new CalendarModeChangedEventArgs((CalendarMode)e.OldValue, mode));
+        }
+        private static CalendarMode ValidateDisplayMode(Calendar o, CalendarMode mode)
+        {
+            if(IsValidDisplayMode(mode))
+            {
+                return mode;
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException(nameof(mode), "Invalid DisplayMode");
+            }
+        }
+        private static bool IsValidDisplayMode(CalendarMode mode)
+        {
+            return mode == CalendarMode.Month
+                || mode == CalendarMode.Year
+                || mode == CalendarMode.Decade;
+        }
+        private void OnDisplayModeChanged(CalendarModeChangedEventArgs args)
+        {
+            DisplayModeChanged?.Invoke(this, args);
+        }
+
+        public static readonly StyledProperty<CalendarSelectionMode> SelectionModeProperty =
+            AvaloniaProperty.Register<Calendar, CalendarSelectionMode>(
+                nameof(SelectionMode),
+                defaultValue: CalendarSelectionMode.SingleDate);
+        /// <summary>
+        /// Gets or sets a value that indicates what kind of selections are
+        /// allowed.
+        /// </summary>
+        /// <value>
+        /// A value that indicates the current selection mode. The default is
+        /// <see cref="F:System.Windows.Controls.CalendarSelectionMode.SingleDate" />.
+        /// </value>
+        /// <remarks>
+        /// <para>
+        /// This property determines whether the Calendar allows no selection,
+        /// selection of a single date, or selection of multiple dates.  The
+        /// selection mode is specified with the CalendarSelectionMode
+        /// enumeration.
+        /// </para>
+        /// <para>
+        /// When this property is changed, all selected dates will be cleared.
+        /// </para>
+        /// </remarks>
+        public CalendarSelectionMode SelectionMode
+        {
+            get { return GetValue(SelectionModeProperty); }
+            set { SetValue(SelectionModeProperty, value); }
+        }
+        private void OnSelectionModeChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (IsValidSelectionMode(e.NewValue))
+            {
+                _displayDateIsChanging = true;
+                SelectedDate = null;
+                _displayDateIsChanging = false;
+                SelectedDates.Clear();
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode");
+            }
+        }
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        /// <param name="value">Inherited code: Requires comment 1.</param>
+        /// <returns>Inherited code: Requires comment 2.</returns>
+        private static bool IsValidSelectionMode(object value)
+        {
+            CalendarSelectionMode mode = (CalendarSelectionMode)value;
+
+            return mode == CalendarSelectionMode.SingleDate
+                || mode == CalendarSelectionMode.SingleRange
+                || mode == CalendarSelectionMode.MultipleRange
+                || mode == CalendarSelectionMode.None;
+        }
+
+        public static readonly DirectProperty<Calendar, DateTime?> SelectedDateProperty =
+            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
+                nameof(SelectedDate),
+                o => o.SelectedDate,
+                (o, v) => o.SelectedDate = v,
+                defaultBindingMode: BindingMode.TwoWay);
+        /// <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 given date is outside the range specified by
+        /// <see cref="P:System.Windows.Controls.Calendar.DisplayDateStart" />
+        /// and <see cref="P:System.Windows.Controls.Calendar.DisplayDateEnd" />
+        /// -or-
+        /// The given date is in the
+        /// <see cref="P:System.Windows.Controls.Calendar.BlackoutDates" />
+        /// collection.
+        /// </exception>
+        /// <exception cref="T:System.InvalidOperationException">
+        /// If set to anything other than null when
+        /// <see cref="P:System.Windows.Controls.Calendar.SelectionMode" /> is
+        /// set to
+        /// <see cref="F:System.Windows.Controls.CalendarSelectionMode.None" />.
+        /// </exception>
+        /// <remarks>
+        /// Use this property when SelectionMode is set to SingleDate.  In other
+        /// modes, this property will always be the first date in SelectedDates.
+        /// </remarks>
+        public DateTime? SelectedDate
+        {
+            get { return _selectedDate; }
+            set { SetAndRaise(SelectedDateProperty, ref _selectedDate, value); }
+        }
+        private void OnSelectedDateChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (!_displayDateIsChanging)
+            {
+                if (SelectionMode != CalendarSelectionMode.None)
+                {
+                    DateTime? addedDate;
+
+                    addedDate = (DateTime?)e.NewValue;
+
+                    if (IsValidDateSelection(this, addedDate))
+                    {
+                        if (addedDate == null)
+                        {
+                            SelectedDates.Clear();
+                        }
+                        else
+                        {
+                            if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
+                            {
+                                foreach (DateTime item in SelectedDates)
+                                {
+                                    RemovedItems.Add(item);
+                                }
+                                SelectedDates.ClearInternal();
+                                // the value is added as a range so that the
+                                // SelectedDatesChanged event can be thrown with
+                                // all the removed items
+                                SelectedDates.AddRange(addedDate.Value, addedDate.Value);
+                            }
+                        }
+
+                        // We update the LastSelectedDate for only the Single
+                        // mode.  For the other modes it automatically gets
+                        // updated when the HoverEnd is updated.
+                        if (SelectionMode == CalendarSelectionMode.SingleDate)
+                        {
+                            LastSelectedDate = addedDate;
+                        }
+                    }
+                    else
+                    {
+                        throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid.");
+                    }
+                }
+                else
+                {
+                    throw new InvalidOperationException("The SelectedDate property cannot be set when the selection mode is None.");
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets a collection of selected dates.
+        /// </summary>
+        /// <value>
+        /// A <see cref="T:System.Windows.Controls.SelectedDatesCollection" />
+        /// object that contains the currently selected dates. The default is an
+        /// empty collection.
+        /// </value>
+        /// <remarks>
+        /// Dates can be added to the collection either individually or in a
+        /// range using the AddRange method.  Depending on the value of the
+        /// SelectionMode property, adding a date or range to the collection may
+        /// cause it to be cleared.  The following table lists how
+        /// CalendarSelectionMode affects the SelectedDates property.
+        /// 
+        ///     CalendarSelectionMode   Description
+        ///     None                    No selections are allowed.  SelectedDate
+        ///                             cannot be set and no values can be added
+        ///                             to SelectedDates.
+        ///                             
+        ///     SingleDate              Only a single date can be selected,
+        ///                             either by setting SelectedDate or the
+        ///                             first value in SelectedDates.  AddRange
+        ///                             cannot be used.
+        ///                             
+        ///     SingleRange             A single range of dates can be selected.
+        ///                             Setting SelectedDate, adding a date
+        ///                             individually to SelectedDates, or using
+        ///                             AddRange will clear all previous values
+        ///                             from SelectedDates.
+        ///     MultipleRange           Multiple non-contiguous ranges of dates
+        ///                             can be selected. Adding a date
+        ///                             individually to SelectedDates or using
+        ///                             AddRange will not clear SelectedDates.
+        ///                             Setting SelectedDate will still clear
+        ///                             SelectedDates, but additional dates or
+        ///                             range can then be added.  Adding a range
+        ///                             that includes some dates that are
+        ///                             already selected or overlaps with
+        ///                             another range results in the union of
+        ///                             the ranges and does not cause an
+        ///                             exception.
+        /// </remarks>
+        public SelectedDatesCollection SelectedDates { get; private set; }
+        private static bool IsSelectionChanged(SelectionChangedEventArgs e)
+        {
+            if (e.AddedItems.Count != e.RemovedItems.Count)
+            {
+                return true;
+            }
+            foreach (DateTime addedDate in e.AddedItems)
+            {
+                if (!e.RemovedItems.Contains(addedDate))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+        internal void OnSelectedDatesCollectionChanged(SelectionChangedEventArgs e)
+        {
+            if (IsSelectionChanged(e))
+            {
+                e.RoutedEvent = SelectingItemsControl.SelectionChangedEvent;
+                e.Source = this;
+                SelectedDatesChanged?.Invoke(this, e);
+            }
+        }
+        
+        internal Collection<DateTime> RemovedItems { get; set; }
+        internal DateTime? LastSelectedDateInternal { get; set; }
+        internal DateTime? LastSelectedDate
+        {
+            get { return LastSelectedDateInternal; }
+            set
+            {
+                LastSelectedDateInternal = value;
+
+                if (SelectionMode == CalendarSelectionMode.None)
+                {
+                    if (FocusButton != null)
+                    {
+                        FocusButton.IsCurrent = false;
+                    }
+                    FocusButton = FindDayButtonFromDay(LastSelectedDate.Value);
+                    if (FocusButton != null)
+                    {
+                        FocusButton.IsCurrent = HasFocusInternal;
+                    }
+                }
+            }
+        }
+
+        internal DateTime SelectedMonth
+        {
+            get { return _selectedMonth; }
+            set
+            {
+                int monthDifferenceStart = DateTimeHelper.CompareYearMonth(value, DisplayDateRangeStart);
+                int monthDifferenceEnd = DateTimeHelper.CompareYearMonth(value, DisplayDateRangeEnd);
+
+                if (monthDifferenceStart >= 0 && monthDifferenceEnd <= 0)
+                {
+                    _selectedMonth = DateTimeHelper.DiscardDayTime(value);
+                }
+                else
+                {
+                    if (monthDifferenceStart < 0)
+                    {
+                        _selectedMonth = DateTimeHelper.DiscardDayTime(DisplayDateRangeStart);
+                    }
+                    else
+                    {
+                        Debug.Assert(monthDifferenceEnd > 0, "monthDifferenceEnd should be greater than 0!");
+                        _selectedMonth = DateTimeHelper.DiscardDayTime(DisplayDateRangeEnd);
+                    }
+                }
+            }
+        }
+        internal DateTime SelectedYear
+        {
+            get { return _selectedYear; }
+            set
+            {
+                if (value.Year < DisplayDateRangeStart.Year)
+                {
+                    _selectedYear = DisplayDateRangeStart;
+                }
+                else
+                {
+                    if (value.Year > DisplayDateRangeEnd.Year)
+                    {
+                        _selectedYear = DisplayDateRangeEnd;
+                    }
+                    else
+                    {
+                        _selectedYear = value;
+                    }
+                }
+            }
+        }
+
+        public static readonly DirectProperty<Calendar, DateTime> DisplayDateProperty =
+            AvaloniaProperty.RegisterDirect<Calendar, DateTime>(
+                nameof(DisplayDate),
+                o => o.DisplayDate,
+                (o, v) => o.DisplayDate = v,
+                defaultBindingMode: BindingMode.TwoWay);
+        /// <summary>
+        /// Gets or sets the date to display.
+        /// </summary>
+        /// <value>The date to display.</value>
+        /// <exception cref="T:System.ArgumentOutOfRangeException">
+        /// The given date is not in the range specified by
+        /// <see cref="P:System.Windows.Controls.Calendar.DisplayDateStart" />
+        /// and
+        /// <see cref="P:System.Windows.Controls.Calendar.DisplayDateEnd" />.
+        /// </exception>
+        /// <remarks>
+        /// <para>
+        /// This property allows the developer to specify a date to display.  If
+        /// this property is a null reference (Nothing in Visual Basic),
+        /// SelectedDate is displayed.  If SelectedDate is also a null reference
+        /// (Nothing in Visual Basic), Today is displayed.  The default is
+        /// Today.
+        /// </para>
+        /// <para>
+        /// To set this property in XAML, use a date specified in the format
+        /// yyyy/mm/dd.  The mm and dd components must always consist of two
+        /// characters, with a leading zero if necessary.  For instance, the
+        /// month of May should be specified as 05.
+        /// </para>
+        /// </remarks>
+        public DateTime DisplayDate
+        {
+            get { return _displayDate; }
+            set { SetAndRaise(DisplayDateProperty, ref _displayDate, value); }
+        }
+        internal DateTime DisplayDateInternal { get; private set; }
+
+        private void OnDisplayDateChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            UpdateDisplayDate(this, (DateTime)e.NewValue, (DateTime)e.OldValue);
+        }
+        private static void UpdateDisplayDate(Calendar c, DateTime addedDate, DateTime removedDate)
+        {
+            Debug.Assert(c != null, "c should not be null!");
+
+            // If DisplayDate < DisplayDateStart, DisplayDate = DisplayDateStart
+            if (DateTime.Compare(addedDate, c.DisplayDateRangeStart) < 0)
+            {
+                c.DisplayDate = c.DisplayDateRangeStart;
+                return;
+            }
+
+            // If DisplayDate > DisplayDateEnd, DisplayDate = DisplayDateEnd
+            if (DateTime.Compare(addedDate, c.DisplayDateRangeEnd) > 0)
+            {
+                c.DisplayDate = c.DisplayDateRangeEnd;
+                return;
+            }
+
+            c.DisplayDateInternal = DateTimeHelper.DiscardDayTime(addedDate);
+            c.UpdateMonths();
+            c.OnDisplayDate(new CalendarDateChangedEventArgs(removedDate, addedDate));
+        }
+        private void OnDisplayDate(CalendarDateChangedEventArgs e)
+        {
+            DisplayDateChanged?.Invoke(this, e);
+        }
+
+        public static readonly DirectProperty<Calendar, DateTime?> DisplayDateStartProperty =
+            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
+                nameof(DisplayDateStart),
+                o => o.DisplayDateStart,
+                (o, v) => o.DisplayDateStart = v,
+                defaultBindingMode: BindingMode.TwoWay);
+        /// <summary>
+        /// Gets or sets the first date to be displayed.
+        /// </summary>
+        /// <value>The first date to display.</value>
+        /// <remarks>
+        /// To set this property in XAML, use a date specified in the format
+        /// yyyy/mm/dd.  The mm and dd components must always consist of two
+        /// characters, with a leading zero if necessary.  For instance, the
+        /// month of May should be specified as 05.
+        /// </remarks>
+        public DateTime? DisplayDateStart
+        {
+            get { return _displayDateStart; }
+            set { SetAndRaise(DisplayDateStartProperty, ref _displayDateStart, value); }
+        }
+        private void OnDisplayDateStartChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (!_displayDateIsChanging)
+            {
+                DateTime? newValue = e.NewValue as DateTime?;
+
+                if (newValue.HasValue)
+                {
+                    // DisplayDateStart coerces to the value of the
+                    // SelectedDateMin if SelectedDateMin < DisplayDateStart
+                    DateTime? selectedDateMin = SelectedDateMin(this);
+
+                    if (selectedDateMin.HasValue && DateTime.Compare(selectedDateMin.Value, newValue.Value) < 0)
+                    {
+                        DisplayDateStart = selectedDateMin.Value;
+                        return;
+                    }
+
+                    // if DisplayDateStart > DisplayDateEnd,
+                    // DisplayDateEnd = DisplayDateStart
+                    if (DateTime.Compare(newValue.Value, DisplayDateRangeEnd) > 0)
+                    {
+                        DisplayDateEnd = DisplayDateStart;
+                    }
+
+                    // If DisplayDate < DisplayDateStart,
+                    // DisplayDate = DisplayDateStart
+                    if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) > 0)
+                    {
+                        DisplayDate = newValue.Value;
+                    }
+                }
+                UpdateMonths();
+            }
+        }
+
+        /// <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>
+        /// <exception cref="System.ArgumentOutOfRangeException">
+        /// Adding a date to this collection when it is already selected or
+        /// adding a date outside the range specified by DisplayDateStart and
+        /// DisplayDateEnd.
+        /// </exception>
+        /// <remarks>
+        /// <para>
+        /// Dates in this collection will appear as disabled on the calendar.
+        /// </para>
+        /// <para>
+        /// To make all past dates not selectable, you can use the
+        /// AddDatesInPast method provided by the collection returned by this
+        /// property.
+        /// </para>
+        /// </remarks>
+        public CalendarBlackoutDatesCollection BlackoutDates { get; private set; }
+
+        private static DateTime? SelectedDateMin(Calendar cal)
+        {
+            DateTime selectedDateMin;
+
+            if (cal.SelectedDates.Count > 0)
+            {
+                selectedDateMin = cal.SelectedDates[0];
+                Debug.Assert(DateTime.Compare(cal.SelectedDate.Value, selectedDateMin) == 0, "The SelectedDate should be the minimum selected date!");
+            }
+            else
+            {
+                return null;
+            }
+
+            foreach (DateTime selectedDate in cal.SelectedDates)
+            {
+                if (DateTime.Compare(selectedDate, selectedDateMin) < 0)
+                {
+                    selectedDateMin = selectedDate;
+                }
+            }
+            return selectedDateMin;
+        }
+        internal DateTime DisplayDateRangeStart
+        {
+            get { return DisplayDateStart.GetValueOrDefault(DateTime.MinValue); }
+        }
+
+        public static readonly DirectProperty<Calendar, DateTime?> DisplayDateEndProperty =
+            AvaloniaProperty.RegisterDirect<Calendar, DateTime?>(
+                nameof(DisplayDateEnd),
+                o => o.DisplayDateEnd,
+                (o, v) => o.DisplayDateEnd = v,
+                defaultBindingMode: BindingMode.TwoWay);
+        
+        /// <summary>
+        /// Gets or sets the last date to be displayed.
+        /// </summary>
+        /// <value>The last date to display.</value>
+        /// <remarks>
+        /// To set this property in XAML, use a date specified in the format
+        /// yyyy/mm/dd.  The mm and dd components must always consist of two
+        /// characters, with a leading zero if necessary.  For instance, the
+        /// month of May should be specified as 05.
+        /// </remarks>
+        public DateTime? DisplayDateEnd
+        {
+            get { return _displayDateEnd; }
+            set { SetAndRaise(DisplayDateEndProperty, ref _displayDateEnd, value); }
+        }
+
+        private void OnDisplayDateEndChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            if (!_displayDateIsChanging)
+            {
+                DateTime? newValue = e.NewValue as DateTime?;
+
+                if (newValue.HasValue)
+                {
+                    // DisplayDateEnd coerces to the value of the
+                    // SelectedDateMax if SelectedDateMax > DisplayDateEnd
+                    DateTime? selectedDateMax = SelectedDateMax(this);
+
+                    if (selectedDateMax.HasValue && DateTime.Compare(selectedDateMax.Value, newValue.Value) > 0)
+                    {
+                        DisplayDateEnd = selectedDateMax.Value;
+                        return;
+                    }
+
+                    // if DisplayDateEnd < DisplayDateStart,
+                    // DisplayDateEnd = DisplayDateStart
+                    if (DateTime.Compare(newValue.Value, DisplayDateRangeStart) < 0)
+                    {
+                        DisplayDateEnd = DisplayDateStart;
+                        return;
+                    }
+
+                    // If DisplayDate > DisplayDateEnd,
+                    // DisplayDate = DisplayDateEnd
+                    if (DateTimeHelper.CompareYearMonth(newValue.Value, DisplayDateInternal) < 0)
+                    {
+                        DisplayDate = newValue.Value;
+                    }
+                }
+                UpdateMonths();
+            }
+        }
+
+        private static DateTime? SelectedDateMax(Calendar cal)
+        {
+            DateTime selectedDateMax;
+
+            if (cal.SelectedDates.Count > 0)
+            {
+                selectedDateMax = cal.SelectedDates[0];
+                Debug.Assert(DateTime.Compare(cal.SelectedDate.Value, selectedDateMax) == 0, "The SelectedDate should be the maximum SelectedDate!");
+            }
+            else
+            {
+                return null;
+            }
+
+            foreach (DateTime selectedDate in cal.SelectedDates)
+            {
+                if (DateTime.Compare(selectedDate, selectedDateMax) > 0)
+                {
+                    selectedDateMax = selectedDate;
+                }
+            }
+            return selectedDateMax;
+        }
+        internal DateTime DisplayDateRangeEnd
+        {
+            get { return DisplayDateEnd.GetValueOrDefault(DateTime.MaxValue); }
+        }
+
+        internal DateTime? HoverStart { get; set; }
+        internal int? HoverStartIndex { get; set; }
+        internal DateTime? HoverEndInternal { get; set; }
+        internal DateTime? HoverEnd
+        {
+            get { return HoverEndInternal; }
+            set
+            {
+                HoverEndInternal = value;
+                LastSelectedDate = value;
+            }
+        }
+        internal int? HoverEndIndex { get; set; }
+        internal bool HasFocusInternal { get; set; }
+        internal bool IsMouseSelection { get; set; }
+
+
+        /// <summary>
+        /// Gets or sets a value indicating whether DatePicker should change its 
+        /// DisplayDate because of a SelectedDate change on its Calendar.
+        /// </summary>
+        internal bool DatePickerDisplayDateFlag { get; set; }
+
+        internal CalendarDayButton FindDayButtonFromDay(DateTime day)
+        {
+            CalendarDayButton b;
+            DateTime? d;
+            CalendarItem monthControl = MonthControl;
+
+            // REMOVE_RTM: should be updated if we support MultiCalendar
+            int count = RowsPerMonth * ColumnsPerMonth;
+            if (monthControl != null)
+            {
+                if (monthControl.MonthView != null)
+                {
+                    for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
+                    {
+                        b = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
+                        d = b.DataContext as DateTime?;
+
+                        if (d.HasValue)
+                        {
+                            if (DateTimeHelper.CompareDays(d.Value, day) == 0)
+                            {
+                                return b;
+                            }
+                        }
+                    }
+                }
+            }
+            return null;
+        }
+
+        private void Calendar_SizeChanged(object sender, EventArgs e)
+        {
+            Debug.Assert(sender is Calendar, "The sender should be a Calendar!");
+
+            var size = Bounds.Size;
+            RectangleGeometry rg = new RectangleGeometry();
+            rg.Rect = new Rect(0, 0, size.Width, size.Height);
+
+            if (Root != null)
+            {
+                Root.Clip = rg;
+            }
+        }
+
+        private void OnSelectedMonthChanged(DateTime? selectedMonth)
+        {
+            if (selectedMonth.HasValue)
+            {
+                Debug.Assert(DisplayMode == CalendarMode.Year, "DisplayMode should be Year!");
+                SelectedMonth = selectedMonth.Value;
+                UpdateMonths();
+            }
+        }
+        private void OnSelectedYearChanged(DateTime? selectedYear)
+        {
+            if (selectedYear.HasValue)
+            {
+                Debug.Assert(DisplayMode == CalendarMode.Decade, "DisplayMode should be Decade!");
+                SelectedYear = selectedYear.Value;
+                UpdateMonths();
+            }
+        }
+        internal void OnHeaderClick()
+        {
+            Debug.Assert(DisplayMode == CalendarMode.Year || DisplayMode == CalendarMode.Decade, "The DisplayMode should be Year or Decade");
+            CalendarItem monthControl = MonthControl;
+            if (monthControl != null && monthControl.MonthView != null && monthControl.YearView != null)
+            {
+                monthControl.MonthView.IsVisible = false;
+                monthControl.YearView.IsVisible = true;
+                UpdateMonths();
+            }
+        }
+
+        internal void ResetStates()
+        {
+            CalendarDayButton d;
+            CalendarItem monthControl = MonthControl;
+            int count = RowsPerMonth * ColumnsPerMonth;
+            if (monthControl != null)
+            {
+                if (monthControl.MonthView != null)
+                {
+                    for (int childIndex = ColumnsPerMonth; childIndex < count; childIndex++)
+                    {
+                        d = monthControl.MonthView.Children[childIndex] as CalendarDayButton;
+                        d.IgnoreMouseOverState();
+                    }
+                }
+            }
+
+        }
+
+        internal void UpdateMonths()
+        {
+            CalendarItem monthControl = MonthControl;
+            if (monthControl != null)
+            {
+                switch (DisplayMode)
+                {
+                    case CalendarMode.Month:
+                        {
+                            monthControl.UpdateMonthMode();
+                            break;
+                        }
+                    case CalendarMode.Year:
+                        {
+                            monthControl.UpdateYearMode();
+                            break;
+                        }
+                    case CalendarMode.Decade:
+                        {
+                            monthControl.UpdateDecadeMode();
+                            break;
+                        }
+                }
+            }
+        }
+
+        internal static bool IsValidDateSelection(Calendar cal, DateTime? value)
+        {
+            if (!value.HasValue)
+            {
+                return true;
+            }
+            else
+            {
+                if (cal.BlackoutDates.Contains(value.Value))
+                {
+                    return false;
+                }
+                else
+                {
+                    cal._displayDateIsChanging = true;
+                    if (DateTime.Compare(value.Value, cal.DisplayDateRangeStart) < 0)
+                    {
+                        cal.DisplayDateStart = value;
+                    }
+                    else if (DateTime.Compare(value.Value, cal.DisplayDateRangeEnd) > 0)
+                    {
+                        cal.DisplayDateEnd = value;
+                    }
+                    cal._displayDateIsChanging = false;
+
+                    return true;
+                }
+            }
+        }
+        private static bool IsValidKeyboardSelection(Calendar cal, DateTime? value)
+        {
+            if (!value.HasValue)
+            {
+                return true;
+            }
+            else
+            {
+                if (cal.BlackoutDates.Contains(value.Value))
+                {
+                    return false;
+                }
+                else
+                {
+                    return (DateTime.Compare(value.Value, cal.DisplayDateRangeStart) >= 0 && DateTime.Compare(value.Value, cal.DisplayDateRangeEnd) <= 0);
+                }
+            }
+        }
+
+        /// <summary>
+        /// This method highlights the days in MultiSelection mode without
+        /// adding them to the SelectedDates collection.
+        /// </summary>
+        internal void HighlightDays()
+        {
+            if (HoverEnd != null && HoverStart != null)
+            {
+                int startIndex, endIndex, i;
+                CalendarDayButton b;
+                DateTime? d;
+                CalendarItem monthControl = MonthControl;
+
+                // This assumes a contiguous set of dates:
+                if (HoverEndIndex != null && HoverStartIndex != null)
+                {
+                    SortHoverIndexes(out startIndex, out endIndex);
+
+                    for (i = startIndex; i <= endIndex; i++)
+                    {
+                        b = monthControl.MonthView.Children[i] as CalendarDayButton;
+                        b.IsSelected = true;
+                        d = b.DataContext as DateTime?;
+
+                        if (d.HasValue && DateTimeHelper.CompareDays(HoverEnd.Value, d.Value) == 0)
+                        {
+                            if (FocusButton != null)
+                            {
+                                FocusButton.IsCurrent = false;
+                            }
+                            b.IsCurrent = HasFocusInternal;
+                            FocusButton = b;
+                        }
+                    }
+                }
+            }
+        }
+        /// <summary>
+        /// This method un-highlights the days that were hovered over but not
+        /// added to the SelectedDates collection or un-highlighted the
+        /// previously selected days in SingleRange Mode.
+        /// </summary>
+        internal void UnHighlightDays()
+        {
+            if (HoverEnd != null && HoverStart != null)
+            {
+                CalendarItem monthControl = MonthControl;
+                CalendarDayButton b;
+                DateTime? d;
+
+                if (HoverEndIndex != null && HoverStartIndex != null)
+                {
+                    int i;
+                    SortHoverIndexes(out int startIndex, out int endIndex);
+
+                    if (SelectionMode == CalendarSelectionMode.MultipleRange)
+                    {
+                        for (i = startIndex; i <= endIndex; i++)
+                        {
+                            b = monthControl.MonthView.Children[i] as CalendarDayButton;
+                            d = b.DataContext as DateTime?;
+
+                            if (d.HasValue)
+                            {
+                                if (!SelectedDates.Contains(d.Value))
+                                {
+                                    b.IsSelected = false;
+                                }
+                            }
+                        }
+                    }
+                    else
+                    {
+                        // It is SingleRange
+                        for (i = startIndex; i <= endIndex; i++)
+                        {
+                            (monthControl.MonthView.Children[i] as CalendarDayButton).IsSelected = false;
+                        }
+                    }
+                }
+            }
+        }
+        internal void SortHoverIndexes(out int startIndex, out int endIndex)
+        {
+            if (DateTimeHelper.CompareDays(HoverEnd.Value, HoverStart.Value) > 0)
+            {
+                startIndex = HoverStartIndex.Value;
+                endIndex = HoverEndIndex.Value;
+            }
+            else
+            {
+                startIndex = HoverEndIndex.Value;
+                endIndex = HoverStartIndex.Value;
+            }
+        }
+
+        internal void OnPreviousClick()
+        {
+            if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+            {
+                DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1);
+                if (d.HasValue)
+                {
+                    if (!LastSelectedDate.HasValue || DateTimeHelper.CompareYearMonth(LastSelectedDate.Value, d.Value) != 0)
+                    {
+                        LastSelectedDate = d.Value;
+                    }
+                    DisplayDate = d.Value;
+                }
+            }
+            else
+            {
+                if (DisplayMode == CalendarMode.Year)
+                {
+                    DateTime? d = DateTimeHelper.AddYears(new DateTime(SelectedMonth.Year, 1, 1), -1);
+
+                    if (d.HasValue)
+                    {
+                        SelectedMonth = d.Value;
+                    }
+                    else
+                    {
+                        SelectedMonth = DateTimeHelper.DiscardDayTime(DisplayDateRangeStart);
+                    }
+                }
+                else
+                {
+                    Debug.Assert(DisplayMode == CalendarMode.Decade, "DisplayMode should be Decade!");
+
+                    DateTime? d = DateTimeHelper.AddYears(new DateTime(SelectedYear.Year, 1, 1), -10);
+
+                    if (d.HasValue)
+                    {
+                        int decade = Math.Max(1, DateTimeHelper.DecadeOfDate(d.Value));
+                        SelectedYear = new DateTime(decade, 1, 1);
+                    }
+                    else
+                    {
+                        SelectedYear = DateTimeHelper.DiscardDayTime(DisplayDateRangeStart);
+                    }
+                }
+                UpdateMonths();
+            }
+        }
+        internal void OnNextClick()
+        {
+            if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+            {
+                DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1);
+                if (d.HasValue)
+                {
+                    if (!LastSelectedDate.HasValue || DateTimeHelper.CompareYearMonth(LastSelectedDate.Value, d.Value) != 0)
+                    {
+                        LastSelectedDate = d.Value;
+                    }
+                    DisplayDate = d.Value;
+                }
+            }
+            else
+            {
+                if (DisplayMode == CalendarMode.Year)
+                {
+                    DateTime? d = DateTimeHelper.AddYears(new DateTime(SelectedMonth.Year, 1, 1), 1);
+
+                    if (d.HasValue)
+                    {
+                        SelectedMonth = d.Value;
+                    }
+                    else
+                    {
+                        SelectedMonth = DateTimeHelper.DiscardDayTime(DisplayDateRangeEnd);
+                    }
+                }
+                else
+                {
+                    Debug.Assert(DisplayMode == CalendarMode.Decade, "DisplayMode should be Decade");
+
+                    DateTime? d = DateTimeHelper.AddYears(new DateTime(SelectedYear.Year, 1, 1), 10);
+
+                    if (d.HasValue)
+                    {
+                        int decade = Math.Max(1, DateTimeHelper.DecadeOfDate(d.Value));
+                        SelectedYear = new DateTime(decade, 1, 1);
+                    }
+                    else
+                    {
+                        SelectedYear = DateTimeHelper.DiscardDayTime(DisplayDateRangeEnd);
+                    }
+                }
+                UpdateMonths();
+            }
+        }
+
+        /// <summary>
+        /// If the day is a trailing day, Update the DisplayDate.
+        /// </summary>
+        /// <param name="selectedDate">Inherited code: Requires comment.</param>
+        internal void OnDayClick(DateTime selectedDate)
+        {
+            Debug.Assert(DisplayMode == CalendarMode.Month, "DisplayMode should be Month!");
+            int i = DateTimeHelper.CompareYearMonth(selectedDate, DisplayDateInternal);
+
+            if (SelectionMode == CalendarSelectionMode.None)
+            {
+                LastSelectedDate = selectedDate;
+            }
+
+            if (i > 0)
+            {
+                OnNextClick();
+            }
+            else if (i < 0)
+            {
+                OnPreviousClick();
+            }
+        }
+        private void OnMonthClick()
+        {
+            CalendarItem monthControl = MonthControl;
+            if (monthControl != null && monthControl.YearView != null && monthControl.MonthView != null)
+            {
+                monthControl.YearView.IsVisible = false;
+                monthControl.MonthView.IsVisible = true;
+
+                if (!LastSelectedDate.HasValue || DateTimeHelper.CompareYearMonth(LastSelectedDate.Value, DisplayDate) != 0)
+                {
+                    LastSelectedDate = DisplayDate;
+                }
+
+                UpdateMonths();
+            }
+        }
+
+        public override string ToString()
+        {
+            if (SelectedDate != null)
+            {
+                return SelectedDate.Value.ToString(DateTimeHelper.GetCurrentDateFormat());
+            }
+            else
+            {
+                return string.Empty;
+            }
+        }
+
+        public event EventHandler<SelectionChangedEventArgs> SelectedDatesChanged;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:System.Windows.Controls.Calendar.DisplayDate" />
+        /// property is changed.
+        /// </summary>
+        /// <remarks>
+        /// This event occurs after DisplayDate is assigned its new value.
+        /// </remarks>
+        public event EventHandler<CalendarDateChangedEventArgs> DisplayDateChanged;
+
+        /// <summary>
+        /// Occurs when the
+        /// <see cref="P:System.Windows.Controls.Calendar.DisplayMode" />
+        /// property is changed.
+        /// </summary>
+        public event EventHandler<CalendarModeChangedEventArgs> DisplayModeChanged;
+
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        internal event EventHandler<PointerReleasedEventArgs> DayButtonMouseUp;
+
+        /// <summary>
+        /// This method adds the days that were selected by Keyboard to the
+        /// SelectedDays Collection.
+        /// </summary>
+        private void AddSelection()
+        {
+            if (HoverEnd != null && HoverStart != null)
+            {
+                foreach (DateTime item in SelectedDates)
+                {
+                    RemovedItems.Add(item);
+                }
+
+                SelectedDates.ClearInternal();
+                // In keyboard selection, we are sure that the collection does
+                // not include any blackout days
+                SelectedDates.AddRange(HoverStart.Value, HoverEnd.Value);
+            }
+        }
+        private void ProcessSelection(bool shift, DateTime? lastSelectedDate, int? index)
+        {
+            if (SelectionMode == CalendarSelectionMode.None && lastSelectedDate != null)
+            {
+                OnDayClick(lastSelectedDate.Value);
+                return;
+            }
+            if (lastSelectedDate != null && IsValidKeyboardSelection(this, lastSelectedDate.Value))
+            {
+                if (SelectionMode == CalendarSelectionMode.SingleRange || SelectionMode == CalendarSelectionMode.MultipleRange)
+                {
+                    foreach (DateTime item in SelectedDates)
+                    {
+                        RemovedItems.Add(item);
+                    }
+                    SelectedDates.ClearInternal();
+                    if (shift)
+                    {
+                        CalendarDayButton b;
+                        _isShiftPressed = true;
+                        if (HoverStart == null)
+                        {
+                            if (LastSelectedDate != null)
+                            {
+                                HoverStart = LastSelectedDate;
+                            }
+                            else
+                            {
+                                if (DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today) == 0)
+                                {
+                                    HoverStart = DateTime.Today;
+                                }
+                                else
+                                {
+                                    HoverStart = DisplayDateInternal;
+                                }
+                            }
+
+                            b = FindDayButtonFromDay(HoverStart.Value);
+                            if (b != null)
+                            {
+                                HoverStartIndex = b.Index;
+                            }
+                        }
+                        // the index of the SelectedDate is always the last
+                        // selectedDate's index
+                        UnHighlightDays();
+                        // If we hit a BlackOutDay with keyboard we do not
+                        // update the HoverEnd
+                        CalendarDateRange range;
+
+                        if (DateTime.Compare(HoverStart.Value, lastSelectedDate.Value) < 0)
+                        {
+                            range = new CalendarDateRange(HoverStart.Value, lastSelectedDate.Value);
+                        }
+                        else
+                        {
+                            range = new CalendarDateRange(lastSelectedDate.Value, HoverStart.Value);
+                        }
+
+                        if (!BlackoutDates.ContainsAny(range))
+                        {
+                            HoverEnd = lastSelectedDate;
+
+                            if (index.HasValue)
+                            {
+                                HoverEndIndex += index;
+                            }
+                            else
+                            {
+                                // For Home, End, PageUp and PageDown Keys there
+                                // is no easy way to predict the index value
+                                b = FindDayButtonFromDay(HoverEndInternal.Value);
+
+                                if (b != null)
+                                {
+                                    HoverEndIndex = b.Index;
+                                }
+                            }
+                        }
+
+                        OnDayClick(HoverEnd.Value);
+                        HighlightDays();
+                    }
+                    else
+                    {
+                        HoverStart = lastSelectedDate;
+                        HoverEnd = lastSelectedDate;
+                        AddSelection();
+                        OnDayClick(lastSelectedDate.Value);
+                    }
+                }
+                else
+                {
+                    // ON CLEAR 
+                    LastSelectedDate = lastSelectedDate.Value;
+                    if (SelectedDates.Count > 0)
+                    {
+                        SelectedDates[0] = lastSelectedDate.Value;
+                    }
+                    else
+                    {
+                        SelectedDates.Add(lastSelectedDate.Value);
+                    }
+                    OnDayClick(lastSelectedDate.Value);
+                }
+            }
+        }
+
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            base.OnPointerReleased(e);
+            if (!HasFocusInternal && e.MouseButton == MouseButton.Left)
+            {
+                FocusManager.Instance.Focus(this);
+            }
+        }
+
+        internal void OnDayButtonMouseUp(PointerReleasedEventArgs e)
+        {
+            DayButtonMouseUp?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Default mouse wheel handler for the calendar control.
+        /// </summary>
+        /// <param name="e">Mouse wheel event args.</param>
+        protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
+        {
+            base.OnPointerWheelChanged(e);
+            if (!e.Handled)
+            {
+                CalendarExtensions.GetMetaKeyState(e.InputModifiers, out bool ctrl, out bool shift);
+
+                if (!ctrl)
+                {
+                    if (e.Delta.Y > 0)
+                    {
+                        ProcessPageUpKey(false);
+                    }
+                    else
+                    {
+                        ProcessPageDownKey(false);
+                    }
+                }
+                else
+                {
+                    if (e.Delta.Y > 0)
+                    {
+                        ProcessDownKey(ctrl, shift);
+                    }
+                    else
+                    {
+                        ProcessUpKey(ctrl, shift);
+                    }
+                }
+                e.Handled = true;
+            }
+        }
+        internal void Calendar_KeyDown(object sender, KeyEventArgs e)
+        {
+            Calendar c = sender as Calendar;
+            Debug.Assert(c != null, "c should not be null!");
+
+            if (!e.Handled && c.IsEnabled)
+            {
+                e.Handled = ProcessCalendarKey(e);
+            }
+        }
+        internal bool ProcessCalendarKey(KeyEventArgs e)
+        {
+            if (DisplayMode == CalendarMode.Month)
+            {
+                if (LastSelectedDate.HasValue && DisplayDateInternal != null)
+                {
+                    // If a blackout day is inactive, when clicked on it, the
+                    // previous inactive day which is not a blackout day can get
+                    // the focus.  In this case we should allow keyboard
+                    // functions on that inactive day
+                    if (DateTimeHelper.CompareYearMonth(LastSelectedDate.Value, DisplayDateInternal) != 0 && FocusButton != null && !FocusButton.IsInactive)
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            // Some keys (e.g. Left/Right) need to be translated in RightToLeft mode
+            Key invariantKey = e.Key;  //InteractionHelper.GetLogicalKey(FlowDirection, e.Key);
+
+            CalendarExtensions.GetMetaKeyState(e.Modifiers, out bool ctrl, out bool shift);
+
+            switch (invariantKey)
+            {
+                case Key.Up:
+                    {
+                        ProcessUpKey(ctrl, shift);
+                        return true;
+                    }
+                case Key.Down:
+                    {
+                        ProcessDownKey(ctrl, shift);
+                        return true;
+                    }
+                case Key.Left:
+                    {
+                        ProcessLeftKey(shift);
+                        return true;
+                    }
+                case Key.Right:
+                    {
+                        ProcessRightKey(shift);
+                        return true;
+                    }
+                case Key.PageDown:
+                    {
+                        ProcessPageDownKey(shift);
+                        return true;
+                    }
+                case Key.PageUp:
+                    {
+                        ProcessPageUpKey(shift);
+                        return true;
+                    }
+                case Key.Home:
+                    {
+                        ProcessHomeKey(shift);
+                        return true;
+                    }
+                case Key.End:
+                    {
+                        ProcessEndKey(shift);
+                        return true;
+                    }
+                case Key.Enter:
+                case Key.Space:
+                    {
+                        return ProcessEnterKey();
+                    }
+            }
+            return false;
+        }
+        internal void ProcessUpKey(bool ctrl, bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        if (ctrl)
+                        {
+                            SelectedMonth = DisplayDateInternal;
+                            DisplayMode = CalendarMode.Year;
+                        }
+                        else
+                        {
+                            DateTime? selectedDate = DateTimeHelper.AddDays(LastSelectedDate.GetValueOrDefault(DateTime.Today), -ColumnsPerMonth);
+                            ProcessSelection(shift, selectedDate, -ColumnsPerMonth);
+                        }
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        if (ctrl)
+                        {
+                            SelectedYear = SelectedMonth;
+                            DisplayMode = CalendarMode.Decade;
+                        }
+                        else
+                        {
+                            DateTime? selectedMonth = DateTimeHelper.AddMonths(_selectedMonth, -ColumnsPerYear);
+                            OnSelectedMonthChanged(selectedMonth);
+                        }
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        if (!ctrl)
+                        {
+                            DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, -ColumnsPerYear);
+                            OnSelectedYearChanged(selectedYear);
+                        }
+                        break;
+                    }
+            }
+        }
+        internal void ProcessDownKey(bool ctrl, bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        if (!ctrl || shift)
+                        {
+                            DateTime? selectedDate = DateTimeHelper.AddDays(LastSelectedDate.GetValueOrDefault(DateTime.Today), ColumnsPerMonth);
+                            ProcessSelection(shift, selectedDate, ColumnsPerMonth);
+                        }
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        if (ctrl)
+                        {
+                            DisplayDate = SelectedMonth;
+                            DisplayMode = CalendarMode.Month;
+                        }
+                        else
+                        {
+                            DateTime? selectedMonth = DateTimeHelper.AddMonths(_selectedMonth, ColumnsPerYear);
+                            OnSelectedMonthChanged(selectedMonth);
+                        }
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        if (ctrl)
+                        {
+                            SelectedMonth = SelectedYear;
+                            DisplayMode = CalendarMode.Year;
+                        }
+                        else
+                        {
+                            DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, ColumnsPerYear);
+                            OnSelectedYearChanged(selectedYear);
+                        }
+                        break;
+                    }
+            }
+        }
+        internal void ProcessLeftKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        DateTime? selectedDate = DateTimeHelper.AddDays(LastSelectedDate.GetValueOrDefault(DateTime.Today), -1);
+                        ProcessSelection(shift, selectedDate, -1);
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime? selectedMonth = DateTimeHelper.AddMonths(_selectedMonth, -1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, -1);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        internal void ProcessRightKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        DateTime? selectedDate = DateTimeHelper.AddDays(LastSelectedDate.GetValueOrDefault(DateTime.Today), 1);
+                        ProcessSelection(shift, selectedDate, 1);
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime? selectedMonth = DateTimeHelper.AddMonths(_selectedMonth, 1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, 1);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        private bool ProcessEnterKey()
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Year:
+                    {
+                        DisplayDate = SelectedMonth;
+                        DisplayMode = CalendarMode.Month;
+                        return true;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        SelectedMonth = SelectedYear;
+                        DisplayMode = CalendarMode.Year;
+                        return true;
+                    }
+            }
+            return false;
+        }
+        internal void ProcessHomeKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        // REMOVE_RTM: Not all types of calendars start with Day1. If Non-Gregorian is supported check this:
+                        DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
+                        ProcessSelection(shift, selectedDate, null);
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime selectedMonth = new DateTime(_selectedMonth.Year, 1, 1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = new DateTime(DateTimeHelper.DecadeOfDate(SelectedYear), 1, 1);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        internal void ProcessEndKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        if (DisplayDate != null)
+                        {
+                            DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
+
+                            if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
+                            {
+                                // since DisplayDate is not equal to
+                                // DateTime.MaxValue we are sure selectedDate is\
+                                // not null
+                                selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1).Value;
+                                selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1).Value;
+                            }
+                            else
+                            {
+                                selectedDate = DateTime.MaxValue;
+                            }
+                            ProcessSelection(shift, selectedDate, null);
+                        }
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime selectedMonth = new DateTime(_selectedMonth.Year, 12, 1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = new DateTime(DateTimeHelper.EndOfDecade(SelectedYear), 1, 1);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        internal void ProcessPageDownKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        DateTime? selectedDate = DateTimeHelper.AddMonths(LastSelectedDate.GetValueOrDefault(DateTime.Today), 1);
+                        ProcessSelection(shift, selectedDate, null);
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime? selectedMonth = DateTimeHelper.AddYears(_selectedMonth, 1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, 10);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        internal void ProcessPageUpKey(bool shift)
+        {
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        DateTime? selectedDate = DateTimeHelper.AddMonths(LastSelectedDate.GetValueOrDefault(DateTime.Today), -1);
+                        ProcessSelection(shift, selectedDate, null);
+                        break;
+                    }
+                case CalendarMode.Year:
+                    {
+                        DateTime? selectedMonth = DateTimeHelper.AddYears(_selectedMonth, -1);
+                        OnSelectedMonthChanged(selectedMonth);
+                        break;
+                    }
+                case CalendarMode.Decade:
+                    {
+                        DateTime? selectedYear = DateTimeHelper.AddYears(SelectedYear, -10);
+                        OnSelectedYearChanged(selectedYear);
+                        break;
+                    }
+            }
+        }
+        private void Calendar_KeyUp(object sender, KeyEventArgs e)
+        {
+            if (!e.Handled && (e.Key == Key.LeftShift || e.Key == Key.RightShift))
+            {
+                ProcessShiftKeyUp();
+            }
+        }
+        internal void ProcessShiftKeyUp()
+        {
+            if (_isShiftPressed && (SelectionMode == CalendarSelectionMode.SingleRange || SelectionMode == CalendarSelectionMode.MultipleRange))
+            {
+                AddSelection();
+                _isShiftPressed = false;
+            }
+        }
+
+        protected override void OnGotFocus(GotFocusEventArgs e)
+        {
+            base.OnGotFocus(e);
+            HasFocusInternal = true;
+
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        DateTime focusDate;
+                        if (LastSelectedDate.HasValue && DateTimeHelper.CompareYearMonth(DisplayDateInternal, LastSelectedDate.Value) == 0)
+                        {
+                            focusDate = LastSelectedDate.Value;
+                        }
+                        else
+                        {
+                            focusDate = DisplayDate;
+                            LastSelectedDate = DisplayDate;
+                        }
+                        Debug.Assert(focusDate != null, "focusDate should not be null!");
+                        FocusButton = FindDayButtonFromDay(focusDate);
+
+                        if (FocusButton != null)
+                        {
+                            FocusButton.IsCurrent = true;
+                        }
+                        break;
+                    }
+                case CalendarMode.Year:
+                case CalendarMode.Decade:
+                    {
+                        if (this.FocusCalendarButton != null)
+                        {
+                            FocusCalendarButton.IsCalendarButtonFocused = true;
+                        }
+                        break;
+                    }
+            }
+        }
+        protected override void OnLostFocus(RoutedEventArgs e)
+        {
+            base.OnLostFocus(e);
+            HasFocusInternal = false;
+
+            switch (DisplayMode)
+            {
+                case CalendarMode.Month:
+                    {
+                        if (FocusButton != null)
+                        {
+                            FocusButton.IsCurrent = false;
+                        }
+                        break;
+                    }
+                case CalendarMode.Year:
+                case CalendarMode.Decade:
+                    {
+                        if (FocusCalendarButton != null)
+                        {
+                            FocusCalendarButton.IsCalendarButtonFocused = false;
+                        }
+                        break;
+                    }
+            }
+        }
+        /// <summary>
+        ///  Called when the IsEnabled property changes.
+        /// </summary>
+        /// <param name="e">Property changed args.</param>
+        private void OnIsEnabledChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            Debug.Assert(e.NewValue is bool, "NewValue should be a boolean!");
+            bool isEnabled = (bool)e.NewValue;
+
+            if (MonthControl != null)
+            {
+                MonthControl.UpdateDisabled(isEnabled);
+            }
+        }
+
+        static Calendar()
+        {
+            IsEnabledProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsEnabledChanged);
+            FirstDayOfWeekProperty.Changed.AddClassHandler<Calendar>(x => x.OnFirstDayOfWeekChanged);
+            IsTodayHighlightedProperty.Changed.AddClassHandler<Calendar>(x => x.OnIsTodayHighlightedChanged);
+            DisplayModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayModePropertyChanged);
+            SelectionModeProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectionModeChanged);
+            SelectedDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnSelectedDateChanged);
+            DisplayDateProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateChanged);
+            DisplayDateStartProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateStartChanged);
+            DisplayDateEndProperty.Changed.AddClassHandler<Calendar>(x => x.OnDisplayDateEndChanged);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:System.Windows.Controls.Calendar" /> class.
+        /// </summary>
+        public Calendar()
+        {
+            UpdateDisplayDate(this, this.DisplayDate, DateTime.MinValue);
+            BlackoutDates = new CalendarBlackoutDatesCollection(this);
+            SelectedDates = new SelectedDatesCollection(this);
+            RemovedItems = new Collection<DateTime>();
+        }
+
+        private const string PART_ElementRoot = "Root";
+        private const string PART_ElementMonth = "CalendarItem";
+        /// <summary>
+        /// Builds the visual tree for the
+        /// <see cref="T:System.Windows.Controls.Calendar" /> when a new
+        /// template is applied.
+        /// </summary>
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+
+            Root = e.NameScope.Find<Panel>(PART_ElementRoot);
+
+            SelectedMonth = DisplayDate;
+            SelectedYear = DisplayDate;
+
+            if (Root != null)
+            {
+                CalendarItem month = e.NameScope.Find<CalendarItem>(PART_ElementMonth);
+
+                if (month != null)
+                {
+                    month.Owner = this;
+                }
+            }
+
+            LayoutUpdated += Calendar_SizeChanged;
+            KeyDown += Calendar_KeyDown;
+            KeyUp += Calendar_KeyUp;
+        }
+
+    }
+}

+ 215 - 0
src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs

@@ -0,0 +1,215 @@
+// (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.Threading;
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+
+namespace Avalonia.Controls.Primitives
+{
+    public sealed class CalendarBlackoutDatesCollection : ObservableCollection<CalendarDateRange>
+    {
+        /// <summary>
+        /// The Calendar whose dates this object represents.
+        /// </summary>
+        private Calendar _owner;
+        
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Primitives.CalendarBlackoutDatesCollection" />
+        /// class.
+        /// </summary>
+        /// <param name="owner">
+        /// The <see cref="T:Avalonia.Controls.Calendar" /> whose dates
+        /// this object represents.
+        /// </param>
+        public CalendarBlackoutDatesCollection(Calendar owner)
+        {
+            _owner = owner ?? throw new ArgumentNullException(nameof(owner));
+        }
+
+        /// <summary>
+        /// Adds all dates before <see cref="P:System.DateTime.Today" /> to the
+        /// collection.
+        /// </summary>
+        public void AddDatesInPast()
+        {
+            Add(new CalendarDateRange(DateTime.MinValue, DateTime.Today.AddDays(-1)));
+        }
+
+        /// <summary>
+        /// Returns a value that represents whether this collection contains the
+        /// specified date.
+        /// </summary>
+        /// <param name="date">The date to search for.</param>
+        /// <returns>
+        /// True if the collection contains the specified date; otherwise,
+        /// false.
+        /// </returns>
+        public bool Contains(DateTime date)
+        {
+            int count = Count;
+            for (int i = 0; i < count; i++)
+            {
+                if (DateTimeHelper.InRange(date, this[i]))
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns a value that represents whether this collection contains the
+        /// specified range of dates.
+        /// </summary>
+        /// <param name="start">The start of the date range.</param>
+        /// <param name="end">The end of the date range.</param>
+        /// <returns>
+        /// True if all dates in the range are contained in the collection;
+        /// otherwise, false.
+        /// </returns>
+        public bool Contains(DateTime start, DateTime end)
+        {
+            DateTime rangeStart;
+            DateTime rangeEnd;
+
+            if (DateTime.Compare(end, start) > -1)
+            {
+                rangeStart = DateTimeHelper.DiscardTime(start).Value;
+                rangeEnd = DateTimeHelper.DiscardTime(end).Value;
+            }
+            else
+            {
+                rangeStart = DateTimeHelper.DiscardTime(end).Value;
+                rangeEnd = DateTimeHelper.DiscardTime(start).Value;
+            }
+
+            int count = Count;
+            for (int i = 0; i < count; i++)
+            {
+                CalendarDateRange range = this[i];
+                if (DateTime.Compare(range.Start, rangeStart) == 0 && DateTime.Compare(range.End, rangeEnd) == 0)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Returns a value that represents whether this collection contains any
+        /// date in the specified range.
+        /// </summary>
+        /// <param name="range">The range of dates to search for.</param>
+        /// <returns>
+        /// True if any date in the range is contained in the collection;
+        /// otherwise, false.
+        /// </returns>
+        public bool ContainsAny(CalendarDateRange range)
+        {
+            return this.Any(r => r.ContainsAny(range));
+        }
+
+        /// <summary>
+        /// Removes all items from the collection.
+        /// </summary>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void ClearItems()
+        {
+            EnsureValidThread();
+
+            base.ClearItems();
+            _owner.UpdateMonths();
+        }
+
+        /// <summary>
+        /// Inserts an item into the collection at the specified index.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index at which item should be inserted.
+        /// </param>
+        /// <param name="item">The object to insert.</param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void InsertItem(int index, CalendarDateRange item)
+        {
+            EnsureValidThread();
+
+            if (!IsValid(item))
+            {
+                throw new ArgumentOutOfRangeException("Value is not valid.");
+            }
+
+            base.InsertItem(index, item);
+            _owner.UpdateMonths();
+        }
+
+        /// <summary>
+        /// Removes the item at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index of the element to remove.
+        /// </param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void RemoveItem(int index)
+        {
+            EnsureValidThread();
+
+            base.RemoveItem(index);
+            _owner.UpdateMonths();
+        }
+
+        /// <summary>
+        /// Replaces the element at the specified index.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index of the element to replace.
+        /// </param>
+        /// <param name="item">
+        /// The new value for the element at the specified index.
+        /// </param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void SetItem(int index, CalendarDateRange item)
+        {
+            EnsureValidThread();
+
+            if (!IsValid(item))
+            {
+                throw new ArgumentOutOfRangeException("Value is not valid.");
+            }
+
+            base.SetItem(index, item);
+            _owner.UpdateMonths();
+        }
+        
+        private bool IsValid(CalendarDateRange item)
+        {
+            foreach (DateTime day in _owner.SelectedDates)
+            {
+                if (DateTimeHelper.InRange(day, item))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+        
+        private void EnsureValidThread()
+        {
+            Dispatcher.UIThread.VerifyAccess();
+        }
+    }
+}

+ 193 - 0
src/Avalonia.Controls/Calendar/CalendarButton.cs

@@ -0,0 +1,193 @@
+// (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.Input;
+using System;
+
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// Represents a button on a
+    /// <see cref="T:Avalonia.Controls.Calendar" />.
+    /// </summary>
+    public sealed class CalendarButton : Button
+    {
+        /// <summary>
+        /// A value indicating whether the button is focused.
+        /// </summary>
+        private bool _isCalendarButtonFocused;
+
+        /// <summary>
+        /// A value indicating whether the button is inactive.
+        /// </summary>
+        private bool _isInactive;
+
+        /// <summary>
+        /// A value indicating whether the button is selected.
+        /// </summary>
+        private bool _isSelected;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Primitives.CalendarButton" />
+        /// class.
+        /// </summary>
+        public CalendarButton()
+            : base()
+        {
+            Content = DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[0];
+        }
+
+        /// <summary>
+        /// Gets or sets the Calendar associated with this button.
+        /// </summary>
+        internal Calendar Owner { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is focused.
+        /// </summary>
+        internal bool IsCalendarButtonFocused
+        {
+            get { return _isCalendarButtonFocused; }
+            set
+            {
+                if (_isCalendarButtonFocused != value)
+                {
+                    _isCalendarButtonFocused = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is inactive.
+        /// </summary>
+        internal bool IsInactive
+        {
+            get { return _isInactive; }
+            set
+            {
+                if (_isInactive != value)
+                {
+                    _isInactive = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is selected.
+        /// </summary>
+        internal bool IsSelected
+        {
+            get { return _isSelected; }
+            set
+            {
+                if (_isSelected != value)
+                {
+                    _isSelected = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Builds the visual tree for the
+        /// <see cref="T:System.Windows.Controls.Primitives.CalendarButton" />
+        /// when a new template is applied.
+        /// </summary>
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+            SetPseudoClasses();
+        }
+        
+        /// <summary>
+        /// Sets PseudoClasses based on current state.
+        /// </summary>
+        private void SetPseudoClasses()
+        {
+            PseudoClasses.Set(":selected", IsSelected);
+            PseudoClasses.Set(":inactive", IsInactive);
+            PseudoClasses.Set(":btnfocused", IsCalendarButtonFocused && IsEnabled);
+        }
+
+        /// <summary>
+        /// Occurs when the left mouse button is pressed (or when the tip of the
+        /// stylus touches the tablet PC) while the mouse pointer is over a
+        /// UIElement.
+        /// </summary>
+        public event EventHandler<PointerPressedEventArgs> CalendarLeftMouseButtonDown;
+
+        /// <summary>
+        /// Occurs when the left mouse button is released (or the tip of the
+        /// stylus is removed from the tablet PC) while the mouse (or the
+        /// stylus) is over a UIElement (or while a UIElement holds mouse
+        /// capture).
+        /// </summary>
+        public event EventHandler<PointerReleasedEventArgs> CalendarLeftMouseButtonUp;
+
+        /// <summary>
+        /// Provides class handling for the MouseLeftButtonDown event that
+        /// occurs when the left mouse button is pressed while the mouse pointer
+        /// is over this control.
+        /// </summary>
+        /// <param name="e">The event data. </param>
+        /// <exception cref="System.ArgumentNullException">
+        /// e is a null reference (Nothing in Visual Basic).
+        /// </exception>
+        /// <remarks>
+        /// This method marks the MouseLeftButtonDown event as handled by
+        /// setting the MouseButtonEventArgs.Handled property of the event data
+        /// to true when the button is enabled and its ClickMode is not set to
+        /// Hover.  Since this method marks the MouseLeftButtonDown event as
+        /// handled in some situations, you should use the Click event instead
+        /// to detect a button click.
+        /// </remarks>
+        protected override void OnPointerPressed(PointerPressedEventArgs e)
+        {
+            base.OnPointerPressed(e);
+            if (e.MouseButton == MouseButton.Left)
+                CalendarLeftMouseButtonDown?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Provides handling for the MouseLeftButtonUp event that occurs when
+        /// the left mouse button is released while the mouse pointer is over
+        /// this control. 
+        /// </summary>
+        /// <param name="e">The event data.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// e is a null reference (Nothing in Visual Basic).
+        /// </exception>
+        /// <remarks>
+        /// This method marks the MouseLeftButtonUp event as handled by setting
+        /// the MouseButtonEventArgs.Handled property of the event data to true
+        /// when the button is enabled and its ClickMode is not set to Hover.
+        /// Since this method marks the MouseLeftButtonUp event as handled in
+        /// some situations, you should use the Click event instead to detect a
+        /// button click.
+        /// </remarks>
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            base.OnPointerReleased(e);
+            if (e.MouseButton == MouseButton.Left)
+                CalendarLeftMouseButtonUp?.Invoke(this, e);
+        }
+        
+        /// <summary>
+        /// We need to simulate the MouseLeftButtonUp event for the
+        /// CalendarButton that stays in Pressed state after MouseCapture is
+        /// released since there is no actual MouseLeftButtonUp event for the
+        /// release.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
+        {
+            e.Handled = false;
+            base.OnPointerReleased(e);
+        }
+    }
+}

+ 79 - 0
src/Avalonia.Controls/Calendar/CalendarDateRange.cs

@@ -0,0 +1,79 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Diagnostics;
+
+namespace Avalonia.Controls
+{
+    public sealed class CalendarDateRange
+    {
+        /// <summary>
+        /// Gets the first date in the represented range.
+        /// </summary>
+        /// <value>The first date in the represented range.</value>
+        public DateTime Start { get; private set; }
+
+        /// <summary>
+        /// Gets the last date in the represented range.
+        /// </summary>
+        /// <value>The last date in the represented range.</value>
+        public DateTime End { get; private set; }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:System.Windows.Controls.CalendarDateRange" /> class
+        /// with a single date.
+        /// </summary>
+        /// <param name="day">The date to be represented by the range.</param>
+        public CalendarDateRange(DateTime day)
+        {
+            Start = day;
+            End = day;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:System.Windows.Controls.CalendarDateRange" /> class
+        /// with a range of dates.
+        /// </summary>
+        /// <param name="start">
+        /// The start of the range to be represented.
+        /// </param>
+        /// <param name="end">The end of the range to be represented.</param>
+        public CalendarDateRange(DateTime start, DateTime end)
+        {
+            if (DateTime.Compare(end, start) >= 0)
+            {
+                Start = start;
+                End = end;
+            }
+            else
+            {
+                // Always use the start for ranges on the same day
+                Start = start;
+                End = start;
+            }
+        }
+
+        /// <summary>
+        /// Returns true if any day in the given DateTime range is contained in
+        /// the current CalendarDateRange.
+        /// </summary>
+        /// <param name="range">Inherited code: Requires comment 1.</param>
+        /// <returns>Inherited code: Requires comment 2.</returns>
+        internal bool ContainsAny(CalendarDateRange range)
+        {
+            Debug.Assert(range != null, "range should not be null!");
+
+            int start = DateTime.Compare(Start, range.Start);
+
+            // Check if any part of the supplied range is contained by this
+            // range or if the supplied range completely covers this range.
+            return (start <= 0 && DateTime.Compare(End, range.Start) >= 0) ||
+                (start >= 0 && DateTime.Compare(Start, range.End) <= 0);
+        }
+    }
+}

+ 253 - 0
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@@ -0,0 +1,253 @@
+// (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.Input;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Text;
+
+namespace Avalonia.Controls.Primitives
+{
+    public sealed class CalendarDayButton : Button
+    {
+        /// <summary>
+        /// Default content for the CalendarDayButton.
+        /// </summary>
+        private const int DefaultContent = 1;
+
+        private bool _isCurrent;
+        private bool _ignoringMouseOverState;
+        private bool _isBlackout;
+        private bool _isToday;
+        private bool _isInactive;
+        private bool _isSelected;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Primitives.CalendarDayButton" />
+        /// class.
+        /// </summary>
+        public CalendarDayButton()
+            : base()
+        {
+            //Focusable = false;
+            Content = DefaultContent.ToString(CultureInfo.CurrentCulture);
+        }
+
+        /// <summary>
+        /// Gets or sets the Calendar associated with this button.
+        /// </summary>
+        internal Calendar Owner { get; set; }
+        internal int Index { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is the focused
+        /// element on the Calendar control.
+        /// </summary>
+        internal bool IsCurrent
+        {
+            get { return _isCurrent; }
+            set
+            {
+                if (_isCurrent != value)
+                {
+                    _isCurrent = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Ensure the button is not in the MouseOver state.
+        /// </summary>
+        /// <remarks>
+        /// If a button is in the MouseOver state when a Popup is closed (as is
+        /// the case when you select a date in the DatePicker control), it will
+        /// continue to think it's in the mouse over state even when the Popup
+        /// opens again and it's not.  This method is used to forcibly clear the
+        /// state by changing the CommonStates state group.
+        /// </remarks>
+        internal void IgnoreMouseOverState()
+        {
+            // TODO: Investigate whether this needs to be done by changing the
+            // state everytime we change any state, or if it can be done once
+            // to properly reset the control.
+
+            _ignoringMouseOverState = false;
+
+            // If the button thinks it's in the MouseOver state (which can
+            // happen when a Popup is closed before the button can change state)
+            // we will override the state so it shows up as normal.
+            if (IsPointerOver)
+            {
+                _ignoringMouseOverState = true;
+                SetPseudoClasses();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this is a blackout date.
+        /// </summary>
+        internal bool IsBlackout
+        {
+            get { return _isBlackout; }
+            set
+            {
+                if (_isBlackout != value)
+                {
+                    _isBlackout = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this button represents
+        /// today.
+        /// </summary>
+        internal bool IsToday
+        {
+            get { return _isToday; }
+            set
+            {
+                if (_isToday != value)
+                {
+                    _isToday = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is inactive.
+        /// </summary>
+        internal bool IsInactive
+        {
+            get { return _isInactive; }
+            set
+            {
+                if (_isInactive != value)
+                {
+                    _isInactive = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether the button is selected.
+        /// </summary>
+        internal bool IsSelected
+        {
+            get { return _isSelected; }
+            set
+            {
+                if (_isSelected != value)
+                {
+                    _isSelected = value;
+                    SetPseudoClasses();
+                }
+            }
+        }
+
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+            SetPseudoClasses();
+        }
+        private void SetPseudoClasses()
+        {
+            if (_ignoringMouseOverState)
+            {
+                PseudoClasses.Set(":pressed", IsPressed);
+                PseudoClasses.Set(":disabled", !IsEnabled);
+            }
+
+            PseudoClasses.Set(":selected", IsSelected);
+            PseudoClasses.Set(":inactive", IsInactive);
+            PseudoClasses.Set(":today", IsToday);
+            PseudoClasses.Set(":blackout", IsBlackout);
+            PseudoClasses.Set(":dayfocused", IsCurrent && IsEnabled);
+        }
+
+        /// <summary>
+        /// Occurs when the left mouse button is pressed (or when the tip of the
+        /// stylus touches the tablet PC) while the mouse pointer is over a
+        /// UIElement.
+        /// </summary>
+        public event EventHandler<PointerPressedEventArgs> CalendarDayButtonMouseDown;
+
+        /// <summary>
+        /// Occurs when the left mouse button is released (or the tip of the
+        /// stylus is removed from the tablet PC) while the mouse (or the
+        /// stylus) is over a UIElement (or while a UIElement holds mouse
+        /// capture).
+        /// </summary>
+        public event EventHandler<PointerReleasedEventArgs> CalendarDayButtonMouseUp;
+
+        /// <summary>
+        /// Provides class handling for the MouseLeftButtonDown event that
+        /// occurs when the left mouse button is pressed while the mouse pointer
+        /// is over this control.
+        /// </summary>
+        /// <param name="e">The event data. </param>
+        /// <exception cref="System.ArgumentNullException">
+        /// e is a null reference (Nothing in Visual Basic).
+        /// </exception>
+        /// <remarks>
+        /// This method marks the MouseLeftButtonDown event as handled by
+        /// setting the MouseButtonEventArgs.Handled property of the event data
+        /// to true when the button is enabled and its ClickMode is not set to
+        /// Hover.  Since this method marks the MouseLeftButtonDown event as
+        /// handled in some situations, you should use the Click event instead
+        /// to detect a button click.
+        /// </remarks>
+        protected override void OnPointerPressed(PointerPressedEventArgs e)
+        {
+            base.OnPointerPressed(e);
+
+            if (e.MouseButton == MouseButton.Left)
+                CalendarDayButtonMouseDown?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// Provides handling for the MouseLeftButtonUp event that occurs when
+        /// the left mouse button is released while the mouse pointer is over
+        /// this control. 
+        /// </summary>
+        /// <param name="e">The event data.</param>
+        /// <exception cref="System.ArgumentNullException">
+        /// e is a null reference (Nothing in Visual Basic).
+        /// </exception>
+        /// <remarks>
+        /// This method marks the MouseLeftButtonUp event as handled by setting
+        /// the MouseButtonEventArgs.Handled property of the event data to true
+        /// when the button is enabled and its ClickMode is not set to Hover.
+        /// Since this method marks the MouseLeftButtonUp event as handled in
+        /// some situations, you should use the Click event instead to detect a
+        /// button click.
+        /// </remarks>
+        protected override void OnPointerReleased(PointerReleasedEventArgs e)
+        {
+            base.OnPointerReleased(e);
+
+            if (e.MouseButton == MouseButton.Left)
+                CalendarDayButtonMouseUp?.Invoke(this, e);
+        }
+
+        /// <summary>
+        /// We need to simulate the MouseLeftButtonUp event for the
+        /// CalendarDayButton that stays in Pressed state after MouseCapture is
+        /// released since there is no actual MouseLeftButtonUp event for the
+        /// release.
+        /// </summary>
+        /// <param name="e">Event arguments.</param>
+        internal void SendMouseLeftButtonUp(PointerReleasedEventArgs e)
+        {
+            e.Handled = false;
+            base.OnPointerReleased(e);
+        }
+    }
+}

+ 21 - 0
src/Avalonia.Controls/Calendar/CalendarExtensions.cs

@@ -0,0 +1,21 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Collections.Generic;
+using Avalonia.Input;
+using System.Diagnostics;
+
+namespace Avalonia.Controls.Primitives
+{
+    internal static class CalendarExtensions
+    {
+        public static void GetMetaKeyState(InputModifiers modifiers, out bool ctrl, out bool shift)
+        {
+            ctrl = (modifiers & InputModifiers.Control) == InputModifiers.Control;
+            shift = (modifiers & InputModifiers.Shift) == InputModifiers.Shift;
+        }
+    }
+}

+ 1264 - 0
src/Avalonia.Controls/Calendar/CalendarItem.cs

@@ -0,0 +1,1264 @@
+// (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.Data;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// Represents the currently displayed month or year on a
+    /// <see cref="T:Avalonia.Controls.Calendar" />.
+    /// </summary>
+    public sealed class CalendarItem : TemplatedControl
+    {
+        /// <summary>
+        /// The number of days per week.
+        /// </summary>
+        private const int NumberOfDaysPerWeek = 7;
+
+        private const string PART_ElementHeaderButton = "HeaderButton";
+        private const string PART_ElementPreviousButton = "PreviousButton";
+        private const string PART_ElementNextButton = "NextButton";
+        private const string PART_ElementMonthView = "MonthView";
+        private const string PART_ElementYearView = "YearView";
+
+        private Button _headerButton;
+        private Button _nextButton;
+        private Button _previousButton;
+        private Grid _monthView;
+        private Grid _yearView;
+        private ITemplate<IControl> _dayTitleTemplate;
+        private CalendarButton _lastCalendarButton;
+        private CalendarDayButton _lastCalendarDayButton;
+        
+        private DateTime _currentMonth;
+        private bool _isMouseLeftButtonDown = false;
+        private bool _isMouseLeftButtonDownYearView = false;
+        private bool _isControlPressed = false;
+
+        private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar();
+
+        private PointerPressedEventArgs _downEventArg;
+        private PointerPressedEventArgs _downEventArgYearView;
+
+        internal Calendar Owner { get; set; }
+        internal CalendarDayButton CurrentButton { get; set; }
+
+        public static StyledProperty<IBrush> HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner<CalendarItem>();
+        public IBrush HeaderBackground
+        {
+            get { return GetValue(HeaderBackgroundProperty); }
+            set { SetValue(HeaderBackgroundProperty, value); }
+        }
+        public static readonly DirectProperty<CalendarItem, ITemplate<IControl>> DayTitleTemplateProperty =
+                AvaloniaProperty.RegisterDirect<CalendarItem, ITemplate<IControl>>(
+                    nameof(DayTitleTemplate),
+                    o => o.DayTitleTemplate,
+                    (o,v) => o.DayTitleTemplate = v,
+                    defaultBindingMode: BindingMode.OneTime);
+        public ITemplate<IControl> DayTitleTemplate
+        {
+            get { return _dayTitleTemplate; }
+            set { SetAndRaise(DayTitleTemplateProperty, ref _dayTitleTemplate, value); }
+        }
+
+        /// <summary>
+        /// Gets the button that allows switching between month mode, year mode,
+        /// and decade mode. 
+        /// </summary>
+        internal Button HeaderButton
+        {
+            get { return _headerButton; }
+            private set
+            {
+                if (_headerButton != null)
+                    _headerButton.Click -= HeaderButton_Click;
+
+                _headerButton = value;
+
+                if (_headerButton != null)
+                {
+                    _headerButton.Click += HeaderButton_Click;
+                    _headerButton.Focusable = false;
+                }
+            }
+        }
+        /// <summary>
+        /// Gets the button that displays the next page of the calendar when it
+        /// is clicked.
+        /// </summary>
+        internal Button NextButton
+        {
+            get { return _nextButton; }
+            private set
+            {
+                if (_nextButton != null)
+                    _nextButton.Click -= NextButton_Click;
+
+                _nextButton = value;
+
+                if (_nextButton != null)
+                {
+                    // If the user does not provide a Content value in template,
+                    // we provide a helper text that can be used in
+                    // Accessibility this text is not shown on the UI, just used
+                    // for Accessibility purposes
+                    if (_nextButton.Content == null)
+                    {
+                        _nextButton.Content = "next button";
+                    }
+
+                    _nextButton.IsVisible = true;
+                    _nextButton.Click += NextButton_Click;
+                    _nextButton.Focusable = false;
+                }
+            }
+        }
+        /// <summary>
+        /// Gets the button that displays the previous page of the calendar when
+        /// it is clicked.
+        /// </summary>
+        internal Button PreviousButton
+        {
+            get { return _previousButton; }
+            private set
+            {
+                if (_previousButton != null)
+                    _previousButton.Click -= PreviousButton_Click;
+
+                _previousButton = value;
+
+                if (_previousButton != null)
+                {
+                    // If the user does not provide a Content value in template,
+                    // we provide a helper text that can be used in
+                    // Accessibility this text is not shown on the UI, just used
+                    // for Accessibility purposes
+                    if (_previousButton.Content == null)
+                    {
+                        _previousButton.Content = "previous button";
+                    }
+
+                    _previousButton.IsVisible = true;
+                    _previousButton.Click += PreviousButton_Click;
+                    _previousButton.Focusable = false;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the Grid that hosts the content when in month mode.
+        /// </summary>
+        internal Grid MonthView
+        {
+            get { return _monthView; }
+            private set
+            {
+                if (_monthView != null)
+                    _monthView.PointerLeave -= MonthView_MouseLeave;
+
+                _monthView = value;
+
+                if (_monthView != null)
+                    _monthView.PointerLeave += MonthView_MouseLeave;
+            }
+        }
+        /// <summary>
+        /// Gets the Grid that hosts the content when in year or decade mode.
+        /// </summary>
+        internal Grid YearView
+        {
+            get { return _yearView; }
+            private set
+            {
+                if (_yearView != null)
+                    _yearView.PointerLeave -= YearView_MouseLeave;
+
+                _yearView = value;
+
+                if (_yearView != null)
+                    _yearView.PointerLeave += YearView_MouseLeave;
+            }
+        }
+
+        private void PopulateGrids()
+        {
+            if (MonthView != null)
+            {
+                for (int i = 0; i < Calendar.RowsPerMonth; i++)
+                {
+                    if (_dayTitleTemplate != null)
+                    {
+                        var cell = _dayTitleTemplate.Build();
+                        cell.DataContext = string.Empty;
+                        cell.SetValue(Grid.RowProperty, 0);
+                        cell.SetValue(Grid.ColumnProperty, i);
+                        MonthView.Children.Add(cell);
+                    }
+                }
+
+                for (int i = 1; i < Calendar.RowsPerMonth; i++)
+                {
+                    for (int j = 0; j < Calendar.ColumnsPerMonth; j++)
+                    {
+                        CalendarDayButton cell = new CalendarDayButton();
+
+                        if (Owner != null)
+                        {
+                            cell.Owner = Owner;
+                        }
+                        cell.SetValue(Grid.RowProperty, i);
+                        cell.SetValue(Grid.ColumnProperty, j);
+                        cell.CalendarDayButtonMouseDown += Cell_MouseLeftButtonDown;
+                        cell.CalendarDayButtonMouseUp += Cell_MouseLeftButtonUp;
+                        cell.PointerEnter += Cell_MouseEnter;
+                        cell.PointerLeave += Cell_MouseLeave;
+                        cell.Click += Cell_Click;
+                        MonthView.Children.Add(cell);
+                    }
+                }
+            }
+
+            if (YearView != null)
+            {
+                CalendarButton month;
+                for (int i = 0; i < Calendar.RowsPerYear; i++)
+                {
+                    for (int j = 0; j < Calendar.ColumnsPerYear; j++)
+                    {
+                        month = new CalendarButton();
+
+                        if (Owner != null)
+                        {
+                            month.Owner = Owner;
+                        }
+                        month.SetValue(Grid.RowProperty, i);
+                        month.SetValue(Grid.ColumnProperty, j);
+                        month.CalendarLeftMouseButtonDown += Month_CalendarButtonMouseDown;
+                        month.CalendarLeftMouseButtonUp += Month_CalendarButtonMouseUp;
+                        month.PointerEnter += Month_MouseEnter;
+                        month.PointerLeave += Month_MouseLeave;
+                        YearView.Children.Add(month);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Builds the visual tree for the
+        /// <see cref="T:System.Windows.Controls.Primitives.CalendarItem" />
+        /// when a new template is applied.
+        /// </summary>
+        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        {
+            base.OnTemplateApplied(e);
+            
+            HeaderButton = e.NameScope.Find<Button>(PART_ElementHeaderButton);
+            PreviousButton = e.NameScope.Find<Button>(PART_ElementPreviousButton);
+            NextButton = e.NameScope.Find<Button>(PART_ElementNextButton);
+            MonthView = e.NameScope.Find<Grid>(PART_ElementMonthView);
+            YearView = e.NameScope.Find<Grid>(PART_ElementYearView);
+            
+            if (Owner != null)
+            {
+                UpdateDisabled(Owner.IsEnabled);
+            }
+
+            PopulateGrids();
+
+            if (MonthView != null && YearView != null)
+            {
+                if (Owner != null)
+                {
+                    Owner.SelectedMonth = Owner.DisplayDateInternal;
+                    Owner.SelectedYear = Owner.DisplayDateInternal;
+
+                    if (Owner.DisplayMode == CalendarMode.Year)
+                    {
+                        UpdateYearMode();
+                    }
+                    else if (Owner.DisplayMode == CalendarMode.Decade)
+                    {
+                        UpdateDecadeMode();
+                    }
+
+                    if (Owner.DisplayMode == CalendarMode.Month)
+                    {
+                        UpdateMonthMode();
+                        MonthView.IsVisible = true;
+                        YearView.IsVisible = false;
+                    }
+                    else
+                    {
+                        YearView.IsVisible = true;
+                        MonthView.IsVisible = false;
+                    }
+                }
+                else
+                {
+                    UpdateMonthMode();
+                    MonthView.IsVisible = true;
+                    YearView.IsVisible = false;
+                }
+            }
+        }
+
+        private void SetDayTitles()
+        {
+            for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++)
+            {
+                var daytitle = MonthView.Children[childIndex];
+                if (daytitle != null)
+                {
+                    if (Owner != null)
+                    {
+                        daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek];
+                    }
+                    else
+                    {
+                        daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek];
+                    }
+                }
+            }
+        }
+        /// <summary>
+        /// How many days of the previous month need to be displayed.
+        /// </summary>
+        private int PreviousMonthDays(DateTime firstOfMonth)
+        {
+            DayOfWeek day = _calendar.GetDayOfWeek(firstOfMonth);
+            int i;
+
+            if (Owner != null)
+            {
+                i = ((day - Owner.FirstDayOfWeek + NumberOfDaysPerWeek) % NumberOfDaysPerWeek);
+            }
+            else
+            {
+                i = ((day - DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek + NumberOfDaysPerWeek) % NumberOfDaysPerWeek);
+            }
+
+            if (i == 0)
+            {
+                return NumberOfDaysPerWeek;
+            }
+            else
+            {
+                return i;
+            }
+        }
+
+        internal void UpdateMonthMode()
+        {
+            if (Owner != null)
+            {
+                Debug.Assert(Owner.DisplayDate != null, "The Owner Calendar's DisplayDate should not be null!");
+                _currentMonth = Owner.DisplayDateInternal;
+            }
+            else
+            {
+                _currentMonth = DateTime.Today;
+            }
+
+            if (_currentMonth != null)
+            {
+                SetMonthModeHeaderButton();
+                SetMonthModePreviousButton(_currentMonth);
+                SetMonthModeNextButton(_currentMonth);
+
+                if (MonthView != null)
+                {
+                    SetDayTitles();
+                    SetCalendarDayButtons(_currentMonth);
+                }
+            }
+        }
+        private void SetMonthModeHeaderButton()
+        {
+            if (HeaderButton != null)
+            {
+                if (Owner != null)
+                {
+                    HeaderButton.Content = Owner.DisplayDateInternal.ToString("Y", DateTimeHelper.GetCurrentDateFormat());
+                    HeaderButton.IsEnabled = true;
+                }
+                else
+                {
+                    HeaderButton.Content = DateTime.Today.ToString("Y", DateTimeHelper.GetCurrentDateFormat());
+                }
+            }
+        }
+        private void SetMonthModeNextButton(DateTime firstDayOfMonth)
+        {
+            if (Owner != null && NextButton != null)
+            {
+                // DisplayDate is equal to DateTime.MaxValue
+                if (DateTimeHelper.CompareYearMonth(firstDayOfMonth, DateTime.MaxValue) == 0)
+                {
+                    NextButton.IsEnabled = false;
+                }
+                else
+                {
+                    // Since we are sure DisplayDate is not equal to
+                    // DateTime.MaxValue, it is safe to use AddMonths  
+                    DateTime firstDayOfNextMonth = _calendar.AddMonths(firstDayOfMonth, 1);
+                    NextButton.IsEnabled = (DateTimeHelper.CompareDays(Owner.DisplayDateRangeEnd, firstDayOfNextMonth) > -1);
+                }
+            }
+        }
+        private void SetMonthModePreviousButton(DateTime firstDayOfMonth)
+        {
+            if (Owner != null && PreviousButton != null)
+            {
+                PreviousButton.IsEnabled = (DateTimeHelper.CompareDays(Owner.DisplayDateRangeStart, firstDayOfMonth) < 0);
+            }
+        }
+
+        private void SetButtonState(CalendarDayButton childButton, DateTime dateToAdd)
+        {
+            if (Owner != null)
+            {
+                childButton.Opacity = 1;
+
+                // If the day is outside the DisplayDateStart/End boundary, do
+                // not show it
+                if (DateTimeHelper.CompareDays(dateToAdd, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareDays(dateToAdd, Owner.DisplayDateRangeEnd) > 0)
+                {
+                    childButton.IsEnabled = false;
+                    childButton.IsToday = false;
+                    childButton.IsSelected = false;
+                    childButton.Opacity = 0;
+                }
+                else
+                {
+                    // SET IF THE DAY IS SELECTABLE OR NOT
+                    if (Owner.BlackoutDates.Contains(dateToAdd))
+                    {
+                        childButton.IsBlackout = true;
+                    }
+                    else
+                    {
+                        childButton.IsBlackout = false;
+                    }
+                    childButton.IsEnabled = true;
+
+                    // SET IF THE DAY IS INACTIVE OR NOT: set if the day is a
+                    // trailing day or not
+                    childButton.IsInactive = (DateTimeHelper.CompareYearMonth(dateToAdd, Owner.DisplayDateInternal) != 0);
+
+                    // SET IF THE DAY IS TODAY OR NOT
+                    childButton.IsToday = (Owner.IsTodayHighlighted && dateToAdd == DateTime.Today);
+
+                    // SET IF THE DAY IS SELECTED OR NOT
+                    childButton.IsSelected = false;
+                    foreach (DateTime item in Owner.SelectedDates)
+                    {
+                        // Since we should be comparing the Date values not
+                        // DateTime values, we can't use
+                        // Owner.SelectedDates.Contains(dateToAdd) directly
+                        childButton.IsSelected |= (DateTimeHelper.CompareDays(dateToAdd, item) == 0);
+                    }
+
+                    // SET THE FOCUS ELEMENT
+                    if (Owner.LastSelectedDate != null)
+                    {
+                        if (DateTimeHelper.CompareDays(Owner.LastSelectedDate.Value, dateToAdd) == 0)
+                        {
+                            if (Owner.FocusButton != null)
+                            {
+                                Owner.FocusButton.IsCurrent = false;
+                            }
+                            Owner.FocusButton = childButton;
+                            if (Owner.HasFocusInternal)
+                            {
+                                Owner.FocusButton.IsCurrent = true;
+                            }
+                        }
+                        else
+                        {
+                            childButton.IsCurrent = false;
+                        }
+                    }
+                }
+            }
+        }
+        private void SetCalendarDayButtons(DateTime firstDayOfMonth)
+        {
+            int lastMonthToDisplay = PreviousMonthDays(firstDayOfMonth);
+            DateTime dateToAdd;
+
+            if (DateTimeHelper.CompareYearMonth(firstDayOfMonth, DateTime.MinValue) > 0)
+            {
+                // DisplayDate is not equal to DateTime.MinValue we can subtract
+                // days from the DisplayDate
+                dateToAdd = _calendar.AddDays(firstDayOfMonth, -lastMonthToDisplay);
+            }
+            else
+            {
+                dateToAdd = firstDayOfMonth;
+            }
+
+            if (Owner != null && Owner.HoverEnd != null && Owner.HoverStart != null)
+            {
+                Owner.HoverEndIndex = null;
+                Owner.HoverStartIndex = null;
+            }
+
+            int count = Calendar.RowsPerMonth * Calendar.ColumnsPerMonth;
+
+            for (int childIndex = Calendar.ColumnsPerMonth; childIndex < count; childIndex++)
+            {
+                CalendarDayButton childButton = MonthView.Children[childIndex] as CalendarDayButton;
+                Debug.Assert(childButton != null, "childButton should not be null!");
+
+                childButton.Index = childIndex;
+                SetButtonState(childButton, dateToAdd);
+
+                // Update the indexes of hoverStart and hoverEnd
+                if (Owner != null && Owner.HoverEnd != null && Owner.HoverStart != null)
+                {
+                    if (DateTimeHelper.CompareDays(dateToAdd, Owner.HoverEnd.Value) == 0)
+                    {
+                        Owner.HoverEndIndex = childIndex;
+                    }
+
+                    if (DateTimeHelper.CompareDays(dateToAdd, Owner.HoverStart.Value) == 0)
+                    {
+                        Owner.HoverStartIndex = childIndex;
+                    }
+                }
+
+                //childButton.Focusable = false;
+                childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat());
+                childButton.DataContext = dateToAdd;
+
+                if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0)
+                {
+                    // Since we are sure DisplayDate is not equal to
+                    // DateTime.MaxValue, it is safe to use AddDays 
+                    dateToAdd = _calendar.AddDays(dateToAdd, 1);
+                }
+                else
+                {
+                    // DisplayDate is equal to the DateTime.MaxValue, so there
+                    // are no trailing days
+                    childIndex++;
+                    for (int i = childIndex; i < count; i++)
+                    {
+                        childButton = MonthView.Children[i] as CalendarDayButton;
+                        Debug.Assert(childButton != null, "childButton should not be null!");
+                        // button needs a content to occupy the necessary space
+                        // for the content presenter
+                        childButton.Content = i.ToString(DateTimeHelper.GetCurrentDateFormat());
+                        childButton.IsEnabled = false;
+                        childButton.Opacity = 0;
+                    }
+                    return;
+                }
+            }
+
+            // If the HoverStart or HoverEndInternal could not be found on the
+            // DisplayMonth set the values of the HoverStartIndex or
+            // HoverEndIndex to be the first or last day indexes on the current
+            // month
+            if (Owner != null && Owner.HoverStart.HasValue && Owner.HoverEndInternal.HasValue)
+            {
+                if (!Owner.HoverEndIndex.HasValue)
+                {
+                    if (DateTimeHelper.CompareDays(Owner.HoverEndInternal.Value, Owner.HoverStart.Value) > 0)
+                    {
+                        Owner.HoverEndIndex = Calendar.ColumnsPerMonth * Calendar.RowsPerMonth - 1;
+                    }
+                    else
+                    {
+                        Owner.HoverEndIndex = Calendar.ColumnsPerMonth;
+                    }
+                }
+
+                if (!Owner.HoverStartIndex.HasValue)
+                {
+                    if (DateTimeHelper.CompareDays(Owner.HoverEndInternal.Value, Owner.HoverStart.Value) > 0)
+                    {
+                        Owner.HoverStartIndex = Calendar.ColumnsPerMonth;
+                    }
+                    else
+                    {
+                        Owner.HoverStartIndex = Calendar.ColumnsPerMonth * Calendar.RowsPerMonth - 1;
+                    }
+                }
+            }
+        }
+
+        internal void UpdateYearMode()
+        {
+            if (Owner != null)
+            {
+                Debug.Assert(Owner.SelectedMonth != null, "The Owner Calendar's SelectedMonth should not be null!");
+                _currentMonth = (DateTime)Owner.SelectedMonth;
+            }
+            else
+            {
+                _currentMonth = DateTime.Today;
+            }
+
+            if (_currentMonth != null)
+            {
+                SetYearModeHeaderButton();
+                SetYearModePreviousButton();
+                SetYearModeNextButton();
+
+                if (YearView != null)
+                {
+                    SetMonthButtonsForYearMode();
+                }
+            }
+        }
+        private void SetYearModeHeaderButton()
+        {
+            if (HeaderButton != null)
+            {
+                HeaderButton.IsEnabled = true;
+                HeaderButton.Content = _currentMonth.Year.ToString(DateTimeHelper.GetCurrentDateFormat());
+            }
+        }
+        private void SetYearModePreviousButton()
+        {
+            if (Owner != null && PreviousButton != null)
+            {
+                PreviousButton.IsEnabled = (Owner.DisplayDateRangeStart.Year != _currentMonth.Year);
+            }
+        }
+        private void SetYearModeNextButton()
+        {
+            if (Owner != null && NextButton != null)
+            {
+                NextButton.IsEnabled = (Owner.DisplayDateRangeEnd.Year != _currentMonth.Year);
+            }
+        }
+
+        private void SetMonthButtonsForYearMode()
+        {
+            int count = 0;
+            foreach (object child in YearView.Children)
+            {
+                CalendarButton childButton = child as CalendarButton;
+                Debug.Assert(childButton != null, "childButton should not be null!");
+                // There should be no time component. Time is 12:00 AM
+                DateTime day = new DateTime(_currentMonth.Year, count + 1, 1);
+                childButton.DataContext = day;
+
+                childButton.Content = DateTimeHelper.GetCurrentDateFormat().AbbreviatedMonthNames[count];
+                childButton.IsVisible = true;
+
+                if (Owner != null)
+                {
+                    if (day.Year == _currentMonth.Year && day.Month == _currentMonth.Month && day.Day == _currentMonth.Day)
+                    {
+                        Owner.FocusCalendarButton = childButton;
+                        childButton.IsCalendarButtonFocused = Owner.HasFocusInternal;
+                    }
+                    else
+                    {
+                        childButton.IsCalendarButtonFocused = false;
+                    }
+
+                    Debug.Assert(Owner.DisplayDateInternal != null, "The Owner Calendar's DisplayDateInternal should not be null!");
+                    childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0);
+
+                    if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0)
+                    {
+                        childButton.IsEnabled = false;
+                        childButton.Opacity = 0;
+                    }
+                    else
+                    {
+                        childButton.IsEnabled = true;
+                        childButton.Opacity = 1;
+                    }
+                }
+
+                childButton.IsInactive = false;
+                count++;
+            }
+        }
+        internal void UpdateDecadeMode()
+        {
+            DateTime selectedYear;
+
+            if (Owner != null)
+            {
+                Debug.Assert(Owner.SelectedYear != null, "The owning Calendar's selected year should not be null!");
+                selectedYear = Owner.SelectedYear;
+                _currentMonth = (DateTime)Owner.SelectedMonth;
+            }
+            else
+            {
+                _currentMonth = DateTime.Today;
+                selectedYear = DateTime.Today;
+            }
+
+            if (_currentMonth != null)
+            {
+                int decade = DateTimeHelper.DecadeOfDate(selectedYear);
+                int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
+
+                SetDecadeModeHeaderButton(decade, decadeEnd);
+                SetDecadeModePreviousButton(decade);
+                SetDecadeModeNextButton(decadeEnd);
+
+                if (YearView != null)
+                {
+                    SetYearButtons(decade, decadeEnd);
+                }
+            }
+        }
+        internal void UpdateYearViewSelection(CalendarButton calendarButton)
+        {
+            if (Owner != null && calendarButton != null && calendarButton.DataContext != null)
+            {
+                Owner.FocusCalendarButton.IsCalendarButtonFocused = false;
+                Owner.FocusCalendarButton = calendarButton;
+                calendarButton.IsCalendarButtonFocused = Owner.HasFocusInternal;
+
+                if (Owner.DisplayMode == CalendarMode.Year)
+                {
+                    Owner.SelectedMonth = (DateTime)calendarButton.DataContext;
+                }
+                else
+                {
+                    Owner.SelectedYear = (DateTime)calendarButton.DataContext;
+                }
+            }
+        }
+
+        private void SetYearButtons(int decade, int decadeEnd)
+        {
+            int year;
+            int count = -1;
+            foreach (object child in YearView.Children)
+            {
+                CalendarButton childButton = child as CalendarButton;
+                Debug.Assert(childButton != null, "childButton should not be null!");
+                year = decade + count;
+
+                if (year <= DateTime.MaxValue.Year && year >= DateTime.MinValue.Year)
+                {
+                    // There should be no time component. Time is 12:00 AM
+                    DateTime day = new DateTime(year, 1, 1);
+                    childButton.DataContext = day;
+                    childButton.Content = year.ToString(DateTimeHelper.GetCurrentDateFormat());
+                    childButton.IsVisible = true;
+
+                    if (Owner != null)
+                    {
+                        if (year == Owner.SelectedYear.Year)
+                        {
+                            Owner.FocusCalendarButton = childButton;
+                            childButton.IsCalendarButtonFocused = Owner.HasFocusInternal;
+                        }
+                        else
+                        {
+                            childButton.IsCalendarButtonFocused = false;
+                        }
+                        childButton.IsSelected = (Owner.DisplayDate.Year == year);
+
+                        if (year < Owner.DisplayDateRangeStart.Year || year > Owner.DisplayDateRangeEnd.Year)
+                        {
+                            childButton.IsEnabled = false;
+                            childButton.Opacity = 0;
+                        }
+                        else
+                        {
+                            childButton.IsEnabled = true;
+                            childButton.Opacity = 1;
+                        }
+                    }
+
+                    // SET IF THE YEAR IS INACTIVE OR NOT: set if the year is a
+                    // trailing year or not
+                    childButton.IsInactive = (year < decade || year > decadeEnd);
+                }
+                else
+                {
+                    childButton.IsEnabled = false;
+                    childButton.Opacity = 0;
+                }
+
+                count++;
+            }
+        }
+        private void SetDecadeModeHeaderButton(int decade, int decadeEnd)
+        {
+            if (HeaderButton != null)
+            {
+                HeaderButton.Content = decade.ToString(CultureInfo.CurrentCulture) + "-" + decadeEnd.ToString(CultureInfo.CurrentCulture);
+                HeaderButton.IsEnabled = false;
+            }
+        }
+        private void SetDecadeModeNextButton(int decadeEnd)
+        {
+            if (Owner != null && NextButton != null)
+            {
+                NextButton.IsEnabled = (Owner.DisplayDateRangeEnd.Year > decadeEnd);
+            }
+        }
+        private void SetDecadeModePreviousButton(int decade)
+        {
+            if (Owner != null && PreviousButton != null)
+            {
+                PreviousButton.IsEnabled = (decade > Owner.DisplayDateRangeStart.Year);
+            }
+        }
+
+        internal void HeaderButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                if (!Owner.HasFocusInternal)
+                {
+                    Owner.Focus();
+                }
+                Button b = sender as Button;
+                DateTime d;
+
+                if (b.IsEnabled)
+                {
+                    if (Owner.DisplayMode == CalendarMode.Month)
+                    {
+                        if (Owner.DisplayDate != null)
+                        {
+                            d = Owner.DisplayDateInternal;
+                            Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
+                        }
+                        Owner.DisplayMode = CalendarMode.Year;
+                    }
+                    else
+                    {
+                        Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!");
+
+                        if (Owner.SelectedMonth != null)
+                        {
+                            d = Owner.SelectedMonth;
+                            Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
+                        }
+                        Owner.DisplayMode = CalendarMode.Decade;
+                    }
+                }
+            }
+        }
+        internal void PreviousButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                if (!Owner.HasFocusInternal)
+                {
+                    Owner.Focus();
+                }
+
+                Button b = sender as Button;
+                if (b.IsEnabled)
+                {
+                    Owner.OnPreviousClick();
+                }
+            }
+        }
+        internal void NextButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                if (!Owner.HasFocusInternal)
+                {
+                    Owner.Focus();
+                }
+                Button b = sender as Button;
+
+                if (b.IsEnabled)
+                {
+                    Owner.OnNextClick();
+                }
+            }
+        }
+
+        internal void Cell_MouseEnter(object sender, PointerEventArgs e)
+        {
+            if (Owner != null)
+            {
+                CalendarDayButton b = sender as CalendarDayButton;
+                if (_isMouseLeftButtonDown && b != null && b.IsEnabled && !b.IsBlackout)
+                {
+                    // Update the states of all buttons to be selected starting
+                    // from HoverStart to b
+                    switch (Owner.SelectionMode)
+                    {
+                        case CalendarSelectionMode.SingleDate:
+                            {
+                                DateTime selectedDate = (DateTime)b.DataContext;
+                                Owner.DatePickerDisplayDateFlag = true;
+                                if (Owner.SelectedDates.Count == 0)
+                                {
+                                    Owner.SelectedDates.Add(selectedDate);
+                                }
+                                else
+                                {
+                                    Owner.SelectedDates[0] = selectedDate;
+                                }
+                                return;
+                            }
+                        case CalendarSelectionMode.SingleRange:
+                        case CalendarSelectionMode.MultipleRange:
+                            {
+                                Debug.Assert(b.DataContext != null, "The DataContext should not be null!");
+                                Owner.UnHighlightDays();
+                                Owner.HoverEndIndex = b.Index;
+                                Owner.HoverEnd = (DateTime)b.DataContext;
+                                // Update the States of the buttons
+                                Owner.HighlightDays();
+                                return;
+                            }
+                    }
+                }
+            }
+        }
+        internal void Cell_MouseLeave(object sender, PointerEventArgs e)
+        {
+            if (_isMouseLeftButtonDown)
+            {
+                CalendarDayButton b = sender as CalendarDayButton;
+                // The button is in Pressed state. Change the state to normal.
+                if (e.Device.Captured == b)
+                    e.Device.Capture(null);
+                // null check is added for unit tests
+                if (_downEventArg != null)
+                {
+                    var arg =
+                        new PointerReleasedEventArgs()
+                        {
+                            Device = _downEventArg.Device,
+                            MouseButton = _downEventArg.MouseButton,
+                            Handled = _downEventArg.Handled,
+                            InputModifiers = _downEventArg.InputModifiers,
+                            Route = _downEventArg.Route,
+                            Source = _downEventArg.Source
+                        };
+
+                    b.SendMouseLeftButtonUp(arg);
+                }
+                _lastCalendarDayButton = b;
+            }
+        }
+        internal void Cell_MouseLeftButtonDown(object sender, PointerPressedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                if (!Owner.HasFocusInternal)
+                {
+                    Owner.Focus();
+                }
+
+                bool ctrl, shift;
+                CalendarExtensions.GetMetaKeyState(e.InputModifiers, out ctrl, out shift);
+                CalendarDayButton b = sender as CalendarDayButton;
+
+                if (b != null)
+                {
+                    _isControlPressed = ctrl;
+                    if (b.IsEnabled && !b.IsBlackout)
+                    {
+                        DateTime selectedDate = (DateTime)b.DataContext;
+                        Debug.Assert(selectedDate != null, "selectedDate should not be null!");
+                        _isMouseLeftButtonDown = true;
+                        // null check is added for unit tests
+                        if (e != null)
+                        {
+                            _downEventArg = e;
+                        }
+
+                        switch (Owner.SelectionMode)
+                        {
+                            case CalendarSelectionMode.None:
+                                {
+                                    return;
+                                }
+                            case CalendarSelectionMode.SingleDate:
+                                {
+                                    Owner.DatePickerDisplayDateFlag = true;
+                                    if (Owner.SelectedDates.Count == 0)
+                                    {
+                                        Owner.SelectedDates.Add(selectedDate);
+                                    }
+                                    else
+                                    {
+                                        Owner.SelectedDates[0] = selectedDate;
+                                    }
+                                    return;
+                                }
+                            case CalendarSelectionMode.SingleRange:
+                                {
+                                    // Set the start or end of the selection
+                                    // range
+                                    if (shift)
+                                    {
+                                        Owner.UnHighlightDays();
+                                        Owner.HoverEnd = selectedDate;
+                                        Owner.HoverEndIndex = b.Index;
+                                        Owner.HighlightDays();
+                                    }
+                                    else
+                                    {
+                                        Owner.UnHighlightDays();
+                                        Owner.HoverStart = selectedDate;
+                                        Owner.HoverStartIndex = b.Index;
+                                    }
+                                    return;
+                                }
+                            case CalendarSelectionMode.MultipleRange:
+                                {
+                                    if (shift)
+                                    {
+                                        if (!ctrl)
+                                        {
+                                            // clear the list, set the states to
+                                            // default
+                                            foreach (DateTime item in Owner.SelectedDates)
+                                            {
+                                                Owner.RemovedItems.Add(item);
+                                            }
+                                            Owner.SelectedDates.ClearInternal();
+                                        }
+                                        Owner.HoverEnd = selectedDate;
+                                        Owner.HoverEndIndex = b.Index;
+                                        Owner.HighlightDays();
+                                    }
+                                    else
+                                    {
+                                        if (!ctrl)
+                                        {
+                                            // clear the list, set the states to
+                                            // default
+                                            foreach (DateTime item in Owner.SelectedDates)
+                                            {
+                                                Owner.RemovedItems.Add(item);
+                                            }
+                                            Owner.SelectedDates.ClearInternal();
+                                            Owner.UnHighlightDays();
+                                        }
+                                        Owner.HoverStart = selectedDate;
+                                        Owner.HoverStartIndex = b.Index;
+                                    }
+                                    return;
+                                }
+                        }
+                    }
+                    else
+                    {
+                        // If a click occurs on a BlackOutDay we set the
+                        // HoverStart to be null
+                        Owner.HoverStart = null;
+                    }
+                }
+                else
+                {
+                    _isControlPressed = false;
+                }
+            }
+        }
+        private void AddSelection(CalendarDayButton b)
+        {
+            if (Owner != null)
+            {
+                Owner.HoverEndIndex = b.Index;
+                Owner.HoverEnd = (DateTime)b.DataContext;
+
+                if (Owner.HoverEnd != null && Owner.HoverStart != null)
+                {
+                    // this is selection with Mouse, we do not guarantee the
+                    // range does not include BlackOutDates.  AddRange method
+                    // will throw away the BlackOutDates based on the
+                    // SelectionMode
+                    Owner.IsMouseSelection = true;
+                    Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value);
+                    Owner.OnDayClick((DateTime)b.DataContext);
+                }
+            }
+        }
+        internal void Cell_MouseLeftButtonUp(object sender, PointerReleasedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                CalendarDayButton b = sender as CalendarDayButton;
+                if (b != null && !b.IsBlackout)
+                {
+                    Owner.OnDayButtonMouseUp(e);
+                }
+                _isMouseLeftButtonDown = false;
+                if (b != null && b.DataContext != null)
+                {
+                    if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate)
+                    {
+                        Owner.OnDayClick((DateTime)b.DataContext);
+                        return;
+                    }
+                    if (Owner.HoverStart.HasValue)
+                    {
+                        switch (Owner.SelectionMode)
+                        {
+                            case CalendarSelectionMode.SingleRange:
+                                {
+                                    // Update SelectedDates
+                                    foreach (DateTime item in Owner.SelectedDates)
+                                    {
+                                        Owner.RemovedItems.Add(item);
+                                    }
+                                    Owner.SelectedDates.ClearInternal();
+                                    AddSelection(b);
+                                    return;
+                                }
+                            case CalendarSelectionMode.MultipleRange:
+                                {
+                                    // add the selection (either single day or
+                                    // SingleRange day)
+                                    AddSelection(b);
+                                    return;
+                                }
+                        }
+                    }
+                    else
+                    {
+                        // If the day is Disabled but a trailing day we should
+                        // be able to switch months
+                        if (b.IsInactive && b.IsBlackout)
+                        {
+                            Owner.OnDayClick((DateTime)b.DataContext);
+                        }
+                    }
+                }
+            }
+        }
+        private void Cell_Click(object sender, RoutedEventArgs e)
+        {
+            if (Owner != null)
+            {
+                if (_isControlPressed && Owner.SelectionMode == CalendarSelectionMode.MultipleRange)
+                {
+                    CalendarDayButton b = sender as CalendarDayButton;
+                    Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+
+                    if (b.IsSelected)
+                    {
+                        Owner.HoverStart = null;
+                        _isMouseLeftButtonDown = false;
+                        b.IsSelected = false;
+                        if (b.DataContext != null)
+                        {
+                            Owner.SelectedDates.Remove((DateTime)b.DataContext);
+                        }
+                    }
+                }
+            }
+            _isControlPressed = false;
+        }
+
+        private void Month_CalendarButtonMouseDown(object sender, PointerPressedEventArgs e)
+        {
+            CalendarButton b = sender as CalendarButton;
+            Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+
+            _isMouseLeftButtonDownYearView = true;
+
+            if (e != null)
+            {
+                _downEventArgYearView = e;
+            }
+
+            UpdateYearViewSelection(b);
+        }
+
+        internal void Month_CalendarButtonMouseUp(object sender, PointerReleasedEventArgs e)
+        {
+            _isMouseLeftButtonDownYearView = false;
+
+            if (Owner != null)
+            {
+                DateTime newmonth = (DateTime)((CalendarButton)sender).DataContext;
+
+                if (Owner.DisplayMode == CalendarMode.Year)
+                {
+                    Owner.DisplayDate = newmonth;
+                    Owner.DisplayMode = CalendarMode.Month;
+                }
+                else
+                {
+                    Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!");
+                    Owner.SelectedMonth = newmonth;
+                    Owner.DisplayMode = CalendarMode.Year;
+                }
+            }
+        }
+
+        private void Month_MouseEnter(object sender, PointerEventArgs e)
+        {
+            if (_isMouseLeftButtonDownYearView)
+            {
+                CalendarButton b = sender as CalendarButton;
+                Debug.Assert(b != null, "The sender should be a non-null CalendarDayButton!");
+                UpdateYearViewSelection(b);
+            }
+        }
+
+        private void Month_MouseLeave(object sender, PointerEventArgs e)
+        {
+            if (_isMouseLeftButtonDownYearView)
+            {
+                CalendarButton b = sender as CalendarButton;
+                // The button is in Pressed state. Change the state to normal.
+                if (e.Device.Captured == b)
+                    e.Device.Capture(null);
+                //b.ReleaseMouseCapture();
+                if (_downEventArgYearView != null)
+                {
+                    var args =
+                        new PointerReleasedEventArgs()
+                        {
+                            Device = _downEventArgYearView.Device,
+                            MouseButton = _downEventArgYearView.MouseButton,
+                            Handled = _downEventArgYearView.Handled,
+                            InputModifiers = _downEventArgYearView.InputModifiers,
+                            Route = _downEventArgYearView.Route,
+                            Source = _downEventArgYearView.Source
+                        };
+
+                    b.SendMouseLeftButtonUp(args);
+                }
+                _lastCalendarButton = b;
+            }
+        }
+        private void MonthView_MouseLeave(object sender, PointerEventArgs e)
+        {
+            if (_lastCalendarDayButton != null)
+            {
+                e.Device.Capture(_lastCalendarDayButton);
+            }
+        }
+
+        private void YearView_MouseLeave(object sender, PointerEventArgs e)
+        {
+            if (_lastCalendarButton != null)
+            {
+                e.Device.Capture(_lastCalendarButton);
+            }
+        }
+        
+        internal void UpdateDisabled(bool isEnabled)
+        {
+            PseudoClasses.Set(":calendardisabled", !isEnabled);
+        }
+    }
+}

+ 155 - 0
src/Avalonia.Controls/Calendar/DateTimeHelper.cs

@@ -0,0 +1,155 @@
+// (c) Copyright Microsoft Corporation.
+// This source is subject to the Microsoft Public License (Ms-PL).
+// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
+// All other rights reserved.
+
+using System;
+using System.Diagnostics;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+    internal static class DateTimeHelper
+    {
+        public static DateTime? AddDays(DateTime time, int days)
+        {
+            System.Globalization.Calendar cal = new GregorianCalendar();
+            try
+            {
+                return cal.AddDays(time, days);
+            }
+            catch (ArgumentException)
+            {
+                return null;
+            }
+        }
+
+        public static DateTime? AddMonths(DateTime time, int months)
+        {
+            System.Globalization.Calendar cal = new GregorianCalendar();
+            try
+            {
+                return cal.AddMonths(time, months);
+            }
+            catch (ArgumentException)
+            {
+                return null;
+            }
+        }
+
+        public static DateTime? AddYears(DateTime time, int years)
+        {
+            System.Globalization.Calendar cal = new GregorianCalendar();
+            try
+            {
+                return cal.AddYears(time, years);
+            }
+            catch (ArgumentException)
+            {
+                return null;
+            }
+        }
+
+        public static int CompareDays(DateTime dt1, DateTime dt2)
+        {
+            return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value);
+        }
+
+        public static int CompareYearMonth(DateTime dt1, DateTime dt2)
+        {
+            return (dt1.Year - dt2.Year) * 12 + (dt1.Month - dt2.Month);
+        }
+
+        public static int DecadeOfDate(DateTime date)
+        {
+            return date.Year - (date.Year % 10);
+        }
+
+        public 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;
+        }
+
+        public static DateTime? DiscardTime(DateTime? d)
+        {
+            if (d == null)
+            {
+                return null;
+            }
+            return d.Value.Date;
+        }
+
+        public static int EndOfDecade(DateTime date)
+        {
+            return DecadeOfDate(date) + 9;
+        }
+
+        public static DateTimeFormatInfo GetCurrentDateFormat()
+        {
+            if (CultureInfo.CurrentCulture.Calendar is GregorianCalendar)
+            {
+                return CultureInfo.CurrentCulture.DateTimeFormat;
+            }
+            else
+            {
+                foreach (System.Globalization.Calendar cal in CultureInfo.CurrentCulture.OptionalCalendars)
+                {
+                    if (cal is GregorianCalendar)
+                    {
+                        // if the default calendar is not Gregorian, return the
+                        // first supported GregorianCalendar dtfi
+                        DateTimeFormatInfo dtfi = new CultureInfo(CultureInfo.CurrentCulture.Name).DateTimeFormat;
+                        dtfi.Calendar = cal;
+                        return dtfi;
+                    }
+                }
+
+                // if there are no GregorianCalendars in the OptionalCalendars
+                // list, use the invariant dtfi
+                DateTimeFormatInfo dt = new CultureInfo(CultureInfo.InvariantCulture.Name).DateTimeFormat;
+                dt.Calendar = new GregorianCalendar();
+                return dt;
+            }
+        }
+        public static bool InRange(DateTime date, CalendarDateRange range)
+        {
+            Debug.Assert(DateTime.Compare(range.Start, range.End) < 1, "The range should start before it ends!");
+
+            if (CompareDays(date, range.Start) > -1 && CompareDays(date, range.End) < 1)
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        public static string ToYearMonthPatternString(DateTime date)
+        {
+            string result = string.Empty;
+            DateTimeFormatInfo format = GetCurrentDateFormat();
+
+            if (format != null)
+            {
+                result = date.ToString(format.YearMonthPattern, format);
+            }
+
+            return result;
+        }
+
+        public static string ToYearString(DateTime date)
+        {
+            string result = string.Empty;
+            DateTimeFormatInfo format = GetCurrentDateFormat();
+
+            if (format != null)
+            {
+                result = date.Year.ToString(format);
+            }
+
+            return result;
+        }
+    }
+}

+ 361 - 0
src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs

@@ -0,0 +1,361 @@
+// (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.Threading;
+using System;
+using System.Collections.ObjectModel;
+using System.Threading;
+
+namespace Avalonia.Controls.Primitives
+{
+    public sealed class SelectedDatesCollection : ObservableCollection<DateTime>
+    {
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        private Collection<DateTime> _addedItems;
+        
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        private bool _isCleared;
+
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        private bool _isRangeAdded;
+
+        /// <summary>
+        /// Inherited code: Requires comment.
+        /// </summary>
+        private Calendar _owner;
+
+        /// <summary>
+        /// Initializes a new instance of the
+        /// <see cref="T:Avalonia.Controls.Primitives.SelectedDatesCollection" />
+        /// class.
+        /// </summary>
+        /// <param name="owner">
+        /// The <see cref="T:Avalonia.Controls.Calendar" /> associated
+        /// with this object.
+        /// </param>
+        public SelectedDatesCollection(Calendar owner)
+        {
+            _owner = owner;
+            _addedItems = new Collection<DateTime>();
+        }
+
+        private void InvokeCollectionChanged(System.Collections.IList removedItems, System.Collections.IList addedItems)
+        {
+            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, addedItems, removedItems));
+        }
+
+        /// <summary>
+        /// Adds all the dates in the specified range, which includes the first
+        /// and last dates, to the collection.
+        /// </summary>
+        /// <param name="start">The first date to add to the collection.</param>
+        /// <param name="end">The last date to add to the collection.</param>
+        public void AddRange(DateTime start, DateTime end)
+        {
+            DateTime? rangeStart;
+
+            // increment parameter specifies if the Days were selected in
+            // Descending order or Ascending order based on this value, we add 
+            // the days in the range either in Ascending order or in Descending
+            // order
+            int increment = (DateTime.Compare(end, start) >= 0) ? 1 : -1;
+
+            _addedItems.Clear();
+
+            rangeStart = start;
+            _isRangeAdded = true;
+
+            if (_owner.IsMouseSelection)
+            {
+                // In Mouse Selection we allow the user to be able to add
+                // multiple ranges in one action in MultipleRange Mode.  In
+                // SingleRange Mode, we only add the first selected range.
+                while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
+                {
+                    if (Calendar.IsValidDateSelection(_owner, rangeStart))
+                    {
+                        Add(rangeStart.Value);
+                    }
+                    else
+                    {
+                        if (_owner.SelectionMode == CalendarSelectionMode.SingleRange)
+                        {
+                            _owner.HoverEnd = rangeStart.Value.AddDays(-increment);
+                            break;
+                        }
+                    }
+
+                    rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
+                }
+            }
+            else
+            {
+                // If CalendarSelectionMode.SingleRange and a user
+                // programmatically tries to add multiple ranges, we will throw
+                // away the old range and replace it with the new one.  In order
+                // to provide the removed items without an additional event, we
+                // are calling ClearInternal
+                if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && Count > 0)
+                {
+                    foreach (DateTime item in this)
+                    {
+                        _owner.RemovedItems.Add(item);
+                    }
+                    ClearInternal();
+                }
+
+                while (rangeStart.HasValue && DateTime.Compare(end, rangeStart.Value) != -increment)
+                {
+                    Add(rangeStart.Value);
+                    rangeStart = DateTimeHelper.AddDays(rangeStart.Value, increment);
+                }
+            }
+
+            _owner.OnSelectedDatesCollectionChanged(new SelectionChangedEventArgs(null, _addedItems, _owner.RemovedItems));
+            _owner.RemovedItems.Clear();
+            _owner.UpdateMonths();
+            _isRangeAdded = false;
+        }
+
+        /// <summary>
+        /// Removes all items from the collection.
+        /// </summary>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void ClearItems()
+        {
+            EnsureValidThread();
+
+            Collection<DateTime> addedItems = new Collection<DateTime>();
+            Collection<DateTime> removedItems = new Collection<DateTime>();
+
+            foreach (DateTime item in this)
+            {
+                removedItems.Add(item);
+            }
+
+            base.ClearItems();
+
+            // The event fires after SelectedDate changes
+            if (_owner.SelectionMode != CalendarSelectionMode.None && _owner.SelectedDate != null)
+            {
+                _owner.SelectedDate = null;
+            }
+
+            if (removedItems.Count != 0)
+            {
+                InvokeCollectionChanged(removedItems, addedItems);
+            }
+            _owner.UpdateMonths();
+        }
+
+        /// <summary>
+        /// Inserts an item into the collection at the specified index.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index at which item should be inserted.
+        /// </param>
+        /// <param name="item">The object to insert.</param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void InsertItem(int index, DateTime item)
+        {
+            EnsureValidThread();
+
+            if (!Contains(item))
+            {
+                Collection<DateTime> addedItems = new Collection<DateTime>();
+
+                if (CheckSelectionMode())
+                {
+                    if (Calendar.IsValidDateSelection(_owner, item))
+                    {
+                        // If the Collection is cleared since it is SingleRange
+                        // and it had another range set the index to 0
+                        if (_isCleared)
+                        {
+                            index = 0;
+                            _isCleared = false;
+                        }
+
+                        base.InsertItem(index, item);
+
+                        // The event fires after SelectedDate changes
+                        if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
+                        {
+                            _owner.SelectedDate = item;
+                        }
+
+                        if (!_isRangeAdded)
+                        {
+                            addedItems.Add(item);
+
+                            InvokeCollectionChanged(_owner.RemovedItems, addedItems);
+                            _owner.RemovedItems.Clear();
+                            int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
+
+                            if (monthDifference < 2 && monthDifference > -2)
+                            {
+                                _owner.UpdateMonths();
+                            }
+                        }
+                        else
+                        {
+                            _addedItems.Add(item);
+                        }
+                    }
+                    else
+                    {
+                        throw new ArgumentOutOfRangeException("SelectedDate value is not valid.");
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Removes the item at the specified index of the collection.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index of the element to remove.
+        /// </param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void RemoveItem(int index)
+        {
+            EnsureValidThread();
+
+            if (index >= Count)
+            {
+                base.RemoveItem(index);
+            }
+            else
+            {
+                Collection<DateTime> addedItems = new Collection<DateTime>();
+                Collection<DateTime> removedItems = new Collection<DateTime>();
+                int monthDifference = DateTimeHelper.CompareYearMonth(this[index], _owner.DisplayDateInternal);
+
+                removedItems.Add(this[index]);
+                base.RemoveItem(index);
+
+                // The event fires after SelectedDate changes
+                if (index == 0)
+                {
+                    if (Count > 0)
+                    {
+                        _owner.SelectedDate = this[0];
+                    }
+                    else
+                    {
+                        _owner.SelectedDate = null;
+                    }
+                }
+
+                InvokeCollectionChanged(removedItems, addedItems);
+
+                if (monthDifference < 2 && monthDifference > -2)
+                {
+                    _owner.UpdateMonths();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Replaces the element at the specified index.
+        /// </summary>
+        /// <param name="index">
+        /// The zero-based index of the element to replace.
+        /// </param>
+        /// <param name="item">
+        /// The new value for the element at the specified index.
+        /// </param>
+        /// <remarks>
+        /// This implementation raises the CollectionChanged event.
+        /// </remarks>
+        protected override void SetItem(int index, DateTime item)
+        {
+            EnsureValidThread();
+
+            if (!Contains(item))
+            {
+                Collection<DateTime> addedItems = new Collection<DateTime>();
+                Collection<DateTime> removedItems = new Collection<DateTime>();
+
+                if (index >= Count)
+                {
+                    base.SetItem(index, item);
+                }
+                else
+                {
+                    if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
+                    {
+                        removedItems.Add(this[index]);
+                        base.SetItem(index, item);
+                        addedItems.Add(item);
+
+                        // The event fires after SelectedDate changes
+                        if (index == 0 && !(_owner.SelectedDate.HasValue && DateTime.Compare(_owner.SelectedDate.Value, item) == 0))
+                        {
+                            _owner.SelectedDate = item;
+                        }
+                        InvokeCollectionChanged(removedItems, addedItems);
+
+                        int monthDifference = DateTimeHelper.CompareYearMonth(item, _owner.DisplayDateInternal);
+
+                        if (monthDifference < 2 && monthDifference > -2)
+                        {
+                            _owner.UpdateMonths();
+                        }
+                    }
+                }
+            }
+        }
+        
+        internal void ClearInternal()
+        {
+            base.ClearItems();
+        }
+        
+        private bool CheckSelectionMode()
+        {
+            if (_owner.SelectionMode == CalendarSelectionMode.None)
+            {
+                throw new InvalidOperationException("The SelectedDate property cannot be set when the selection mode is None.");
+            }
+            if (_owner.SelectionMode == CalendarSelectionMode.SingleDate && Count > 0)
+            {
+                throw new InvalidOperationException("The SelectedDates collection can be changed only in a multiple selection mode. Use the SelectedDate in a single selection mode.");
+            }
+
+            // if user tries to add an item into the SelectedDates in
+            // SingleRange mode, we throw away the old range and replace it with
+            // the new one in order to provide the removed items without an
+            // additional event, we are calling ClearInternal
+            if (_owner.SelectionMode == CalendarSelectionMode.SingleRange && !_isRangeAdded && Count > 0)
+            {
+                foreach (DateTime item in this)
+                {
+                    _owner.RemovedItems.Add(item);
+                }
+                ClearInternal();
+                _isCleared = true;
+            }
+            return true;
+        }
+        
+        private void EnsureValidThread()
+        {
+            Dispatcher.UIThread.VerifyAccess();
+        }
+    }
+}

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

@@ -179,7 +179,7 @@ namespace Avalonia.Controls
             {
                 ThrowIfPseudoclass(name, "removed");
 
-                if (!Contains(name))
+                if (Contains(name))
                 {
                     c.Add(name);
                 }

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

@@ -621,7 +621,6 @@ namespace Avalonia.Controls
             Contract.Requires<ArgumentNullException>(property != null);
             Contract.Requires<ArgumentNullException>(selector != null);
             Contract.Requires<ArgumentNullException>(className != null);
-            Contract.Requires<ArgumentNullException>(property != null);
 
             if (string.IsNullOrWhiteSpace(className))
             {
@@ -774,7 +773,9 @@ namespace Avalonia.Controls
 
                     foreach (var child in control.LogicalChildren)
                     {
-                        if (child is Control c && !c.IsSet(DataContextProperty))
+                        if (child is Control c && 
+                            c.InheritanceParent == control &&
+                            !c.IsSet(DataContextProperty))
                         {
                             DataContextNotifying(c, notifying);
                         }

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

@@ -96,6 +96,16 @@ namespace Avalonia.Controls
             this.UpdateSelectionBoxItem(this.SelectedItem);
         }
 
+        protected override void OnGotFocus(GotFocusEventArgs e)
+        {
+            base.OnGotFocus(e);
+
+            if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
+            {
+                e.Handled = UpdateSelectionFromEventSource(e.Source);
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnKeyDown(KeyEventArgs e)
         {
@@ -104,7 +114,7 @@ namespace Avalonia.Controls
             if (!e.Handled)
             {
                 if (e.Key == Key.F4 ||
-                    (e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
+                    ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
                 {
                     IsDropDownOpen = !IsDropDownOpen;
                     e.Handled = true;
@@ -114,6 +124,27 @@ namespace Avalonia.Controls
                     IsDropDownOpen = false;
                     e.Handled = true;
                 }
+
+                if (!IsDropDownOpen)
+                {
+                    if (e.Key == Key.Down)
+                    {
+                        if (SelectedIndex == -1)
+                            SelectedIndex = 0;
+                        
+                        if (++SelectedIndex >= ItemCount)
+                            SelectedIndex = 0;
+                        
+                        e.Handled = true;
+                    }
+                    else if (e.Key == Key.Up)
+                    {
+                        if (--SelectedIndex < 0)
+                            SelectedIndex = ItemCount - 1;
+                        
+                        e.Handled = true;
+                    }
+                }
             }
         }
 

+ 11 - 0
src/Avalonia.Controls/ItemsControl.cs

@@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Utils;
+using Avalonia.Input;
 using Avalonia.LogicalTree;
 using Avalonia.Metadata;
 
@@ -106,6 +107,12 @@ namespace Avalonia.Controls
             set { SetAndRaise(ItemsProperty, ref _items, value); }
         }
 
+        public int ItemCount
+        {
+            get;
+            private set;
+        }
+
         /// <summary>
         /// Gets or sets the panel used to display the items.
         /// </summary>
@@ -352,6 +359,10 @@ namespace Avalonia.Controls
                     RemoveControlItemsFromLogicalChildren(e.OldItems);
                     break;
             }
+            
+            int? count = (Items as IList)?.Count;
+            if (count != null)
+                ItemCount = (int)count;
 
             var collection = sender as ICollection;
             PseudoClasses.Set(":empty", collection == null || collection.Count == 0);

+ 6 - 5
src/Avalonia.Controls/Panel.cs

@@ -115,6 +115,11 @@ namespace Avalonia.Controls
                     VisualChildren.AddRange(e.NewItems.OfType<Visual>());
                     break;
 
+                case NotifyCollectionChangedAction.Move:
+                    LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+                    break;
+
                 case NotifyCollectionChangedAction.Remove:
                     controls = e.OldItems.OfType<Control>().ToList();
                     LogicalChildren.RemoveAll(controls);
@@ -132,11 +137,7 @@ namespace Avalonia.Controls
                     break;
 
                 case NotifyCollectionChangedAction.Reset:
-                    controls = e.OldItems.OfType<Control>().ToList();
-                    LogicalChildren.Clear();
-                    VisualChildren.Clear();
-                    VisualChildren.AddRange(_children);
-                    break;
+                    throw new NotSupportedException();
             }
 
             InvalidateMeasure();

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

@@ -305,7 +305,7 @@ namespace Avalonia.Controls.Primitives
                 .OfType<IControl>()
                 .FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
 
-            return item as IControl;
+            return item;
         }
 
         /// <inheritdoc/>

+ 28 - 8
src/Avalonia.Controls/Primitives/ToggleButton.cs

@@ -9,26 +9,37 @@ namespace Avalonia.Controls.Primitives
 {
     public class ToggleButton : Button
     {
-        public static readonly DirectProperty<ToggleButton, bool> IsCheckedProperty =
-            AvaloniaProperty.RegisterDirect<ToggleButton, bool>(
-                "IsChecked",
+        public static readonly DirectProperty<ToggleButton, bool?> IsCheckedProperty =
+            AvaloniaProperty.RegisterDirect<ToggleButton, bool?>(
+                nameof(IsChecked),
                 o => o.IsChecked,
-                (o,v) => o.IsChecked = v,
+                (o, v) => o.IsChecked = v,
                 defaultBindingMode: BindingMode.TwoWay);
 
-        private bool _isChecked;
+        public static readonly StyledProperty<bool> IsThreeStateProperty =
+            AvaloniaProperty.Register<ToggleButton, bool>(nameof(IsThreeState));
+
+        private bool? _isChecked = false;
 
         static ToggleButton()
         {
-            PseudoClass(IsCheckedProperty, ":checked");
+            PseudoClass(IsCheckedProperty, c => c == true, ":checked");
+            PseudoClass(IsCheckedProperty, c => c == false, ":unchecked");
+            PseudoClass(IsCheckedProperty, c => c == null, ":indeterminate");
         }
 
-        public bool IsChecked
+        public bool? IsChecked
         {
             get { return _isChecked; }
             set { SetAndRaise(IsCheckedProperty, ref _isChecked, value); }
         }
 
+        public bool IsThreeState
+        {
+            get => GetValue(IsThreeStateProperty);
+            set => SetValue(IsThreeStateProperty, value);
+        }
+
         protected override void OnClick()
         {
             Toggle();
@@ -37,7 +48,16 @@ namespace Avalonia.Controls.Primitives
 
         protected virtual void Toggle()
         {
-            IsChecked = !IsChecked;
+            if (IsChecked.HasValue)
+                if (IsChecked.Value)
+                    if (IsThreeState)
+                        IsChecked = null;
+                    else
+                        IsChecked = false;
+                else
+                    IsChecked = true;
+            else
+                IsChecked = false;
         }
     }
 }

+ 2 - 3
src/Avalonia.Controls/ProgressBar.cs

@@ -28,9 +28,6 @@ namespace Avalonia.Controls
         {
             ValueProperty.Changed.AddClassHandler<ProgressBar>(x => x.ValueChanged);
 
-            HorizontalAlignmentProperty.OverrideDefaultValue<ProgressBar>(HorizontalAlignment.Left);
-            VerticalAlignmentProperty.OverrideDefaultValue<ProgressBar>(VerticalAlignment.Top);
-
             IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>(
                 (p, e) => { if (p._indicator != null) p.UpdateIsIndeterminate((bool)e.NewValue); });
             OrientationProperty.Changed.AddClassHandler<ProgressBar>(
@@ -112,8 +109,10 @@ namespace Avalonia.Controls
         private void UpdateIsIndeterminate(bool isIndeterminate)
         {
             if (isIndeterminate)
+            {
                 if (_indeterminateAnimation == null || _indeterminateAnimation.Disposed)
                     _indeterminateAnimation = IndeterminateAnimation.StartAnimation(this);
+            }
             else
                 _indeterminateAnimation?.Dispose();
         }

+ 5 - 4
src/Avalonia.Controls/RadioButton.cs

@@ -17,17 +17,17 @@ namespace Avalonia.Controls
 
         protected override void Toggle()
         {
-            if (!IsChecked)
+            if (!IsChecked.GetValueOrDefault())
             {
                 IsChecked = true;
             }
         }
 
-        private void IsCheckedChanged(bool value)
+        private void IsCheckedChanged(bool? value)
         {
             var parent = this.GetVisualParent();
 
-            if (value && parent != null)
+            if (value.GetValueOrDefault() && parent != null)
             {
                 var siblings = parent
                     .GetVisualChildren()
@@ -36,7 +36,8 @@ namespace Avalonia.Controls
 
                 foreach (var sibling in siblings)
                 {
-                    sibling.IsChecked = false;
+                    if (sibling.IsChecked.GetValueOrDefault())
+                        sibling.IsChecked = false;
                 }
             }
         }

+ 44 - 2
src/Avalonia.Controls/Shapes/Shape.cs

@@ -29,6 +29,8 @@ namespace Avalonia.Controls.Shapes
         private Matrix _transform = Matrix.Identity;
         private Geometry _definingGeometry;
         private Geometry _renderedGeometry;
+        bool _calculateTransformOnArrange = false;
+
 
         static Shape()
         {
@@ -150,13 +152,53 @@ namespace Avalonia.Controls.Shapes
             this._definingGeometry = null;
             InvalidateMeasure();
         }
-
+        
         protected override Size MeasureOverride(Size availableSize)
+        {
+            bool deferCalculateTransform;
+            switch (Stretch)
+            {
+                case Stretch.Fill:
+                case Stretch.UniformToFill:
+                    deferCalculateTransform = double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height);
+                    break;
+                case Stretch.Uniform:
+                    deferCalculateTransform = double.IsInfinity(availableSize.Width) && double.IsInfinity(availableSize.Height);
+                    break;
+                case Stretch.None:
+                default:
+                    deferCalculateTransform = false;
+                    break;
+            }
+
+            if (deferCalculateTransform)
+            {
+                _calculateTransformOnArrange = true;
+                return DefiningGeometry.Bounds.Size;
+            }
+            else
+            {
+                _calculateTransformOnArrange = false;
+                return CalculateShapeSizeAndSetTransform(availableSize);
+            }
+        }
+        
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            if(_calculateTransformOnArrange)
+            {
+                _calculateTransformOnArrange = false;
+                CalculateShapeSizeAndSetTransform(finalSize);
+            }
+
+            return finalSize;
+        }
+        private Size CalculateShapeSizeAndSetTransform(Size availableSize)
         {
             // This should probably use GetRenderBounds(strokeThickness) but then the calculations
             // will multiply the stroke thickness as well, which isn't correct.
             var (size, transform) = CalculateSizeAndTransform(availableSize, DefiningGeometry.Bounds, Stretch);
-            
+
             if (_transform != transform)
             {
                 _transform = transform;

+ 1 - 1
src/Avalonia.Controls/Templates/FuncTreeDataTemplate.cs

@@ -62,7 +62,7 @@ namespace Avalonia.Controls.Templates
         /// <returns>The child items, or null if no child items.</returns>
         public InstancedBinding ItemsSelector(object item)
         {
-            return new InstancedBinding(this?._itemsSelector(item));
+            return InstancedBinding.OneTime(this?._itemsSelector(item));
         }
 
         /// <summary>

+ 12 - 0
src/Avalonia.Controls/TextBox.cs

@@ -97,6 +97,7 @@ namespace Avalonia.Controls
         private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
         private bool _ignoreTextChanges;
         private IEnumerable<Exception> _dataValidationErrors;
+        private static readonly string[] invalidCharacters = new String[1]{"\u007f"};
 
         static TextBox()
         {
@@ -280,6 +281,7 @@ namespace Avalonia.Controls
         {
             if (!IsReadOnly)
             {
+                input = RemoveInvalidCharacters(input);
                 string text = Text ?? string.Empty;
                 int caretIndex = CaretIndex;
                 if (!string.IsNullOrEmpty(input))
@@ -295,6 +297,16 @@ namespace Avalonia.Controls
             }
         }
 
+        public string RemoveInvalidCharacters(string text)
+        {
+            for (var i = 0; i < invalidCharacters.Length; i++)
+            {
+                text = text.Replace(invalidCharacters[i], string.Empty);
+            }
+
+            return text;
+        }
+
         private async void Copy()
         {
             await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))

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

@@ -176,10 +176,7 @@ namespace Avalonia.Controls
 
                 SelectedItem = item;
 
-                if (SelectedItem != null)
-                {
-                    MarkContainerSelected(container, true);
-                }
+                MarkContainerSelected(container, true);
             }
         }
 

+ 5 - 3
src/Avalonia.Controls/VirtualizingStackPanel.cs

@@ -134,12 +134,14 @@ namespace Avalonia.Controls
 
         protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
         {
+            if (from == null)
+                return null;
+
             var logicalScrollable = Parent as ILogicalScrollable;
-            var fromControl = from as IControl;
 
-            if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
+            if (logicalScrollable?.IsLogicalScrollEnabled == true)
             {
-                return logicalScrollable.GetControlInDirection(direction, fromControl);
+                return logicalScrollable.GetControlInDirection(direction, from);
             }
             else
             {

+ 0 - 1
src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj

@@ -50,5 +50,4 @@
     </EmbeddedResource>
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
-  <Import Project="..\..\build\Splat.props" />
 </Project>

+ 0 - 53
src/Avalonia.Diagnostics/LogManager.cs

@@ -1,53 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using Avalonia.Layout;
-using Splat;
-
-namespace Avalonia.Diagnostics
-{
-    public class LogManager : ILogManager
-    {
-        private static LogManager s_instance;
-
-        public static LogManager Instance => s_instance ?? (s_instance = new LogManager());
-
-        public ILogger Logger
-        {
-            get;
-            set;
-        }
-
-        public bool LogPropertyMessages
-        {
-            get;
-            set;
-        }
-
-        public bool LogLayoutMessages
-        {
-            get;
-            set;
-        }
-
-        public static void Enable(ILogger logger)
-        {
-            Instance.Logger = logger;
-            Locator.CurrentMutable.Register(() => Instance, typeof(ILogManager));
-        }
-
-        public IFullLogger GetLogger(Type type)
-        {
-            if ((type == typeof(AvaloniaObject) && LogPropertyMessages) ||
-                (type == typeof(Layoutable) && LogLayoutMessages))
-            {
-                return new WrappingFullLogger(Logger, type);
-            }
-            else
-            {
-                return new WrappingFullLogger(new NullLogger(), type);
-            }
-        }
-    }
-}

+ 6 - 0
src/Avalonia.Diagnostics/Views/TreePage.xaml.cs

@@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
 
             if (layer != null)
             {
+                if (_adorner != null)
+                {
+                    ((Panel)_adorner.Parent).Children.Remove(_adorner);
+                    _adorner = null;
+                }
+
                 _adorner = new Rectangle
                 {
                     Fill = new SolidColorBrush(0x80a0c5e8),

+ 1 - 0
src/Avalonia.Logging.Serilog/Avalonia.Logging.Serilog.csproj

@@ -28,6 +28,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
+    <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
   </ItemGroup>
   <Import Project="..\..\build\Serilog.props" />
 </Project>

+ 51 - 0
src/Avalonia.Logging.Serilog/SerilogExtensions.cs

@@ -0,0 +1,51 @@
+using System;
+using Avalonia.Controls;
+using Serilog;
+using SerilogLevel = Serilog.Events.LogEventLevel;
+
+namespace Avalonia.Logging.Serilog
+{
+    /// <summary>
+    /// Extension methods for Serilog logging.
+    /// </summary>
+    public static class SerilogExtensions
+    {
+        /// <summary>
+        /// Logs Avalonia events to the <see cref="System.Diagnostics.Debug"/> sink.
+        /// </summary>
+        /// <typeparam name="T">The application class type.</typeparam>
+        /// <param name="builder">The app builder instance.</param>
+        /// <param name="level">The minimum level to log.</param>
+        /// <returns>The app builder instance.</returns>
+        public static T LogToDebug<T>(
+            this T builder,
+            LogEventLevel level = LogEventLevel.Warning)
+                where T : AppBuilderBase<T>, new()
+        {
+            SerilogLogger.Initialize(new LoggerConfiguration()
+                .MinimumLevel.Is((SerilogLevel)level)
+                .WriteTo.Debug(outputTemplate: "{Area}: {Message}")
+                .CreateLogger());
+            return builder;
+        }
+
+        /// <summary>
+        /// Logs Avalonia events to the <see cref="System.Diagnostics.Trace"/> sink.
+        /// </summary>
+        /// <typeparam name="T">The application class type.</typeparam>
+        /// <param name="builder">The app builder instance.</param>
+        /// <param name="level">The minimum level to log.</param>
+        /// <returns>The app builder instance.</returns>
+        public static T LogToTrace<T>(
+            this T builder,
+            LogEventLevel level = LogEventLevel.Warning)
+                where T : AppBuilderBase<T>, new()
+        {
+            SerilogLogger.Initialize(new LoggerConfiguration()
+                .MinimumLevel.Is((SerilogLevel)level)
+                .WriteTo.Trace(outputTemplate: "{Area}: {Message}")
+                .CreateLogger());
+            return builder;
+        }
+    }
+}

+ 0 - 1
src/Avalonia.ReactiveUI/Avalonia.ReactiveUI.csproj

@@ -16,5 +16,4 @@
   </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
   <Import Project="..\..\build\ReactiveUI.props" />
-  <Import Project="..\..\build\Splat.props" />
 </Project>

+ 1 - 2
src/Avalonia.Remote.Protocol/BsonStreamTransport.cs

@@ -64,7 +64,6 @@ namespace Avalonia.Remote.Protocol
 
         async Task Reader()
         {
-            Task.Yield();
             try
             {
                 while (true)
@@ -147,4 +146,4 @@ namespace Avalonia.Remote.Protocol
         public event Action<IAvaloniaRemoteTransportConnection, object> OnMessage;
         public event Action<IAvaloniaRemoteTransportConnection, Exception> OnException;
     }
-}
+}

+ 31 - 29
src/Avalonia.Styling/Styling/Setter.cs

@@ -135,45 +135,47 @@ namespace Avalonia.Styling
 
         private InstancedBinding Clone(InstancedBinding sourceInstance, IStyle style, IObservable<bool> activator)
         {
-            InstancedBinding cloned;
-
             if (activator != null)
             {
                 var description = style?.ToString();
 
-                if (sourceInstance.Mode == BindingMode.TwoWay || sourceInstance.Mode == BindingMode.OneWayToSource)
-                {
-                    var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
-                    cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
-                }
-                else if (sourceInstance.Mode == BindingMode.OneTime)
-                {
-                    var activated = new ActivatedValue(activator, sourceInstance.Value, description);
-                    cloned = new InstancedBinding(activated, BindingMode.OneWay, BindingPriority.StyleTrigger);
-                }
-                else
+                switch (sourceInstance.Mode)
                 {
-                    var activated = new ActivatedObservable(activator, sourceInstance.Observable ?? sourceInstance.Subject, description);
-                    cloned = new InstancedBinding(activated, sourceInstance.Mode, BindingPriority.StyleTrigger);
+                    case BindingMode.OneTime:
+                        if (sourceInstance.Observable != null)
+                        {
+                            var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
+                            return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
+                        }
+                        else
+                        {
+                            var activated = new ActivatedValue(activator, sourceInstance.Value, description);
+                            return InstancedBinding.OneTime(activated, BindingPriority.StyleTrigger);
+                        }
+                    case BindingMode.OneWay:
+                        {
+                            var activated = new ActivatedObservable(activator, sourceInstance.Observable, description);
+                            return InstancedBinding.OneWay(activated, BindingPriority.StyleTrigger);
+                        }
+                    case BindingMode.OneWayToSource:
+                        {
+                            var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
+                            return InstancedBinding.OneWayToSource(activated, BindingPriority.StyleTrigger);
+                        }
+                    case BindingMode.TwoWay:
+                        {
+                            var activated = new ActivatedSubject(activator, sourceInstance.Subject, description);
+                            return InstancedBinding.TwoWay(activated, BindingPriority.StyleTrigger);
+                        }
+                    default:
+                        throw new NotSupportedException("Unsupported BindingMode.");
                 }
+
             }
             else
             {
-                if (sourceInstance.Subject != null)
-                {
-                    cloned = new InstancedBinding(sourceInstance.Subject, sourceInstance.Mode, BindingPriority.Style);
-                }
-                else if (sourceInstance.Observable != null)
-                {
-                    cloned = new InstancedBinding(sourceInstance.Observable, sourceInstance.Mode, BindingPriority.Style);
-                }
-                else
-                {
-                    cloned = new InstancedBinding(sourceInstance.Value, BindingPriority.Style);
-                }
+                return sourceInstance.WithPriority(BindingPriority.Style);
             }
-
-            return cloned;
         }
     }
 }

+ 30 - 0
src/Avalonia.Themes.Default/Calendar.xaml

@@ -0,0 +1,30 @@
+<!--
+// (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">
+  <Style Selector="Calendar">
+    <!--<Setter Property="Focusable" Value="False" />-->
+    <Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}" />
+    <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderDarkBrush}" />
+    <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}" />
+    <Setter Property="HeaderBackground" Value="{DynamicResource ThemeAccentBrush2}" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <StackPanel Name="Root"
+                    HorizontalAlignment="Center">
+
+          <CalendarItem Name="CalendarItem"
+                        Background="{TemplateBinding Background}"
+                        BorderBrush="{TemplateBinding BorderBrush}"
+                        BorderThickness="{TemplateBinding BorderThickness}"
+                        HeaderBackground="{TemplateBinding HeaderBackground}"/>
+
+        </StackPanel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+</Styles>

+ 80 - 0
src/Avalonia.Themes.Default/CalendarButton.xaml

@@ -0,0 +1,80 @@
+<!--
+// (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">
+  <Style Selector="CalendarButton">
+    <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" />
+    <Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" />
+    <Setter Property="HorizontalContentAlignment" Value="Center" />
+    <Setter Property="VerticalContentAlignment" Value="Center" />
+    <Setter Property="MinWidth" Value="37" />
+    <Setter Property="MinHeight" Value="38" />
+    <Setter Property="Focusable" Value="False"/>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Grid>
+
+          <Rectangle Name="SelectedBackground"
+                     Opacity="0.75"
+                     Fill="{TemplateBinding Background}"/>
+
+          <Rectangle Name="Background"
+                     Opacity="0.5"
+                     Fill="{TemplateBinding Background}"/>
+
+          <!--Focusable="False"-->
+          <ContentControl Name="Content"
+                          Foreground="#FF333333"
+                          Content="{TemplateBinding Content}"
+                          ContentTemplate="{TemplateBinding ContentTemplate}"
+                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                          FontSize="{TemplateBinding FontSize}"
+                          Margin="1,0,1,1"/>
+
+          <Rectangle Name="FocusVisual"
+                     StrokeThickness="1"
+                     Stroke="{DynamicResource HighlightBrush}"
+                     IsHitTestVisible="False"/>
+
+        </Grid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="CalendarButton /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarButton:pointerover /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+  <Style Selector="CalendarButton:pressed /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+  <Style Selector="CalendarButton /template/ Rectangle#SelectedBackground">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarButton:selected /template/ Rectangle#SelectedBackground">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+  <Style Selector="CalendarButton /template/ ContentControl#Content">
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
+  </Style>
+  <Style Selector="CalendarButton:inactive /template/ ContentControl#Content">
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
+  </Style>
+
+
+  <Style Selector="CalendarButton /template/ Rectangle#FocusVisual">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarButton:btnfocused /template/ Rectangle#FocusVisual">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+</Styles>

+ 116 - 0
src/Avalonia.Themes.Default/CalendarDayButton.xaml

@@ -0,0 +1,116 @@
+<!--
+// (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">
+  <Style Selector="CalendarDayButton">
+    <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush2}" />
+    <Setter Property="FontSize" Value="{DynamicResource FontSizeSmall}" />
+    <Setter Property="HorizontalContentAlignment" Value="Center" />
+    <Setter Property="VerticalContentAlignment" Value="Center" />
+    <Setter Property="MinWidth" Value="5" />
+    <Setter Property="MinHeight" Value="5" />
+    <Setter Property="Focusable" Value="False"/>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Panel Background="Transparent">
+
+          <Rectangle Name="TodayBackground"
+                     Fill="{DynamicResource HighlightBrush}"/>
+
+          <Rectangle Name="SelectedBackground"
+                     Opacity="0.75"
+                     Fill="{TemplateBinding Background}"/>
+
+          <Rectangle Name="Background"
+                     Opacity="0.5"
+                     Fill="{TemplateBinding Background}"/>
+
+          <ContentControl Name="Content"
+                          Content="{TemplateBinding Content}"
+                          ContentTemplate="{TemplateBinding ContentTemplate}"
+                          HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
+                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
+                          FontSize="{TemplateBinding FontSize}"
+                          Margin="5,1,5,1"/>
+
+          <Path Name="BlackoutVisual"
+                Margin="3"
+                HorizontalAlignment="Stretch"
+                VerticalAlignment="Stretch"
+                RenderTransformOrigin="0.5,0.5"
+                Fill="#FF000000"
+                Stretch="Fill"
+                Data="M8.1772461,11.029181 L10.433105,11.029181 L11.700684,12.801641 L12.973633,11.029181 L15.191895,11.029181 L12.844727,13.999395 L15.21875,17.060919 L12.962891,17.060919 L11.673828,15.256231 L10.352539,17.060919 L8.1396484,17.060919 L10.519043,14.042364 z" />
+          
+          <Rectangle Name="FocusVisual"
+                     StrokeThickness="1"
+                     Stroke="{DynamicResource HighlightBrush}"
+                     IsHitTestVisible="False"/>
+
+        </Panel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarDayButton:pointerover /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+  <Style Selector="CalendarDayButton:pressed /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ Rectangle#SelectedBackground">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarDayButton:selected /template/ Rectangle#SelectedBackground">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ ContentControl#Content">
+    <Setter Property="Opacity" Value="1"/>
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
+  </Style>
+  <Style Selector="CalendarDayButton:disabled /template/ Rectangle#Background">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarDayButton:disabled /template/ ContentControl#Content">
+    <Setter Property="Opacity" Value="0.3"/>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ Rectangle#FocusVisual">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarDayButton:dayfocused /template/ Rectangle#FocusVisual">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ Rectangle#TodayBackground">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarDayButton:today /template/ Rectangle#TodayBackground">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+
+
+  <Style Selector="CalendarDayButton:inactive /template/ ContentControl#Content">
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundLightBrush}"/>
+  </Style>
+  <Style Selector="CalendarDayButton:today /template/ ContentControl#Content">
+    <Setter Property="Foreground" Value="#FFFFFFFF"/>
+  </Style>
+
+  <Style Selector="CalendarDayButton /template/ Path#BlackoutVisual">
+    <Setter Property="Opacity" Value="0"/>
+  </Style>
+  <Style Selector="CalendarDayButton:blackout /template/ Path#BlackoutVisual">
+    <Setter Property="Opacity" Value="0.3"/>
+  </Style>  
+
+</Styles>

+ 183 - 0
src/Avalonia.Themes.Default/CalendarItem.xaml

@@ -0,0 +1,183 @@
+<!--
+// (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="CalendarItem">
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Panel>
+          <Border BorderThickness="{TemplateBinding BorderThickness}"
+                  BorderBrush="{TemplateBinding BorderBrush}"
+                  Background="{TemplateBinding Background}"
+                  Margin="0,2,0,2"
+                  CornerRadius="1">
+
+            <Border CornerRadius="1"
+                    BorderBrush="{DynamicResource ThemeBackgroundBrush}"
+                    BorderThickness="2">
+              <Grid>
+                <Grid.RowDefinitions>
+                  <RowDefinition Height="Auto" />
+                  <RowDefinition Height="*" />
+                </Grid.RowDefinitions>
+                <Grid.ColumnDefinitions>
+                  <ColumnDefinition Width="Auto" />
+                  <ColumnDefinition Width="Auto" />
+                  <ColumnDefinition Width="Auto" />
+                </Grid.ColumnDefinitions>
+
+                <Grid.Styles>
+                  <Style Selector="Button.CalendarHeader">
+                    <Setter Property="Cursor" Value="Hand"/>
+                    <Setter Property="Background" Value="Transparent"/>
+                    <Setter Property="BorderThickness" Value="0"/>
+                  </Style>
+                    <Style Selector="Button.CalendarHeader:pressed  /template/ ContentPresenter">
+                    <Setter Property="Background" Value="Transparent"/>
+                  </Style>
+
+                  <Style Selector="Button.CalendarNavigation">
+                    <Setter Property="Height" Value="20"/>
+                    <Setter Property="Width" Value="28"/>
+                  </Style>
+                  <Style Selector="Button.CalendarNavigation > Path">
+                    <Setter Property="Fill" Value="{DynamicResource ThemeForegroundBrush}"/>
+                  </Style>
+                  <Style Selector="Button.CalendarNavigation:pointerover > Path">
+                    <Setter Property="Fill" Value="{DynamicResource HighlightBrush}"/>
+                  </Style>
+
+                  <Style Selector="Button#HeaderButton:pointerover">
+                    <Setter Property="Foreground" Value="{DynamicResource HighlightBrush}"/>
+                  </Style>
+                </Grid.Styles>
+
+                <Rectangle Grid.ColumnSpan="3"
+                           Fill="{TemplateBinding HeaderBackground}"
+                           Stretch="Fill"
+                           VerticalAlignment="Top"
+                           Height="22"/>
+
+                <Button Name="PreviousButton"
+                        Classes="CalendarHeader CalendarNavigation"
+                        IsVisible="False"
+                        HorizontalAlignment="Left">
+
+                  <Path Margin="14,-6,0,0"
+                        Height="10"
+                        Width="6"
+                        VerticalAlignment="Center"
+                        HorizontalAlignment="Left"
+                        Stretch="Fill"
+                        Data="M288.75,232.25 L288.75,240.625 L283,236.625 z" />
+                  
+                </Button>
+                
+                <Button Name="HeaderButton"
+                        Classes="CalendarHeader"
+                        Grid.Column="1"
+                        FontWeight="Bold"
+                        FontSize="10.5"
+                        HorizontalAlignment="Center"
+                        VerticalAlignment="Center" 
+                        Padding="1,5,1,9"/>
+
+                <Button Name="NextButton"
+                        Classes="CalendarHeader CalendarNavigation"
+                        Grid.Column="2"
+                        IsVisible="False"
+                        HorizontalAlignment="Right" >
+
+                  <Path Margin="0,-6,14,0"
+                        Height="10"
+                        Width="6"
+                        Stretch="Fill"
+                        VerticalAlignment="Center"
+                        HorizontalAlignment="Right"
+                        Data="M282.875,231.875 L282.875,240.375 L288.625,236 z" />
+                  
+                </Button>
+
+                <Grid Name="MonthView"
+                      Grid.Row="1"
+                      Grid.ColumnSpan="3"
+                      IsVisible="False"
+                      Margin="6,-1,6,6">
+                  <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                  </Grid.RowDefinitions>
+                  <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                  </Grid.ColumnDefinitions>
+                </Grid>
+
+                <Grid Name="YearView"
+                      Grid.Row="1"
+                      Grid.ColumnSpan="3"
+                      IsVisible="False"
+                      Margin="6,-3,7,6">
+                  <Grid.RowDefinitions>
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                    <RowDefinition Height="Auto" />
+                  </Grid.RowDefinitions>
+                  <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                    <ColumnDefinition Width="Auto" />
+                  </Grid.ColumnDefinitions>
+                </Grid>
+
+              </Grid>
+            </Border>
+          </Border>
+
+
+          <Rectangle Name="DisabledVisual"
+                     Stretch="Fill"
+                     Fill="#FFFFFFFF"
+                     Opacity="{DynamicResource ThemeDisabledOpacity}"
+                     Margin="0,2,0,2" />
+
+        </Panel>
+      </ControlTemplate>
+    </Setter>
+    <Setter Property="DayTitleTemplate">
+      <Template>
+        <TextBlock FontWeight="Bold"
+                   FontSize="9.5"
+                   Margin="0,4,0,4"
+                   HorizontalAlignment="Center"
+                   VerticalAlignment="Center"
+                   Text="{Binding}" />
+      </Template>
+
+    </Setter>
+  </Style>
+
+  <Style Selector="CalendarItem /template/ Rectangle#DisabledVisual">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
+  <Style Selector="CalendarItem:calendardisabled /template/ Rectangle#DisabledVisual">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
+  
+</Styles>

+ 23 - 8
src/Avalonia.Themes.Default/CheckBox.xaml

@@ -12,14 +12,23 @@
                   Width="18"
                   Height="18"
                   VerticalAlignment="Center">
-            <Path Name="checkMark"
-                  Fill="{DynamicResource HighlightBrush}"
-                  Width="11"
-                  Height="10"
-                  Stretch="Uniform"
-                  HorizontalAlignment="Center"
-                  VerticalAlignment="Center"
-                  Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/>
+            <Panel>
+              <Path Name="checkMark"
+                    Fill="{DynamicResource HighlightBrush}"
+                    Width="11"
+                    Height="10"
+                    Stretch="Uniform"
+                    HorizontalAlignment="Center"
+                    VerticalAlignment="Center"
+                    Data="M 1145.607177734375,430 C1145.607177734375,430 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1141.449951171875,435.0772705078125 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1139.232177734375,433.0999755859375 1138,434.5538330078125 1138,434.5538330078125 1138,434.5538330078125 1141.482177734375,438 1141.482177734375,438 1141.482177734375,438 1141.96875,437.9375 1141.96875,437.9375 1141.96875,437.9375 1147,431.34619140625 1147,431.34619140625 1147,431.34619140625 1145.607177734375,430 1145.607177734375,430 z"/>
+              <Rectangle Name="indeterminateMark"
+                         Fill="{DynamicResource HighlightBrush}"
+                         Width="10"
+                         Height="10"
+                         Stretch="Uniform"
+                         HorizontalAlignment="Center"
+                         VerticalAlignment="Center"/>
+            </Panel>
           </Border>
           <ContentPresenter Name="PART_ContentPresenter"
                             Content="{TemplateBinding Content}"
@@ -37,9 +46,15 @@
   <Style Selector="CheckBox /template/ Path#checkMark">
     <Setter Property="IsVisible" Value="False"/>
   </Style>
+  <Style Selector="CheckBox /template/ Rectangle#indeterminateMark">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
   <Style Selector="CheckBox:checked /template/ Path#checkMark">
     <Setter Property="IsVisible" Value="True"/>
   </Style>
+  <Style Selector="CheckBox:indeterminate /template/ Rectangle#indeterminateMark">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
   <Style Selector="CheckBox:disabled /template/ Border#border">
     <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
   </Style>

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

@@ -36,4 +36,8 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.TreeViewItem.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Window.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.EmbeddableControlRoot.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.CalendarButton.xaml?assembly=Avalonia.Themes.Default"/>
+  <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"/>
 </Styles>

+ 14 - 0
src/Avalonia.Themes.Default/RadioButton.xaml

@@ -20,6 +20,14 @@
                    UseLayoutRounding="False"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"/>
+          <Ellipse Name="indeterminateMark"
+                   Fill="{DynamicResource ThemeAccentBrush}"
+                   Width="10"
+                   Height="10"
+                   Stretch="Uniform"
+                   UseLayoutRounding="False"
+                   HorizontalAlignment="Center"
+                   VerticalAlignment="Center"/>
           <ContentPresenter Name="PART_ContentPresenter"
                             Content="{TemplateBinding Content}"
                             ContentTemplate="{TemplateBinding ContentTemplate}"
@@ -36,9 +44,15 @@
   <Style Selector="RadioButton /template/ Ellipse#checkMark">
     <Setter Property="IsVisible" Value="False"/>
   </Style>
+  <Style Selector="RadioButton /template/ Ellipse#indeterminateMark">
+    <Setter Property="IsVisible" Value="False"/>
+  </Style>
   <Style Selector="RadioButton:checked /template/ Ellipse#checkMark">
     <Setter Property="IsVisible" Value="True"/>
   </Style>
+  <Style Selector="RadioButton:indeterminate /template/ Ellipse#indeterminateMark">
+    <Setter Property="IsVisible" Value="True"/>
+  </Style>
   <Style Selector="RadioButton:disabled /template/ Ellipse#border">
     <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}"/>
   </Style>

+ 21 - 4
src/Avalonia.Visuals/Animation/PageSlide.cs

@@ -15,6 +15,15 @@ namespace Avalonia.Animation
     /// </summary>
     public class PageSlide : IPageTransition
     {
+        /// <summary>
+        /// The axis on which the PageSlide should occur
+        /// </summary>
+        public enum SlideAxis
+        {
+            Horizontal,
+            Vertical
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="PageSlide"/> class.
         /// </summary>
@@ -26,9 +35,11 @@ namespace Avalonia.Animation
         /// Initializes a new instance of the <see cref="PageSlide"/> class.
         /// </summary>
         /// <param name="duration">The duration of the animation.</param>
-        public PageSlide(TimeSpan duration)
+        /// <param name="orientation">The axis on which the animation should occur</param>
+        public PageSlide(TimeSpan duration, SlideAxis orientation = SlideAxis.Horizontal)
         {
             Duration = duration;
+            Orientation = orientation;
         }
 
         /// <summary>
@@ -36,6 +47,11 @@ namespace Avalonia.Animation
         /// </summary>
         public TimeSpan Duration { get; set; }
 
+        /// <summary>
+        /// Gets the duration of the animation.
+        /// </summary>
+        public SlideAxis Orientation { get; set; }
+
         /// <summary>
         /// Starts the animation.
         /// </summary>
@@ -55,7 +71,8 @@ namespace Avalonia.Animation
         {
             var tasks = new List<Task>();
             var parent = GetVisualParent(from, to);
-            var distance = parent.Bounds.Width;
+            var distance = Orientation == SlideAxis.Horizontal ? parent.Bounds.Width : parent.Bounds.Height;
+            var translateProperty = Orientation == SlideAxis.Horizontal ? TranslateTransform.XProperty : TranslateTransform.YProperty;
 
             if (from != null)
             {
@@ -63,7 +80,7 @@ namespace Avalonia.Animation
                 from.RenderTransform = transform;
                 tasks.Add(Animate.Property(
                     transform,
-                    TranslateTransform.XProperty,
+                    translateProperty,
                     0.0,
                     forward ? -distance : distance,
                     LinearEasing.For<double>(),
@@ -77,7 +94,7 @@ namespace Avalonia.Animation
                 to.IsVisible = true;
                 tasks.Add(Animate.Property(
                     transform,
-                    TranslateTransform.XProperty,
+                    translateProperty,
                     forward ? distance : -distance,
                     0.0,
                     LinearEasing.For<double>(),

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

@@ -320,7 +320,7 @@ namespace Avalonia.Media
                     if (c == 'E')
                     {
                         readSign = false;
-                        readExponent = c == 'E';
+                        readExponent = true;
                     }
                 }
                 else

+ 1 - 1
src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs

@@ -192,7 +192,7 @@ namespace Avalonia.Rendering.SceneGraph
                             UpdateLayer(node, scene.Layers[node.LayerRoot]);
                         }
                     }
-                    else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
+                    else if (node.LayerRoot == node.Visual && node.Parent != null)
                     {
                         ClearLayer(scene, node);
                     }

+ 13 - 0
src/Avalonia.Visuals/Visual.cs

@@ -537,6 +537,19 @@ namespace Avalonia
                         v.SetVisualParent(null);
                     }
 
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    foreach (Visual v in e.OldItems)
+                    {
+                        v.SetVisualParent(null);
+                    }
+
+                    foreach (Visual v in e.NewItems)
+                    {
+                        v.SetVisualParent(this);
+                    }
+
                     break;
             }
         }

+ 0 - 3
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -39,7 +39,6 @@
         <Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
         <Compile Include="MarkupExtensions\StyleIncludeExtension.cs" />
         <Compile Include="PortableXaml\AvaloniaXamlContext.cs" />
-        <Compile Include="PortableXaml\XamlBinding.cs" />
         <Compile Include="PortableXaml\AttributeExtensions.cs" />
         <Compile Include="PortableXaml\AvaloniaMemberAttributeProvider.cs" />
         <Compile Include="PortableXaml\AvaloniaNameScope.cs" />
@@ -73,8 +72,6 @@
         <Compile Include="Data\DelayedBinding.cs" />
         <Compile Include="Data\MultiBinding.cs" />
         <Compile Include="Data\RelativeSource.cs" />
-        <Compile Include="Data\StyleResourceBinding.cs" />
-        <Compile Include="MarkupExtensions\StyleResourceExtension.cs" />
         <Compile Include="MarkupExtensions\BindingExtension.cs" />
         <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
         <Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />

+ 39 - 72
src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs

@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// <summary>
         /// Gets or sets the binding path.
         /// </summary>
-        public string Path { get; set; }
+        public string Path { get; set; } = "";
 
         /// <summary>
         /// Gets or sets the binding priority.
@@ -83,6 +83,8 @@ namespace Avalonia.Markup.Xaml.Data
         /// </summary>
         public object Source { get; set; }
 
+        internal WeakReference DefaultAnchor { get; set; }
+
         /// <inheritdoc/>
         public InstancedBinding Initiate(
             IAvaloniaObject target,
@@ -91,51 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
             bool enableDataValidation = false)
         {
             Contract.Requires<ArgumentNullException>(target != null);
-
-            var pathInfo = ParsePath(Path);
-            ValidateState(pathInfo);
+            anchor = anchor ?? DefaultAnchor?.Target;
+            
             enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-
+            
             ExpressionObserver observer;
 
-            if (pathInfo.ElementName != null || ElementName != null)
+            if (ElementName != null)
             {
                 observer = CreateElementObserver(
                     (target as IControl) ?? (anchor as IControl),
-                    pathInfo.ElementName ?? ElementName,
-                    pathInfo.Path);
+                    ElementName,
+                    Path,
+                    enableDataValidation);
             }
             else if (Source != null)
             {
-                observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
+                observer = CreateSourceObserver(Source, Path, enableDataValidation);
             }
             else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
             {
                 observer = CreateDataContexObserver(
                     target,
-                    pathInfo.Path,
+                    Path,
                     targetProperty == Control.DataContextProperty,
                     anchor,
                     enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.Self)
             {
-                observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+                observer = CreateSourceObserver(target, Path, enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
             {
-                observer = CreateTemplatedParentObserver(target, pathInfo.Path);
+                observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
             }
             else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
             {
-                if (RelativeSource.AncestorType == null)
+                if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
                 {
-                    throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
+                    throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
                 }
 
                 observer = CreateFindAncestorObserver(
                     (target as IControl) ?? (anchor as IControl),
-                    pathInfo.Path);
+                    RelativeSource,
+                    Path,
+                    enableDataValidation);
             }
             else
             {
@@ -164,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
             return new InstancedBinding(subject, Mode, Priority);
         }
 
-        private static PathInfo ParsePath(string path)
-        {
-            var result = new PathInfo();
-
-            if (string.IsNullOrWhiteSpace(path) || path == ".")
-            {
-                result.Path = string.Empty;
-            }
-            else if (path.StartsWith("#"))
-            {
-                var dot = path.IndexOf('.');
-
-                if (dot != -1)
-                {
-                    result.Path = path.Substring(dot + 1);
-                    result.ElementName = path.Substring(1, dot - 1);
-                }
-                else
-                {
-                    result.Path = string.Empty;
-                    result.ElementName = path.Substring(1);
-                }
-            }
-            else
-            {
-                result.Path = path;
-            }
-
-            return result;
-        }
-
-        private void ValidateState(PathInfo pathInfo)
-        {
-            if (pathInfo.ElementName != null && ElementName != null)
-            {
-                throw new InvalidOperationException(
-                    "ElementName property cannot be set when an #elementName path is provided.");
-            }
-
-            if ((pathInfo.ElementName != null || ElementName != null) &&
-                RelativeSource != null)
-            {
-                throw new InvalidOperationException(
-                    "ElementName property cannot be set with a RelativeSource.");
-            }
-        }
-
         private ExpressionObserver CreateDataContexObserver(
             IAvaloniaObject target,
             string path,
@@ -252,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
             }
         }
 
-        private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
+        private ExpressionObserver CreateElementObserver(
+            IControl target,
+            string elementName,
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
@@ -260,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
             var result = new ExpressionObserver(
                 ControlLocator.Track(target, elementName),
                 path,
-                false,
+                enableDataValidation,
                 description);
             return result;
         }
 
         private ExpressionObserver CreateFindAncestorObserver(
             IControl target,
-            string path)
+            RelativeSource relativeSource,
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
             return new ExpressionObserver(
-                ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
-                path);
+                ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
+                path,
+                enableDataValidation);
         }
 
         private ExpressionObserver CreateSourceObserver(
             object source,
             string path,
-            bool enabledDataValidation)
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(source != null);
 
-            return new ExpressionObserver(source, path, enabledDataValidation);
+            return new ExpressionObserver(source, path, enableDataValidation);
         }
 
         private ExpressionObserver CreateTemplatedParentObserver(
             IAvaloniaObject target,
-            string path)
+            string path,
+            bool enableDataValidation)
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
@@ -299,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
             var result = new ExpressionObserver(
                 () => target.GetValue(Control.TemplatedParentProperty),
                 path,
-                update);
+                update,
+                enableDataValidation);
 
             return result;
         }
@@ -324,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
         {
             public string Path { get; set; }
             public string ElementName { get; set; }
+            public RelativeSource RelativeSource { get; set; }
         }
     }
-}
+}

+ 7 - 28
src/Markup/Avalonia.Markup.Xaml/Data/MultiBinding.cs

@@ -62,41 +62,20 @@ namespace Avalonia.Markup.Xaml.Data
             }
 
             var targetType = targetProperty?.PropertyType ?? typeof(object);
-            var result = new BehaviorSubject<object>(AvaloniaProperty.UnsetValue);
             var children = Bindings.Select(x => x.Initiate(target, null));
             var input = children.Select(x => x.Subject).CombineLatest().Select(x => ConvertValue(x, targetType));
-            input.Subscribe(result);
-            return new InstancedBinding(result, Mode, Priority);
-        }
-
-        /// <summary>
-        /// Applies a binding subject to a property on an instance.
-        /// </summary>
-        /// <param name="target">The target instance.</param>
-        /// <param name="property">The target property.</param>
-        /// <param name="subject">The binding subject.</param>
-        internal void Bind(IAvaloniaObject target, AvaloniaProperty property, ISubject<object> subject)
-        {
             var mode = Mode == BindingMode.Default ?
-                property.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
+                targetProperty.GetMetadata(target.GetType()).DefaultBindingMode : Mode;
 
             switch (mode)
             {
-                case BindingMode.Default:
-                case BindingMode.OneWay:
-                    target.Bind(property, subject, Priority);
-                    break;
-                case BindingMode.TwoWay:
-                    throw new NotSupportedException("TwoWay MultiBinding not currently supported.");
                 case BindingMode.OneTime:
-                    target.GetObservable(Control.DataContextProperty).Subscribe(dataContext =>
-                    {
-                        subject.Take(1).Subscribe(x => target.SetValue(property, x, Priority));
-                    });                    
-                    break;
-                case BindingMode.OneWayToSource:
-                    target.GetObservable(property).Subscribe(subject);
-                    break;
+                    return InstancedBinding.OneTime(input, Priority);
+                case BindingMode.OneWay:
+                    return InstancedBinding.OneWay(input, Priority);
+                default:
+                    throw new NotSupportedException(
+                        "MultiBinding currently only supports OneTime and OneWay BindingMode.");
             }
         }
 

+ 2 - 0
src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs

@@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
         /// Gets or sets a value that describes the type of relative source lookup.
         /// </summary>
         public RelativeSourceMode Mode { get; set; }
+
+        public TreeType Tree { get; set; } = TreeType.Visual;
     }
 }

+ 0 - 69
src/Markup/Avalonia.Markup.Xaml/Data/StyleResourceBinding.cs

@@ -1,69 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Disposables;
-using System.Reactive.Linq;
-using System.Reactive.Subjects;
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Styling;
-
-namespace Avalonia.Markup.Xaml.Data
-{
-    public class StyleResourceBinding : IBinding
-    {
-        /// <summary>
-        /// Initializes a new instance of the <see cref="StyleResourceBinding"/> class.
-        /// </summary>
-        /// <param name="name">The resource name.</param>
-        public StyleResourceBinding(string name)
-        {
-            Name = name;
-        }
-
-        /// <inheritdoc/>
-        public BindingMode Mode => BindingMode.OneTime;
-
-        /// <summary>
-        /// Gets the resource name.
-        /// </summary>
-        public string Name { get; }
-
-        /// <inheritdoc/>
-        public BindingPriority Priority => BindingPriority.LocalValue;
-
-        /// <inheritdoc/>
-        public InstancedBinding Initiate(
-            IAvaloniaObject target,
-            AvaloniaProperty targetProperty,
-            object anchor = null,
-            bool enableDataValidation = false)
-        {
-            var host = (target as IControl) ?? (anchor as IControl);
-            var style = anchor as IStyle;
-            var resource = AvaloniaProperty.UnsetValue;
-
-            if (host != null)
-            {
-                resource = host.FindResource(Name);
-            }
-            else if (style != null)
-            {
-                if (!style.TryGetResource(Name, out resource))
-                {
-                    resource = AvaloniaProperty.UnsetValue;
-                }
-            }
-
-            if (resource != AvaloniaProperty.UnsetValue)
-            {
-                return new InstancedBinding(resource, Priority);
-            }
-            else
-            {
-                return null;
-            }
-        }
-    }
-}

+ 173 - 5
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@@ -7,8 +7,13 @@ using System;
 
 namespace Avalonia.Markup.Xaml.MarkupExtensions
 {
+    using Avalonia.Controls;
+    using Avalonia.Styling;
+    using Portable.Xaml;
+    using Portable.Xaml.ComponentModel;
     using Portable.Xaml.Markup;
     using PortableXaml;
+    using System.ComponentModel;
 
     [MarkupExtensionReturnType(typeof(IBinding))]
     public class BindingExtension : MarkupExtension
@@ -24,19 +29,182 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
         public override object ProvideValue(IServiceProvider serviceProvider)
         {
-            var b = new Binding
+            var descriptorContext = (ITypeDescriptorContext)serviceProvider;
+
+            var pathInfo = ParsePath(Path, descriptorContext);
+            ValidateState(pathInfo);
+
+            return new Binding
             {
                 Converter = Converter,
                 ConverterParameter = ConverterParameter,
-                ElementName = ElementName,
+                ElementName = pathInfo.ElementName ?? ElementName,
                 FallbackValue = FallbackValue,
                 Mode = Mode,
-                Path = Path,
+                Path = pathInfo.Path,
                 Priority = Priority,
-                RelativeSource = RelativeSource
+                RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
+                DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
             };
+        }
+
+        private class PathInfo
+        {
+            public string Path { get; set; }
+            public string ElementName { get; set; }
+            public RelativeSource RelativeSource { get; set; }
+        }
+
+        private void ValidateState(PathInfo pathInfo)
+        {
+            if (pathInfo.ElementName != null && ElementName != null)
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set when an #elementName path is provided.");
+            }
+
+            if (pathInfo.RelativeSource != null && RelativeSource != null)
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set when a $self or $parent path is provided.");
+            }
+
+            if ((pathInfo.ElementName != null || ElementName != null) &&
+                (pathInfo.RelativeSource != null || RelativeSource != null))
+            {
+                throw new InvalidOperationException(
+                    "ElementName property cannot be set with a RelativeSource.");
+            }
+        }
+
+        private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
+        {
+            var result = new PathInfo();
+
+            if (string.IsNullOrWhiteSpace(path) || path == ".")
+            {
+                result.Path = string.Empty;
+            }
+            else if (path.StartsWith("#"))
+            {
+                var dot = path.IndexOf('.');
+
+                if (dot != -1)
+                {
+                    result.Path = path.Substring(dot + 1);
+                    result.ElementName = path.Substring(1, dot - 1);
+                }
+                else
+                {
+                    result.Path = string.Empty;
+                    result.ElementName = path.Substring(1);
+                }
+            }
+            else if (path.StartsWith("$"))
+            {
+                var relativeSource = new RelativeSource
+                {
+                    Tree = TreeType.Logical
+                };
+                result.RelativeSource = relativeSource;
+                var dot = path.IndexOf('.');
+                string relativeSourceMode;
+                if (dot != -1)
+                {
+                    result.Path = path.Substring(dot + 1);
+                    relativeSourceMode = path.Substring(1, dot - 1);
+                }
+                else
+                {
+                    result.Path = string.Empty;
+                    relativeSourceMode = path.Substring(1);
+                }
+
+                if (relativeSourceMode == "self")
+                {
+                    relativeSource.Mode = RelativeSourceMode.Self;
+                }
+                else if (relativeSourceMode == "parent")
+                {
+                    relativeSource.Mode = RelativeSourceMode.FindAncestor;
+                    relativeSource.AncestorLevel = 1;
+                }
+                else if (relativeSourceMode.StartsWith("parent["))
+                {
+                    relativeSource.Mode = RelativeSourceMode.FindAncestor;
+                    var parentConfigStart = relativeSourceMode.IndexOf('[');
+                    if (!relativeSourceMode.EndsWith("]"))
+                    {
+                        throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
+                    }
+                    var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
+                    if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
+                    {
+                        throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
+                    }
+                    else if (parentConfigParams.Length == 1)
+                    {
+                        if (int.TryParse(parentConfigParams[0], out int level))
+                        {
+                            relativeSource.AncestorType = null;
+                            relativeSource.AncestorLevel = level + 1;
+                        }
+                        else
+                        {
+                            relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+                        }
+                    }
+                    else
+                    {
+                        relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+                        relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
+                    }
+                }
+                else
+                {
+                    throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
+                }
+            }
+            else
+            {
+                result.Path = path;
+            }
+
+            return result;
+        }
+
+        private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
+        {
+            var parts = ancestorTypeName.Split(':');
+            if (parts.Length == 0 || parts.Length > 2)
+            {
+                throw new InvalidOperationException("Invalid type name");
+            }
+
+            if (parts.Length == 1)
+            {
+                return context.ResolveType(string.Empty, parts[0]);
+            }
+            else
+            {
+                return context.ResolveType(parts[0], parts[1]);
+            }
+        }
+
+        private static object GetDefaultAnchor(ITypeDescriptorContext context)
+        {
+            object anchor = null;
+
+            // The target is not a control, so we need to find an anchor that will let us look
+            // up named controls and style resources. First look for the closest IControl in
+            // the context.
+            anchor = context.GetFirstAmbientValue<IControl>();
 
-            return XamlBinding.FromMarkupExtensionContext(b, serviceProvider);
+            // If a control was not found, then try to find the highest-level style as the XAML
+            // file could be a XAML file containing only styles.
+            return anchor ??
+                    context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
+                    context.GetLastOrDefaultAmbientValue<IStyle>();
         }
 
         public IValueConverter Converter { get; set; }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/DynamicResourceExtension.cs

@@ -51,7 +51,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
 
             if (control != null)
             {
-                return new InstancedBinding(control.GetResourceObservable(ResourceKey));
+                return InstancedBinding.OneWay(control.GetResourceObservable(ResourceKey));
             }
 
             return null;

+ 0 - 31
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StyleResourceExtension.cs

@@ -1,31 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using Avalonia.Data;
-using Avalonia.Markup.Xaml.Data;
-using System;
-
-namespace Avalonia.Markup.Xaml.MarkupExtensions
-{
-    using Portable.Xaml.Markup;
-    using PortableXaml;
-
-    [MarkupExtensionReturnType(typeof(IBinding))]
-    public class StyleResourceExtension : MarkupExtension
-    {
-        public StyleResourceExtension(string name)
-        {
-            Name = name;
-        }
-
-        public override object ProvideValue(IServiceProvider serviceProvider)
-        {
-            return XamlBinding.FromMarkupExtensionContext(
-                            new StyleResourceBinding(Name),
-                            serviceProvider);
-        }
-
-        [ConstructorArgument("name")]
-        public string Name { get; set; }
-    }
-}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs

@@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
                 ElementName = ElementName,
                 Mode = Mode,
                 RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
-                Path = Path,
+                Path = Path ?? string.Empty,
                 Priority = Priority,
             };
         }

+ 1 - 11
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlType.cs

@@ -212,11 +212,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                                             value);
                 }
 
-                if (value is XamlBinding)
-                {
-                    value = (value as XamlBinding).Value;
-                }
-
                 if (UpdateListInsteadSet &&
                     value != null &&
                     UpdateListInsteadSetValue(instance, value))
@@ -317,9 +312,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
                         if (!Member.AssignBinding)
                             ApplyBinding(obj, (IBinding)value);
                         else
-                            obj.SetValue(Property, value is XamlBinding ?
-                                                        (value as XamlBinding).Value :
-                                                        value);
+                            obj.SetValue(Property, value);
                     }
                     else
                     {
@@ -348,12 +341,9 @@ namespace Avalonia.Markup.Xaml.PortableXaml
             {
                 var control = obj as IControl;
                 var property = Property;
-                var xamlBinding = binding as XamlBinding;
 
                 if (control != null && property != Control.DataContextProperty)
                     DelayedBinding.Add(control, property, binding);
-                else if (xamlBinding != null)
-                    obj.Bind(property, xamlBinding.Value, xamlBinding.Anchor?.Target);
                 else
                     obj.Bind(property, binding);
             }

+ 0 - 63
src/Markup/Avalonia.Markup.Xaml/PortableXaml/XamlBinding.cs

@@ -1,63 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Data;
-using Avalonia.Styling;
-using Portable.Xaml;
-using Portable.Xaml.ComponentModel;
-using System.ComponentModel;
-using Portable.Xaml.Markup;
-using System;
-
-namespace Avalonia.Markup.Xaml.PortableXaml
-{
-    internal class XamlBinding : IBinding
-    {
-        public static IBinding FromMarkupExtensionContext(
-                                    IBinding binding,
-                                    IServiceProvider serviceProvider)
-        {
-            var context = (ITypeDescriptorContext)serviceProvider;
-            var pvt = context.GetService<IProvideValueTarget>();
-
-            if (pvt.TargetObject is IControl) return binding;
-
-            object anchor = GetDefaultAnchor(context);
-
-            if (anchor == null) return binding;
-
-            return new XamlBinding(binding, anchor);
-        }
-
-        private static object GetDefaultAnchor(ITypeDescriptorContext context)
-        {
-            object anchor = null;
-
-            // The target is not a control, so we need to find an anchor that will let us look
-            // up named controls and style resources. First look for the closest IControl in
-            // the context.
-            anchor = context.GetFirstAmbientValue<IControl>();
-
-            // If a control was not found, then try to find the highest-level style as the XAML
-            // file could be a XAML file containing only styles.
-            return anchor ??
-                    context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
-                    context.GetLastOrDefaultAmbientValue<IStyle>();
-        }
-
-        private XamlBinding(IBinding binding, object anchor)
-        {
-            Value = binding;
-
-            Anchor = new WeakReference(anchor);
-        }
-
-        public WeakReference Anchor { get; }
-
-        public IBinding Value { get; }
-
-        public InstancedBinding Initiate(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor = null, bool enableDataValidation = false)
-        {
-            return Value.Initiate(target, targetProperty,
-                            anchor ?? Anchor.Target, enableDataValidation);
-        }
-    }
-}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/PortableXaml/portable.xaml.github

@@ -1 +1 @@
-Subproject commit f226a516fe2bc191145c1fbf732f5a087f121a33
+Subproject commit d50ae8335eb50d4b9606de6f5fa1cbbc78bfd72f

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

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

+ 72 - 19
src/Markup/Avalonia.Markup/ControlLocator.cs

@@ -11,6 +11,21 @@ using Avalonia.VisualTree;
 
 namespace Avalonia.Markup
 {
+    /// <summary>
+    /// The type of tree via which to track a control.
+    /// </summary>
+    public enum TreeType
+    {
+        /// <summary>
+        /// The visual tree.
+        /// </summary>
+        Visual,
+        /// <summary>
+        /// The logical tree.
+        /// </summary>
+        Logical,
+    }
+
     /// <summary>
     /// Locates controls relative to other controls.
     /// </summary>
@@ -27,13 +42,13 @@ namespace Avalonia.Markup
         {
             var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
                 x => relativeTo.AttachedToLogicalTree += x,
-                x => relativeTo.DetachedFromLogicalTree += x)
+                x => relativeTo.AttachedToLogicalTree -= x)
                 .Select(x => ((IControl)x.Sender).FindNameScope())
                 .StartWith(relativeTo.FindNameScope());
 
             var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
                 x => relativeTo.DetachedFromLogicalTree += x,
-                x => relativeTo.DetachedFromLogicalTree += x)
+                x => relativeTo.DetachedFromLogicalTree -= x)
                 .Select(x => (INameScope)null);
 
             return attached.Merge(detached).Select(nameScope =>
@@ -68,37 +83,75 @@ namespace Avalonia.Markup
         /// <param name="relativeTo">
         /// The control relative from which the other control should be found.
         /// </param>
-        /// <param name="ancestorType">The type of the ancestor to find.</param>
+        /// <param name="tree">The tree via which to track the control.</param>
         /// <param name="ancestorLevel">
         /// The level of ancestor control to look for. Use 0 for the first ancestor of the
         /// requested type.
         /// </param>
-        public static IObservable<IControl> Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
+        /// <param name="ancestorType">The type of the ancestor to find.</param>
+        public static IObservable<IControl> Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
+        {
+            return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
+            {
+                if (isAttachedToTree)
+                {
+                    if (tree == TreeType.Visual)
+                    {
+                        return relativeTo.GetVisualAncestors()
+                            .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+                            .ElementAtOrDefault(ancestorLevel) as IControl; 
+                    }
+                    else
+                    {
+                        return relativeTo.GetLogicalAncestors()
+                            .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+                            .ElementAtOrDefault(ancestorLevel) as IControl;
+                    }
+                }
+                else
+                {
+                    return null;
+                }
+            });
+        }
+
+        private static IObservable<bool> TrackAttachmentToTree(IControl relativeTo, TreeType tree)
+        {
+            return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
+        }
+
+        private static IObservable<bool> TrackAttachmentToVisualTree(IControl relativeTo)
         {
             var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
                 x => relativeTo.AttachedToVisualTree += x,
-                x => relativeTo.DetachedFromVisualTree += x)
+                x => relativeTo.AttachedToVisualTree -= x)
                 .Select(x => true)
                 .StartWith(relativeTo.IsAttachedToVisualTree);
 
             var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
                 x => relativeTo.DetachedFromVisualTree += x,
-                x => relativeTo.DetachedFromVisualTree += x)
+                x => relativeTo.DetachedFromVisualTree -= x)
                 .Select(x => false);
 
-            return attached.Merge(detached).Select(isAttachedToVisualTree =>
-            {
-                if (isAttachedToVisualTree)
-                {
-                    return relativeTo.GetVisualAncestors()
-                        .Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
-                        .ElementAtOrDefault(ancestorLevel) as IControl;
-                }
-                else
-                {
-                    return null;
-                }
-            });
+            var attachmentStatus = attached.Merge(detached);
+            return attachmentStatus;
+        }
+
+        private static IObservable<bool> TrackAttachmentToLogicalTree(IControl relativeTo)
+        {
+            var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
+                x => relativeTo.AttachedToLogicalTree += x,
+                x => relativeTo.AttachedToLogicalTree -= x)
+                .Select(x => true)
+                .StartWith(relativeTo.IsAttachedToLogicalTree);
+
+            var detached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(
+                x => relativeTo.DetachedFromLogicalTree += x,
+                x => relativeTo.DetachedFromLogicalTree -= x)
+                .Select(x => false);
+
+            var attachmentStatus = attached.Merge(detached);
+            return attachmentStatus;
         }
     }
 }

+ 1 - 9
src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs

@@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
                     }
                 }
 
-                if (!r.End)
-                {
-                    r.Take();
-                    return result;
-                }
-                else
-                {
-                    throw new ExpressionParseException(r.Position, "Expected ']'.");
-                }
+                throw new ExpressionParseException(r.Position, "Expected ']'.");
             }
 
             return null;

+ 71 - 17
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@@ -2,44 +2,80 @@
 using Avalonia.Platform;
 using MonoMac.AppKit;
 using System.Runtime.InteropServices;
+using Avalonia.Threading;
 using MonoMac.CoreGraphics;
 
 namespace Avalonia.MonoMac
 {
     class EmulatedFramebuffer : ILockedFramebuffer
     {
+        private readonly TopLevelImpl.TopLevelView _view;
         private readonly CGSize _logicalSize;
-        public EmulatedFramebuffer(NSView view)
+        private readonly bool _isDeferred;
+
+        [DllImport("libc")]
+        static extern void memset(IntPtr p, int c, IntPtr size);
+
+        public EmulatedFramebuffer(TopLevelImpl.TopLevelView view)
         {
-            _logicalSize = view.Frame.Size;
-            var pixelSize = view.ConvertSizeToBacking(_logicalSize);
+            _view = view;
+
+            _isDeferred = !Dispatcher.UIThread.CheckAccess();
+            _logicalSize = _view.LogicalSize;
+            var pixelSize = _view.PixelSize;
             Width = (int)pixelSize.Width;
             Height = (int)pixelSize.Height;
             RowBytes = Width * 4;
             Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
             Format = PixelFormat.Rgba8888;
-            Address = Marshal.AllocHGlobal(Height * RowBytes);
+            var size = Height * RowBytes;
+            Address = Marshal.AllocHGlobal(size);
+            memset(Address, 0, new IntPtr(size));
         }
-
+        
         public void Dispose()
         {
             if (Address == IntPtr.Zero)
                 return;
             var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
-
-            using (var colorSpace = CGColorSpace.CreateDeviceRGB())
-            using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
-                colorSpace, (CGImageAlphaInfo) nfo))
-            using (var image = bContext.ToImage())
-            using (var nscontext = NSGraphicsContext.CurrentContext)
-            using (var context = nscontext.GraphicsPort)
+            CGImage image = null;
+            try
             {
-                context.SetFillColor(255, 255, 255, 255);
-                context.FillRect(new CGRect(default(CGPoint), _logicalSize));
-                context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
+                using (var colorSpace = CGColorSpace.CreateDeviceRGB())
+                using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
+                    colorSpace, (CGImageAlphaInfo)nfo))
+                    image = bContext.ToImage();
+                lock (_view.SyncRoot)
+                {
+                    if(!_isDeferred)
+                    {
+                        using (var nscontext = NSGraphicsContext.CurrentContext)
+                        using (var context = nscontext.GraphicsPort)
+                        {
+                            context.SetFillColor(255, 255, 255, 255);
+                            context.FillRect(new CGRect(default(CGPoint), _view.LogicalSize));
+                            context.TranslateCTM(0, _view.LogicalSize.Height - _logicalSize.Height);
+                            context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
+                            context.Flush();
+                            nscontext.FlushGraphics();
+                        }
+                    }
+                }
             }
-            Marshal.FreeHGlobal(Address);
-            Address = IntPtr.Zero;
+            finally
+            {
+                if (image != null)
+                {
+                    if (!_isDeferred)
+                        image.Dispose();
+                    else
+                        _view.SetBackBufferImage(new SavedImage(image, _logicalSize));
+                }
+                Marshal.FreeHGlobal(Address);
+                Address = IntPtr.Zero;
+            }
+
+
         }
 
         public IntPtr Address { get; private set; }
@@ -49,4 +85,22 @@ namespace Avalonia.MonoMac
         public Vector Dpi { get; }
         public PixelFormat Format { get; }
     }
+
+    class SavedImage : IDisposable
+    {
+        public CGImage Image { get; private set; }
+        public CGSize LogicalSize { get; }
+
+        public SavedImage(CGImage image, CGSize logicalSize)
+        {
+            Image = image;
+            LogicalSize = logicalSize;
+        }
+
+        public void Dispose()
+        {
+            Image?.Dispose();
+            Image = null;
+        }
+    }
 }

+ 42 - 4
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@@ -1,10 +1,13 @@
 using System;
+using System.Threading;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform;
 using Avalonia.Input;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using MonoMac.AppKit;
+using MonoMac.Foundation;
+using MonoMac.ObjCRuntime;
 
 namespace Avalonia.MonoMac
 {
@@ -16,9 +19,11 @@ namespace Avalonia.MonoMac
         internal static NSApplication App;
         private static bool s_monoMacInitialized;
         private static bool s_showInDock = true;
+        private static IRenderLoop s_renderLoop;
 
         void DoInitialize()
         {
+            InitializeMonoMac();
             AvaloniaLocator.CurrentMutable
                 .Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
                 .Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
@@ -27,9 +32,8 @@ namespace Avalonia.MonoMac
                 .Bind<IPlatformSettings>().ToConstant(this)
                 .Bind<IWindowingPlatform>().ToConstant(this)
                 .Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
+                .Bind<IRenderLoop>().ToConstant(s_renderLoop)
                 .Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
-
-            InitializeMonoMac();
         }
 
         public static void Initialize()
@@ -39,13 +43,44 @@ namespace Avalonia.MonoMac
 
         }
 
+
+        /// <summary>
+        /// See "Using POSIX Threads in a Cocoa Application" section here:
+        /// https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html#//apple_ref/doc/uid/20000738-125024
+        /// </summary>
+        class ThreadHelper : NSObject
+        {
+            private readonly AutoResetEvent _event = new AutoResetEvent(false);
+            private const string InitThreadingName = "initThreading";
+            [Export(InitThreadingName)]
+            public void DoNothing()
+            {
+                _event.Set();
+            }
+
+            public static void InitializeCocoaThreadingLocks()
+            {
+                var helper = new ThreadHelper();
+                var thread = new NSThread(helper, Selector.FromHandle(Selector.GetHandle(InitThreadingName)), new NSObject());
+                thread.Start();
+                helper._event.WaitOne();
+                helper._event.Dispose();
+                if (!NSThread.IsMultiThreaded)
+                {
+                    throw new Exception("Unable to initialize Cocoa threading");
+                }
+            }
+        }
+
         void InitializeMonoMac()
         {
             if(s_monoMacInitialized)
                 return;
             NSApplication.Init();
+            ThreadHelper.InitializeCocoaThreadingLocks();
             App = NSApplication.SharedApplication;
             UpdateActivationPolicy();
+            s_renderLoop = new RenderLoop(); //TODO: use CVDisplayLink
             s_monoMacInitialized = true;
         }
 
@@ -64,6 +99,7 @@ namespace Avalonia.MonoMac
             }
         }
 
+        public static bool UseDeferredRendering { get; set; } = true;
 
         public Size DoubleClickSize => new Size(4, 4);
         public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval);
@@ -87,9 +123,11 @@ namespace Avalonia
 {
     public static class MonoMacPlatformExtensions
     {
-        public static T UseMonoMac<T>(this T builder) where T : AppBuilderBase<T>, new()
+        public static T UseMonoMac<T>(this T builder, bool? useDeferredRendering = null) where T : AppBuilderBase<T>, new()
         {
-            return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
+            if (useDeferredRendering.HasValue)
+                MonoMac.MonoMacPlatform.UseDeferredRendering = useDeferredRendering.Value;
+            return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize, "MonoMac");
         }
     }
 }

+ 22 - 10
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@@ -3,14 +3,18 @@ using System.Threading;
 using Avalonia.Platform;
 using Avalonia.Threading;
 using MonoMac.AppKit;
+using MonoMac.CoreFoundation;
 using MonoMac.CoreGraphics;
 using MonoMac.Foundation;
+using MonoMac.ObjCRuntime;
 
 namespace Avalonia.MonoMac
 {
-    class PlatformThreadingInterface : IPlatformThreadingInterface
+    class PlatformThreadingInterface : NSObject, IPlatformThreadingInterface
     {
         private bool _signaled;
+        private const string SignaledSelectorName = "avaloniauiSignaled";
+        private readonly Selector _signaledSelector = new Selector(SignaledSelectorName);
         public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
         public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
 
@@ -27,18 +31,25 @@ namespace Avalonia.MonoMac
                     return;
                 _signaled = true;
             }
-            NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
-            {
-                lock (this)
+            PerformSelector(_signaledSelector, NSThread.MainThread, this, false,
+                new[]
                 {
-                    if (!_signaled)
-                        return;
-                    _signaled = false;
-                }
-                Signaled?.Invoke(null);
-            });
+                    NSRunLoop.NSDefaultRunLoopMode, NSRunLoop.NSRunLoopEventTracking, NSRunLoop.NSRunLoopModalPanelMode,
+                    NSRunLoop.NSRunLoopCommonModes, NSRunLoop.NSRunLoopConnectionReplyMode
+                });
         }
 
+        [Export(SignaledSelectorName)]
+        public void CallSignaled()
+        {
+            lock (this)
+            {
+                if (!_signaled)
+                    return;
+                _signaled = false;
+            }
+            Signaled?.Invoke(null);
+        }
 
 
         public void RunLoop(CancellationToken cancellationToken)
@@ -55,6 +66,7 @@ namespace Avalonia.MonoMac
                 var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantFuture, NSRunLoop.NSDefaultRunLoopMode, true);
                 if (ev != null)
                 {
+                    Console.WriteLine("NSEVENT");
                     app.SendEvent(ev);
                     ev.Dispose();
                 }

+ 33 - 0
src/OSX/Avalonia.MonoMac/RenderLoop.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Platform;
+using Avalonia.Rendering;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+    //TODO: Switch to using CVDisplayLink
+    public class RenderLoop : IRenderLoop
+    {
+        private readonly object _lock = new object();
+        private readonly IDisposable _timer;
+
+        public RenderLoop()
+        {
+            _timer = AvaloniaLocator.Current.GetService<IRuntimePlatform>().StartSystemTimer(new TimeSpan(0, 0, 0, 0, 1000 / 60),
+                () =>
+                {
+                    lock (_lock)
+                    {
+                        using (new NSAutoreleasePool())
+                        {
+                            Tick?.Invoke(this, EventArgs.Empty);
+                        }
+                    }
+                });
+        }
+
+        public event EventHandler<EventArgs> Tick;
+    }
+}

+ 85 - 4
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@@ -5,8 +5,9 @@ using Avalonia.Input.Raw;
 using Avalonia.Platform;
 using Avalonia.Controls.Platform.Surfaces;
 using Avalonia.Rendering;
+using Avalonia.Threading;
 using MonoMac.AppKit;
-
+using MonoMac.CoreFoundation;
 using MonoMac.CoreGraphics;
 using MonoMac.Foundation;
 using MonoMac.ObjCRuntime;
@@ -24,6 +25,7 @@ namespace Avalonia.MonoMac
 
         protected virtual void OnInput(RawInputEventArgs args)
         {
+            Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
             Input?.Invoke(args);
         }
 
@@ -36,6 +38,14 @@ namespace Avalonia.MonoMac
             private readonly IKeyboardDevice _keyboard;
             private NSTrackingArea _area;
             private NSCursor _cursor;
+            private bool _nonUiRedrawQueued;
+
+            public CGSize PixelSize { get; set; }
+
+            public CGSize LogicalSize { get; set; }
+
+            private SavedImage _backBuffer;
+            public object SyncRoot { get; } = new object();
 
             public TopLevelView(TopLevelImpl tl)
             {
@@ -44,17 +54,75 @@ namespace Avalonia.MonoMac
                 _keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
             }
 
+            protected override void Dispose(bool disposing)
+            {
+                if (disposing)
+                {
+                    _backBuffer?.Dispose();
+                    _backBuffer = null;
+                }
+                base.Dispose(disposing);
+            }
+
             public override bool ConformsToProtocol(IntPtr protocol)
             {
                 var rv = base.ConformsToProtocol(protocol);
                 return rv;
             }
 
+            public override bool IsOpaque => false;
+
             public override void DrawRect(CGRect dirtyRect)
             {
+                lock (SyncRoot)
+                    _nonUiRedrawQueued = false;
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Render);
+                lock (SyncRoot)
+                {
+                    if (_backBuffer != null)
+                    {
+                        using (var context = NSGraphicsContext.CurrentContext.GraphicsPort)
+                        {
+                            context.SetFillColor(255, 255, 255, 255);
+                            context.FillRect(new CGRect(default(CGPoint), LogicalSize));
+                            context.TranslateCTM(0, LogicalSize.Height - _backBuffer.LogicalSize.Height);
+                            context.DrawImage(new CGRect(default(CGPoint), _backBuffer.LogicalSize), _backBuffer.Image);
+                            context.Flush();
+                            NSGraphicsContext.CurrentContext.FlushGraphics();
+                        }
+                    }
+                }
                 _tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect());
             }
 
+            public void SetBackBufferImage(SavedImage image)
+            {
+                lock (SyncRoot)
+                {
+                    _backBuffer?.Dispose();
+                    _backBuffer = image;
+                    if (image == null)
+                        return;
+
+                    if (_nonUiRedrawQueued)
+                        return;
+                    _nonUiRedrawQueued = true;
+                    Dispatcher.UIThread.InvokeAsync(
+                        () =>
+                        {
+                            lock (SyncRoot)
+                            {
+                                if (!_nonUiRedrawQueued)
+                                    return;
+                                _nonUiRedrawQueued = false;
+                            }
+                            SetNeedsDisplayInRect(Frame);
+                            Display();
+                        }, DispatcherPriority.Render);
+
+                }
+            }
+            
             [Export("viewDidChangeBackingProperties:")]
             public void ViewDidChangeBackingProperties()
             {
@@ -78,7 +146,12 @@ namespace Avalonia.MonoMac
 
             public override void SetFrameSize(CGSize newSize)
             {
-                base.SetFrameSize(newSize);
+                lock (SyncRoot)
+                {
+                    base.SetFrameSize(newSize);
+                    LogicalSize = Frame.Size;
+                    PixelSize = ConvertSizeToBacking(LogicalSize);
+                }
 
                 if (_area != null)
                 {
@@ -92,6 +165,7 @@ namespace Avalonia.MonoMac
                 AddTrackingArea(_area);
                 UpdateCursor();
                 _tl?.Resized?.Invoke(_tl.ClientSize);
+                Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
             }
 
             InputModifiers GetModifiers(NSEventModifierMask mod)
@@ -348,9 +422,16 @@ namespace Avalonia.MonoMac
             View.Dispose();
         }
 
-        public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
+        public IRenderer CreateRenderer(IRenderRoot root) =>
+            MonoMacPlatform.UseDeferredRendering
+                ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>())
+                : (IRenderer) new ImmediateRenderer(root);
 
-        public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame);
+        public void Invalidate(Rect rect)
+        {
+            if (!MonoMacPlatform.UseDeferredRendering)
+                View.SetNeedsDisplayInRect(View.Frame);
+        }
 
         public abstract Point PointToClient(Point point);
 

+ 0 - 1
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
             _finishedCallback = finishedCallback;
             _directWriteFactory = directWriteFactory;
             _imagingFactory = imagingFactory;
-            _swapChain = swapChain;
             _renderTarget.BeginDraw();
         }
 

+ 1 - 1
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs

@@ -468,7 +468,7 @@ namespace Avalonia.Base.UnitTests
                 object anchor = null,
                 bool enableDataValidation = false)
             {
-                return new InstancedBinding(_source, BindingMode.OneTime);
+                return InstancedBinding.OneTime(_source);
             }
         }
 

+ 2 - 2
tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs

@@ -113,8 +113,8 @@ namespace Avalonia.Base.UnitTests
         {
             var p1 = new TestProperty<string>("p1", typeof(Class1));
 
-            Assert.NotEqual(p1, null);
-            Assert.NotEqual(null, p1);
+            Assert.NotNull(p1);
+            Assert.NotNull(p1);
             Assert.False(p1 == null);
             Assert.False(null == p1);
             Assert.False(p1.Equals(null));

+ 22 - 0
tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs

@@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
             Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
         }
 
+        [Fact]
+        public void MoveRange_Raises_Correct_CollectionChanged_Event()
+        {
+            var target = new AvaloniaList<int>(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+            var raised = false;
+
+            target.CollectionChanged += (s, e) =>
+            {
+                Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
+                Assert.Equal(0, e.OldStartingIndex);
+                Assert.Equal(10, e.NewStartingIndex);
+                Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
+                Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
+                raised = true;
+            };
+
+            target.MoveRange(0, 9, 10);
+
+            Assert.True(raised);
+            Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
+        }
+
         [Fact]
         public void Adding_Item_Should_Raise_CollectionChanged()
         {

+ 2 - 2
tests/Avalonia.Base.UnitTests/PriorityValueTests.cs

@@ -206,7 +206,7 @@ namespace Avalonia.Base.UnitTests
 
             Assert.Equal(2, target.GetBindings().Count());
             disposable.Dispose();
-            Assert.Equal(1, target.GetBindings().Count());
+            Assert.Single(target.GetBindings());
         }
 
         [Fact]
@@ -248,7 +248,7 @@ namespace Avalonia.Base.UnitTests
 
             Assert.Equal(2, target.GetBindings().Count());
             subject.OnCompleted();
-            Assert.Equal(1, target.GetBindings().Count());
+            Assert.Single(target.GetBindings());
         }
 
         [Fact]

+ 277 - 0
tests/Avalonia.Controls.UnitTests/CalendarTests.cs

@@ -0,0 +1,277 @@
+// 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 Xunit;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Controls.UnitTests
+{
+    public class CalendarTests
+    {
+        private static bool CompareDates(DateTime first, DateTime second)
+        {
+            return first.Year == second.Year && 
+                first.Month == second.Month &&
+                first.Day == second.Day;
+        }
+
+        [Fact]
+        public void SelectedDatesChanged_Should_Fire_When_SelectedDate_Set()
+        {
+            bool handled = false;
+            Calendar calendar = new Calendar();
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+            calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(delegate
+            {
+                handled = true;
+            });
+            DateTime value = new DateTime(2000, 10, 10);
+            calendar.SelectedDate = value;
+            Assert.True(handled);
+        }
+
+        [Fact]
+        public void DisplayDateChanged_Should_Fire_When_DisplayDate_Set()
+        {
+            bool handled = false;
+            Calendar calendar = new Calendar();
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+            calendar.DisplayDateChanged += new EventHandler<CalendarDateChangedEventArgs>(delegate
+            {
+                handled = true;
+            });
+            DateTime value = new DateTime(2000, 10, 10);
+            calendar.DisplayDate = value;
+            Assert.True(handled);
+        }
+
+        [Fact]
+        public void Setting_Selected_Date_To_Blackout_Date_Should_Throw()
+        {
+            Calendar calendar = new Calendar();
+            calendar.BlackoutDates.AddDatesInPast();
+
+            Assert.ThrowsAny<ArgumentOutOfRangeException>(
+                () => calendar.SelectedDate = DateTime.Today.AddDays(-1));
+        }
+
+        [Fact]
+        public void Setting_Selected_Date_To_Blackout_Date_Should_Throw_Range()
+        {
+            Calendar calendar = new Calendar();
+            calendar.BlackoutDates.Add(new CalendarDateRange(DateTime.Today, DateTime.Today.AddDays(10)));
+
+            calendar.SelectedDate = DateTime.Today.AddDays(-1);
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(-1)));
+            Assert.True(CompareDates(calendar.SelectedDate.Value, calendar.SelectedDates[0]));
+
+            calendar.SelectedDate = DateTime.Today.AddDays(11);
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(11)));
+            Assert.True(CompareDates(calendar.SelectedDate.Value, calendar.SelectedDates[0]));
+
+            Assert.ThrowsAny<ArgumentOutOfRangeException>(
+                () => calendar.SelectedDate = DateTime.Today.AddDays(5));
+        }
+
+        [Fact]
+        public void Adding_Blackout_Dates_Containing_Selected_Date_Should_Throw()
+        {
+            Calendar calendar = new Calendar();
+            calendar.SelectedDate = DateTime.Today.AddDays(5);
+
+            Assert.ThrowsAny<ArgumentOutOfRangeException>(
+                () => calendar.BlackoutDates.Add(new CalendarDateRange(DateTime.Today, DateTime.Today.AddDays(10))));
+        }
+
+        [Fact]
+        public void DisplayDateStartEnd_Should_Constrain_Display_Date()
+        {
+            Calendar calendar = new Calendar();
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+            calendar.DisplayDateStart = new DateTime(2005, 12, 30);
+
+            DateTime value = new DateTime(2005, 12, 15);
+            calendar.DisplayDate = value;
+            Assert.True(CompareDates(calendar.DisplayDate, calendar.DisplayDateStart.Value));
+
+            value = new DateTime(2005, 12, 30);
+            calendar.DisplayDate = value;
+            Assert.True(CompareDates(calendar.DisplayDate, value));
+
+            value = DateTime.MaxValue;
+            calendar.DisplayDate = value;
+            Assert.True(CompareDates(calendar.DisplayDate, value));
+
+            calendar.DisplayDateEnd = new DateTime(2010, 12, 30);
+            Assert.True(CompareDates(calendar.DisplayDate, calendar.DisplayDateEnd.Value));
+        }
+
+        [Fact]
+        public void Setting_DisplayDateEnd_Should_Alter_DispalyDate_And_DisplayDateStart()
+        {
+            Calendar calendar = new Calendar();
+            DateTime value = new DateTime(2000, 1, 30);
+
+            calendar.DisplayDate = value;
+            calendar.DisplayDateEnd = value;
+            calendar.DisplayDateStart = value;
+            Assert.True(CompareDates(calendar.DisplayDateStart.Value, value));
+            Assert.True(CompareDates(calendar.DisplayDateEnd.Value, value));
+
+            value = value.AddMonths(2);
+            calendar.DisplayDateStart = value;
+            Assert.True(CompareDates(calendar.DisplayDateStart.Value, value));
+            Assert.True(CompareDates(calendar.DisplayDateEnd.Value, value));
+            Assert.True(CompareDates(calendar.DisplayDate, value));
+        }
+
+        [Fact]
+        public void Display_Date_Range_End_Will_Contain_SelectedDate()
+        {
+            Calendar calendar = new Calendar();
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+
+            calendar.SelectedDate = DateTime.MaxValue;
+            Assert.True(CompareDates((DateTime)calendar.SelectedDate, DateTime.MaxValue));
+
+            calendar.DisplayDateEnd = DateTime.MaxValue.AddDays(-1);
+            Assert.True(CompareDates((DateTime)calendar.DisplayDateEnd, DateTime.MaxValue));
+        }
+
+
+        /// <summary>
+        /// The days added to the SelectedDates collection.
+        /// </summary>
+        private IList<object> _selectedDatesChangedAddedDays;
+
+        /// <summary>
+        /// The days removed from the SelectedDates collection.
+        /// </summary>
+        private IList<object> _selectedDateChangedRemovedDays;
+
+        /// <summary>
+        /// The number of times the SelectedDatesChanged event has been fired.
+        /// </summary>
+        private int _selectedDatesChangedCount;
+
+        /// <summary>
+        /// Handle the SelectedDatesChanged event.
+        /// </summary>
+        /// <param name="sender">The calendar.</param>
+        /// <param name="e">Event arguments.</param>
+        private void OnSelectedDatesChanged(object sender, SelectionChangedEventArgs e)
+        {
+            _selectedDatesChangedAddedDays =
+                e.AddedItems
+                 .Cast<object>()
+                 .ToList();
+            _selectedDateChangedRemovedDays = 
+                e.RemovedItems
+                 .Cast<object>()
+                 .ToList();
+            _selectedDatesChangedCount++;
+        }
+
+        /// <summary>
+        /// Clear the variables used to track the SelectedDatesChanged event.
+        /// </summary>
+        private void ResetSelectedDatesChanged()
+        {
+            if (_selectedDatesChangedAddedDays != null)
+            {
+                _selectedDatesChangedAddedDays.Clear();
+            }
+
+            if (_selectedDateChangedRemovedDays != null)
+            {
+                _selectedDateChangedRemovedDays.Clear();
+            }
+
+            _selectedDatesChangedCount = 0;
+        }
+
+        [Fact]
+        public void SingleDate_Selection_Behavior()
+        {
+            ResetSelectedDatesChanged();
+            Calendar calendar = new Calendar();
+            calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(OnSelectedDatesChanged);
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+            calendar.SelectedDate = DateTime.Today;
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
+            Assert.True(calendar.SelectedDates.Count == 1);
+            Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
+            Assert.True(_selectedDatesChangedCount == 1);
+            Assert.True(_selectedDatesChangedAddedDays.Count == 1);
+            Assert.True(_selectedDateChangedRemovedDays.Count == 0);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDate = DateTime.Today;
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
+            Assert.True(calendar.SelectedDates.Count == 1);
+            Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
+            Assert.True(_selectedDatesChangedCount == 0);
+
+            calendar.ClearValue(Calendar.SelectedDateProperty);
+
+            calendar.SelectionMode = CalendarSelectionMode.None;
+            Assert.True(calendar.SelectedDates.Count == 0);
+            Assert.Null(calendar.SelectedDate);
+
+            calendar.SelectionMode = CalendarSelectionMode.SingleDate;
+
+            calendar.SelectedDates.Add(DateTime.Today.AddDays(1));
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(1)));
+            Assert.True(calendar.SelectedDates.Count == 1);
+
+            Assert.ThrowsAny<InvalidOperationException>(
+                () => calendar.SelectedDates.Add(DateTime.Today.AddDays(2)));
+        }
+
+        [Fact]
+        public void SingleRange_Selection_Behavior()
+        {
+            ResetSelectedDatesChanged();
+            Calendar calendar = new Calendar();
+            calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(OnSelectedDatesChanged);
+            calendar.SelectionMode = CalendarSelectionMode.SingleRange;
+            calendar.SelectedDate = DateTime.Today;
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
+            Assert.True(calendar.SelectedDates.Count == 1);
+            Assert.True(CompareDates(calendar.SelectedDates[0], DateTime.Today));
+            Assert.True(_selectedDatesChangedCount == 1);
+            Assert.True(_selectedDatesChangedAddedDays.Count == 1);
+            Assert.True(_selectedDateChangedRemovedDays.Count == 0);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDates.Clear();
+            Assert.Null(calendar.SelectedDate);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDates.AddRange(DateTime.Today, DateTime.Today.AddDays(10));
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today));
+            Assert.True(calendar.SelectedDates.Count == 11);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDates.AddRange(DateTime.Today, DateTime.Today.AddDays(10));
+            Assert.True(calendar.SelectedDates.Count == 11);
+            Assert.True(_selectedDatesChangedCount == 0);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDates.AddRange(DateTime.Today.AddDays(-20), DateTime.Today);
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(-20)));
+            Assert.True(calendar.SelectedDates.Count == 21);
+            Assert.True(_selectedDatesChangedCount == 1);
+            Assert.True(_selectedDatesChangedAddedDays.Count == 21);
+            Assert.True(_selectedDateChangedRemovedDays.Count == 11);
+            ResetSelectedDatesChanged();
+
+            calendar.SelectedDates.Add(DateTime.Today.AddDays(100));
+            Assert.True(CompareDates(calendar.SelectedDate.Value, DateTime.Today.AddDays(100)));
+            Assert.True(calendar.SelectedDates.Count == 1);
+        }
+    }
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott