Explorar o código

Merge branch 'master' into compiled-bindings

Jeremy Koritzinsky %!s(int64=5) %!d(string=hai) anos
pai
achega
16675b3512
Modificáronse 100 ficheiros con 5715 adicións e 391 borrados
  1. 4 4
      Avalonia.sln
  2. 16 2
      native/Avalonia.Native/inc/avalonia-native.h
  3. 18 3
      native/Avalonia.Native/src/OSX/controlhost.mm
  4. 10 3
      native/Avalonia.Native/src/OSX/window.h
  5. 292 30
      native/Avalonia.Native/src/OSX/window.mm
  6. 5 0
      samples/ControlCatalog/MainView.xaml
  7. 0 7
      samples/ControlCatalog/MainView.xaml.cs
  8. 29 13
      samples/ControlCatalog/MainWindow.xaml
  9. 123 0
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml
  10. 30 0
      samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs
  11. 8 18
      samples/ControlCatalog/Pages/ProgressBarPage.xaml
  12. 16 5
      samples/ControlCatalog/Pages/SliderPage.xaml
  13. 97 0
      samples/ControlCatalog/Pages/SplitViewPage.xaml
  14. 21 0
      samples/ControlCatalog/Pages/SplitViewPage.xaml.cs
  15. 19 0
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
  16. 19 0
      samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs
  17. 65 0
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  18. 46 0
      samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs
  19. 9 0
      samples/interop/NativeEmbedSample/MainWindow.xaml
  20. 6 0
      src/Avalonia.Animation/Easing/Easing.cs
  21. 85 0
      src/Avalonia.Animation/Easing/SplineEasing.cs
  22. 16 19
      src/Avalonia.Animation/KeySpline.cs
  23. 25 0
      src/Avalonia.Animation/KeySplineTypeConverter.cs
  24. 1 3
      src/Avalonia.Base/AvaloniaProperty.cs
  25. 2 2
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  26. 1 0
      src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
  27. 2 0
      src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs
  28. 2 1
      src/Avalonia.Base/Collections/Pooled/PooledList.cs
  29. 19 15
      src/Avalonia.Base/Data/BindingValue.cs
  30. 26 10
      src/Avalonia.Base/Data/Core/LogicalNotNode.cs
  31. 10 6
      src/Avalonia.Base/Data/Optional.cs
  32. 1 1
      src/Avalonia.Base/DirectPropertyBase.cs
  33. 0 2
      src/Avalonia.Base/IStyledPropertyMetadata.cs
  34. 140 0
      src/Avalonia.Base/Metadata/NullableAttributes.cs
  35. 1 6
      src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs
  36. 4 3
      src/Avalonia.Base/PropertyStore/LocalValueEntry.cs
  37. 1 2
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  38. 45 0
      src/Avalonia.Base/Utilities/StyleClassParser.cs
  39. 1 1
      src/Avalonia.Base/ValueStore.cs
  40. 3 0
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  41. 86 0
      src/Avalonia.Controls/Chrome/CaptionButtons.cs
  42. 117 0
      src/Avalonia.Controls/Chrome/TitleBar.cs
  43. 412 0
      src/Avalonia.Controls/DateTimePickers/DatePicker.cs
  44. 531 0
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  45. 19 0
      src/Avalonia.Controls/DateTimePickers/DatePickerSelectedValueChangedEventArgs.cs
  46. 566 0
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  47. 25 0
      src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs
  48. 292 0
      src/Avalonia.Controls/DateTimePickers/TimePicker.cs
  49. 262 0
      src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
  50. 15 0
      src/Avalonia.Controls/DateTimePickers/TimePickerSelectedValueChangedEventArgs.cs
  51. 73 16
      src/Avalonia.Controls/NativeControlHost.cs
  52. 38 0
      src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs
  53. 2 2
      src/Avalonia.Controls/Platform/INativeControlHostImpl.cs
  54. 46 0
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  55. 29 0
      src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs
  56. 14 1
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  57. 132 32
      src/Avalonia.Controls/ProgressBar.cs
  58. 1 0
      src/Avalonia.Controls/Properties/AssemblyInfo.cs
  59. 0 2
      src/Avalonia.Controls/SelectionModel.cs
  60. 57 9
      src/Avalonia.Controls/Slider.cs
  61. 487 0
      src/Avalonia.Controls/SplitView.cs
  62. 14 0
      src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs
  63. 4 8
      src/Avalonia.Controls/TickBar.cs
  64. 5 3
      src/Avalonia.Controls/TopLevel.cs
  65. 6 3
      src/Avalonia.Controls/Utils/IEnumerableUtils.cs
  66. 134 1
      src/Avalonia.Controls/Window.cs
  67. 23 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  68. 23 1
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  69. 9 1
      src/Avalonia.Diagnostics/Diagnostics/DevTools.cs
  70. 28 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs
  71. 14 9
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  72. 10 4
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs
  73. 78 0
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs
  74. 9 1
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  75. 27 10
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs
  76. 20 2
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
  77. 6 1
      src/Avalonia.Input/InputElement.cs
  78. 3 1
      src/Avalonia.Input/Navigation/TabNavigation.cs
  79. 20 20
      src/Avalonia.Layout/LayoutManager.cs
  80. 4 5
      src/Avalonia.Native/NativeControlHostImpl.cs
  81. 12 0
      src/Avalonia.Native/PopupImpl.cs
  82. 85 1
      src/Avalonia.Native/WindowImpl.cs
  83. 13 3
      src/Avalonia.Native/WindowImplBase.cs
  84. 0 5
      src/Avalonia.Styling/IStyledElement.cs
  85. 0 1
      src/Avalonia.Styling/StyledElement.cs
  86. 0 1
      src/Avalonia.Styling/Styling/Styles.cs
  87. 69 0
      src/Avalonia.Themes.Default/CaptionButtons.xaml
  88. 1 0
      src/Avalonia.Themes.Default/DefaultTheme.xaml
  89. 10 2
      src/Avalonia.Themes.Default/ProgressBar.xaml
  90. 6 3
      src/Avalonia.Themes.Default/Slider.xaml
  91. 53 0
      src/Avalonia.Themes.Default/TitleBar.xaml
  92. 3 1
      src/Avalonia.Themes.Default/Window.xaml
  93. 7 36
      src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml
  94. 8 37
      src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml
  95. 72 0
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml
  96. 71 0
      src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml
  97. 68 0
      src/Avalonia.Themes.Fluent/CaptionButtons.xaml
  98. 338 0
      src/Avalonia.Themes.Fluent/DatePicker.xaml
  99. 6 1
      src/Avalonia.Themes.Fluent/FluentTheme.xaml
  100. 34 10
      src/Avalonia.Themes.Fluent/FocusAdorner.xaml

+ 4 - 4
Avalonia.sln

@@ -201,9 +201,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Ava
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.FreeDesktop", "src\Avalonia.FreeDesktop\Avalonia.FreeDesktop.csproj", "{4D36CEC8-53F2-40A5-9A37-79AAE356E2DA}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid.UnitTests", "tests\Avalonia.Controls.DataGrid.UnitTests\Avalonia.Controls.DataGrid.UnitTests.csproj", "{351337F5-D66F-461B-A957-4EF60BDB4BA6}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeEmbedSample", "samples\interop\NativeEmbedSample\NativeEmbedSample.csproj", "{3C84E04B-36CF-4D0D-B965-C26DD649D1F3}"
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Fluent", "src\Avalonia.Themes.Fluent\Avalonia.Themes.Fluent.csproj", "{C42D2FC1-A531-4ED4-84B9-89AEC7C962FC}"
 EndProject
@@ -211,8 +211,8 @@ Global
 	GlobalSection(SharedMSBuildProjectFiles) = preSolution
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5
-		src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 4
-		src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 4
+		src\Shared\PlatformSupport\PlatformSupport.projitems*{4488ad85-1495-4809-9aa4-ddfe0a48527e}*SharedItemsImports = 5
+		src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5
 		src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5
 		src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13

+ 16 - 2
native/Avalonia.Native/inc/avalonia-native.h

@@ -205,6 +205,15 @@ enum AvnMenuItemToggleType
     Radio
 };
 
+enum AvnExtendClientAreaChromeHints
+{
+    AvnNoChrome = 0,
+    AvnSystemChrome = 0x01,
+    AvnPreferSystemChrome = 0x02,
+    AvnOSXThickTitleBar = 0x08,
+    AvnDefaultChrome = AvnSystemChrome,
+};
+
 AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
 {
 public:
@@ -278,6 +287,11 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
     virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
     virtual HRESULT SetWindowState(AvnWindowState state) = 0;
     virtual HRESULT GetWindowState(AvnWindowState*ret) = 0;
+    virtual HRESULT TakeFocusFromChildren() = 0;
+    virtual HRESULT SetExtendClientArea (bool enable) = 0;
+    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) = 0;
+    virtual HRESULT GetExtendTitleBarHeight (double*ret) = 0;
+    virtual HRESULT SetExtendTitleBarHeight (double value) = 0;
 };
 
 AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
@@ -493,8 +507,8 @@ AVNCOM(IAvnNativeControlHostTopLevelAttachment, 21) : IUnknown
     virtual void* GetParentHandle() = 0;
     virtual HRESULT InitializeWithChildHandle(void* child) = 0;
     virtual HRESULT AttachTo(IAvnNativeControlHost* host) = 0;
-    virtual void MoveTo(float x, float y, float width, float height) = 0;
-    virtual void Hide() = 0;
+    virtual void ShowInBounds(float x, float y, float width, float height) = 0;
+    virtual void HideWithSize(float width, float height) = 0;
     virtual void ReleaseChild() = 0;
 };
 

+ 18 - 3
native/Avalonia.Native/src/OSX/controlhost.mm

@@ -97,7 +97,7 @@ public:
         return S_OK;
     };
     
-    virtual void MoveTo(float x, float y, float width, float height) override
+    virtual void ShowInBounds(float x, float y, float width, float height) override
     {
         if(_child == nil)
             return;
@@ -106,7 +106,7 @@ public:
             IAvnNativeControlHostTopLevelAttachment* slf = this;
             slf->AddRef();
             dispatch_async(dispatch_get_main_queue(), ^{
-                slf->MoveTo(x, y, width, height);
+                slf->ShowInBounds(x, y, width, height);
                 slf->Release();
             });
             return;
@@ -122,9 +122,24 @@ public:
             [[_holder superview] setNeedsDisplay:true];
     }
     
-    virtual void Hide() override
+    virtual void HideWithSize(float width, float height) override
     {
+        if(_child == nil)
+            return;
+        if(AvnInsidePotentialDeadlock::IsInside())
+        {
+            IAvnNativeControlHostTopLevelAttachment* slf = this;
+            slf->AddRef();
+            dispatch_async(dispatch_get_main_queue(), ^{
+                slf->HideWithSize(width, height);
+                slf->Release();
+            });
+            return;
+        }
+        
+        NSRect frame = {0, 0, width, height};
         [_holder setHidden: true];
+        [_child setFrame: frame];
     }
     
     virtual void ReleaseChild() override

+ 10 - 3
native/Avalonia.Native/src/OSX/window.h

@@ -3,9 +3,6 @@
 
 class WindowBaseImpl;
 
-@interface AutoFitContentVisualEffectView : NSVisualEffectView
-@end
-
 @interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
 -(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
 -(NSEvent* _Nonnull) lastMouseDownEvent;
@@ -15,6 +12,14 @@ class WindowBaseImpl;
 -(AvnPixelSize) getPixelSize;
 @end
 
+@interface AutoFitContentView : NSView
+-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
+-(void) ShowTitleBar: (bool) show;
+-(void) SetTitleBarHeightHint: (double) height;
+-(void) SetContent: (NSView* _Nonnull) content;
+-(void) ShowBlur: (bool) show;
+@end
+
 @interface AvnWindow : NSWindow <NSWindowDelegate>
 +(void) closeAll;
 -(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
@@ -27,6 +32,8 @@ class WindowBaseImpl;
 -(void) showWindowMenuWithAppMenu;
 -(void) applyMenu:(NSMenu* _Nullable)menu;
 -(double) getScaling;
+-(double) getExtendedTitleBarHeight;
+-(void) setIsExtended:(bool)value;
 @end
 
 struct INSWindowHolder

+ 292 - 30
native/Avalonia.Native/src/OSX/window.mm

@@ -6,8 +6,6 @@
 #include <OpenGL/gl.h>
 #include "rendertarget.h"
 
-
-
 class WindowBaseImpl : public virtual ComSingleObject<IAvnWindowBase, &IID_IAvnWindowBase>, public INSWindowHolder
 {
 private:
@@ -20,7 +18,7 @@ public:
         View = NULL;
         Window = NULL;
     }
-    NSVisualEffectView* VisualEffect;
+    AutoFitContentView* StandardContainer;
     AvnView* View;
     AvnWindow* Window;
     ComPtr<IAvnWindowBaseEvents> BaseEvents;
@@ -39,6 +37,7 @@ public:
         _glContext = gl;
         renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl];
         View = [[AvnView alloc] initWithParent:this];
+        StandardContainer = [[AutoFitContentView new] initWithContent:View];
 
         Window = [[AvnWindow alloc] initWithParent:this];
         
@@ -49,12 +48,8 @@ public:
         [Window setStyleMask:NSWindowStyleMaskBorderless];
         [Window setBackingType:NSBackingStoreBuffered];
         
-        VisualEffect = [AutoFitContentVisualEffectView new];
-        [VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
-        [VisualEffect setMaterial:NSVisualEffectMaterialLight];
-        [VisualEffect setAutoresizesSubviews:true];
-        
-        [Window setContentView: View];
+        [Window setOpaque:false];
+        [Window setContentView: StandardContainer];
     }
     
     virtual HRESULT ObtainNSWindowHandle(void** ret) override
@@ -116,10 +111,15 @@ public:
         {
             SetPosition(lastPositionSet);
             UpdateStyle();
-            
-            [Window makeKeyAndOrderFront:Window];
-            [NSApp activateIgnoringOtherApps:YES];
-            
+            if(ShouldTakeFocusOnShow())
+            {
+                [Window makeKeyAndOrderFront:Window];
+                [NSApp activateIgnoringOtherApps:YES];
+            }
+            else
+            {
+                [Window orderFront: Window];
+            }
             [Window setTitle:_lastTitle];
             
             _shown = true;
@@ -128,6 +128,11 @@ public:
         }
     }
     
+    virtual bool ShouldTakeFocusOnShow()
+    {
+        return true;
+    }
+    
     virtual HRESULT Hide () override
     {
         @autoreleasepool
@@ -400,12 +405,7 @@ public:
     
     virtual HRESULT SetBlurEnabled (bool enable) override
     {
-        [Window setContentView: enable ? VisualEffect : View];
-        
-        if(enable)
-        {
-            [VisualEffect addSubview:View];
-        }
+        [StandardContainer ShowBlur:enable];
         
         return S_OK;
     }
@@ -482,6 +482,8 @@ private:
     bool _inSetWindowState;
     NSRect _preZoomSize;
     bool _transitioningWindowState;
+    bool _isClientAreaExtended;
+    AvnExtendClientAreaChromeHints _extendClientHints;
     
     FORWARD_IUNKNOWN()
     BEGIN_INTERFACE_MAP()
@@ -495,6 +497,8 @@ private:
     ComPtr<IAvnWindowEvents> WindowEvents;
     WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
     {
+        _isClientAreaExtended = false;
+        _extendClientHints = AvnDefaultChrome;
         _fullScreenActive = false;
         _canResize = true;
         _decorations = SystemDecorationsFull;
@@ -513,8 +517,20 @@ private:
             if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
                 NSView *titlebarView = [subview subviews][0];
                 for (id button in titlebarView.subviews) {
-                    if ([button isKindOfClass:[NSButton class]]) {
-                        [button setHidden: (_decorations != SystemDecorationsFull)];
+                    if ([button isKindOfClass:[NSButton class]])
+                    {
+                        if(_isClientAreaExtended)
+                        {
+                            auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+                            
+                            [button setHidden: !wantsChrome];
+                        }
+                        else
+                        {
+                            [button setHidden: (_decorations != SystemDecorationsFull)];
+                        }
+                        
+                        [button setWantsLayer:true];
                     }
                 }
             }
@@ -590,6 +606,35 @@ private:
             
             if(_lastWindowState != state)
             {
+                if(_isClientAreaExtended)
+                {
+                    if(_lastWindowState == FullScreen)
+                    {
+                        // we exited fs.
+                       if(_extendClientHints & AvnOSXThickTitleBar)
+                       {
+                          Window.toolbar = [NSToolbar new];
+                          Window.toolbar.showsBaselineSeparator = false;
+                       }
+
+                       [Window setTitlebarAppearsTransparent:true];
+
+                       [StandardContainer setFrameSize: StandardContainer.frame.size];
+                    }
+                    else if(state == FullScreen)
+                    {
+                        // we entered fs.
+                        if(_extendClientHints & AvnOSXThickTitleBar)
+                        {
+                            Window.toolbar = nullptr;
+                        }
+                       
+                        [Window setTitlebarAppearsTransparent:false];
+                        
+                        [StandardContainer setFrameSize: StandardContainer.frame.size];
+                    }
+                }
+                
                 _lastWindowState = state;
                 WindowEvents->WindowStateChanged(state);
             }
@@ -646,8 +691,6 @@ private:
                 return S_OK;
             }
             
-            auto currentFrame = [Window frame];
-            
             UpdateStyle();
             
             HideOrShowTrafficLights();
@@ -774,6 +817,90 @@ private:
         }
     }
     
+    virtual HRESULT TakeFocusFromChildren () override
+    {
+        if(Window == nil)
+            return S_OK;
+        if([Window isKeyWindow])
+            [Window makeFirstResponder: View];
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendClientArea (bool enable) override
+    {
+        _isClientAreaExtended = enable;
+        
+        if(enable)
+        {
+            Window.titleVisibility = NSWindowTitleHidden;
+            
+            [Window setTitlebarAppearsTransparent:true];
+            
+            auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+            
+            if (wantsTitleBar)
+            {
+                [StandardContainer ShowTitleBar:true];
+            }
+            else
+            {
+                [StandardContainer ShowTitleBar:false];
+            }
+            
+            if(_extendClientHints & AvnOSXThickTitleBar)
+            {
+                Window.toolbar = [NSToolbar new];
+                Window.toolbar.showsBaselineSeparator = false;
+            }
+            else
+            {
+                Window.toolbar = nullptr;
+            }
+        }
+        else
+        {
+            Window.titleVisibility = NSWindowTitleVisible;
+            Window.toolbar = nullptr;
+            [Window setTitlebarAppearsTransparent:false];
+            View.layer.zPosition = 0;
+        }
+        
+        [Window setIsExtended:enable];
+        
+        HideOrShowTrafficLights();
+        
+        UpdateStyle();
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override
+    {
+        _extendClientHints = hints;
+        
+        SetExtendClientArea(_isClientAreaExtended);
+        return S_OK;
+    }
+    
+    virtual HRESULT GetExtendTitleBarHeight (double*ret) override
+    {
+        if(ret == nullptr)
+        {
+            return E_POINTER;
+        }
+        
+        *ret = [Window getExtendedTitleBarHeight];
+        
+        return S_OK;
+    }
+    
+    virtual HRESULT SetExtendTitleBarHeight (double value) override
+    {
+        [StandardContainer SetTitleBarHeightHint:value];
+        return S_OK;
+    }
+    
     void EnterFullScreenMode ()
     {
         _fullScreenActive = true;
@@ -783,8 +910,9 @@ private:
         [Window setTitlebarAppearsTransparent:NO];
         [Window setTitle:_lastTitle];
         
-        [Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
-        
+        Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
+        Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
+    
         [Window toggleFullScreen:nullptr];
     }
     
@@ -932,19 +1060,120 @@ protected:
         {
             s |= NSWindowStyleMaskMiniaturizable;
         }
+        
+        if(_isClientAreaExtended)
+        {
+            s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
+        }
         return s;
     }
 };
 
 NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
 
-@implementation AutoFitContentVisualEffectView
+@implementation AutoFitContentView
+{
+    NSVisualEffectView* _titleBarMaterial;
+    NSBox* _titleBarUnderline;
+    NSView* _content;
+    NSVisualEffectView* _blurBehind;
+    double _titleBarHeightHint;
+    bool _settingSize;
+}
+
+-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
+{
+    _titleBarHeightHint = -1;
+    _content = content;
+    _settingSize = false;
+
+    [self setAutoresizesSubviews:true];
+    [self setWantsLayer:true];
+    
+    _titleBarMaterial = [NSVisualEffectView new];
+    [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
+    [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
+    [_titleBarMaterial setWantsLayer:true];
+    _titleBarMaterial.hidden = true;
+    
+    _titleBarUnderline = [NSBox new];
+    _titleBarUnderline.boxType = NSBoxSeparator;
+    _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
+    _titleBarUnderline.hidden = true;
+    
+    [self addSubview:_titleBarMaterial];
+    [self addSubview:_titleBarUnderline];
+    
+    _blurBehind = [NSVisualEffectView new];
+    [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+    [_blurBehind setMaterial:NSVisualEffectMaterialLight];
+    [_blurBehind setWantsLayer:true];
+    _blurBehind.hidden = true;
+    
+    [self addSubview:_blurBehind];
+    [self addSubview:_content];
+    
+    [self setWantsLayer:true];
+    return self;
+}
+
+-(void) ShowBlur:(bool)show
+{
+    _blurBehind.hidden = !show;
+}
+
+-(void) ShowTitleBar: (bool) show
+{
+    _titleBarMaterial.hidden = !show;
+    _titleBarUnderline.hidden = !show;
+}
+
+-(void) SetTitleBarHeightHint: (double) height
+{
+    _titleBarHeightHint = height;
+    
+    [self setFrameSize:self.frame.size];
+}
+
 -(void)setFrameSize:(NSSize)newSize
 {
-    [super setFrameSize:newSize];
-    if([[self subviews] count] == 0)
+    if(_settingSize)
+    {
         return;
-    [[self subviews][0] setFrameSize: newSize];
+    }
+    
+    _settingSize = true;
+    [super setFrameSize:newSize];
+    
+    [_blurBehind setFrameSize:newSize];
+    [_content setFrameSize:newSize];
+    
+    auto window = objc_cast<AvnWindow>([self window]);
+    
+    // TODO get actual titlebar size
+    
+    double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
+    
+    NSRect tbar;
+    tbar.origin.x = 0;
+    tbar.origin.y = newSize.height - height;
+    tbar.size.width = newSize.width;
+    tbar.size.height = height;
+    
+    [_titleBarMaterial setFrame:tbar];
+    tbar.size.height = height < 1 ? 0 : 1;
+    [_titleBarUnderline setFrame:tbar];
+    _settingSize = false;
+}
+
+-(void) SetContent: (NSView* _Nonnull) content
+{
+    if(content != nullptr)
+    {
+        [content removeFromSuperview];
+        [self addSubview:content];
+        _content = content;
+    }
 }
 @end
 
@@ -1504,15 +1733,43 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     bool _canBecomeKeyAndMain;
     bool _closed;
     bool _isEnabled;
+    bool _isExtended;
     AvnMenu* _menu;
     double _lastScaling;
 }
 
+-(void) setIsExtended:(bool)value;
+{
+    _isExtended = value;
+}
+
 -(double) getScaling
 {
     return _lastScaling;
 }
 
+-(double) getExtendedTitleBarHeight
+{
+    if(_isExtended)
+    {
+        for (id subview in self.contentView.superview.subviews)
+        {
+            if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
+            {
+                NSView *titlebarView = [subview subviews][0];
+
+                return (double)titlebarView.frame.size.height;
+            }
+        }
+
+        return -1;
+    }
+    else
+    {
+        return 0;
+    }
+}
+
 +(void)closeAll
 {
     NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]];
@@ -1631,6 +1888,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
     [self setOpaque:NO];
     [self setBackgroundColor: [NSColor clearColor]];
     [self invalidateShadow];
+    _isExtended = false;
     return self;
 }
 
@@ -1858,7 +2116,6 @@ private:
         WindowEvents = events;
         [Window setLevel:NSPopUpMenuWindowLevel];
     }
-    
 protected:
     virtual NSWindowStyleMask GetStyle() override
     {
@@ -1876,6 +2133,11 @@ protected:
             return S_OK;
         }
     }
+public:
+    virtual bool ShouldTakeFocusOnShow() override
+    {
+        return false;
+    }
 };
 
 extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)

+ 5 - 0
samples/ControlCatalog/MainView.xaml

@@ -29,6 +29,9 @@
                ScrollViewer.HorizontalScrollBarVisibility="Disabled">
           <pages:DataGridPage/>
       </TabItem>
+      <TabItem Header="Date/Time Picker">
+        <pages:DateTimePickerPage/>
+      </TabItem>
       <TabItem Header="CalendarDatePicker">
         <pages:CalendarDatePickerPage/></TabItem>
       <TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
@@ -54,6 +57,7 @@
       <TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
       <TabItem Header="ScrollViewer"><pages:ScrollViewerPage/></TabItem>
       <TabItem Header="Slider"><pages:SliderPage/></TabItem>
+      <TabItem Header="SplitView"><pages:SplitViewPage/></TabItem>
       <TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
       <TabItem Header="TabStrip"><pages:TabStripPage/></TabItem>
       <TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
@@ -62,6 +66,7 @@
       <TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
       <TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
       <TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
+      <TabItem Header="Window Customizations"><pages:WindowCustomizationsPage/></TabItem>
       <TabControl.Tag>
         <StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
           <ComboBox x:Name="Decorations" SelectedIndex="0">

+ 0 - 7
samples/ControlCatalog/MainView.xaml.cs

@@ -58,13 +58,6 @@ namespace ControlCatalog
                 if (VisualRoot is Window window)
                     window.SystemDecorations = (SystemDecorations)decorations.SelectedIndex;
             };
-
-            var transparencyLevels = this.Find<ComboBox>("TransparencyLevels");
-            transparencyLevels.SelectionChanged += (sender, e) =>
-            {
-                if (VisualRoot is Window window)
-                    window.TransparencyLevelHint = (WindowTransparencyLevel)transparencyLevels.SelectedIndex;
-            };
         }
 
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)

+ 29 - 13
samples/ControlCatalog/MainWindow.xaml

@@ -7,7 +7,13 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
         xmlns:v="clr-namespace:ControlCatalog.Views"
-        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{DynamicResource SystemControlPageBackgroundAltHighBrush}">
+        ExtendClientAreaToDecorationsHint="{Binding ExtendClientAreaEnabled}"
+        ExtendClientAreaChromeHints="{Binding ChromeHints}"
+        ExtendClientAreaTitleBarHeightHint="{Binding TitleBarHeight}"
+        TransparencyLevelHint="{Binding TransparencyLevel}"
+        TransparencyBackgroundFallback="Transparent"
+        x:Name="MainWindow"
+        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="{x:Null}">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">
@@ -56,20 +62,30 @@
     </NativeMenu>
   </NativeMenu.Menu>
 
- <Window.DataTemplates>
+  <Window.DataTemplates>
     <DataTemplate DataType="vm:NotificationViewModel">
       <v:CustomNotificationView />
     </DataTemplate>
   </Window.DataTemplates>
-  <DockPanel LastChildFill="True">
-    <Menu Name="MainMenu" DockPanel.Dock="Top">
-      <MenuItem Header="File">
-        <MenuItem Header="Exit" Command="{Binding ExitCommand}" />
-      </MenuItem>
-      <MenuItem Header="Help">
-        <MenuItem Header="About" Command="{Binding AboutCommand}" />
-      </MenuItem>
-    </Menu>
-    <local:MainView />
-  </DockPanel>
+  <Panel>
+    <Panel Margin="{Binding #MainWindow.OffScreenMargin}">
+      <DockPanel Background="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
+        <Menu Name="MainMenu" DockPanel.Dock="Top">
+          <MenuItem Header="File">
+            <MenuItem Header="Exit" Command="{Binding ExitCommand}" />
+          </MenuItem>
+          <MenuItem Header="Help">
+            <MenuItem Header="About" Command="{Binding AboutCommand}" />
+          </MenuItem>
+        </Menu>
+        <local:MainView />
+      </DockPanel>
+    </Panel>
+    <Border IsVisible="{Binding ExtendClientAreaEnabled}" BorderThickness="1 1 1 0" CornerRadius="4 4 0 0" BorderBrush="#55000000" Height="22" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="250 8 0 0">
+      <Border.Background>
+        <SolidColorBrush Color="White" Opacity="0.7" />
+      </Border.Background>
+      <TextBlock Margin="5 5 5 0" Text="Content In Title Bar" />
+    </Border>
+  </Panel>
 </Window>

+ 123 - 0
samples/ControlCatalog/Pages/DateTimePickerPage.xaml

@@ -0,0 +1,123 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.DateTimePickerPage">
+  <StackPanel Orientation="Vertical" Spacing="4" HorizontalAlignment="Stretch">
+    <TextBlock Classes="h1">DatePicker and TimePicker</TextBlock>
+    <TextBlock Name="DatePickerDesc" Classes="h2" TextWrapping="Wrap"/>
+    <TextBlock Name="TimePickerDesc" Classes="h2" TextWrapping="Wrap"/>
+
+    <StackPanel Orientation="Vertical"
+                Margin="16"
+                HorizontalAlignment="Stretch"
+                Spacing="16">
+      <TextBlock FontSize="18">A simple DatePicker with a header</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <DatePicker Header="Pick a date" />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;DatePicker Header="Pick a date" /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+      
+      <TextBlock FontSize="18">A DatePicker with day formatted and year hidden.</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <DatePicker x:Name="Control2" DayFormat="d (ddd)"
+                             YearVisible="False" />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;DatePicker DayFormat="d (ddd)" YearVisible="False" /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+
+      <Border Background="{DynamicResource SystemControlHighlightBaseLowBrush}" BorderThickness="1" Margin="15" />
+
+      <TextBlock FontSize="18">A simple TimePicker.</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <TimePicker />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;TimePicker /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+
+      <TextBlock FontSize="18">A TimePicker with a header and minute increments specified.</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <TimePicker Header="Arrival time" MinuteIncrement="15" />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;TimePicker Header="Arrival time" MinuteIncrement="15" /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+
+      <TextBlock FontSize="18">A TimePicker using a 12-hour clock.</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;TimePicker ClockIdentifier="12HourClock" Header="12 hour clock" /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+
+      <TextBlock FontSize="18">A TimePicker using a 24-hour clock.</TextBlock>
+      <StackPanel Orientation="Vertical">
+        <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+                BorderThickness="1" Padding="15">
+          <TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" />
+        </Border>
+        <Panel Background="{DynamicResource SystemControlBackgroundBaseLowBrush}">
+          <TextBlock Padding="15">
+            <TextBlock.Text>
+              <x:String>
+                &lt;TimePicker ClockIdentifier="24HourClock" Header="24 hour clock" /&gt;
+              </x:String>
+            </TextBlock.Text>
+          </TextBlock>
+        </Panel>
+      </StackPanel>
+      
+    </StackPanel>
+  </StackPanel>
+</UserControl>

+ 30 - 0
samples/ControlCatalog/Pages/DateTimePickerPage.xaml.cs

@@ -0,0 +1,30 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class DateTimePickerPage : UserControl
+    {
+        public DateTimePickerPage()
+        {
+            this.InitializeComponent();
+            this.FindControl<TextBlock>("DatePickerDesc").Text = "Use a DatePicker to let users set a date in your app, " +
+                "for example to schedule an appointment. The DatePicker displays three controls for month, day, and year. " +
+                "These controls are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+                "Order of month, day, and year is dynamically set based on user date settings";
+
+            this.FindControl<TextBlock>("TimePickerDesc").Text = "Use a TimePicker to let users set a time in your app, for example " +
+                "to set a reminder. The TimePicker displays three controls for hour, minute, and AM / PM(if necessary).These controls " +
+                "are easy to use with touch or mouse, and they can be styled and configured in several different ways. " +
+                "12 - hour or 24 - hour clock and visiblility of AM / PM is dynamically set based on user time settings, or can be overridden.";
+
+
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 8 - 18
samples/ControlCatalog/Pages/ProgressBarPage.xaml

@@ -1,29 +1,19 @@
-<UserControl xmlns="https://github.com/avaloniaui"
-             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             x:Class="ControlCatalog.Pages.ProgressBarPage">
+<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="ControlCatalog.Pages.ProgressBarPage">
   <StackPanel Orientation="Vertical" Spacing="4">
     <TextBlock Classes="h1">ProgressBar</TextBlock>
     <TextBlock Classes="h2">A progress bar control</TextBlock>
-
     <StackPanel>
-      <CheckBox
-          x:Name="showProgress"
-          Margin="10,16,0,0"
-          Content="Show Progress Text" />
-      <StackPanel Orientation="Horizontal"
-                  Margin="0,16,0,0"
-                  HorizontalAlignment="Center"
-                  Spacing="16">
+      <CheckBox x:Name="showProgress" Margin="10,16,0,0" Content="Show Progress Text" />
+      <CheckBox x:Name="isIndeterminate" Margin="10,16,0,0" Content="Toggle Indeterminate" />
+      <StackPanel Orientation="Horizontal" Margin="0,16,0,0" HorizontalAlignment="Center" Spacing="16">
         <StackPanel Spacing="16">
-          <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
-          <ProgressBar IsIndeterminate="True"/>
+          <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #hprogress.Value}" />
         </StackPanel>
-        <ProgressBar ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
-        <ProgressBar Orientation="Vertical" IsIndeterminate="True" />
+        <ProgressBar IsIndeterminate="{Binding #isIndeterminate.IsChecked}" ShowProgressText="{Binding #showProgress.IsChecked}" Value="{Binding #vprogress.Value}" Orientation="Vertical" />
       </StackPanel>
       <StackPanel Margin="16">
-        <Slider Name="hprogress" Maximum="100" Value="40"/>
-        <Slider Name="vprogress" Maximum="100" Value="60"/>
+        <Slider Name="hprogress" Maximum="100" Value="40" />
+        <Slider Name="vprogress" Maximum="100" Value="60" />
       </StackPanel>
     </StackPanel>
   </StackPanel>

+ 16 - 5
samples/ControlCatalog/Pages/SliderPage.xaml

@@ -6,11 +6,22 @@
     <TextBlock Classes="h2">A control that lets the user select from a range of values by moving a Thumb control along a Track.</TextBlock>
 
     <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0 16 0 0" Spacing="16">
-      <Slider Value="0"
-              Minimum="0"
-              Maximum="100"
-              TickFrequency="10"
-              Width="300"/>
+      <StackPanel Orientation="Vertical"
+                  HorizontalAlignment="Center">
+        <Slider Value="0"
+                Minimum="0"
+                Maximum="100"
+                TickFrequency="10"
+                Width="300" />
+        <Slider Name="CustomTickedSlider"
+                Value="0"
+                Minimum="0"
+                Maximum="100"
+                TickPlacement="BottomRight"
+                IsSnapToTickEnabled="True"
+                Ticks="0,20,25,40,75,100"
+                Width="300" />
+      </StackPanel>
       <Slider Value="0"
               Minimum="0"
               Maximum="100"

+ 97 - 0
samples/ControlCatalog/Pages/SplitViewPage.xaml

@@ -0,0 +1,97 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.SplitViewPage">
+
+  <Border>
+
+    <Grid ColumnDefinitions="*,225">
+
+      <StackPanel Grid.Column="1" Orientation="Vertical" Spacing="4" Margin="5">
+        <ToggleButton Name="PaneOpenButton"
+                      Content="IsPaneOpen"
+                      IsChecked="{Binding IsPaneOpen, ElementName=SplitView}" />
+
+        <ToggleButton Name="UseLightDismissOverlayModeButton"
+                      Content="UseLightDismissOverlayMode"
+                      IsChecked="{Binding UseLightDismissOverlayMode, ElementName=SplitView}" />
+
+        <ToggleSwitch OffContent="Left" OnContent="Right" Content="Placement" IsChecked="{Binding !IsLeft}" />
+
+        <TextBlock Text="DisplayMode" />
+        <ComboBox Name="DisplayModeSelector" Width="170" Margin="10" SelectedIndex="{Binding DisplayMode}">
+          <ComboBoxItem>Inline</ComboBoxItem>
+          <ComboBoxItem>CompactInline</ComboBoxItem>
+          <ComboBoxItem>Overlay</ComboBoxItem>
+          <ComboBoxItem>CompactOverlay</ComboBoxItem>
+        </ComboBox>
+
+        <TextBlock Text="PaneBackground" />
+        <ComboBox Name="PaneBackgroundSelector" SelectedIndex="0" Width="170" Margin="10">
+          <ComboBoxItem Tag="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}">SystemControlBackgroundChromeMediumLowBrush</ComboBoxItem>
+          <ComboBoxItem Tag="Red">Red</ComboBoxItem>
+          <ComboBoxItem Tag="Blue">Blue</ComboBoxItem>
+          <ComboBoxItem Tag="Green">Green</ComboBoxItem>
+        </ComboBox>
+
+        <TextBlock Text="{Binding Value, ElementName=OpenPaneLengthSlider, StringFormat='{}OpenPaneLength: {0}'}" />
+        <Slider Name="OpenPaneLengthSlider" Value="256" Minimum="128" Maximum="500"
+                Width="150" />
+
+        <TextBlock Text="{Binding Value, ElementName=CompactPaneLengthSlider, StringFormat='{}CompactPaneLength: {0}'}" />
+        <Slider Name="CompactPaneLengthSlider" Value="48" Minimum="24" Maximum="128"
+                Width="150" />
+
+      </StackPanel>
+
+      <Border BorderBrush="{DynamicResource SystemControlHighlightBaseLowBrush}"
+            BorderThickness="1">
+        <!--{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}-->
+        <SplitView Name="SplitView"
+                      PanePlacement="{Binding PanePlacement}"
+                      PaneBackground="{Binding SelectedItem.Tag, ElementName=PaneBackgroundSelector}"
+                      OpenPaneLength="{Binding Value, ElementName=OpenPaneLengthSlider}"
+                      CompactPaneLength="{Binding Value, ElementName=CompactPaneLengthSlider}"
+                      DisplayMode="{Binding CurrentDisplayMode}">
+          <SplitView.Pane>
+            <Grid>
+              <Grid.RowDefinitions>
+                <RowDefinition Height="Auto" />
+                <RowDefinition Height="*" />
+                <RowDefinition Height="Auto" />
+              </Grid.RowDefinitions>
+              <TextBlock Text="PANE CONTENT" FontWeight="Bold" Name="PaneHeader" Margin="5,12,0,0" />
+              <ListBoxItem Grid.Row="1" VerticalAlignment="Top" Margin="0 10">
+                <StackPanel Orientation="Horizontal">
+                  <!--Path glyph from materialdesignicons.com-->
+                  <Border Width="48">
+                    <Viewbox Width="24" Height="24" HorizontalAlignment="Left">
+                      <Canvas Width="24" Height="24">
+                        <Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}" Data="M16 17V19H2V17S2 13 9 13 16 17 16 17M12.5 7.5A3.5 3.5 0 1 0 9 11A3.5 3.5 0 0 0 12.5 7.5M15.94 13A5.32 5.32 0 0 1 18 17V19H22V17S22 13.37 15.94 13M15 4A3.39 3.39 0 0 0 13.07 4.59A5 5 0 0 1 13.07 10.41A3.39 3.39 0 0 0 15 11A3.5 3.5 0 0 0 15 4Z" />
+                      </Canvas>
+                    </Viewbox>
+                  </Border>
+                  <TextBlock Text="People" VerticalAlignment="Center" />
+                </StackPanel>
+              </ListBoxItem>
+              <TextBlock Grid.Row="2" Text="Item at bottom" Margin="60,12" />
+            </Grid>
+          </SplitView.Pane>
+
+          <Grid>
+            <TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Center" VerticalAlignment="Center"  Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+            <TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" TextAlignment="Left"  Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+            <TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" HorizontalAlignment="Right" TextAlignment="Left"  Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+            <TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" TextAlignment="Left"  Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+            <TextBlock FontSize="14" FontWeight="700" Text="SplitViewContent" VerticalAlignment="Bottom" HorizontalAlignment="Right" TextAlignment="Left"  Foreground="{DynamicResource SystemControlForegroundBaseHighBrush}" />
+          </Grid>
+
+        </SplitView>
+      </Border>
+      
+    </Grid>
+  </Border>
+
+</UserControl>

+ 21 - 0
samples/ControlCatalog/Pages/SplitViewPage.xaml.cs

@@ -0,0 +1,21 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+    public class SplitViewPage : UserControl
+    {
+        public SplitViewPage()
+        {
+            this.InitializeComponent();
+            DataContext = new SplitViewPageViewModel();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 19 - 0
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml

@@ -0,0 +1,19 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="ControlCatalog.Pages.WindowCustomizationsPage">
+  <StackPanel Spacing="10"  Margin="25">
+    <CheckBox Content="Extend Client Area to Decorations" IsChecked="{Binding ExtendClientAreaEnabled}" />
+    <CheckBox Content="Title Bar" IsChecked="{Binding SystemTitleBarEnabled}" />    
+    <CheckBox Content="Prefer System Chrome" IsChecked="{Binding PreferSystemChromeEnabled}" />
+    <Slider Minimum="-1" Maximum="200" Value="{Binding TitleBarHeight}" />
+    <ComboBox x:Name="TransparencyLevels" SelectedIndex="{Binding TransparencyLevel}">
+      <ComboBoxItem>None</ComboBoxItem>
+      <ComboBoxItem>Transparent</ComboBoxItem>
+      <ComboBoxItem>Blur</ComboBoxItem>
+      <ComboBoxItem>AcrylicBlur</ComboBoxItem>
+    </ComboBox>
+  </StackPanel>
+</UserControl>

+ 19 - 0
samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml.cs

@@ -0,0 +1,19 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace ControlCatalog.Pages
+{
+    public class WindowCustomizationsPage : UserControl
+    {
+        public WindowCustomizationsPage()
+        {
+            this.InitializeComponent();
+        }
+
+        private void InitializeComponent()
+        {
+            AvaloniaXamlLoader.Load(this);
+        }
+    }
+}

+ 65 - 0
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@@ -3,6 +3,8 @@ using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Notifications;
 using Avalonia.Dialogs;
+using Avalonia.Platform;
+using System;
 using ReactiveUI;
 
 namespace ControlCatalog.ViewModels
@@ -14,6 +16,12 @@ namespace ControlCatalog.ViewModels
         private bool _isMenuItemChecked = true;
         private WindowState _windowState;
         private WindowState[] _windowStates;
+        private int _transparencyLevel;
+        private ExtendClientAreaChromeHints _chromeHints;
+        private bool _extendClientAreaEnabled;
+        private bool _systemTitleBarEnabled;        
+        private bool _preferSystemChromeEnabled;
+        private double _titleBarHeight;
 
         public MainWindowViewModel(IManagedNotificationManager notificationManager)
         {
@@ -62,6 +70,63 @@ namespace ControlCatalog.ViewModels
                 WindowState.Maximized,
                 WindowState.FullScreen,
             };
+
+            this.WhenAnyValue(x => x.SystemTitleBarEnabled, x=>x.PreferSystemChromeEnabled)
+                .Subscribe(x =>
+                {
+                    var hints = ExtendClientAreaChromeHints.NoChrome | ExtendClientAreaChromeHints.OSXThickTitleBar;
+
+                    if(x.Item1)
+                    {
+                        hints |= ExtendClientAreaChromeHints.SystemChrome;
+                    }
+
+                    if(x.Item2)
+                    {
+                        hints |= ExtendClientAreaChromeHints.PreferSystemChrome;
+                    }
+
+                    ChromeHints = hints;
+                });
+
+            SystemTitleBarEnabled = true;            
+            TitleBarHeight = -1;
+        }        
+
+        public int TransparencyLevel
+        {
+            get { return _transparencyLevel; }
+            set { this.RaiseAndSetIfChanged(ref _transparencyLevel, value); }
+        }        
+
+        public ExtendClientAreaChromeHints ChromeHints
+        {
+            get { return _chromeHints; }
+            set { this.RaiseAndSetIfChanged(ref _chromeHints, value); }
+        }        
+
+        public bool ExtendClientAreaEnabled
+        {
+            get { return _extendClientAreaEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _extendClientAreaEnabled, value); }
+        }        
+
+        public bool SystemTitleBarEnabled
+        {
+            get { return _systemTitleBarEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _systemTitleBarEnabled, value); }
+        }        
+
+        public bool PreferSystemChromeEnabled
+        {
+            get { return _preferSystemChromeEnabled; }
+            set { this.RaiseAndSetIfChanged(ref _preferSystemChromeEnabled, value); }
+        }        
+
+        public double TitleBarHeight
+        {
+            get { return _titleBarHeight; }
+            set { this.RaiseAndSetIfChanged(ref _titleBarHeight, value); }
         }
 
         public WindowState WindowState

+ 46 - 0
samples/ControlCatalog/ViewModels/SplitViewPageViewModel.cs

@@ -0,0 +1,46 @@
+using System;
+using Avalonia.Controls;
+using ReactiveUI;
+
+namespace ControlCatalog.ViewModels
+{
+    public class SplitViewPageViewModel : ReactiveObject
+    {
+        private bool _isLeft = true;
+        private int _displayMode = 3; //CompactOverlay
+
+        public bool IsLeft
+        {
+            get => _isLeft;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref _isLeft, value);
+                this.RaisePropertyChanged(nameof(PanePlacement));
+            }
+        }
+        
+        public int DisplayMode
+        {
+            get => _displayMode;
+            set
+            {
+                this.RaiseAndSetIfChanged(ref _displayMode, value);
+                this.RaisePropertyChanged(nameof(CurrentDisplayMode));
+            }
+        }
+
+        public SplitViewPanePlacement PanePlacement => _isLeft ? SplitViewPanePlacement.Left : SplitViewPanePlacement.Right;
+        
+        public SplitViewDisplayMode CurrentDisplayMode
+        {
+            get
+            {
+                if (Enum.IsDefined(typeof(SplitViewDisplayMode), _displayMode))
+                {
+                    return (SplitViewDisplayMode)_displayMode;
+                }
+                return SplitViewDisplayMode.CompactOverlay;
+            }
+        }
+    }
+}

+ 9 - 0
samples/interop/NativeEmbedSample/MainWindow.xaml

@@ -20,7 +20,16 @@
     <DockPanel DockPanel.Dock="Top">
       <Button DockPanel.Dock="Right" Click="ShowPopupDelay">Show popup (delay)</Button>
       <Button DockPanel.Dock="Right" Click="ShowPopup">Show popup</Button>
+      <Border DockPanel.Dock="Right" Background="#c0c0c0">
+        <ToolTip.Tip>
+          <ToolTip>
+              <TextBlock>Text</TextBlock>
+          </ToolTip>
+        </ToolTip.Tip>
+        <TextBlock>Tooltip</TextBlock>
+      </Border>
       <TextBox Text="Lorem ipsum dolor sit amet"/>
+      
     </DockPanel>
     <Grid ColumnDefinitions="*,5,*">
       <DockPanel>

+ 6 - 0
src/Avalonia.Animation/Easing/Easing.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
+using System.Globalization;
 using System.Linq;
 
 namespace Avalonia.Animation.Easings
@@ -25,6 +26,11 @@ namespace Avalonia.Animation.Easings
         /// <returns>Returns the instance of the parsed type.</returns>
         public static Easing Parse(string e)
         {
+            if (e.Contains(','))
+            {
+                return new SplineEasing(KeySpline.Parse(e, CultureInfo.InvariantCulture));
+            }
+
             if (_easingTypes == null)
             {
                 _easingTypes = new Dictionary<string, Type>();

+ 85 - 0
src/Avalonia.Animation/Easing/SplineEasing.cs

@@ -0,0 +1,85 @@
+namespace Avalonia.Animation.Easings
+{
+    /// <summary>
+    /// Eases a <see cref="double"/> value
+    /// using a user-defined cubic bezier curve.
+    /// Good for custom easing functions that doesn't quite
+    /// fit with the built-in ones. 
+    /// </summary>
+    public class SplineEasing : Easing
+    {
+        /// <summary>
+        /// X coordinate of the first control point
+        /// </summary>
+        public double X1
+        {
+            get => _internalKeySpline.ControlPointX1;
+            set
+            {
+                _internalKeySpline.ControlPointX1 = value;
+            }
+        }
+
+        /// <summary>
+        /// Y coordinate of the first control point
+        /// </summary>
+        public double Y1
+        {
+            get => _internalKeySpline.ControlPointY1;
+            set
+            {
+                _internalKeySpline.ControlPointY1 = value;
+            }
+        }
+
+        /// <summary>
+        /// X coordinate of the second control point
+        /// </summary> 
+        public double X2
+        {
+            get => _internalKeySpline.ControlPointX2;
+            set
+            {
+                _internalKeySpline.ControlPointX2 = value;
+            }
+        }
+
+        /// <summary>
+        /// Y coordinate of the second control point
+        /// </summary>
+        public double Y2
+        {
+            get => _internalKeySpline.ControlPointY2;
+            set
+            {
+                _internalKeySpline.ControlPointY2 = value;
+            }
+        }
+
+        private readonly KeySpline _internalKeySpline;
+
+        public SplineEasing(double x1 = 0d, double y1 = 0d, double x2 = 1d, double y2 = 1d)
+        {
+            _internalKeySpline = new KeySpline();
+
+            this.X1 = x1;
+            this.Y1 = y1;
+            this.X2 = x2;
+            this.Y1 = y2;
+        }
+
+        public SplineEasing(KeySpline keySpline)
+        {
+            _internalKeySpline = keySpline;
+        }
+        
+        public SplineEasing()
+        {
+            _internalKeySpline = new KeySpline();
+        }
+
+        /// <inheritdoc/>
+        public override double Ease(double progress) =>
+            _internalKeySpline.GetSplineProgress(progress);
+    }
+}

+ 16 - 19
src/Avalonia.Animation/KeySpline.cs

@@ -81,7 +81,10 @@ namespace Avalonia.Animation
         /// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
         public static KeySpline Parse(string value, CultureInfo culture)
         {
-            using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline."))
+            if (culture is null)
+                culture = CultureInfo.InvariantCulture;
+
+            using (var tokenizer = new StringTokenizer((string)value, culture, exceptionMessage: $"Invalid KeySpline string: \"{value}\"."))
             {
                 return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
             }
@@ -98,6 +101,7 @@ namespace Avalonia.Animation
                 if (IsValidXValue(value))
                 {
                     _controlPointX1 = value;
+                    _isDirty = true;
                 }
                 else
                 {
@@ -112,7 +116,11 @@ namespace Avalonia.Animation
         public double ControlPointY1
         {
             get => _controlPointY1;
-            set => _controlPointY1 = value;
+            set
+            {
+                _controlPointY1 = value;
+                _isDirty = true;
+            }
         }
 
         /// <summary>
@@ -126,6 +134,7 @@ namespace Avalonia.Animation
                 if (IsValidXValue(value))
                 {
                     _controlPointX2 = value;
+                    _isDirty = true;
                 }
                 else
                 {
@@ -140,7 +149,11 @@ namespace Avalonia.Animation
         public double ControlPointY2
         {
             get => _controlPointY2;
-            set => _controlPointY2 = value;
+            set
+            {
+                _controlPointY2 = value;
+                _isDirty = true;
+            }
         }
 
         /// <summary>
@@ -330,20 +343,4 @@ namespace Avalonia.Animation
             }
         }
     }
-
-    /// <summary>
-    /// Converts string values to <see cref="KeySpline"/> values
-    /// </summary>
-    public class KeySplineTypeConverter : TypeConverter
-    {
-        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
-        {
-            return sourceType == typeof(string);
-        }
-
-        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
-        {
-            return KeySpline.Parse((string)value, culture);
-        }
-    }
 }

+ 25 - 0
src/Avalonia.Animation/KeySplineTypeConverter.cs

@@ -0,0 +1,25 @@
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+// Ported from WPF open-source code.
+// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
+
+namespace Avalonia.Animation
+{
+    /// <summary>
+    /// Converts string values to <see cref="KeySpline"/> values
+    /// </summary>
+    public class KeySplineTypeConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+        {
+            return sourceType == typeof(string);
+        }
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        {
+            return KeySpline.Parse((string)value, culture);
+        }
+    }
+}

+ 1 - 3
src/Avalonia.Base/AvaloniaProperty.cs

@@ -160,8 +160,6 @@ namespace Avalonia
         /// </summary>
         internal int Id { get; }
 
-        internal bool HasChangedSubscriptions => _changed?.HasObservers ?? false;
-
         /// <summary>
         /// Provides access to a property's binding via the <see cref="AvaloniaObject"/>
         /// indexer.
@@ -513,7 +511,7 @@ namespace Avalonia
         /// <returns>
         /// An <see cref="IDisposable"/> if setting the property can be undone, otherwise null.
         /// </returns>
-        internal abstract IDisposable? RouteSetValue(
+        internal abstract IDisposable RouteSetValue(
             IAvaloniaObject o,
             object value,
             BindingPriority priority);

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

@@ -362,7 +362,7 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <remarks>
         /// You won't usually want to call this method directly, instead use the
-        /// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TOwner, TValue, TValue}, Action{IAvaloniaObject, bool})"/>
+        /// <see cref="AvaloniaProperty.Register{TOwner, TValue}(string, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue}, Action{IAvaloniaObject, bool})"/>
         /// method.
         /// </remarks>
         public void Register(Type type, AvaloniaProperty property)
@@ -413,7 +413,7 @@ namespace Avalonia
         /// <param name="property">The property.</param>
         /// <remarks>
         /// You won't usually want to call this method directly, instead use the
-        /// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{THost, TValue, TValue})"/>
+        /// <see cref="AvaloniaProperty.RegisterAttached{THost, TValue}(string, Type, TValue, bool, Data.BindingMode, Func{TValue, bool}, Func{IAvaloniaObject, TValue, TValue})"/>
         /// method.
         /// </remarks>
         public void RegisterAttached(Type type, AvaloniaProperty property)

+ 1 - 0
src/Avalonia.Base/Collections/AvaloniaListExtensions.cs

@@ -140,6 +140,7 @@ namespace Avalonia.Collections
             }
         }
 
+        [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
         public static IAvaloniaReadOnlyList<TDerived> CreateDerivedList<TSource, TDerived>(
             this IAvaloniaReadOnlyList<TSource> collection,
             Func<TSource, TDerived> select)

+ 2 - 0
src/Avalonia.Base/Collections/Pooled/IReadOnlyPooledList.cs

@@ -13,9 +13,11 @@ namespace Avalonia.Collections.Pooled
 
     public interface IReadOnlyPooledList<T> : IReadOnlyList<T>
     {
+#pragma warning disable CS0419
         /// <summary>
         /// Gets a <see cref="System.ReadOnlySpan{T}"/> for the items currently in the collection.
         /// </summary>
+#pragma warning restore CS0419
         ReadOnlySpan<T> Span { get; }
     }
 }

+ 2 - 1
src/Avalonia.Base/Collections/Pooled/PooledList.cs

@@ -138,7 +138,6 @@ namespace Avalonia.Collections.Pooled
         /// initially empty, but will have room for the given number of elements
         /// before any reallocations are required.
         /// </summary>
-        /// <param name="sizeToCapacity">If true, Count of list equals capacity. Depending on ClearMode, rented items may or may not hold dirty values.</param>
         public PooledList(int capacity, ClearMode clearMode, ArrayPool<T> customPool, bool sizeToCapacity)
         {
             if (capacity < 0)
@@ -499,11 +498,13 @@ namespace Avalonia.Collections.Pooled
         public void AddRange(T[] array)
             => AddRange(array.AsSpan());
 
+#pragma warning disable CS0419
         /// <summary>
         /// Adds the elements of the given <see cref="ReadOnlySpan{T}"/> to the end of this list. If
         /// required, the capacity of the list is increased to twice the previous
         /// capacity or the new size, whichever is larger.
         /// </summary>
+#pragma warning restore CS0419
         public void AddRange(ReadOnlySpan<T> span)
         {
             var newSpan = InsertSpan(_size, span.Length, false);

+ 19 - 15
src/Avalonia.Base/Data/BindingValue.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Diagnostics.CodeAnalysis;
 using Avalonia.Utilities;
 
 #nullable enable
@@ -81,14 +82,14 @@ namespace Avalonia.Data
     /// </remarks>
     public readonly struct BindingValue<T>
     {
-        private readonly T _value;
+        [AllowNull] private readonly T _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="BindingValue{T}"/> struct with a type of
         /// <see cref="BindingValueType.Value"/>
         /// </summary>
         /// <param name="value">The value.</param>
-        public BindingValue(T value)
+        public BindingValue([AllowNull] T value)
         {
             ValidateValue(value);
             _value = value;
@@ -96,7 +97,7 @@ namespace Avalonia.Data
             Error = null;
         }
 
-        private BindingValue(BindingValueType type, T value, Exception? error)
+        private BindingValue(BindingValueType type, [AllowNull] T value, Exception? error)
         {
             _value = value;
             Type = type;
@@ -154,7 +155,7 @@ namespace Avalonia.Data
                 BindingValueType.UnsetValue => AvaloniaProperty.UnsetValue,
                 BindingValueType.DoNothing => BindingOperations.DoNothing,
                 BindingValueType.Value => _value,
-                BindingValueType.BindingError => 
+                BindingValueType.BindingError =>
                     new BindingNotification(Error, BindingErrorType.Error),
                 BindingValueType.BindingErrorWithFallback =>
                     new BindingNotification(Error, BindingErrorType.Error, Value),
@@ -175,7 +176,7 @@ namespace Avalonia.Data
         /// The binding type is <see cref="BindingValueType.UnsetValue"/> or
         /// <see cref="BindingValueType.DoNothing"/>.
         /// </exception>
-        public BindingValue<T> WithValue(T value)
+        public BindingValue<T> WithValue([AllowNull] T value)
         {
             if (Type == BindingValueType.DoNothing)
             {
@@ -190,6 +191,7 @@ namespace Avalonia.Data
         /// Gets the value of the binding value if present, otherwise the default value.
         /// </summary>
         /// <returns>The value.</returns>
+        [return: MaybeNull]
         public T GetValueOrDefault() => HasValue ? _value : default;
 
         /// <summary>
@@ -206,6 +208,7 @@ namespace Avalonia.Data
         /// The value if present and of the correct type, `default(TResult)` if the value is
         /// not present or of an incorrect type.
         /// </returns>
+        [return: MaybeNull]
         public TResult GetValueOrDefault<TResult>()
         {
             return HasValue ?
@@ -222,7 +225,8 @@ namespace Avalonia.Data
         /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
         /// value is not present.
         /// </returns>
-        public TResult GetValueOrDefault<TResult>(TResult defaultValue)
+        [return: MaybeNull]
+        public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
         {
             return HasValue ?
                 _value is TResult result ? result : default
@@ -242,7 +246,7 @@ namespace Avalonia.Data
                 UnsetValueType _ => Unset,
                 DoNothingType _ => DoNothing,
                 BindingNotification n => n.ToBindingValue().Cast<T>(),
-                _ => (T)value
+                _ => new BindingValue<T>((T)value)
             };
         }
 
@@ -250,7 +254,7 @@ namespace Avalonia.Data
         /// Creates a binding value from an instance of the underlying value type.
         /// </summary>
         /// <param name="value">The value.</param>
-        public static implicit operator BindingValue<T>(T value) => new BindingValue<T>(value);
+        public static implicit operator BindingValue<T>([AllowNull] T value) => new BindingValue<T>(value);
 
         /// <summary>
         /// Creates a binding value from an <see cref="Optional{T}"/>.
@@ -278,7 +282,7 @@ namespace Avalonia.Data
         /// <param name="e">The binding error.</param>
         public static BindingValue<T> BindingError(Exception e)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(BindingValueType.BindingError, default, e);
         }
@@ -290,7 +294,7 @@ namespace Avalonia.Data
         /// <param name="fallbackValue">The fallback value.</param>
         public static BindingValue<T> BindingError(Exception e, T fallbackValue)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(BindingValueType.BindingErrorWithFallback, fallbackValue, e);
         }
@@ -303,7 +307,7 @@ namespace Avalonia.Data
         /// <param name="fallbackValue">The fallback value.</param>
         public static BindingValue<T> BindingError(Exception e, Optional<T> fallbackValue)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(
                 fallbackValue.HasValue ?
@@ -319,7 +323,7 @@ namespace Avalonia.Data
         /// <param name="e">The data validation error.</param>
         public static BindingValue<T> DataValidationError(Exception e)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(BindingValueType.DataValidationError, default, e);
         }
@@ -331,7 +335,7 @@ namespace Avalonia.Data
         /// <param name="fallbackValue">The fallback value.</param>
         public static BindingValue<T> DataValidationError(Exception e, T fallbackValue)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(BindingValueType.DataValidationErrorWithFallback, fallbackValue, e);
         }
@@ -344,7 +348,7 @@ namespace Avalonia.Data
         /// <param name="fallbackValue">The fallback value.</param>
         public static BindingValue<T> DataValidationError(Exception e, Optional<T> fallbackValue)
         {
-            e = e ?? throw new ArgumentNullException("e");
+            e = e ?? throw new ArgumentNullException(nameof(e));
 
             return new BindingValue<T>(
                 fallbackValue.HasValue ?
@@ -354,7 +358,7 @@ namespace Avalonia.Data
                 e);
         }
 
-        private static void ValidateValue(T value)
+        private static void ValidateValue([AllowNull] T value)
         {
             if (value is UnsetValueType)
             {

+ 26 - 10
src/Avalonia.Base/Data/Core/LogicalNotNode.cs

@@ -12,8 +12,19 @@ namespace Avalonia.Data.Core
             base.NextValueChanged(Negate(value));
         }
 
-        private static object Negate(object v)
+        private static object Negate(object value)
         {
+            var notification = value as BindingNotification;
+            var v = BindingNotification.ExtractValue(value);
+
+            BindingNotification GenerateError(Exception e)
+            {
+                notification ??= new BindingNotification(AvaloniaProperty.UnsetValue);
+                notification.AddError(e, BindingErrorType.Error);
+                notification.ClearValue();
+                return notification;
+            }
+
             if (v != AvaloniaProperty.UnsetValue)
             {
                 var s = v as string;
@@ -28,9 +39,7 @@ namespace Avalonia.Data.Core
                     }
                     else
                     {
-                        return new BindingNotification(
-                            new InvalidCastException($"Unable to convert '{s}' to bool."), 
-                            BindingErrorType.Error);
+                        return GenerateError(new InvalidCastException($"Unable to convert '{s}' to bool."));
                     }
                 }
                 else
@@ -38,24 +47,31 @@ namespace Avalonia.Data.Core
                     try
                     {
                         var boolean = Convert.ToBoolean(v, CultureInfo.InvariantCulture);
-                        return !boolean;
+
+                        if (notification is object)
+                        {
+                            notification.SetValue(!boolean);
+                            return notification;
+                        }
+                        else
+                        {
+                            return !boolean;
+                        }
                     }
                     catch (InvalidCastException)
                     {
                         // The error message here is "Unable to cast object of type 'System.Object'
                         // to type 'System.IConvertible'" which is kinda useless so provide our own.
-                        return new BindingNotification(
-                            new InvalidCastException($"Unable to convert '{v}' to bool."),
-                            BindingErrorType.Error);
+                        return GenerateError(new InvalidCastException($"Unable to convert '{v}' to bool."));
                     }
                     catch (Exception e)
                     {
-                        return new BindingNotification(e, BindingErrorType.Error);
+                        return GenerateError(e);
                     }
                 }
             }
 
-            return AvaloniaProperty.UnsetValue;
+            return notification ?? AvaloniaProperty.UnsetValue;
         }
 
         public object Transform(object value)

+ 10 - 6
src/Avalonia.Base/Data/Optional.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
 
 #nullable enable
 
@@ -22,13 +23,13 @@ namespace Avalonia.Data
     /// </remarks>
     public readonly struct Optional<T> : IEquatable<Optional<T>>
     {
-        private readonly T _value;
+        [AllowNull] private readonly T _value;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="Optional{T}"/> struct with value.
         /// </summary>
         /// <param name="value">The value.</param>
-        public Optional(T value)
+        public Optional([AllowNull] T value)
         {
             _value = value;
             HasValue = true;
@@ -48,7 +49,7 @@ namespace Avalonia.Data
         public T Value => HasValue ? _value : throw new InvalidOperationException("Optional has no value.");
 
         /// <inheritdoc/>
-        public override bool Equals(object obj) => obj is Optional<T> o && this == o;
+        public override bool Equals(object? obj) => obj is Optional<T> o && this == o;
 
         /// <inheritdoc/>
         public bool Equals(Optional<T> other) => this == other;
@@ -69,6 +70,7 @@ namespace Avalonia.Data
         /// Gets the value if present, otherwise the default value.
         /// </summary>
         /// <returns>The value.</returns>
+        [return: MaybeNull]
         public T GetValueOrDefault() => HasValue ? _value : default;
 
         /// <summary>
@@ -85,6 +87,7 @@ namespace Avalonia.Data
         /// The value if present and of the correct type, `default(TResult)` if the value is
         /// not present or of an incorrect type.
         /// </returns>
+        [return: MaybeNull]
         public TResult GetValueOrDefault<TResult>()
         {
             return HasValue ?
@@ -101,7 +104,8 @@ namespace Avalonia.Data
         /// present but not of the correct type or null, or <paramref name="defaultValue"/> if the
         /// value is not present.
         /// </returns>
-        public TResult GetValueOrDefault<TResult>(TResult defaultValue)
+        [return: MaybeNull]
+        public TResult GetValueOrDefault<TResult>([AllowNull] TResult defaultValue)
         {
             return HasValue ?
                 _value is TResult result ? result : default
@@ -112,7 +116,7 @@ namespace Avalonia.Data
         /// Creates an <see cref="Optional{T}"/> from an instance of the underlying value type.
         /// </summary>
         /// <param name="value">The value.</param>
-        public static implicit operator Optional<T>(T value) => new Optional<T>(value);
+        public static implicit operator Optional<T>([AllowNull] T value) => new Optional<T>(value);
 
         /// <summary>
         /// Compares two <see cref="Optional{T}"/>s for inequality.
@@ -128,7 +132,7 @@ namespace Avalonia.Data
         /// <param name="x">The first value.</param>
         /// <param name="y">The second value.</param>
         /// <returns>True if the values are equal; otherwise false.</returns>
-        public static bool operator==(Optional<T> x, Optional<T> y)
+        public static bool operator ==(Optional<T> x, Optional<T> y)
         {
             if (!x.HasValue && !y.HasValue)
             {

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

@@ -120,7 +120,7 @@ namespace Avalonia
             return o.GetValue<TValue>(this);
         }
 
-        internal override object RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
+        internal override object? RouteGetBaseValue(IAvaloniaObject o, BindingPriority maxPriority)
         {
             return o.GetValue<TValue>(this);
         }

+ 0 - 2
src/Avalonia.Base/IStyledPropertyMetadata.cs

@@ -1,5 +1,3 @@
-using System;
-
 namespace Avalonia
 {
     /// <summary>

+ 140 - 0
src/Avalonia.Base/Metadata/NullableAttributes.cs

@@ -0,0 +1,140 @@
+#pragma warning disable MA0048 // File name must match type name
+#define INTERNAL_NULLABLE_ATTRIBUTES
+#if NETSTANDARD2_0 ||  NETCOREAPP2_0 ||  NETCOREAPP2_1 ||  NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
+
+// https://github.com/dotnet/corefx/blob/48363ac826ccf66fbe31a5dcb1dc2aab9a7dd768/src/Common/src/CoreLib/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
+
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Diagnostics.CodeAnalysis
+{
+    /// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class AllowNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class DisallowNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class MaybeNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class NotNullAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class MaybeNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter may be null.
+        /// </param>
+        public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class NotNullWhenAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified return value condition.</summary>
+        /// <param name="returnValue">
+        /// The return value condition. If the method returns this value, the associated parameter will not be null.
+        /// </param>
+        public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
+
+        /// <summary>Gets the return value condition.</summary>
+        public bool ReturnValue { get; }
+    }
+
+    /// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
+    [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class NotNullIfNotNullAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the associated parameter name.</summary>
+        /// <param name="parameterName">
+        /// The associated parameter name.  The output will be non-null if the argument to the parameter specified is non-null.
+        /// </param>
+        public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
+
+        /// <summary>Gets the associated parameter name.</summary>
+        public string ParameterName { get; }
+    }
+
+    /// <summary>Applied to a method that will never return under any circumstance.</summary>
+    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class DoesNotReturnAttribute : Attribute
+    { }
+
+    /// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
+    [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
+#if INTERNAL_NULLABLE_ATTRIBUTES
+    internal
+#else
+    public
+#endif
+        sealed class DoesNotReturnIfAttribute : Attribute
+    {
+        /// <summary>Initializes the attribute with the specified parameter value.</summary>
+        /// <param name="parameterValue">
+        /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
+        /// the associated parameter matches this value.
+        /// </param>
+        public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
+
+        /// <summary>Gets the condition parameter value.</summary>
+        public bool ParameterValue { get; }
+    }
+}
+#endif

+ 1 - 6
src/Avalonia.Base/PropertyStore/IPriorityValueEntry.cs

@@ -1,7 +1,4 @@
-using System;
-using Avalonia.Data;
-
-#nullable enable
+#nullable enable
 
 namespace Avalonia.PropertyStore
 {
@@ -10,8 +7,6 @@ namespace Avalonia.PropertyStore
     /// </summary>
     internal interface IPriorityValueEntry : IValue
     {
-        BindingPriority Priority { get; }
-
         void Reparent(IValueSink sink);
     }
 

+ 4 - 3
src/Avalonia.Base/PropertyStore/LocalValueEntry.cs

@@ -1,4 +1,5 @@
-using Avalonia.Data;
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Data;
 
 #nullable enable
 
@@ -11,9 +12,9 @@ namespace Avalonia.PropertyStore
     /// <typeparam name="T">The property type.</typeparam>
     internal class LocalValueEntry<T> : IValue<T>
     {
-        private T _value;
+        [AllowNull] private T _value;
 
-        public LocalValueEntry(T value) => _value = value;
+        public LocalValueEntry([AllowNull] T value) => _value = value;
         public BindingPriority Priority => BindingPriority.LocalValue;
         Optional<object> IValue.GetValue() => new Optional<object>(_value);
         

+ 1 - 2
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Diagnostics;
 using Avalonia.Data;
 
 namespace Avalonia
@@ -35,7 +34,7 @@ namespace Avalonia
         /// <summary>
         /// Gets the value coercion callback, if any.
         /// </summary>
-        public Func<IAvaloniaObject, TValue, TValue>? CoerceValue { get; private set; }
+        public Func<IAvaloniaObject, TValue, TValue> CoerceValue { get; private set; }
 
         object IStyledPropertyMetadata.DefaultValue => DefaultValue;
 

+ 45 - 0
src/Avalonia.Base/Utilities/StyleClassParser.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Globalization;
+
+namespace Avalonia.Utilities
+{
+#if !BUILDTASK
+    public
+#endif
+    static class StyleClassParser
+    {
+        public static ReadOnlySpan<char> ParseStyleClass(this ref CharacterReader r)
+        {
+            if (IsValidIdentifierStart(r.Peek))
+            {
+                return r.TakeWhile(c => IsValidIdentifierChar(c));
+            }
+            else
+            {
+                return ReadOnlySpan<char>.Empty;
+            }
+        }
+
+        private static bool IsValidIdentifierStart(char c)
+        {
+            return char.IsLetter(c) || c == '_';
+        }
+
+        private static bool IsValidIdentifierChar(char c)
+        {
+            if (IsValidIdentifierStart(c) || c == '-')
+            {
+                return true;
+            }
+            else
+            {
+                var cat = CharUnicodeInfo.GetUnicodeCategory(c);
+                return cat == UnicodeCategory.NonSpacingMark ||
+                       cat == UnicodeCategory.SpacingCombiningMark ||
+                       cat == UnicodeCategory.ConnectorPunctuation ||
+                       cat == UnicodeCategory.Format ||
+                       cat == UnicodeCategory.DecimalDigitNumber;
+            }
+        }
+    }
+}

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

@@ -162,7 +162,7 @@ namespace Avalonia
                         _sink.ValueChanged(new AvaloniaPropertyChangedEventArgs<T>(
                             _owner,
                             property,
-                            old,
+                            new Optional<T>(old),
                             default,
                             BindingPriority.Unset));
                     }

+ 3 - 0
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@@ -54,6 +54,9 @@
       <Compile Include="..\Markup\Avalonia.Markup\Markup\Parsers\BindingExpressionGrammar.cs">
         <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
       </Compile>
+      <Compile Include="../Avalonia.Base/Utilities/StyleClassParser.cs">
+        <Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
+      </Compile>
       <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\**\obj\**\*.cs" />
       <Compile Remove="../Markup/Avalonia.Markup.Xaml/XamlIl\xamlil.github\src\XamlIl\TypeSystem\SreTypeSystem.cs" />
       <PackageReference Include="Avalonia.Unofficial.Cecil" Version="20190417.2.0" PrivateAssets="All" />

+ 86 - 0
src/Avalonia.Controls/Chrome/CaptionButtons.cs

@@ -0,0 +1,86 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Chrome
+{
+    /// <summary>
+    /// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
+    /// </summary>
+    public class CaptionButtons : TemplatedControl
+    {
+        private CompositeDisposable? _disposables;
+        private Window? _hostWindow;
+
+        public void Attach(Window hostWindow)
+        {
+            if (_disposables == null)
+            {
+                _hostWindow = hostWindow;
+
+                _disposables = new CompositeDisposable
+                {
+                    _hostWindow.GetObservable(Window.WindowStateProperty)
+                    .Subscribe(x =>
+                    {
+                        PseudoClasses.Set(":minimized", x == WindowState.Minimized);
+                        PseudoClasses.Set(":normal", x == WindowState.Normal);
+                        PseudoClasses.Set(":maximized", x == WindowState.Maximized);
+                        PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
+                    })
+                };
+            }
+        }
+
+        public void Detach()
+        {
+            if (_disposables != null)
+            {
+                var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
+
+                layer?.Children.Remove(this);
+
+                _disposables.Dispose();
+                _disposables = null;
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            var closeButton = e.NameScope.Get<Panel>("PART_CloseButton");
+            var restoreButton = e.NameScope.Get<Panel>("PART_RestoreButton");
+            var minimiseButton = e.NameScope.Get<Panel>("PART_MinimiseButton");
+            var fullScreenButton = e.NameScope.Get<Panel>("PART_FullScreenButton");
+
+            closeButton.PointerReleased += (sender, e) => _hostWindow?.Close();
+
+            restoreButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
+                }
+            };
+
+            minimiseButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = WindowState.Minimized;
+                }
+            };
+
+            fullScreenButton.PointerReleased += (sender, e) =>
+            {
+                if (_hostWindow != null)
+                {
+                    _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen;
+                }
+            };
+        }
+    }
+}

+ 117 - 0
src/Avalonia.Controls/Chrome/TitleBar.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Reactive.Disposables;
+using Avalonia.Controls.Primitives;
+
+#nullable enable
+
+namespace Avalonia.Controls.Chrome
+{
+    /// <summary>
+    /// Draws a titlebar when managed client decorations are enabled.
+    /// </summary>
+    public class TitleBar : TemplatedControl
+    {
+        private CompositeDisposable? _disposables;
+        private readonly Window? _hostWindow;
+        private CaptionButtons? _captionButtons;
+
+        public TitleBar(Window hostWindow)
+        {
+            _hostWindow = hostWindow;
+        }
+
+        public TitleBar()
+        {
+
+        }
+
+        public void Attach()
+        {
+            if (_disposables == null)
+            {
+                var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
+
+                layer?.Children.Add(this);
+
+                if (_hostWindow != null)
+                {
+                    _disposables = new CompositeDisposable
+                    {
+                        _hostWindow.GetObservable(Window.WindowDecorationMarginProperty)
+                            .Subscribe(x => UpdateSize()),
+
+                        _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty)
+                            .Subscribe(x => UpdateSize()),
+
+                        _hostWindow.GetObservable(Window.OffScreenMarginProperty)
+                            .Subscribe(x => UpdateSize()),
+
+                        _hostWindow.GetObservable(Window.WindowStateProperty)
+                            .Subscribe(x =>
+                            {
+                                PseudoClasses.Set(":minimized", x == WindowState.Minimized);
+                                PseudoClasses.Set(":normal", x == WindowState.Normal);
+                                PseudoClasses.Set(":maximized", x == WindowState.Maximized);
+                                PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen);
+                            })
+                    };
+
+                    _captionButtons?.Attach(_hostWindow);
+                }
+
+                UpdateSize();
+            }
+        }
+
+        private void UpdateSize()
+        {
+            if (_hostWindow != null)
+            {
+                Margin = new Thickness(
+                    _hostWindow.OffScreenMargin.Left,
+                    _hostWindow.OffScreenMargin.Top,
+                    _hostWindow.OffScreenMargin.Right,
+                    _hostWindow.OffScreenMargin.Bottom);
+
+                if (_hostWindow.WindowState != WindowState.FullScreen)
+                {
+                    Height = _hostWindow.WindowDecorationMargin.Top;
+
+                    if (_captionButtons != null)
+                    {
+                        _captionButtons.Height = Height;
+                    }
+                }
+            }
+        }
+
+        public void Detach()
+        {
+            if (_disposables != null)
+            {
+                var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow);
+
+                layer?.Children.Remove(this);
+
+                _disposables.Dispose();
+                _disposables = null;
+
+                _captionButtons?.Detach();
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            _captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons");
+
+            if (_hostWindow != null)
+            {
+                _captionButtons.Attach(_hostWindow);
+            }
+
+            UpdateSize();
+        }
+    }
+}

+ 412 - 0
src/Avalonia.Controls/DateTimePickers/DatePicker.cs

@@ -0,0 +1,412 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Controls.Templates;
+using Avalonia.Interactivity;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// A control to allow the user to select a date
+    /// </summary>
+    public class DatePicker : TemplatedControl
+    {
+        /// <summary>
+        /// Define the <see cref="DayFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, string> DayFormatProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(DayFormat),
+                x => x.DayFormat, (x, v) => x.DayFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="DayVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, bool> DayVisibleProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(DayVisible),
+                x => x.DayVisible, (x, v) => x.DayVisible = v);
+
+        /// <summary>
+        /// Defines the <see cref="Header"/> Property
+        /// </summary>
+        public static readonly StyledProperty<object> HeaderProperty =
+            AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
+
+        /// <summary>
+        /// Defines the <see cref="HeaderTemplate"/> Property
+        /// </summary>
+        public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
+            AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
+
+        /// <summary>
+        /// Defines the <see cref="MaxYear"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, DateTimeOffset> MaxYearProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MaxYear), 
+                x => x.MaxYear, (x, v) => x.MaxYear = v);
+
+        /// <summary>
+        /// Defines the <see cref="MinYear"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, DateTimeOffset> MinYearProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset>(nameof(MinYear), 
+                x => x.MinYear, (x, v) => x.MinYear = v);
+
+        /// <summary>
+        /// Defines the <see cref="MonthFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, string> MonthFormatProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(MonthFormat), 
+                x => x.MonthFormat, (x, v) => x.MonthFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="MonthVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, bool> MonthVisibleProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(MonthVisible), 
+                x => x.MonthVisible, (x, v) => x.MonthVisible = v);
+
+        /// <summary>
+        /// Defiens the <see cref="YearFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, string> YearFormatProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, string>(nameof(YearFormat), 
+                x => x.YearFormat, (x, v) => x.YearFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="YearVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, bool> YearVisibleProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, bool>(nameof(YearVisible), 
+                x => x.YearVisible, (x, v) => x.YearVisible = v);
+
+        /// <summary>
+        /// Defines the <see cref="SelectedDate"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePicker, DateTimeOffset?> SelectedDateProperty =
+            AvaloniaProperty.RegisterDirect<DatePicker, DateTimeOffset?>(nameof(SelectedDate), 
+                x => x.SelectedDate, (x, v) => x.SelectedDate = v);
+
+        //Template Items
+        private Button _flyoutButton;
+        private TextBlock _dayText;
+        private TextBlock _monthText;
+        private TextBlock _yearText;
+        private Grid _container;
+        private Rectangle _spacer1;
+        private Rectangle _spacer2;
+        private Popup _popup;
+        private DatePickerPresenter _presenter;
+
+        private bool _areControlsAvailable;
+
+        private string _dayFormat = "%d";
+        private bool _dayVisible = true;
+        private DateTimeOffset _maxYear;
+        private DateTimeOffset _minYear;
+        private string _monthFormat = "MMMM";
+        private bool _monthVisible = true;
+        private string _yearFormat = "yyyy";
+        private bool _yearVisible = true;
+        private DateTimeOffset? _selectedDate;
+
+        public DatePicker()
+        {
+            PseudoClasses.Set(":hasnodate", true);
+            var now = DateTimeOffset.Now;
+            _minYear = new DateTimeOffset(now.Date.Year - 100, 1, 1, 0, 0, 0, now.Offset);
+            _maxYear = new DateTimeOffset(now.Date.Year + 100, 12, 31, 0, 0, 0, now.Offset);
+        }
+
+        public string DayFormat
+        {
+            get => _dayFormat;
+            set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the day is visible
+        /// </summary>
+        public bool DayVisible
+        {
+            get => _dayVisible;
+            set
+            {
+                SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
+                SetGrid();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the DatePicker header
+        /// </summary>
+        public object Header
+        {
+            get => GetValue(HeaderProperty);
+            set => SetValue(HeaderProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the header template
+        /// </summary>
+        public IDataTemplate HeaderTemplate
+        {
+            get => GetValue(HeaderTemplateProperty);
+            set => SetValue(HeaderTemplateProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum year for the picker
+        /// </summary>
+        public DateTimeOffset MaxYear
+        {
+            get => _maxYear;
+            set
+            {
+                if (value < MinYear)
+                    throw new InvalidOperationException("MaxDate cannot be less than MinDate");
+                SetAndRaise(MaxYearProperty, ref _maxYear, value);
+
+                if (SelectedDate.HasValue && SelectedDate.Value > value)
+                    SelectedDate = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum year for the picker
+        /// </summary>
+        public DateTimeOffset MinYear
+        {
+            get => _minYear;
+            set
+            {
+                if (value > MaxYear)
+                    throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
+                SetAndRaise(MinYearProperty, ref _minYear, value);
+
+                if (SelectedDate.HasValue && SelectedDate.Value < value)
+                    SelectedDate = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the month format
+        /// </summary>
+        public string MonthFormat
+        {
+            get => _monthFormat;
+            set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the month is visible
+        /// </summary>
+        public bool MonthVisible
+        {
+            get => _monthVisible;
+            set
+            {
+                SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
+                SetGrid();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the year format
+        /// </summary>
+        public string YearFormat
+        {
+            get => _yearFormat;
+            set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the year is visible
+        /// </summary>
+        public bool YearVisible
+        {
+            get => _yearVisible;
+            set
+            {
+                SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
+                SetGrid();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the Selected Date for the picker, can be null
+        /// </summary>
+        public DateTimeOffset? SelectedDate
+        {
+            get => _selectedDate;
+            set
+            {
+                var old = _selectedDate;
+                SetAndRaise(SelectedDateProperty, ref _selectedDate, value);
+                SetSelectedDateText();
+                OnSelectedDateChanged(this, new DatePickerSelectedValueChangedEventArgs(old, value));
+            }
+        }
+
+        /// <summary>
+        /// Raised when the <see cref="SelectedDate"/> changes
+        /// </summary>
+        public event EventHandler<DatePickerSelectedValueChangedEventArgs> SelectedDateChanged;
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            _areControlsAvailable = false;
+            if (_flyoutButton != null)
+                _flyoutButton.Click -= OnFlyoutButtonClicked;
+            if (_presenter != null)
+            {
+                _presenter.Confirmed -= OnConfirmed;
+                _presenter.Dismissed -= OnDismissPicker;
+            }
+
+            base.OnApplyTemplate(e);
+            _flyoutButton = e.NameScope.Find<Button>("FlyoutButton");
+            _dayText = e.NameScope.Find<TextBlock>("DayText");
+            _monthText = e.NameScope.Find<TextBlock>("MonthText");
+            _yearText = e.NameScope.Find<TextBlock>("YearText");
+            _container = e.NameScope.Find<Grid>("ButtonContentGrid");
+            _spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer");
+            _spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer");
+            _popup = e.NameScope.Find<Popup>("Popup");
+            _presenter = e.NameScope.Find<DatePickerPresenter>("PickerPresenter");
+
+            _areControlsAvailable = true;
+
+            SetGrid();
+            SetSelectedDateText();
+
+            if (_flyoutButton != null)
+                _flyoutButton.Click += OnFlyoutButtonClicked;
+
+            if (_presenter != null)
+            {
+                _presenter.Confirmed += OnConfirmed;
+                _presenter.Dismissed += OnDismissPicker;
+
+                _presenter[!DatePickerPresenter.MaxYearProperty] = this[!MaxYearProperty];
+                _presenter[!DatePickerPresenter.MinYearProperty] = this[!MinYearProperty];
+
+                _presenter[!DatePickerPresenter.MonthVisibleProperty] = this[!MonthVisibleProperty];
+                _presenter[!DatePickerPresenter.MonthFormatProperty] = this[!MonthFormatProperty];
+
+                _presenter[!DatePickerPresenter.DayVisibleProperty] = this[!DayVisibleProperty];
+                _presenter[!DatePickerPresenter.DayFormatProperty] = this[!DayFormatProperty];
+
+                _presenter[!DatePickerPresenter.YearVisibleProperty] = this[!YearVisibleProperty];
+                _presenter[!DatePickerPresenter.YearFormatProperty] = this[!YearFormatProperty];
+            }
+        }
+
+        private void OnDismissPicker(object sender, EventArgs e)
+        {
+            _popup.Close();
+            Focus();
+        }
+
+        private void OnConfirmed(object sender, EventArgs e)
+        {
+            _popup.Close();
+            SelectedDate = _presenter.Date;
+        }
+
+        private void SetGrid()
+        {
+            if (_container == null)
+                return;
+
+            var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+            var columns = new List<(TextBlock, int)>
+            {
+                (_monthText, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
+                (_yearText, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
+                (_dayText, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1),
+            };
+
+            columns.Sort((x, y) => x.Item2 - y.Item2);
+            _container.ColumnDefinitions.Clear();
+
+            var columnIndex = 0;
+
+            foreach (var column in columns)
+            {
+                if (column.Item1 is null)
+                    continue;
+
+                column.Item1.IsVisible = column.Item2 != -1;
+
+                if (column.Item2 != -1)
+                {
+                    if (columnIndex > 0)
+                    {
+                        _container.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto));
+                    }
+
+                    _container.ColumnDefinitions.Add(
+                        new ColumnDefinition(column.Item1 == _monthText ? 138 : 78, GridUnitType.Star));
+
+                    if (column.Item1.Parent is null)
+                    {
+                        _container.Children.Add(column.Item1);
+                    }
+
+                    Grid.SetColumn(column.Item1, (columnIndex++ * 2));
+                }
+            }
+
+            Grid.SetColumn(_spacer1, 1);
+            Grid.SetColumn(_spacer2, 3);
+            _spacer1.IsVisible = columnIndex > 1;
+            _spacer2.IsVisible = columnIndex > 2;
+        }
+
+        private void SetSelectedDateText()
+        {
+            if (!_areControlsAvailable)
+                return;
+
+            if (SelectedDate.HasValue)
+            {
+                PseudoClasses.Set(":hasnodate", false);
+                var selDate = SelectedDate.Value;
+                _monthText.Text = selDate.ToString(MonthFormat);
+                _yearText.Text = selDate.ToString(YearFormat);
+                _dayText.Text = selDate.ToString(DayFormat);
+            }
+            else
+            {
+                PseudoClasses.Set(":hasnodate", true);
+                _monthText.Text = "month";
+                _yearText.Text = "year";
+                _dayText.Text = "day";
+            }
+        }
+
+        private void OnFlyoutButtonClicked(object sender, RoutedEventArgs e)
+        {
+            if (_presenter == null)
+                throw new InvalidOperationException("No DatePickerPresenter found");
+
+            _presenter.Date = SelectedDate ?? DateTimeOffset.Now;
+
+            _popup.IsOpen = true;
+
+            var deltaY = _presenter.GetOffsetForPopup();
+
+            //The extra 5 px I think is related to default popup placement behavior
+            _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
+                Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
+                 Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
+        }
+
+        protected virtual void OnSelectedDateChanged(object sender, DatePickerSelectedValueChangedEventArgs e)
+        {
+            SelectedDateChanged?.Invoke(sender, e);
+        }
+    }
+}

+ 531 - 0
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@@ -0,0 +1,531 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines the presenter used for selecting a date for a 
+    /// <see cref="DatePicker"/>
+    /// </summary>
+    public class DatePickerPresenter : PickerPresenterBase
+    {
+        /// <summary>
+        /// Defines the <see cref="Date"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> DateProperty =
+            AvaloniaProperty.RegisterDirect<DatePickerPresenter, DateTimeOffset>(nameof(Date), 
+                x => x.Date, (x, v) => x.Date = v);
+
+        /// <summary>
+        /// Defines the <see cref="DayFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, string> DayFormatProperty =
+            DatePicker.DayFormatProperty.AddOwner<DatePickerPresenter>(x => 
+            x.DayFormat, (x, v) => x.DayFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="DayVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, bool> DayVisibleProperty =
+            DatePicker.DayVisibleProperty.AddOwner<DatePickerPresenter>(x => 
+            x.DayVisible, (x, v) => x.DayVisible = v);
+
+        /// <summary>
+        /// Defines the <see cref="MaxYear"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MaxYearProperty =
+            DatePicker.MaxYearProperty.AddOwner<DatePickerPresenter>(x => 
+            x.MaxYear, (x, v) => x.MaxYear = v);
+
+        /// <summary>
+        /// Defines the <see cref="MinYear"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, DateTimeOffset> MinYearProperty =
+            DatePicker.MinYearProperty.AddOwner<DatePickerPresenter>(x => 
+            x.MinYear, (x, v) => x.MinYear = v);
+
+        /// <summary>
+        /// Defines the <see cref="MonthFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, string> MonthFormatProperty =
+            DatePicker.MonthFormatProperty.AddOwner<DatePickerPresenter>(x => 
+            x.MonthFormat, (x, v) => x.MonthFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="MonthVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, bool> MonthVisibleProperty =
+            DatePicker.MonthVisibleProperty.AddOwner<DatePickerPresenter>(x => 
+            x.MonthVisible, (x, v) => x.MonthVisible = v);
+
+        /// <summary>
+        /// Defines the <see cref="YearFormat"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, string> YearFormatProperty =
+            DatePicker.YearFormatProperty.AddOwner<DatePickerPresenter>(x => 
+            x.YearFormat, (x, v) => x.YearFormat = v);
+
+        /// <summary>
+        /// Defines the <see cref="YearVisible"/> Property
+        /// </summary>
+        public static readonly DirectProperty<DatePickerPresenter, bool> YearVisibleProperty =
+            DatePicker.YearVisibleProperty.AddOwner<DatePickerPresenter>(x => 
+            x.YearVisible, (x, v) => x.YearVisible = v);
+
+        //Template Items
+        private Grid _pickerContainer;
+        private Button _acceptButton;
+        private Button _dismissButton;
+        private Rectangle _spacer1;
+        private Rectangle _spacer2;
+        private Panel _monthHost;
+        private Panel _yearHost;
+        private Panel _dayHost;
+        private DateTimePickerPanel _monthSelector;
+        private DateTimePickerPanel _yearSelector;
+        private DateTimePickerPanel _daySelector;
+        private Button _monthUpButton;
+        private Button _dayUpButton;
+        private Button _yearUpButton;
+        private Button _monthDownButton;
+        private Button _dayDownButton;
+        private Button _yearDownButton;
+
+        private DateTimeOffset _date;
+        private string _dayFormat = "%d";
+        private bool _dayVisible = true;
+        private DateTimeOffset _maxYear;
+        private DateTimeOffset _minYear;
+        private string _monthFormat = "MMMM";
+        private bool _monthVisible = true;
+        private string _yearFormat = "yyyy";
+        private bool _yearVisible = true;
+        private DateTimeOffset _syncDate;
+
+        private GregorianCalendar _calendar;
+        private bool _suppressUpdateSelection;
+
+        public DatePickerPresenter()
+        {
+            var now = DateTimeOffset.Now;
+            _minYear = new DateTimeOffset(now.Year - 100, 1, 1, 0, 0, 0, now.Offset);
+            _maxYear = new DateTimeOffset(now.Year + 100, 12, 31, 0, 0, 0, now.Offset);
+            _date = now;
+            _calendar = new GregorianCalendar();
+            KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
+        }
+
+        /// <summary>
+        /// Gets or sets the current Date for the picker
+        /// </summary>
+        public DateTimeOffset Date
+        {
+            get => _date;
+            set
+            {
+                SetAndRaise(DateProperty, ref _date, value);
+                _syncDate = Date;
+                InitPicker();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the DayFormat
+        /// </summary>
+        public string DayFormat
+        {
+            get => _dayFormat;
+            set => SetAndRaise(DayFormatProperty, ref _dayFormat, value);
+        }
+
+        /// <summary>
+        /// Get or sets whether the Day selector is visible
+        /// </summary>
+        public bool DayVisible
+        {
+            get => _dayVisible;
+            set
+            {
+                SetAndRaise(DayVisibleProperty, ref _dayVisible, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum pickable year
+        /// </summary>
+        public DateTimeOffset MaxYear
+        {
+            get => _maxYear;
+            set
+            {
+                if (value < MinYear)
+                    throw new InvalidOperationException("MaxDate cannot be less than MinDate");
+                SetAndRaise(MaxYearProperty, ref _maxYear, value);
+
+                if (Date > value)
+                    Date = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum pickable year
+        /// </summary>
+        public DateTimeOffset MinYear
+        {
+            get => _minYear;
+            set
+            {
+                if (value > MaxYear)
+                    throw new InvalidOperationException("MinDate cannot be greater than MaxDate");
+                SetAndRaise(MinYearProperty, ref _minYear, value);
+
+                if (Date < value)
+                    Date = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the month format
+        /// </summary>
+        public string MonthFormat
+        {
+            get => _monthFormat;
+            set => SetAndRaise(MonthFormatProperty, ref _monthFormat, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the month selector is visible
+        /// </summary>
+        public bool MonthVisible
+        {
+            get => _monthVisible;
+            set
+            {
+                SetAndRaise(MonthVisibleProperty, ref _monthVisible, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the year format
+        /// </summary>
+        public string YearFormat
+        {
+            get => _yearFormat;
+            set => SetAndRaise(YearFormatProperty, ref _yearFormat, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the year selector is visible
+        /// </summary>
+        public bool YearVisible
+        {
+            get => _yearVisible;
+            set
+            {
+                SetAndRaise(YearVisibleProperty, ref _yearVisible, value);
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+            //These are requirements, so throw if not found
+            _pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
+            _monthHost = e.NameScope.Get<Panel>("MonthHost");
+            _dayHost = e.NameScope.Get<Panel>("DayHost");
+            _yearHost = e.NameScope.Get<Panel>("YearHost");
+
+            _monthSelector = e.NameScope.Get<DateTimePickerPanel>("MonthSelector");
+            _monthSelector.SelectionChanged += OnMonthChanged;
+
+            _daySelector = e.NameScope.Get<DateTimePickerPanel>("DaySelector");
+            _daySelector.SelectionChanged += OnDayChanged;
+
+            _yearSelector = e.NameScope.Get<DateTimePickerPanel>("YearSelector");
+            _yearSelector.SelectionChanged += OnYearChanged;
+
+            _acceptButton = e.NameScope.Get<Button>("AcceptButton");
+
+            _monthUpButton = e.NameScope.Find<RepeatButton>("MonthUpButton");
+            if (_monthUpButton != null)
+            {
+                _monthUpButton.Click += OnSelectorButtonClick;
+            }
+            _monthDownButton = e.NameScope.Find<RepeatButton>("MonthDownButton");
+            if (_monthDownButton != null)
+            {
+                _monthDownButton.Click += OnSelectorButtonClick;
+            }
+
+            _dayUpButton = e.NameScope.Find<RepeatButton>("DayUpButton");
+            if (_dayUpButton != null)
+            {
+                _dayUpButton.Click += OnSelectorButtonClick;
+            }
+            _dayDownButton = e.NameScope.Find<RepeatButton>("DayDownButton");
+            if (_dayDownButton != null)
+            {
+                _dayDownButton.Click += OnSelectorButtonClick;
+            }
+
+            _yearUpButton = e.NameScope.Find<RepeatButton>("YearUpButton");
+            if (_yearUpButton != null)
+            {
+                _yearUpButton.Click += OnSelectorButtonClick;
+            }
+            _yearDownButton = e.NameScope.Find<RepeatButton>("YearDownButton");
+            if (_yearDownButton != null)
+            {
+                _yearDownButton.Click += OnSelectorButtonClick;
+            }
+
+            _dismissButton = e.NameScope.Find<Button>("DismissButton");
+            _spacer1 = e.NameScope.Find<Rectangle>("FirstSpacer");
+            _spacer2 = e.NameScope.Find<Rectangle>("SecondSpacer");
+
+            if (_acceptButton != null)
+            {
+                _acceptButton.Click += OnAcceptButtonClicked;
+            }
+            if (_dismissButton != null)
+            {
+                _dismissButton.Click += OnDismissButtonClicked;
+            }
+            InitPicker();
+        }
+
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Escape:
+                    OnDismiss();
+                    e.Handled = true;
+                    break;
+                case Key.Tab:
+                    var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
+                    KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+                    e.Handled = true;
+                    break;
+                case Key.Enter:
+                    Date = _syncDate;
+                    OnConfirmed();
+                    e.Handled = true;
+                    break;
+            }
+            base.OnKeyDown(e);
+        }
+
+        /// <summary>
+        /// Initializes the picker selectors.
+        /// </summary>
+        private void InitPicker()
+        {
+            //OnApplyTemplate must've been called before we can init here...
+            if (_pickerContainer == null)
+                return;
+
+            _suppressUpdateSelection = true;
+
+            _monthSelector.MaximumValue = 12;
+            _monthSelector.MinimumValue = 1;
+            _monthSelector.ItemFormat = MonthFormat;
+
+            _daySelector.ItemFormat = DayFormat;
+
+            _yearSelector.MaximumValue = MaxYear.Year;
+            _yearSelector.MinimumValue = MinYear.Year;
+            _yearSelector.ItemFormat = YearFormat;
+
+            SetGrid();
+
+            //Date should've been set when we reach this point
+            var dt = Date;
+            if (DayVisible)
+            {
+                GregorianCalendar gc = new GregorianCalendar();
+                var maxDays = gc.GetDaysInMonth(dt.Year, dt.Month);
+                _daySelector.MaximumValue = maxDays;
+                _daySelector.MinimumValue = 1;
+                _daySelector.SelectedValue = dt.Day;
+                _daySelector.FormatDate = dt.Date;
+            }
+
+            if (MonthVisible)
+                _monthSelector.SelectedValue = dt.Month;
+
+            if (YearVisible)
+                _yearSelector.SelectedValue = dt.Year;
+            _suppressUpdateSelection = false;
+
+            SetInitialFocus();
+        }
+
+        private void SetGrid()
+        {
+            var fmt = CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern;
+            var columns = new List<(Panel, int)>
+            {
+                (_monthHost, MonthVisible ? fmt.IndexOf("m", StringComparison.OrdinalIgnoreCase) : -1),
+                (_yearHost, YearVisible ? fmt.IndexOf("y", StringComparison.OrdinalIgnoreCase) : -1),
+                (_dayHost, DayVisible ? fmt.IndexOf("d", StringComparison.OrdinalIgnoreCase) : -1),
+            };
+
+            columns.Sort((x, y) => x.Item2 - y.Item2);
+            _pickerContainer.ColumnDefinitions.Clear();
+
+            var columnIndex = 0;
+
+            foreach (var column in columns)
+            {
+                if (column.Item1 is null)
+                    continue;
+
+                column.Item1.IsVisible = column.Item2 != -1;
+
+                if (column.Item2 != -1)
+                {
+                    if (columnIndex > 0)
+                    {
+                        _pickerContainer.ColumnDefinitions.Add(new ColumnDefinition(0, GridUnitType.Auto));
+                    }
+
+                    _pickerContainer.ColumnDefinitions.Add(
+                        new ColumnDefinition(column.Item1 == _monthHost ? 138 : 78, GridUnitType.Star));
+
+                    if (column.Item1.Parent is null)
+                    {
+                        _pickerContainer.Children.Add(column.Item1);
+                    }
+
+                    Grid.SetColumn(column.Item1, (columnIndex++ * 2));
+                }
+            }
+
+            Grid.SetColumn(_spacer1, 1);
+            Grid.SetColumn(_spacer2, 3);
+            _spacer1.IsVisible = columnIndex > 1;
+            _spacer2.IsVisible = columnIndex > 2;
+        }
+
+        private void SetInitialFocus()
+        {
+            int monthCol = MonthVisible ? Grid.GetColumn(_monthHost) : int.MaxValue;
+            int dayCol = DayVisible ? Grid.GetColumn(_dayHost) : int.MaxValue;
+            int yearCol = YearVisible ? Grid.GetColumn(_yearHost) : int.MaxValue;
+
+            if (monthCol < dayCol && monthCol < yearCol)
+            {
+                KeyboardDevice.Instance?.SetFocusedElement(_monthSelector, NavigationMethod.Pointer, KeyModifiers.None);
+            }
+            else if (dayCol < monthCol && dayCol < yearCol)
+            {
+                KeyboardDevice.Instance?.SetFocusedElement(_daySelector, NavigationMethod.Pointer, KeyModifiers.None);
+            }
+            else if (yearCol < monthCol && yearCol < dayCol)
+            {
+                KeyboardDevice.Instance?.SetFocusedElement(_yearSelector, NavigationMethod.Pointer, KeyModifiers.None);
+            }
+        }
+
+        private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            OnDismiss();
+        }
+
+        private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            Date = _syncDate;
+            OnConfirmed();
+        }
+
+        private void OnSelectorButtonClick(object sender, RoutedEventArgs e)
+        {
+            if (sender == _monthUpButton)
+                _monthSelector.ScrollUp();
+            else if (sender == _monthDownButton)
+                _monthSelector.ScrollDown();
+            else if (sender == _yearUpButton)
+                _yearSelector.ScrollUp();
+            else if (sender == _yearDownButton)
+                _yearSelector.ScrollDown();
+            else if (sender == _dayUpButton)
+                _daySelector.ScrollUp();
+            else if (sender == _dayDownButton)
+                _daySelector.ScrollDown();
+        }
+
+        private void OnYearChanged(object sender, EventArgs e)
+        {
+            if (_suppressUpdateSelection)
+                return;
+
+            int maxDays = _calendar.GetDaysInMonth(_yearSelector.SelectedValue, _syncDate.Month);
+            var newDate = new DateTimeOffset(_yearSelector.SelectedValue, _syncDate.Month,
+                _syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
+
+            _syncDate = newDate;
+
+            //We don't need to update the days if not displaying day, not february
+            if (!DayVisible || _syncDate.Month != 2)
+                return;
+
+            _suppressUpdateSelection = true;
+
+            _daySelector.FormatDate = newDate.Date;
+
+            if (_daySelector.MaximumValue != maxDays)
+                _daySelector.MaximumValue = maxDays;
+            else
+                _daySelector.RefreshItems();
+
+            _suppressUpdateSelection = false;
+        }
+
+        private void OnDayChanged(object sender, EventArgs e)
+        {
+            if (_suppressUpdateSelection)
+                return;
+            _syncDate = new DateTimeOffset(_syncDate.Year, _syncDate.Month, _daySelector.SelectedValue, 0, 0, 0, _syncDate.Offset);
+        }
+
+        private void OnMonthChanged(object sender, EventArgs e)
+        {
+            if (_suppressUpdateSelection)
+                return;
+
+            int maxDays = _calendar.GetDaysInMonth(_syncDate.Year, _monthSelector.SelectedValue);
+            var newDate = new DateTimeOffset(_syncDate.Year, _monthSelector.SelectedValue,
+                _syncDate.Day > maxDays ? maxDays : _syncDate.Day, 0, 0, 0, _syncDate.Offset);
+
+            if (!DayVisible)
+            {
+                _syncDate = newDate;
+                return;
+            }
+
+            _suppressUpdateSelection = true;
+
+            _daySelector.FormatDate = newDate.Date;
+            _syncDate = newDate;
+
+            if (_daySelector.MaximumValue != maxDays)
+                _daySelector.MaximumValue = maxDays;
+            else
+                _daySelector.RefreshItems();
+
+            _suppressUpdateSelection = false;
+        }
+
+        internal double GetOffsetForPopup()
+        {
+            var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
+            return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_monthSelector.ItemHeight / 2);
+        }
+    }
+}

+ 19 - 0
src/Avalonia.Controls/DateTimePickers/DatePickerSelectedValueChangedEventArgs.cs

@@ -0,0 +1,19 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines the argument passed when the <see cref="DatePicker"/> SelectedDate changes
+    /// </summary>
+    public class DatePickerSelectedValueChangedEventArgs
+    {
+        public DateTimeOffset? NewDate { get; }
+        public DateTimeOffset? OldDate { get; }
+
+        public DatePickerSelectedValueChangedEventArgs(DateTimeOffset? oldDate, DateTimeOffset? newDate)
+        {
+            NewDate = newDate;
+            OldDate = oldDate;
+        }
+    }
+}

+ 566 - 0
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@@ -0,0 +1,566 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls.Primitives
+{
+    public enum DateTimePickerPanelType
+    {
+        Year,
+        Month,
+        Day,
+        Hour,
+        Minute,
+        TimePeriod //AM or PM
+    }
+
+    public class DateTimePickerPanel : Panel, ILogicalScrollable
+    {
+        /// <summary>
+        /// Defines the <see cref="ItemHeight"/> property
+        /// </summary>
+        public static readonly StyledProperty<double> ItemHeightProperty =
+            AvaloniaProperty.Register<DateTimePickerPanel, double>(nameof(ItemHeight), 40.0);
+
+        /// <summary>
+        /// Defines the <see cref="PanelType"/> property
+        /// </summary>
+        public static readonly StyledProperty<DateTimePickerPanelType> PanelTypeProperty =
+            AvaloniaProperty.Register<DateTimePickerPanel, DateTimePickerPanelType>(nameof(PanelType));
+
+        /// <summary>
+        /// Defines the <see cref="ItemFormat"/> property
+        /// </summary>
+        public static readonly StyledProperty<string> ItemFormatProperty =
+            AvaloniaProperty.Register<DateTimePickerPanel, string>(nameof(ItemFormat), "yyyy");
+
+        /// <summary>
+        /// Defines the <see cref="ShouldLoop"/> property
+        /// </summary>
+        public static readonly StyledProperty<bool> ShouldLoopProperty =
+            AvaloniaProperty.Register<DateTimePickerPanel, bool>(nameof(ShouldLoop));
+
+        //Backing fields for properties
+        private int _minimumValue = 1;
+        private int _maximumValue = 2;
+        private int _selectedValue = 1;
+        private int _increment = 1;
+
+        //Helper fields
+        private int _selectedIndex = 0;
+        private int _totalItems;
+        private int _numItemsAboveBelowSelected;
+        private int _range;
+        private double _extentOne;
+        private Size _extent;
+        private Vector _offset;
+        private bool _hasInit;
+        private bool _suppressUpdateOffset;
+        private ListBoxItem _pressedItem;
+
+        public DateTimePickerPanel()
+        {
+            FormatDate = DateTime.Now;
+            AddHandler(ListBoxItem.PointerPressedEvent, OnItemPointerDown, Avalonia.Interactivity.RoutingStrategies.Bubble);
+            AddHandler(ListBoxItem.PointerReleasedEvent, OnItemPointerUp, Avalonia.Interactivity.RoutingStrategies.Bubble);
+        }
+
+        static DateTimePickerPanel()
+        {
+            FocusableProperty.OverrideDefaultValue<DateTimePickerPanel>(true);
+            AffectsMeasure<DateTimePickerPanel>(ItemHeightProperty);
+        }
+
+        /// <summary>
+        /// Gets or sets what this panel displays in date or time units
+        /// </summary>
+        public DateTimePickerPanelType PanelType
+        {
+            get => GetValue(PanelTypeProperty);
+            set => SetValue(PanelTypeProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the height of each item
+        /// </summary>
+        public double ItemHeight
+        {
+            get => GetValue(ItemHeightProperty);
+            set => SetValue(ItemHeightProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the string format for the items, using standard
+        /// .net DateTime or TimeSpan formatting. Format must match panel type
+        /// </summary>
+        public string ItemFormat
+        {
+            get => GetValue(ItemFormatProperty);
+            set => SetValue(ItemFormatProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the panel should loop
+        /// </summary>
+        public bool ShouldLoop
+        {
+            get => GetValue(ShouldLoopProperty);
+            set => SetValue(ShouldLoopProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the minimum value
+        /// </summary>
+        public int MinimumValue
+        {
+            get => _minimumValue;
+            set
+            {
+                if (value > MaximumValue)
+                    throw new InvalidOperationException("Minimum cannot be greater than Maximum");
+                _minimumValue = value;
+                UpdateHelperInfo();
+                var sel = CoerceSelected(SelectedValue);
+                if (sel != SelectedValue)
+                    SelectedValue = sel;
+                UpdateItems();
+                InvalidateArrange();
+                RaiseScrollInvalidated(EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the maximum value
+        /// </summary>
+        public int MaximumValue
+        {
+            get => _maximumValue;
+            set
+            {
+                if (value < MinimumValue)
+                    throw new InvalidOperationException("Maximum cannot be less than Minimum");
+                _maximumValue = value;
+                UpdateHelperInfo();
+                var sel = CoerceSelected(SelectedValue);
+                if (sel != SelectedValue)
+                    SelectedValue = sel;
+                UpdateItems();
+                InvalidateArrange();
+                RaiseScrollInvalidated(EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the selected value
+        /// </summary>
+        public int SelectedValue
+        {
+            get => _selectedValue;
+            set
+            {
+                if (value > MaximumValue || value < MinimumValue)
+                    throw new ArgumentOutOfRangeException("SelectedValue");
+
+                var sel = CoerceSelected(value);
+                _selectedValue = sel;
+                _selectedIndex = (value - MinimumValue) / Increment;
+
+                if (!ShouldLoop)
+                    CreateOrDestroyItems(Children);
+
+                if (!_suppressUpdateOffset)
+                    _offset = new Vector(0, ShouldLoop ? _selectedIndex * ItemHeight + (_extentOne * 50) :
+                        _selectedIndex * ItemHeight);
+
+                UpdateItems();
+                InvalidateArrange();
+                RaiseScrollInvalidated(EventArgs.Empty);
+
+                SelectionChanged?.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the increment
+        /// </summary>
+        public int Increment
+        {
+            get => _increment;
+            set
+            {
+                if (value <= 0 || value > _range)
+                    throw new ArgumentOutOfRangeException("Increment");
+                _increment = value;
+                UpdateHelperInfo();
+                var sel = CoerceSelected(SelectedValue);
+                if (sel != SelectedValue)
+                    SelectedValue = sel;
+                UpdateItems();
+                InvalidateArrange();
+                RaiseScrollInvalidated(EventArgs.Empty);
+            }
+        }
+
+        //Used to help format the date (if applicable), for ex.,
+        //if we're want to display the day of week, we need context
+        //for the month/year, this is our context
+        internal DateTime FormatDate { get; set; }
+
+        public Vector Offset
+        {
+            get => _offset;
+            set
+            {
+                var old = _offset;
+                _offset = value;
+                var dy = _offset.Y - old.Y;
+                var children = Children;
+
+                if (dy > 0) // Scroll Down
+                {
+                    int numContsToMove = 0;
+                    for (int i = 0; i < children.Count; i++)
+                    {
+                        if (children[i].Bounds.Bottom - dy < 0)
+                            numContsToMove++;
+                        else
+                            break;
+                    }
+                    children.MoveRange(0, numContsToMove, children.Count);
+
+                    var scrollHeight = _extent.Height - Viewport.Height;
+                    if (ShouldLoop && value.Y >= scrollHeight - _extentOne)
+                        _offset = new Vector(0, value.Y - (_extentOne * 50));
+                }
+                else if (dy < 0) // Scroll Up
+                {
+                    int numContsToMove = 0;
+                    for (int i = children.Count - 1; i >= 0; i--)
+                    {
+                        if (children[i].Bounds.Top - dy > Bounds.Height)
+                            numContsToMove++;
+                        else
+                            break;
+                    }
+                    children.MoveRange(children.Count - numContsToMove, numContsToMove, 0);
+                    if (ShouldLoop && value.Y < _extentOne)
+                        _offset = new Vector(0, value.Y + (_extentOne * 50));
+                }
+
+                //Setting selection will handle all invalidation
+                var newSel = (Offset.Y / ItemHeight) % _totalItems;
+                _suppressUpdateOffset = true;
+                SelectedValue = (int)newSel * Increment + MinimumValue;
+                _suppressUpdateOffset = false;
+            }
+        }
+
+        public bool CanHorizontallyScroll { get => false; set { } }
+
+        public bool CanVerticallyScroll { get => true; set { } }
+
+        public bool IsLogicalScrollEnabled => true;
+
+        public Size ScrollSize => new Size(0, ItemHeight);
+
+        public Size PageScrollSize => new Size(0, ItemHeight * 4);
+
+        public Size Extent => _extent;
+
+        public Size Viewport => new Size(0, ItemHeight);
+
+        public event EventHandler ScrollInvalidated;
+
+        public event EventHandler SelectionChanged;
+
+        protected override Size MeasureOverride(Size availableSize)
+        {
+            if (double.IsInfinity(availableSize.Width) ||
+                double.IsInfinity(availableSize.Height))
+                throw new InvalidOperationException("Panel must have finite height");
+
+            if (!_hasInit)
+                UpdateHelperInfo();
+
+            double initY = (availableSize.Height / 2.0) - (ItemHeight / 2.0);
+            _numItemsAboveBelowSelected = (int)Math.Ceiling(initY / ItemHeight) + 1;
+
+            var children = Children;
+
+            CreateOrDestroyItems(children);
+
+            for (int i = 0; i < children.Count; i++)
+                children[i].Measure(availableSize);
+
+            if (!_hasInit)
+            {
+                UpdateItems();
+                RaiseScrollInvalidated(EventArgs.Empty);
+                _hasInit = true;
+            }
+
+            return availableSize;
+        }
+
+        protected override Size ArrangeOverride(Size finalSize)
+        {
+            if (Children.Count == 0)
+                return base.ArrangeOverride(finalSize);
+
+            var itemHgt = ItemHeight;
+            var children = Children;
+            Rect rc;
+            double initY = (finalSize.Height / 2.0) - (itemHgt / 2.0);
+
+            if (ShouldLoop)
+            {
+                var currentSet = Math.Truncate(Offset.Y / _extentOne);
+                initY += (_extentOne * currentSet) + (_selectedIndex - _numItemsAboveBelowSelected) * ItemHeight;
+
+                for (int i = 0; i < children.Count; i++)
+                {
+                    rc = new Rect(0, initY - Offset.Y, finalSize.Width, itemHgt);
+                    children[i].Arrange(rc);
+                    initY += itemHgt;
+                }
+            }
+            else
+            {
+                var first = Math.Max(0, (_selectedIndex - _numItemsAboveBelowSelected));
+                for (int i = 0; i < children.Count; i++)
+                {
+                    rc = new Rect(0, (initY + first * itemHgt) - Offset.Y, finalSize.Width, itemHgt);
+                    children[i].Arrange(rc);
+                    initY += itemHgt;
+                }
+            }
+
+            return finalSize;
+        }
+
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Up:
+                    ScrollUp();
+                    e.Handled = true;
+                    break;
+                case Key.Down:
+                    ScrollDown();
+                    e.Handled = true;
+                    break;
+                case Key.PageUp:
+                    ScrollUp(4);
+                    e.Handled = true;
+                    break;
+                case Key.PageDown:
+                    ScrollDown(4);
+                    e.Handled = true;
+                    break;
+            }
+            base.OnKeyDown(e);
+        }
+
+        /// <summary>
+        /// Refreshes the content of the visible items
+        /// </summary>
+        public void RefreshItems()
+        {
+            UpdateItems();
+        }
+
+        /// <summary>
+        /// Scrolls up the specified number of items
+        /// </summary>
+        public void ScrollUp(int numItems = 1)
+        {
+            var newY = Math.Max(Offset.Y - (numItems * ItemHeight), 0);
+            Offset = new Vector(0, newY);
+        }
+
+        /// <summary>
+        /// Scrolls down the specified number of items
+        /// </summary>
+        public void ScrollDown(int numItems = 1)
+        {
+            var scrollHeight = _extent.Height - Viewport.Height;
+            var newY = Math.Min(Offset.Y + (numItems * ItemHeight), scrollHeight);
+            Offset = new Vector(0, newY);
+        }
+
+        /// <summary>
+        /// Updates helper fields used in various calculations
+        /// </summary>
+        private void UpdateHelperInfo()
+        {
+            _range = _maximumValue - _minimumValue + 1;
+            _totalItems = (int)Math.Ceiling((double)_range / _increment);
+
+            var itemHgt = ItemHeight;
+            //If looping, measure 100x as many items as we actually have
+            _extent = new Size(0, ShouldLoop ? _totalItems * itemHgt * 100 : _totalItems * itemHgt);
+
+            //Height of 1 "set" of items
+            _extentOne = _totalItems * itemHgt;
+            _offset = new Vector(0, ShouldLoop ? _extentOne * 50 + _selectedIndex * itemHgt : _selectedIndex * itemHgt);
+
+        }
+
+        /// <summary>
+        /// Ensures enough containers are visible in the viewport
+        /// </summary>
+        /// <param name="children"></param>
+        private void CreateOrDestroyItems(Controls children)
+        {
+            int totalItemsInViewport = _numItemsAboveBelowSelected * 2 + 1;
+
+            if (!ShouldLoop)
+            {
+                int numItemAboveSelect = _numItemsAboveBelowSelected;
+                if (_selectedIndex - _numItemsAboveBelowSelected < 0)
+                    numItemAboveSelect = _selectedIndex;
+                int numItemBelowSelect = _numItemsAboveBelowSelected;
+                if (_selectedIndex + _numItemsAboveBelowSelected >= _totalItems)
+                    numItemBelowSelect = _totalItems - _selectedIndex - 1;
+
+                totalItemsInViewport = numItemBelowSelect + numItemAboveSelect + 1;
+            }
+
+            while (children.Count < totalItemsInViewport)
+            {
+                children.Add(new ListBoxItem
+                {
+                    Height = ItemHeight,
+                    Classes = new Classes("DateTimePickerItem", $"{PanelType}Item"),
+                    VerticalContentAlignment = Avalonia.Layout.VerticalAlignment.Center,
+                    Focusable = false
+                });
+            }
+            if (children.Count > totalItemsInViewport)
+            {
+                var numToRemove = children.Count - totalItemsInViewport;
+                children.RemoveRange(children.Count - numToRemove, numToRemove);
+            }
+        }
+
+        /// <summary>
+        /// Updates item content based on the current selection 
+        /// and the panel type
+        /// </summary>
+        private void UpdateItems()
+        {
+            var children = Children;
+            var min = MinimumValue;
+            var panelType = PanelType;
+            var selected = SelectedValue;
+            var max = MaximumValue;
+
+            int first;
+            if (ShouldLoop)
+            {
+                first = (_selectedIndex - _numItemsAboveBelowSelected) % _totalItems;
+                first = first < 0 ? min + (first + _totalItems) * Increment : min + first * Increment;
+            }
+            else
+            {
+                first = min + Math.Max(0, _selectedIndex - _numItemsAboveBelowSelected) * Increment;
+            }
+
+            for (int i = 0; i < children.Count; i++)
+            {
+                ListBoxItem item = (ListBoxItem)children[i];
+                item.Content = FormatContent(first, panelType);
+                item.Tag = first;
+                item.IsSelected = first == selected;
+                first += Increment;
+                if (first > max)
+                    first = min;
+            }
+        }
+
+        private string FormatContent(int value, DateTimePickerPanelType panelType)
+        {
+            switch (panelType)
+            {
+                case DateTimePickerPanelType.Year:
+                    return new DateTime(value, FormatDate.Month, FormatDate.Day).ToString(ItemFormat);
+                case DateTimePickerPanelType.Month:
+                    return new DateTime(FormatDate.Year, value, FormatDate.Day).ToString(ItemFormat);
+                case DateTimePickerPanelType.Day:
+                    return new DateTime(FormatDate.Year, FormatDate.Month, value).ToString(ItemFormat);
+                case DateTimePickerPanelType.Hour:
+                    return new TimeSpan(value, 0, 0).ToString(ItemFormat);
+                case DateTimePickerPanelType.Minute:
+                    return new TimeSpan(0, value, 0).ToString(ItemFormat);
+                case DateTimePickerPanelType.TimePeriod:
+                    return value == MinimumValue ? CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator :
+                        CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator;
+                default:
+                        return "";
+            }
+        }
+
+        /// <summary>
+        /// Ensures the <see cref="SelectedValue"/> is within the bounds and
+        /// follows the current Increment
+        /// </summary>
+        private int CoerceSelected(int newValue)
+        {
+            if (newValue < MinimumValue)
+                return MinimumValue;
+            if (newValue > MaximumValue)
+                return MaximumValue;
+
+            if (newValue % Increment != 0)
+            {
+                var items = Enumerable.Range(MinimumValue, MaximumValue + 1).Where(i => i % Increment == 0).ToList();
+                var nearest = items.Aggregate((x, y) => Math.Abs(x - newValue) > Math.Abs(y - newValue) ? y : x);
+                return items.IndexOf(nearest) * Increment;
+            }
+            return newValue;
+        }
+
+        private void OnItemPointerDown(object sender, PointerPressedEventArgs e)
+        {
+            if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+            {
+                _pressedItem = GetItemFromSource((IVisual)e.Source);
+                e.Handled = true;
+            }
+        }
+
+        private void OnItemPointerUp(object sender, PointerReleasedEventArgs e)
+        {
+            if (e.GetCurrentPoint(this).Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased &&
+                _pressedItem != null)
+            {
+                SelectedValue = (int)GetItemFromSource((IVisual)e.Source).Tag;
+                _pressedItem = null;
+                e.Handled = true;
+            }
+        }
+
+        //Helper to get ListBoxItem from pointerevent source
+        private ListBoxItem GetItemFromSource(IVisual src)
+        {
+            var item = src;
+            while (item != null && !(item is ListBoxItem))
+            {
+                item = item.VisualParent;
+            }
+            return (ListBoxItem)item;
+        }
+
+        public bool BringIntoView(IControl target, Rect targetRect) { return false; }
+
+        public IControl GetControlInDirection(NavigationDirection direction, IControl from) { return null; }
+
+        public void RaiseScrollInvalidated(EventArgs e)
+        {
+            ScrollInvalidated?.Invoke(this, e);
+        }
+    }
+}

+ 25 - 0
src/Avalonia.Controls/DateTimePickers/PickerPresenterBase.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Controls.Primitives
+{
+    /// <summary>
+    /// Defines the base class for Date and Time PickerPresenters
+    /// </summary>
+    public abstract class PickerPresenterBase : TemplatedControl
+    {
+        protected virtual void OnConfirmed()
+        {
+            Confirmed?.Invoke(this, EventArgs.Empty);
+        }
+
+        protected virtual void OnDismiss()
+        {
+            Dismissed.Invoke(this, EventArgs.Empty);
+        }
+
+        public event EventHandler Confirmed;
+        public event EventHandler Dismissed;
+    }
+}

+ 292 - 0
src/Avalonia.Controls/DateTimePickers/TimePicker.cs

@@ -0,0 +1,292 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Controls.Templates;
+using System;
+using System.Globalization;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// A control to allow the user to select a time
+    /// </summary>
+    public class TimePicker : TemplatedControl
+    {
+        /// <summary>
+        /// Defines the <see cref="MinuteIncrement"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePicker, int> MinuteIncrementProperty =
+            AvaloniaProperty.RegisterDirect<TimePicker, int>(nameof(MinuteIncrement), 
+                x => x.MinuteIncrement, (x, v) => x.MinuteIncrement = v);
+
+        /// <summary>
+        /// Defines the <see cref="Header"/> property
+        /// </summary>
+        public static readonly StyledProperty<object> HeaderProperty =
+            AvaloniaProperty.Register<DatePicker, object>(nameof(Header));
+
+        /// <summary>
+        /// Defines the <see cref="HeaderTemplate"/> property
+        /// </summary>
+        public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
+            AvaloniaProperty.Register<DatePicker, IDataTemplate>(nameof(HeaderTemplate));
+
+        /// <summary>
+        /// Defines the <see cref="ClockIdentifier"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePicker, string> ClockIdentifierProperty =
+           AvaloniaProperty.RegisterDirect<TimePicker, string>(nameof(ClockIdentifier), 
+               x => x.ClockIdentifier, (x, v) => x.ClockIdentifier = v);
+
+        /// <summary>
+        /// Defines the <see cref="SelectedTime"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePicker, TimeSpan?> SelectedTimeProperty =
+            AvaloniaProperty.RegisterDirect<TimePicker, TimeSpan?>(nameof(SelectedTime), 
+                x => x.SelectedTime, (x, v) => x.SelectedTime = v);
+
+        //Template Items
+        private TimePickerPresenter _presenter;
+        private Button _flyoutButton;
+        private Border _firstPickerHost;
+        private Border _secondPickerHost;
+        private Border _thirdPickerHost;
+        private TextBlock _hourText;
+        private TextBlock _minuteText;
+        public TextBlock _periodText;
+        private Rectangle _firstSplitter;
+        private Rectangle _secondSplitter;
+        private Grid _contentGrid;
+        private Popup _popup;
+
+        private TimeSpan? _selectedTime;
+        private int _minuteIncrement = 1;
+        private string _clockIdentifier = "12HourClock";
+
+        public TimePicker()
+        {
+            PseudoClasses.Set(":hasnotime", true);
+
+            var timePattern = CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern;
+            if (timePattern.IndexOf("H") != -1)
+                _clockIdentifier = "24HourClock";
+        }
+
+        /// <summary>
+        /// Gets or sets the minute increment in the picker
+        /// </summary>
+        public int MinuteIncrement
+        {
+            get => _minuteIncrement;
+            set
+            {
+                if (value < 1 || value > 59)
+                    throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
+                SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
+                SetSelectedTimeText();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the header
+        /// </summary>
+        public object Header
+        {
+            get => GetValue(HeaderProperty);
+            set => SetValue(HeaderProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the header template
+        /// </summary>
+        public IDataTemplate HeaderTemplate
+        {
+            get => GetValue(HeaderTemplateProperty);
+            set => SetValue(HeaderTemplateProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the clock identifier, either 12HourClock or 24HourClock
+        /// </summary>
+        public string ClockIdentifier
+        {
+            get => _clockIdentifier;
+            set
+            {
+                if (!(string.IsNullOrEmpty(value) || value == "" || value == "12HourClock" || value == "24HourClock"))
+                    throw new ArgumentException("Invalid ClockIdentifier");
+                SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
+                SetGrid();
+                SetSelectedTimeText();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the selected time. Can be null.
+        /// </summary>
+        public TimeSpan? SelectedTime
+        {
+            get => _selectedTime;
+            set
+            {
+                var old = _selectedTime;
+                SetAndRaise(SelectedTimeProperty, ref _selectedTime, value);
+                OnSelectedTimeChanged(old, value);
+                SetSelectedTimeText();
+            }
+        }
+
+        /// <summary>
+        /// Raised when the <see cref="SelectedTime"/> property changes
+        /// </summary>
+        public event EventHandler<TimePickerSelectedValueChangedEventArgs> SelectedTimeChanged;
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            if (_flyoutButton != null)
+                _flyoutButton.Click -= OnFlyoutButtonClicked;
+
+            if(_presenter != null)
+            {
+                _presenter.Confirmed -= OnConfirmed;
+                _presenter.Dismissed -= OnDismissPicker;
+            }
+            base.OnApplyTemplate(e);
+
+            _flyoutButton = e.NameScope.Find<Button>("FlyoutButton");
+
+            _firstPickerHost = e.NameScope.Find<Border>("FirstPickerHost");
+            _secondPickerHost = e.NameScope.Find<Border>("SecondPickerHost");
+            _thirdPickerHost = e.NameScope.Find<Border>("ThirdPickerHost");
+
+            _hourText = e.NameScope.Find<TextBlock>("HourTextBlock");
+            _minuteText = e.NameScope.Find<TextBlock>("MinuteTextBlock");
+            _periodText = e.NameScope.Find<TextBlock>("PeriodTextBlock");
+
+            _firstSplitter = e.NameScope.Find<Rectangle>("FirstColumnDivider");
+            _secondSplitter = e.NameScope.Find<Rectangle>("SecondColumnDivider");
+
+            _contentGrid = e.NameScope.Find<Grid>("FlyoutButtonContentGrid");
+
+            _popup = e.NameScope.Find<Popup>("Popup");
+            _presenter = e.NameScope.Find<TimePickerPresenter>("PickerPresenter");
+
+
+            if (_flyoutButton != null)
+                _flyoutButton.Click += OnFlyoutButtonClicked;
+
+            SetGrid();
+            SetSelectedTimeText();
+
+            if (_presenter != null)
+            {
+                _presenter.Confirmed += OnConfirmed;
+                _presenter.Dismissed += OnDismissPicker;
+
+                _presenter[!TimePickerPresenter.MinuteIncrementProperty] = this[!MinuteIncrementProperty];
+                _presenter[!TimePickerPresenter.ClockIdentifierProperty] = this[!ClockIdentifierProperty];
+            }
+
+        }
+
+        private void SetGrid()
+        {
+            if (_contentGrid == null)
+                return;
+
+            bool use24HourClock = ClockIdentifier == "24HourClock";
+
+            if (!use24HourClock)
+            {
+                _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
+                _thirdPickerHost.IsVisible = true;
+                _secondSplitter.IsVisible = true;
+
+                Grid.SetColumn(_firstPickerHost, 0);
+                Grid.SetColumn(_secondPickerHost, 2);
+                Grid.SetColumn(_thirdPickerHost, 4);
+
+                Grid.SetColumn(_firstSplitter, 1);
+                Grid.SetColumn(_secondSplitter, 3);
+            }
+            else
+            {
+                _contentGrid.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
+                _thirdPickerHost.IsVisible = false;
+                _secondSplitter.IsVisible = false;
+
+                Grid.SetColumn(_firstPickerHost, 0);
+                Grid.SetColumn(_secondPickerHost, 2);
+
+                Grid.SetColumn(_firstSplitter, 1);
+            }
+        }
+
+        private void SetSelectedTimeText()
+        {
+            if (_hourText == null || _minuteText == null || _periodText == null)
+                return;
+
+            var time = SelectedTime;
+            if (time.HasValue)
+            {
+                var newTime = SelectedTime.Value;
+
+                if (ClockIdentifier == "12HourClock")
+                {
+                    var hr = newTime.Hours;
+                    hr = hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
+                    newTime = new TimeSpan(hr, newTime.Minutes, 0);
+                }
+                _hourText.Text = newTime.ToString("%h");
+
+                _minuteText.Text = newTime.ToString("mm");
+                PseudoClasses.Set(":hasnotime", false);
+
+                _periodText.Text = time.Value.Hours >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
+                    CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
+
+            }
+            else
+            {
+                _hourText.Text = "hour";
+                _minuteText.Text = "minute";
+                PseudoClasses.Set(":hasnotime", true);
+
+                _periodText.Text = DateTime.Now.Hour >= 12 ? CultureInfo.CurrentCulture.DateTimeFormat.PMDesignator :
+                    CultureInfo.CurrentCulture.DateTimeFormat.AMDesignator;
+            }
+        }
+
+        protected virtual void OnSelectedTimeChanged(TimeSpan? oldTime, TimeSpan? newTime)
+        {
+            SelectedTimeChanged?.Invoke(this, new TimePickerSelectedValueChangedEventArgs(oldTime, newTime));
+        }
+
+        private void OnFlyoutButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            _presenter.Time = SelectedTime ?? DateTime.Now.TimeOfDay;
+
+            _popup.IsOpen = true;
+
+            var deltaY = _presenter.GetOffsetForPopup();
+
+            //The extra 5 px I think is related to default popup placement behavior
+            _popup.Host.ConfigurePosition(_popup.PlacementTarget, PlacementMode.AnchorAndGravity, new Point(0, deltaY + 5),
+                Primitives.PopupPositioning.PopupAnchor.Bottom, Primitives.PopupPositioning.PopupGravity.Bottom,
+                 Primitives.PopupPositioning.PopupPositionerConstraintAdjustment.SlideY);
+        }
+
+        private void OnDismissPicker(object sender, EventArgs e)
+        {
+            _popup.Close();
+            Focus();
+        }
+
+        private void OnConfirmed(object sender, EventArgs e)
+        {
+            _popup.Close();
+            SelectedTime = _presenter.Time;
+        }
+
+    }
+}

+ 262 - 0
src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs

@@ -0,0 +1,262 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using System;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines the presenter used for selecting a time. Intended for use with
+    /// <see cref="TimePicker"/> but can be used independently
+    /// </summary>
+    public class TimePickerPresenter : PickerPresenterBase
+    {
+        /// <summary>
+        /// Defines the <see cref="MinuteIncrement"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePickerPresenter, int> MinuteIncrementProperty =
+            TimePicker.MinuteIncrementProperty.AddOwner<TimePickerPresenter>(x => x.MinuteIncrement,
+                (x, v) => x.MinuteIncrement = v);
+
+        /// <summary>
+        /// Defines the <see cref="ClockIdentifier"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePickerPresenter, string> ClockIdentifierProperty =
+            TimePicker.ClockIdentifierProperty.AddOwner<TimePickerPresenter>(x => x.ClockIdentifier,
+                (x, v) => x.ClockIdentifier = v);
+
+        /// <summary>
+        /// Defines the <see cref="Time"/> property
+        /// </summary>
+        public static readonly DirectProperty<TimePickerPresenter, TimeSpan> TimeProperty =
+            AvaloniaProperty.RegisterDirect<TimePickerPresenter, TimeSpan>(nameof(Time),
+                x => x.Time, (x, v) => x.Time = v);
+
+        public TimePickerPresenter()
+        {
+            Time = DateTime.Now.TimeOfDay;
+            KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
+        }
+
+        //TemplateItems
+        private Grid _pickerContainer;
+        private Button _acceptButton;
+        private Button _dismissButton;
+        private Rectangle _spacer2;
+        private Panel _periodHost;
+        private DateTimePickerPanel _hourSelector;
+        private DateTimePickerPanel _minuteSelector;
+        private DateTimePickerPanel _periodSelector;
+        private Button _hourUpButton;
+        private Button _minuteUpButton;
+        private Button _periodUpButton;
+        private Button _hourDownButton;
+        private Button _minuteDownButton;
+        private Button _periodDownButton;
+
+        //Backing Fields
+        private TimeSpan _Time;
+        private int _minuteIncrement = 1;
+        private string _clockIdentifier = "12HourClock";
+
+        /// <summary>
+        /// Gets or sets the minute increment in the selector
+        /// </summary>
+        public int MinuteIncrement
+        {
+            get => _minuteIncrement;
+            set
+            {
+                if (value < 1 || value > 59)
+                    throw new ArgumentOutOfRangeException("1 >= MinuteIncrement <= 59");
+                SetAndRaise(MinuteIncrementProperty, ref _minuteIncrement, value);
+                InitPicker();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the current clock identifier, either 12HourClock or 24HourClock
+        /// </summary>
+        public string ClockIdentifier
+        {
+            get => _clockIdentifier;
+            set
+            {
+                if (string.IsNullOrEmpty(value) || value == "" || !(value == "12HourClock" || value == "24HourClock"))
+                    throw new ArgumentException("Invalid ClockIdentifier");
+                SetAndRaise(ClockIdentifierProperty, ref _clockIdentifier, value);
+                InitPicker();
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the current time
+        /// </summary>
+        public TimeSpan Time
+        {
+            get => _Time;
+            set
+            {
+                SetAndRaise(TimeProperty, ref _Time, value);
+                InitPicker();
+            }
+        }
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            _pickerContainer = e.NameScope.Get<Grid>("PickerContainer");
+            _periodHost = e.NameScope.Get<Panel>("PeriodHost");
+
+            _hourSelector = e.NameScope.Get<DateTimePickerPanel>("HourSelector");
+            _minuteSelector = e.NameScope.Get<DateTimePickerPanel>("MinuteSelector");
+            _periodSelector = e.NameScope.Get<DateTimePickerPanel>("PeriodSelector");
+
+            _spacer2 = e.NameScope.Get<Rectangle>("SecondSpacer");
+
+            _acceptButton = e.NameScope.Get<Button>("AcceptButton");
+            _acceptButton.Click += OnAcceptButtonClicked;
+
+            _hourUpButton = e.NameScope.Find<RepeatButton>("HourUpButton");
+            if (_hourUpButton != null)
+                _hourUpButton.Click += OnSelectorButtonClick;
+            _hourDownButton = e.NameScope.Find<RepeatButton>("HourDownButton");
+            if (_hourDownButton != null)
+                _hourDownButton.Click += OnSelectorButtonClick;
+
+            _minuteUpButton = e.NameScope.Find<RepeatButton>("MinuteUpButton");
+            if (_minuteUpButton != null)
+                _minuteUpButton.Click += OnSelectorButtonClick;
+            _minuteDownButton = e.NameScope.Find<RepeatButton>("MinuteDownButton");
+            if (_minuteDownButton != null)
+                _minuteDownButton.Click += OnSelectorButtonClick;
+
+            _periodUpButton = e.NameScope.Find<RepeatButton>("PeriodUpButton");
+            if (_periodUpButton != null)
+                _periodUpButton.Click += OnSelectorButtonClick;
+            _periodDownButton = e.NameScope.Find<RepeatButton>("PeriodDownButton");
+            if (_periodDownButton != null)
+                _periodDownButton.Click += OnSelectorButtonClick;
+
+            _dismissButton = e.NameScope.Find<Button>("DismissButton");
+            if (_dismissButton != null)
+                _dismissButton.Click += OnDismissButtonClicked;
+
+            InitPicker();
+        }
+
+        protected override void OnKeyDown(KeyEventArgs e)
+        {
+            switch (e.Key)
+            {
+                case Key.Escape:
+                    OnDismiss();
+                    e.Handled = true;
+                    break;
+                case Key.Tab:
+                    var nextFocus = KeyboardNavigationHandler.GetNext(FocusManager.Instance.Current, NavigationDirection.Next);
+                    KeyboardDevice.Instance?.SetFocusedElement(nextFocus, NavigationMethod.Tab, KeyModifiers.None);
+                    e.Handled = true;
+                    break;
+                case Key.Enter:
+                    OnConfirmed();
+                    e.Handled = true;
+                    break;
+            }
+            base.OnKeyDown(e);
+        }
+
+        protected override void OnConfirmed()
+        {
+            var hr = _hourSelector.SelectedValue;
+            var min = _minuteSelector.SelectedValue;
+            var per = _periodSelector.SelectedValue;
+
+            if (ClockIdentifier == "12HourClock")
+            {
+                hr = per == 1 ? hr + 12 : per == 0 && hr == 12 ? 0 : hr;
+            }
+
+            Time = new TimeSpan(hr, min, 0);
+
+            base.OnConfirmed();
+        }
+
+        private void InitPicker()
+        {
+            if (_pickerContainer == null)
+                return;
+
+            bool clock12 = ClockIdentifier == "12HourClock";
+            _hourSelector.MaximumValue = clock12 ? 12 : 23;
+            _hourSelector.MinimumValue = clock12 ? 1 : 0;
+            _hourSelector.ItemFormat = "%h";
+            var hr = Time.Hours;
+            _hourSelector.SelectedValue = !clock12 ? hr :
+                hr > 12 ? hr - 12 : hr == 0 ? 12 : hr;
+
+            _minuteSelector.MaximumValue = 59;
+            _minuteSelector.MinimumValue = 0;
+            _minuteSelector.Increment = MinuteIncrement;
+            _minuteSelector.SelectedValue = Time.Minutes;
+            _minuteSelector.ItemFormat = "mm";
+
+            _periodSelector.MaximumValue = 1;
+            _periodSelector.MinimumValue = 0;
+            _periodSelector.SelectedValue = hr >= 12 ? 1 : 0;
+
+            SetGrid();
+            KeyboardDevice.Instance?.SetFocusedElement(_hourSelector, NavigationMethod.Pointer, KeyModifiers.None);
+        }
+
+        private void SetGrid()
+        {
+            if (ClockIdentifier == "12HourClock")
+            {
+                _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*,Auto,*");
+                _spacer2.IsVisible = true;
+                _periodHost.IsVisible = true;
+            }
+            else
+            {
+                _pickerContainer.ColumnDefinitions = new ColumnDefinitions("*,Auto,*");
+                _spacer2.IsVisible = false;
+                _periodHost.IsVisible = false;
+            }
+        }
+
+        private void OnDismissButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            OnDismiss();
+        }
+
+        private void OnAcceptButtonClicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
+        {
+            OnConfirmed();
+        }
+
+        private void OnSelectorButtonClick(object sender, RoutedEventArgs e)
+        {
+            if (sender == _hourUpButton)
+                _hourSelector.ScrollUp();
+            else if (sender == _hourDownButton)
+                _hourSelector.ScrollDown();
+            else if (sender == _minuteUpButton)
+                _minuteSelector.ScrollUp();
+            else if (sender == _minuteDownButton)
+                _minuteSelector.ScrollDown();
+            else if (sender == _periodUpButton)
+                _periodSelector.ScrollUp();
+            else if (sender == _periodDownButton)
+                _periodSelector.ScrollDown();
+        }
+
+        internal double GetOffsetForPopup()
+        {
+            var acceptDismissButtonHeight = _acceptButton != null ? _acceptButton.Bounds.Height : 41;
+            return -(MaxHeight - acceptDismissButtonHeight) / 2 - (_hourSelector.ItemHeight / 2);
+        }
+    }
+}

+ 15 - 0
src/Avalonia.Controls/DateTimePickers/TimePickerSelectedValueChangedEventArgs.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public class TimePickerSelectedValueChangedEventArgs
+    {
+        public TimeSpan? OldTime { get; }
+        public TimeSpan? NewTime { get; }
+        public TimePickerSelectedValueChangedEventArgs(TimeSpan? old, TimeSpan? newT)
+        {
+            OldTime = old;
+            NewTime = newT;
+        }
+    }
+}

+ 73 - 16
src/Avalonia.Controls/NativeControlHost.cs

@@ -1,7 +1,9 @@
+using System;
+using System.Collections.Generic;
 using Avalonia.Controls.Platform;
-using Avalonia.LogicalTree;
 using Avalonia.Platform;
 using Avalonia.Threading;
+using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -12,14 +14,18 @@ namespace Avalonia.Controls
         private INativeControlHostControlTopLevelAttachment _attachment;
         private IPlatformHandle _nativeControlHandle;
         private bool _queuedForDestruction;
+        private bool _queuedForMoveResize;
+        private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
+        private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
         static NativeControlHost()
         {
             IsVisibleProperty.Changed.AddClassHandler<NativeControlHost>(OnVisibleChanged);
-            TransformedBoundsProperty.Changed.AddClassHandler<NativeControlHost>(OnBoundsChanged);
         }
 
-        private static void OnBoundsChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2) 
-            => host.UpdateHost();
+        public NativeControlHost()
+        {
+            _propertyChangedHandler = PropertyChangedHandler;
+        }
 
         private static void OnVisibleChanged(NativeControlHost host, AvaloniaPropertyChangedEventArgs arg2)
             => host.UpdateHost();
@@ -27,21 +33,46 @@ namespace Avalonia.Controls
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
             _currentRoot = e.Root as TopLevel;
+            var visual = (IVisual)this;
+            while (visual != _currentRoot)
+            {
+
+                if (visual is Visual v)
+                {
+                    v.PropertyChanged += _propertyChangedHandler;
+                    _propertyChangedSubscriptions.Add(v);
+                }
+
+                visual = visual.GetVisualParent();
+            }
+
             UpdateHost();
         }
 
+        private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
+        {
+            if (e.IsEffectiveValueChange && e.Property == BoundsProperty)
+                EnqueueForMoveResize();
+        }
+
         protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
         {
             _currentRoot = null;
+            if (_propertyChangedSubscriptions != null)
+            {
+                foreach (var v in _propertyChangedSubscriptions)
+                    v.PropertyChanged -= _propertyChangedHandler;
+                _propertyChangedSubscriptions.Clear();
+            }
             UpdateHost();
         }
 
 
-        void UpdateHost()
+        private void UpdateHost()
         {
+            _queuedForMoveResize = false;
             _currentHost = (_currentRoot?.PlatformImpl as ITopLevelImplWithNativeControlHost)?.NativeControlHost;
             var needsAttachment = _currentHost != null;
-            var needsShow = needsAttachment && IsEffectivelyVisible && TransformedBounds.HasValue;
             
             if (needsAttachment)
             {
@@ -93,22 +124,46 @@ namespace Avalonia.Controls
                 }
             }
 
-            if (needsShow)
-                _attachment?.ShowInBounds(TransformedBounds.Value);
-            else if (needsAttachment)
-                _attachment?.Hide();
+            if (_attachment?.AttachedTo != _currentHost)
+                return;
+
+            TryUpdateNativeControlPosition();
+        }
+
+        
+        private Rect? GetAbsoluteBounds()
+        {
+            var bounds = Bounds;
+            var position = this.TranslatePoint(bounds.Position, _currentRoot);
+            if (position == null)
+                return null;
+            return new Rect(position.Value, bounds.Size);
+        }
+
+        void EnqueueForMoveResize()
+        {
+            if(_queuedForMoveResize)
+                return;
+            _queuedForMoveResize = true;
+            Dispatcher.UIThread.Post(UpdateHost, DispatcherPriority.Render);
         }
 
         public bool TryUpdateNativeControlPosition()
         {
-            var needsShow = _currentHost != null && IsEffectivelyVisible && TransformedBounds.HasValue;
+            if (_currentHost == null)
+                return false;
+            
+            var bounds = GetAbsoluteBounds();
+            var needsShow = IsEffectivelyVisible && bounds.HasValue;
 
-            if(needsShow)
-                _attachment?.ShowInBounds(TransformedBounds.Value);
-            return needsShow;
+            if (needsShow)
+                _attachment?.ShowInBounds(bounds.Value);
+            else
+                _attachment?.HideWithSize(Bounds.Size);
+            return false;
         }
 
-        void CheckDestruction()
+        private void CheckDestruction()
         {
             _queuedForDestruction = false;
             if (_currentRoot == null)
@@ -117,10 +172,12 @@ namespace Avalonia.Controls
         
         protected virtual IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
         {
+            if (_currentHost == null)
+                throw new InvalidOperationException();
             return _currentHost.CreateDefaultChild(parent);
         }
 
-        void DestroyNativeControl()
+        private void DestroyNativeControl()
         {
             if (_nativeControlHandle != null)
             {

+ 38 - 0
src/Avalonia.Controls/Platform/ExtendClientAreaChromeHints.cs

@@ -0,0 +1,38 @@
+using System;
+
+namespace Avalonia.Platform
+{
+    /// <summary>
+    /// Hint for Window Chrome when ClientArea is Extended.
+    /// </summary>
+    [Flags]
+    public enum ExtendClientAreaChromeHints
+    {
+        /// <summary>
+        /// The will be no chrome at all.
+        /// </summary>
+        NoChrome,
+
+        /// <summary>
+        /// The default for the platform.
+        /// </summary>
+        Default = SystemChrome,
+
+        /// <summary>
+        /// Use SystemChrome
+        /// </summary>
+        SystemChrome = 0x01,
+
+        /// <summary>
+        /// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
+        /// This is because Windows Chrome can not be shown ontop of user content.
+        /// </summary>
+        PreferSystemChrome = 0x02,
+
+        /// <summary>
+        /// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
+        /// slightly lower than normal.
+        /// </summary>
+        OSXThickTitleBar = 0x08,               
+    }
+}

+ 2 - 2
src/Avalonia.Controls/Platform/INativeControlHostImpl.cs

@@ -21,8 +21,8 @@ namespace Avalonia.Controls.Platform
     {
         INativeControlHostImpl AttachedTo { get; set; }
         bool IsCompatibleWith(INativeControlHostImpl host);
-        void Hide();
-        void ShowInBounds(TransformedBounds transformedBounds);
+        void HideWithSize(Size size);
+        void ShowInBounds(Rect rect);
     }
 
     public interface ITopLevelImplWithNativeControlHost

+ 46 - 0
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -68,6 +68,34 @@ namespace Avalonia.Platform
         /// </summary>
         Func<bool> Closing { get; set; }
 
+        /// <summary>
+        /// Gets a value to indicate if the platform was able to extend client area to non-client area.
+        /// </summary>
+        bool IsClientAreaExtendedToDecorations { get; }
+
+        /// <summary>
+        /// Gets or Sets an action that is called whenever one of the extend client area properties changed.
+        /// </summary>
+        Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
+
+        /// <summary>
+        /// Gets a flag that indicates if Managed decorations i.e. caption buttons are required.
+        /// This property is used when <see cref="IsClientAreaExtendedToDecorations"/> is set.
+        /// </summary>
+        bool NeedsManagedDecorations { get; }
+
+        /// <summary>
+        /// Gets a thickness that describes the amount each side of the non-client area extends into the client area.
+        /// It includes the titlebar.
+        /// </summary>
+        Thickness ExtendedMargins { get; }
+
+        /// <summary>
+        /// Gets a thickness that describes the margin around the window that is offscreen.
+        /// This may happen when a window is maximized and <see cref="IsClientAreaExtendedToDecorations"/> is set.
+        /// </summary>
+        Thickness OffScreenMargin { get; }
+
         /// <summary>
         /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
         /// </summary>
@@ -94,5 +122,23 @@ namespace Avalonia.Platform
         /// </summary>
         /// 
         void SetMinMaxSize(Size minSize, Size maxSize);
+
+        /// <summary>
+        /// Sets if the ClientArea is extended into the non-client area.
+        /// </summary>
+        /// <param name="extendIntoClientAreaHint">true to enable, false to disable</param>
+        void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint);        
+
+        /// <summary>
+        /// Sets hints that configure how the client area extends. 
+        /// </summary>
+        /// <param name="hints"></param>
+        void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints);
+
+        /// <summary>
+        /// Sets how big the non-client titlebar area should be.
+        /// </summary>
+        /// <param name="titleBarHeight">-1 for platform default, otherwise the height in DIPs.</param>
+        void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight);       
     }
 }

+ 29 - 0
src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs

@@ -0,0 +1,29 @@
+using System.Linq;
+using Avalonia.Rendering;
+using Avalonia.VisualTree;
+
+#nullable enable
+
+namespace Avalonia.Controls.Primitives
+{
+    public class ChromeOverlayLayer : Panel, ICustomSimpleHitTest
+    {
+        public static ChromeOverlayLayer? GetOverlayLayer(IVisual visual)
+        {
+            foreach (var v in visual.GetVisualAncestors())
+                if (v is VisualLayerManager vlm)
+                    if (vlm.OverlayLayer != null)
+                        return vlm.ChromeOverlayLayer;
+
+            if (visual is TopLevel tl)
+            {
+                var layers = tl.GetVisualDescendants().OfType<VisualLayerManager>().FirstOrDefault();
+                return layers?.ChromeOverlayLayer;
+            }
+
+            return null;
+        }
+
+        public bool HitTest(Point point) => Children.HitTestCustom(point);
+    }
+}

+ 14 - 1
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@@ -6,7 +6,9 @@ namespace Avalonia.Controls.Primitives
     public class VisualLayerManager : Decorator
     {
         private const int AdornerZIndex = int.MaxValue - 100;
-        private const int OverlayZIndex = int.MaxValue - 99;
+        private const int ChromeZIndex = int.MaxValue - 99;
+        private const int OverlayZIndex = int.MaxValue - 98;
+
         private ILogicalRoot _logicalRoot;
         private readonly List<Control> _layers = new List<Control>();
         
@@ -24,6 +26,17 @@ namespace Avalonia.Controls.Primitives
             }
         }
 
+        public ChromeOverlayLayer ChromeOverlayLayer
+        {
+            get
+            {
+                var rv = FindLayer<ChromeOverlayLayer>();
+                if (rv == null)
+                    AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex);
+                return rv;
+            }
+        }
+
         public OverlayLayer OverlayLayer
         {
             get

+ 132 - 32
src/Avalonia.Controls/ProgressBar.cs

@@ -1,8 +1,7 @@
-
 using System;
 using Avalonia.Controls.Primitives;
-using Avalonia.Data;
 using Avalonia.Layout;
+using Avalonia.Media;
 
 namespace Avalonia.Controls
 {
@@ -11,6 +10,92 @@ namespace Avalonia.Controls
     /// </summary>
     public class ProgressBar : RangeBase
     {
+        public class ProgressBarTemplateProperties : AvaloniaObject
+        {
+            private double _container2Width;
+            private double _containerWidth;
+            private double _containerAnimationStartPosition;
+            private double _containerAnimationEndPosition;
+            private double _container2AnimationStartPosition;
+            private double _container2AnimationEndPosition;
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationStartPositionProperty =
+           AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+               nameof(ContainerAnimationStartPosition),
+               p => p.ContainerAnimationStartPosition,
+               (p, o) => p.ContainerAnimationStartPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerAnimationEndPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(ContainerAnimationEndPosition),
+                    p => p.ContainerAnimationEndPosition,
+                    (p, o) => p.ContainerAnimationEndPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationStartPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2AnimationStartPosition),
+                    p => p.Container2AnimationStartPosition,
+                    (p, o) => p.Container2AnimationStartPosition = o, 0d);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2AnimationEndPositionProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2AnimationEndPosition),
+                    p => p.Container2AnimationEndPosition,
+                    (p, o) => p.Container2AnimationEndPosition = o);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> Container2WidthProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(Container2Width),
+                    p => p.Container2Width,
+                    (p, o) => p.Container2Width = o);
+
+            public static readonly DirectProperty<ProgressBarTemplateProperties, double> ContainerWidthProperty =
+                AvaloniaProperty.RegisterDirect<ProgressBarTemplateProperties, double>(
+                    nameof(ContainerWidth),
+                    p => p.ContainerWidth,
+                    (p, o) => p.ContainerWidth = o);
+
+            public double ContainerAnimationStartPosition
+            {
+                get => _containerAnimationStartPosition;
+                set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value);
+            }
+
+            public double ContainerAnimationEndPosition
+            {
+                get => _containerAnimationEndPosition;
+                set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
+            }
+
+            public double Container2AnimationStartPosition
+            {
+                get => _container2AnimationStartPosition;
+                set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value);
+            }
+
+            public double Container2Width
+            {
+                get => _container2Width;
+                set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
+            }
+
+            public double ContainerWidth
+            {
+                get => _containerWidth;
+                set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
+            }
+
+            public double Container2AnimationEndPosition
+            {
+                get => _container2AnimationEndPosition;
+                set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
+            }
+        }
+
+        private double _indeterminateStartingOffset;
+        private double _indeterminateEndingOffset;
+        private Border _indicator;
+
         public static readonly StyledProperty<bool> IsIndeterminateProperty =
             AvaloniaProperty.Register<ProgressBar, bool>(nameof(IsIndeterminate));
 
@@ -20,19 +105,33 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<Orientation> OrientationProperty =
             AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
 
-        private static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public static readonly DirectProperty<ProgressBar, double> IndeterminateStartingOffsetProperty =
             AvaloniaProperty.RegisterDirect<ProgressBar, double>(
                 nameof(IndeterminateStartingOffset),
                 p => p.IndeterminateStartingOffset,
                 (p, o) => p.IndeterminateStartingOffset = o);
 
-        private static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public static readonly DirectProperty<ProgressBar, double> IndeterminateEndingOffsetProperty =
             AvaloniaProperty.RegisterDirect<ProgressBar, double>(
                 nameof(IndeterminateEndingOffset),
                 p => p.IndeterminateEndingOffset,
                 (p, o) => p.IndeterminateEndingOffset = o);
 
-        private Border _indicator;
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public double IndeterminateStartingOffset
+        {
+            get => _indeterminateStartingOffset;
+            set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
+        }
+
+        [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
+        public double IndeterminateEndingOffset
+        {
+            get => _indeterminateEndingOffset;
+            set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
+        }
 
         static ProgressBar()
         {
@@ -45,6 +144,8 @@ namespace Avalonia.Controls
             UpdatePseudoClasses(IsIndeterminate, Orientation);
         }
 
+        public ProgressBarTemplateProperties TemplateProperties { get; } = new ProgressBarTemplateProperties();
+
         public bool IsIndeterminate
         {
             get => GetValue(IsIndeterminateProperty);
@@ -62,19 +163,6 @@ namespace Avalonia.Controls
             get => GetValue(OrientationProperty);
             set => SetValue(OrientationProperty, value);
         }
-        private double _indeterminateStartingOffset;
-        private double IndeterminateStartingOffset
-        {
-            get => _indeterminateStartingOffset;
-            set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
-        }
-
-        private double _indeterminateEndingOffset;
-        private double IndeterminateEndingOffset
-        {
-            get => _indeterminateEndingOffset;
-            set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
-        }
 
         /// <inheritdoc/>
         protected override Size ArrangeOverride(Size finalSize)
@@ -111,21 +199,33 @@ namespace Avalonia.Controls
             {
                 if (IsIndeterminate)
                 {
-                    if (Orientation == Orientation.Horizontal)
-                    {
-                        var width = bounds.Width / 5.0;
-                        IndeterminateStartingOffset = -width;
-                        _indicator.Width = width;
-                        IndeterminateEndingOffset = bounds.Width;
+                    // Pulled from ModernWPF.
 
-                    }
-                    else
-                    {
-                        var height = bounds.Height / 5.0;
-                        IndeterminateStartingOffset = -bounds.Height;
-                        _indicator.Height = height;
-                        IndeterminateEndingOffset = height;
-                    }
+                    var dim = Orientation == Orientation.Horizontal ? bounds.Width : bounds.Height;
+                    var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
+                    var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
+
+                    TemplateProperties.ContainerWidth = barIndicatorWidth;
+                    TemplateProperties.Container2Width = barIndicatorWidth2;
+
+                    TemplateProperties.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
+                    TemplateProperties.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
+
+                    TemplateProperties.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
+                    TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
+
+                    // Remove these properties when we switch to fluent as default and removed the old one.
+                    IndeterminateStartingOffset = -(dim / 5d);
+                    IndeterminateEndingOffset = dim;
+
+                    var padding = Padding;
+                    var rectangle = new RectangleGeometry(
+                        new Rect(
+                            padding.Left,
+                            padding.Top,
+                            bounds.Width - (padding.Right + padding.Left),
+                            bounds.Height - (padding.Bottom + padding.Top)
+                            ));
                 }
                 else
                 {

+ 1 - 0
src/Avalonia.Controls/Properties/AssemblyInfo.cs

@@ -13,3 +13,4 @@ using Avalonia.Metadata;
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")]
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")]
+[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Chrome")]

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

@@ -189,8 +189,6 @@ namespace Avalonia.Controls
             }
             set
             {
-                var isSelected = IsSelectedWithPartialAt(value);
-
                 if (!IsSelectedAt(value) || SelectedItems.Count > 1)
                 {
                     using var operation = new Operation(this);

+ 57 - 9
src/Avalonia.Controls/Slider.cs

@@ -1,4 +1,5 @@
 using System;
+using Avalonia.Collections;
 using Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
 using Avalonia.Input;
@@ -64,6 +65,12 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<TickPlacement> TickPlacementProperty =
             AvaloniaProperty.Register<TickBar, TickPlacement>(nameof(TickPlacement), 0d);
 
+        /// <summary>
+        /// Defines the <see cref="TicksProperty"/> property.
+        /// </summary>
+        public static readonly StyledProperty<AvaloniaList<double>> TicksProperty =
+            TickBar.TicksProperty.AddOwner<Slider>();
+
         // Slider required parts
         private bool _isDragging = false;
         private Track _track;
@@ -83,7 +90,8 @@ namespace Avalonia.Controls
             PressedMixin.Attach<Slider>();
             OrientationProperty.OverrideDefaultValue(typeof(Slider), Orientation.Horizontal);
             Thumb.DragStartedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragStarted(e), RoutingStrategies.Bubble);
-            Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e), RoutingStrategies.Bubble);
+            Thumb.DragCompletedEvent.AddClassHandler<Slider>((x, e) => x.OnThumbDragCompleted(e),
+                RoutingStrategies.Bubble);
         }
 
         /// <summary>
@@ -94,6 +102,15 @@ namespace Avalonia.Controls
             UpdatePseudoClasses(Orientation);
         }
 
+        /// <summary>
+        /// Defines the ticks to be drawn on the tick bar.
+        /// </summary>
+        public AvaloniaList<double> Ticks
+        {
+            get => GetValue(TicksProperty);
+            set => SetValue(TicksProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the orientation of a <see cref="Slider"/>.
         /// </summary>
@@ -240,19 +257,50 @@ namespace Avalonia.Controls
         /// <param name="value">Value that want to snap to closest Tick.</param>
         private double SnapToTick(double value)
         {
-            var previous = Minimum;
-            var next = Maximum;
-
-            if (TickFrequency > 0.0)
+            if (IsSnapToTickEnabled)
             {
-                previous = Minimum + (Math.Round((value - Minimum) / TickFrequency) * TickFrequency);
-                next = Math.Min(Maximum, previous + TickFrequency);
+                double previous = Minimum;
+                double next = Maximum;
+
+                // This property is rarely set so let's try to avoid the GetValue
+                var ticks = Ticks;
+
+                // If ticks collection is available, use it.
+                // Note that ticks may be unsorted.
+                if ((ticks != null) && (ticks.Count > 0))
+                {
+                    for (int i = 0; i < ticks.Count; i++)
+                    {
+                        double tick = ticks[i];
+                        if (MathUtilities.AreClose(tick, value))
+                        {
+                            return value;
+                        }
+
+                        if (MathUtilities.LessThan(tick, value) && MathUtilities.GreaterThan(tick, previous))
+                        {
+                            previous = tick;
+                        }
+                        else if (MathUtilities.GreaterThan(tick, value) && MathUtilities.LessThan(tick, next))
+                        {
+                            next = tick;
+                        }
+                    }
+                }
+                else if (MathUtilities.GreaterThan(TickFrequency, 0.0))
+                {
+                    previous = Minimum + (Math.Round(((value - Minimum) / TickFrequency)) * TickFrequency);
+                    next = Math.Min(Maximum, previous + TickFrequency);
+                }
+
+                // Choose the closest value between previous and next. If tie, snap to 'next'.
+                value = MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
             }
 
-            // Choose the closest value between previous and next. If tie, snap to 'next'.
-            return MathUtilities.GreaterThanOrClose(value, (previous + next) * 0.5) ? next : previous;
+            return value;
         }
 
+
         private void UpdatePseudoClasses(Orientation o)
         {
             PseudoClasses.Set(":vertical", o == Orientation.Vertical);

+ 487 - 0
src/Avalonia.Controls/SplitView.cs

@@ -0,0 +1,487 @@
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using Avalonia.Platform;
+using Avalonia.VisualTree;
+using System;
+using System.Reactive.Disposables;
+
+namespace Avalonia.Controls
+{
+    /// <summary>
+    /// Defines constants for how the SplitView Pane should display
+    /// </summary>
+    public enum SplitViewDisplayMode
+    {
+        /// <summary>
+        /// Pane is displayed next to content, and does not auto collapse
+        /// when tapped outside
+        /// </summary>
+        Inline,
+        /// <summary>
+        /// Pane is displayed next to content. When collapsed, pane is still
+        /// visible according to CompactPaneLength. Pane does not auto collapse
+        /// when tapped outside
+        /// </summary>
+        CompactInline,
+        /// <summary>
+        /// Pane is displayed above content. Pane collapses when tapped outside
+        /// </summary>
+        Overlay,
+        /// <summary>
+        /// Pane is displayed above content. When collapsed, pane is still
+        /// visible according to CompactPaneLength. Pane collapses when tapped outside
+        /// </summary>
+        CompactOverlay
+    }
+
+    /// <summary>
+    /// Defines constants for where the Pane should appear
+    /// </summary>
+    public enum SplitViewPanePlacement
+    {
+        Left,
+        Right
+    }
+
+    public class SplitViewTemplateSettings : AvaloniaObject
+    {
+        internal SplitViewTemplateSettings() { }
+
+        public static readonly StyledProperty<double> ClosedPaneWidthProperty =
+            AvaloniaProperty.Register<SplitViewTemplateSettings, double>(nameof(ClosedPaneWidth), 0d);
+
+        public static readonly StyledProperty<GridLength> PaneColumnGridLengthProperty =
+            AvaloniaProperty.Register<SplitViewTemplateSettings, GridLength>(nameof(PaneColumnGridLength));
+
+        public double ClosedPaneWidth
+        {
+            get => GetValue(ClosedPaneWidthProperty);
+            internal set => SetValue(ClosedPaneWidthProperty, value);
+        }
+
+        public GridLength PaneColumnGridLength
+        {
+            get => GetValue(PaneColumnGridLengthProperty);
+            internal set => SetValue(PaneColumnGridLengthProperty, value);
+        }
+    }
+
+    /// <summary>
+    /// A control with two views: A collapsible pane and an area for content
+    /// </summary>
+    public class SplitView : TemplatedControl
+    {
+        /*
+            Pseudo classes & combos
+            :open / :closed
+            :compactoverlay :compactinline :overlay :inline
+            :left :right
+        */
+
+        /// <summary>
+        /// Defines the <see cref="Content"/> property
+        /// </summary>
+        public static readonly StyledProperty<IControl> ContentProperty =
+            AvaloniaProperty.Register<SplitView, IControl>(nameof(Content));
+
+        /// <summary>
+        /// Defines the <see cref="CompactPaneLength"/> property
+        /// </summary>
+        public static readonly StyledProperty<double> CompactPaneLengthProperty =
+            AvaloniaProperty.Register<SplitView, double>(nameof(CompactPaneLength), defaultValue: 48);
+
+        /// <summary>
+        /// Defines the <see cref="DisplayMode"/> property
+        /// </summary>
+        public static readonly StyledProperty<SplitViewDisplayMode> DisplayModeProperty =
+            AvaloniaProperty.Register<SplitView, SplitViewDisplayMode>(nameof(DisplayMode), defaultValue: SplitViewDisplayMode.Overlay);
+
+        /// <summary>
+        /// Defines the <see cref="IsPaneOpen"/> property
+        /// </summary>
+        public static readonly DirectProperty<SplitView, bool> IsPaneOpenProperty =
+            AvaloniaProperty.RegisterDirect<SplitView, bool>(nameof(IsPaneOpen),
+                x => x.IsPaneOpen, (x, v) => x.IsPaneOpen = v);
+
+        /// <summary>
+        /// Defines the <see cref="OpenPaneLength"/> property
+        /// </summary>
+        public static readonly StyledProperty<double> OpenPaneLengthProperty =
+            AvaloniaProperty.Register<SplitView, double>(nameof(OpenPaneLength), defaultValue: 320);
+
+        /// <summary>
+        /// Defines the <see cref="PaneBackground"/> property
+        /// </summary>
+        public static readonly StyledProperty<IBrush> PaneBackgroundProperty =
+            AvaloniaProperty.Register<SplitView, IBrush>(nameof(PaneBackground));
+
+        /// <summary>
+        /// Defines the <see cref="PanePlacement"/> property
+        /// </summary>
+        public static readonly StyledProperty<SplitViewPanePlacement> PanePlacementProperty =
+            AvaloniaProperty.Register<SplitView, SplitViewPanePlacement>(nameof(PanePlacement));
+
+        /// <summary>
+        /// Defines the <see cref="Pane"/> property
+        /// </summary>
+        public static readonly StyledProperty<IControl> PaneProperty =
+            AvaloniaProperty.Register<SplitView, IControl>(nameof(Pane));
+
+        /// <summary>
+        /// Defines the <see cref="UseLightDismissOverlayMode"/> property
+        /// </summary>
+        public static readonly StyledProperty<bool> UseLightDismissOverlayModeProperty =
+            AvaloniaProperty.Register<SplitView, bool>(nameof(UseLightDismissOverlayMode));
+
+        /// <summary>
+        /// Defines the <see cref="TemplateSettings"/> property
+        /// </summary>
+        public static readonly StyledProperty<SplitViewTemplateSettings> TemplateSettingsProperty =
+            AvaloniaProperty.Register<SplitView, SplitViewTemplateSettings>(nameof(TemplateSettings));
+
+        private bool _isPaneOpen;
+        private Panel _pane;
+        private CompositeDisposable _pointerDisposables;
+
+        public SplitView()
+        {
+            PseudoClasses.Add(":overlay");
+            PseudoClasses.Add(":left");
+
+            TemplateSettings = new SplitViewTemplateSettings();
+        }
+
+        static SplitView()
+        {
+            UseLightDismissOverlayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnUseLightDismissChanged(v));
+            CompactPaneLengthProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnCompactPaneLengthChanged(v));
+            PanePlacementProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnPanePlacementChanged(v));
+            DisplayModeProperty.Changed.AddClassHandler<SplitView>((x, v) => x.OnDisplayModeChanged(v));
+        }
+
+        /// <summary>
+        /// Gets or sets the content of the SplitView
+        /// </summary>
+        [Content]
+        public IControl Content
+        {
+            get => GetValue(ContentProperty);
+            set => SetValue(ContentProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the length of the pane when in <see cref="SplitViewDisplayMode.CompactOverlay"/> 
+        /// or <see cref="SplitViewDisplayMode.CompactInline"/> mode
+        /// </summary>
+        public double CompactPaneLength
+        {
+            get => GetValue(CompactPaneLengthProperty);
+            set => SetValue(CompactPaneLengthProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="SplitViewDisplayMode"/> for the SplitView
+        /// </summary>
+        public SplitViewDisplayMode DisplayMode
+        {
+            get => GetValue(DisplayModeProperty);
+            set => SetValue(DisplayModeProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether the pane is open or closed
+        /// </summary>
+        public bool IsPaneOpen
+        {
+            get => _isPaneOpen;
+            set
+            {
+                if (value == _isPaneOpen)
+                {
+                    return;
+                }
+
+                if (value)
+                {
+                    OnPaneOpening(this, null);
+                    SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
+
+                    PseudoClasses.Add(":open");
+                    PseudoClasses.Remove(":closed");
+                    OnPaneOpened(this, null);
+                }
+                else
+                {
+                    SplitViewPaneClosingEventArgs args = new SplitViewPaneClosingEventArgs(false);
+                    OnPaneClosing(this, args);
+                    if (!args.Cancel)
+                    {
+                        SetAndRaise(IsPaneOpenProperty, ref _isPaneOpen, value);
+
+                        PseudoClasses.Add(":closed");
+                        PseudoClasses.Remove(":open");
+                        OnPaneClosed(this, null);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the length of the pane when open
+        /// </summary>
+        public double OpenPaneLength
+        {
+            get => GetValue(OpenPaneLengthProperty);
+            set => SetValue(OpenPaneLengthProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the background of the pane
+        /// </summary>
+        public IBrush PaneBackground
+        {
+            get => GetValue(PaneBackgroundProperty);
+            set => SetValue(PaneBackgroundProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the <see cref="SplitViewPanePlacement"/> for the SplitView
+        /// </summary>
+        public SplitViewPanePlacement PanePlacement
+        {
+            get => GetValue(PanePlacementProperty);
+            set => SetValue(PanePlacementProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the Pane for the SplitView
+        /// </summary>
+        public IControl Pane
+        {
+            get => GetValue(PaneProperty);
+            set => SetValue(PaneProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets whether WinUI equivalent LightDismissOverlayMode is enabled
+        /// <para>When enabled, and the pane is open in Overlay or CompactOverlay mode,
+        /// the contents of the splitview are darkened to visually separate the open pane
+        /// and the rest of the SplitView</para>
+        /// </summary>
+        public bool UseLightDismissOverlayMode
+        {
+            get => GetValue(UseLightDismissOverlayModeProperty);
+            set => SetValue(UseLightDismissOverlayModeProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the TemplateSettings for the SplitView
+        /// </summary>
+        public SplitViewTemplateSettings TemplateSettings
+        {
+            get => GetValue(TemplateSettingsProperty);
+            set => SetValue(TemplateSettingsProperty, value);
+        }
+
+        /// <summary>
+        /// Fired when the pane is closed
+        /// </summary>
+        public event EventHandler<EventArgs> PaneClosed;
+
+        /// <summary>
+        /// Fired when the pane is closing
+        /// </summary>
+        public event EventHandler<SplitViewPaneClosingEventArgs> PaneClosing;
+
+        /// <summary>
+        /// Fired when the pane is opened
+        /// </summary>
+        public event EventHandler<EventArgs> PaneOpened;
+
+        /// <summary>
+        /// Fired when the pane is opening
+        /// </summary>
+        public event EventHandler<EventArgs> PaneOpening;
+
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+            _pane = e.NameScope.Find<Panel>("PART_PaneRoot");
+        }
+
+        protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnAttachedToVisualTree(e);
+
+            var topLevel = this.VisualRoot;
+            if (topLevel is Window window)
+            {
+                //Logic adapted from Popup
+                //Basically if we're using an overlay DisplayMode, close the pane if we don't click on the pane
+                IDisposable subscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler,
+                    Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
+                {
+                    subscribe(target, handler);
+                    return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
+                }
+
+                _pointerDisposables = new CompositeDisposable(
+                    window.AddDisposableHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel),
+                    InputManager.Instance?.Process.Subscribe(OnNonClientClick),
+                    subscribeToEventHandler<Window, EventHandler>(window, Window_Deactivated,
+                    (x, handler) => x.Deactivated += handler, (x, handler) => x.Deactivated -= handler),
+                    subscribeToEventHandler<IWindowImpl, Action>(window.PlatformImpl, OnWindowLostFocus,
+                    (x, handler) => x.LostFocus += handler, (x, handler) => x.LostFocus -= handler));
+            }
+        }
+
+        protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+        {
+            base.OnDetachedFromVisualTree(e);
+            _pointerDisposables?.Dispose();
+        }
+
+        private void OnWindowLostFocus()
+        {
+            if (IsPaneOpen && ShouldClosePane())
+            {
+                IsPaneOpen = false;
+            }                
+        }
+
+        private void PointerPressedOutside(object sender, PointerPressedEventArgs e)
+        {
+            if (!IsPaneOpen)
+            {
+                return;
+            }
+
+            //If we click within the Pane, don't do anything
+            //Otherwise, ClosePane if open & using an overlay display mode
+            bool closePane = ShouldClosePane();
+            if (!closePane)
+            {
+                return;
+            }
+
+            var src = e.Source as IVisual;
+            while (src != null)
+            {
+                if (src == _pane)
+                {
+                    closePane = false;
+                    break;
+                }
+
+                src = src.VisualParent;
+            }
+            if (closePane)
+            {
+                IsPaneOpen = false;
+                e.Handled = true;
+            }
+        }
+
+        private void OnNonClientClick(RawInputEventArgs obj)
+        {
+            if (!IsPaneOpen)
+            {
+                return;
+            }              
+
+            var mouse = obj as RawPointerEventArgs;
+            if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
+
+            {
+                if (ShouldClosePane())
+                    IsPaneOpen = false;
+            }
+        }
+
+        private void Window_Deactivated(object sender, EventArgs e)
+        {
+            if (IsPaneOpen && ShouldClosePane())
+            {
+                IsPaneOpen = false;
+            }                
+        }
+
+        private bool ShouldClosePane()
+        {
+            return (DisplayMode == SplitViewDisplayMode.CompactOverlay || DisplayMode == SplitViewDisplayMode.Overlay);
+        }
+
+        protected virtual void OnPaneOpening(SplitView sender, EventArgs args)
+        {
+            PaneOpening?.Invoke(sender, args);
+        }
+
+        protected virtual void OnPaneOpened(SplitView sender, EventArgs args)
+        {
+            PaneOpened?.Invoke(sender, args);
+        }
+
+        protected virtual void OnPaneClosing(SplitView sender, SplitViewPaneClosingEventArgs args)
+        {
+            PaneClosing?.Invoke(sender, args);
+        }
+
+        protected virtual void OnPaneClosed(SplitView sender, EventArgs args)
+        {
+            PaneClosed?.Invoke(sender, args);
+        }
+
+        private void OnCompactPaneLengthChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var newLen = (double)e.NewValue;
+            var displayMode = DisplayMode;
+            if (displayMode == SplitViewDisplayMode.CompactInline)
+            {
+                TemplateSettings.ClosedPaneWidth = newLen;
+            }
+            else if (displayMode == SplitViewDisplayMode.CompactOverlay)
+            {
+                TemplateSettings.ClosedPaneWidth = newLen;
+                TemplateSettings.PaneColumnGridLength = new GridLength(newLen, GridUnitType.Pixel);
+            }
+        }
+
+        private void OnPanePlacementChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var oldState = e.OldValue.ToString().ToLower();
+            var newState = e.NewValue.ToString().ToLower();
+            PseudoClasses.Remove($":{oldState}");
+            PseudoClasses.Add($":{newState}");
+        }
+
+        private void OnDisplayModeChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var oldState = e.OldValue.ToString().ToLower();
+            var newState = e.NewValue.ToString().ToLower();
+
+            PseudoClasses.Remove($":{oldState}");
+            PseudoClasses.Add($":{newState}");
+
+            var (closedPaneWidth, paneColumnGridLength) = (SplitViewDisplayMode)e.NewValue switch
+            {
+                SplitViewDisplayMode.Overlay => (0, new GridLength(0, GridUnitType.Pixel)),
+                SplitViewDisplayMode.CompactOverlay => (CompactPaneLength, new GridLength(CompactPaneLength, GridUnitType.Pixel)),
+                SplitViewDisplayMode.Inline => (0, new GridLength(0, GridUnitType.Auto)),
+                SplitViewDisplayMode.CompactInline => (CompactPaneLength, new GridLength(0, GridUnitType.Auto)),
+                _ => throw new NotImplementedException(),
+            };
+            TemplateSettings.ClosedPaneWidth = closedPaneWidth;
+            TemplateSettings.PaneColumnGridLength = paneColumnGridLength;
+        }
+
+        private void OnUseLightDismissChanged(AvaloniaPropertyChangedEventArgs e)
+        {
+            var mode = (bool)e.NewValue;
+            PseudoClasses.Set(":lightdismiss", mode);
+        }
+    }
+}

+ 14 - 0
src/Avalonia.Controls/SplitViewPaneClosingEventArgs.cs

@@ -0,0 +1,14 @@
+using System;
+
+namespace Avalonia.Controls
+{
+    public class SplitViewPaneClosingEventArgs : EventArgs
+    {
+        public bool Cancel { get; set; }
+
+        public SplitViewPaneClosingEventArgs(bool cancel)
+        {
+            Cancel = cancel;
+        }
+    }
+}

+ 4 - 8
src/Avalonia.Controls/TickBar.cs

@@ -1,8 +1,4 @@
-using System;
-using System.Collections.Generic;
-using Avalonia.Controls.Primitives;
-using Avalonia.Data;
-using Avalonia.Data.Converters;
+using Avalonia.Collections;
 using Avalonia.Layout;
 using Avalonia.Media;
 using Avalonia.Utilities;
@@ -135,15 +131,15 @@ namespace Avalonia.Controls
         /// <summary>
         /// Defines the <see cref="Ticks"/> property.
         /// </summary>
-        public static readonly StyledProperty<List<double>> TicksProperty =
-            AvaloniaProperty.Register<TickBar, List<double>>(nameof(Ticks));
+        public static readonly StyledProperty<AvaloniaList<double>> TicksProperty =
+            AvaloniaProperty.Register<TickBar, AvaloniaList<double>>(nameof(Ticks));
 
         /// <summary>
         /// The Ticks property contains collection of value of type Double which
         /// are the logical positions use to draw the ticks.
         /// The property value is a <see cref="DoubleCollection" />.
         /// </summary>
-        public List<double> Ticks
+        public AvaloniaList<double> Ticks
         {
             get { return GetValue(TicksProperty); }
             set { SetValue(TicksProperty, value); }

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

@@ -340,6 +340,9 @@ namespace Avalonia.Controls
                 _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved;
             }
 
+            Renderer?.Dispose();
+            Renderer = null;
+            
             var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
             ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
 
@@ -349,8 +352,7 @@ namespace Avalonia.Controls
             (this as IInputRoot).MouseDevice?.TopLevelClosed(this);
             PlatformImpl = null;
             OnClosed(EventArgs.Empty);
-            Renderer?.Dispose();
-            Renderer = null;
+
             LayoutManager?.Dispose();
         }
 
@@ -403,7 +405,7 @@ namespace Avalonia.Controls
                 }
                 else
                 {
-                    _transparencyFallbackBorder.Background = Brushes.Transparent;
+                    _transparencyFallbackBorder.Background = null;
                 }
             }
 

+ 6 - 3
src/Avalonia.Controls/Utils/IEnumerableUtils.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections;
+using System.Collections.Generic;
 using System.Linq;
 
 namespace Avalonia.Controls.Utils
@@ -15,12 +16,14 @@ namespace Avalonia.Controls.Utils
         {
             if (items != null)
             {
-                var collection = items as ICollection;
-
-                if (collection != null)
+                if (items is ICollection collection)
                 {
                     return collection.Count;
                 }
+                else if (items is IReadOnlyCollection<object> readOnly)
+                {
+                    return readOnly.Count;
+                }
                 else
                 {
                     return Enumerable.Count(items.Cast<object>());

+ 134 - 1
src/Avalonia.Controls/Window.cs

@@ -4,6 +4,7 @@ using System.ComponentModel;
 using System.Linq;
 using System.Reactive.Linq;
 using System.Threading.Tasks;
+using Avalonia.Controls.Chrome;
 using Avalonia.Controls.Platform;
 using Avalonia.Data;
 using Avalonia.Input;
@@ -69,7 +70,11 @@ namespace Avalonia.Controls
     /// </summary>
     public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot
     {
-        private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();
+        private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>();        
+        private TitleBar _managedTitleBar;
+        private bool _isExtendedIntoWindowDecorations;
+        private Thickness _windowDecorationMargin;
+        private Thickness _offScreenMargin;
 
         /// <summary>
         /// Defines the <see cref="SizeToContent"/> property.
@@ -87,6 +92,37 @@ namespace Avalonia.Controls
                 o => o.HasSystemDecorations,
                 (o, v) => o.HasSystemDecorations = v);
 
+        /// <summary>
+        /// Defines the <see cref="ExtendClientAreaToDecorationsHint"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> ExtendClientAreaToDecorationsHintProperty =
+            AvaloniaProperty.Register<Window, bool>(nameof(ExtendClientAreaToDecorationsHint), false);
+
+        public static readonly StyledProperty<ExtendClientAreaChromeHints> ExtendClientAreaChromeHintsProperty =
+            AvaloniaProperty.Register<Window, ExtendClientAreaChromeHints>(nameof(ExtendClientAreaChromeHints), ExtendClientAreaChromeHints.Default);
+
+        public static readonly StyledProperty<double> ExtendClientAreaTitleBarHeightHintProperty =
+            AvaloniaProperty.Register<Window, double>(nameof(ExtendClientAreaTitleBarHeightHint), -1);
+
+        /// <summary>
+        /// Defines the <see cref="IsExtendedIntoWindowDecorations"/> property.
+        /// </summary>
+        public static readonly DirectProperty<Window, bool> IsExtendedIntoWindowDecorationsProperty =
+            AvaloniaProperty.RegisterDirect<Window, bool>(nameof(IsExtendedIntoWindowDecorations),
+                o => o.IsExtendedIntoWindowDecorations,
+                unsetValue: false);
+
+        /// <summary>
+        /// Defines the <see cref="WindowDecorationMargin"/> property.
+        /// </summary>
+        public static readonly DirectProperty<Window, Thickness> WindowDecorationMarginProperty =
+            AvaloniaProperty.RegisterDirect<Window, Thickness>(nameof(WindowDecorationMargin),
+                o => o.WindowDecorationMargin);
+
+        public static readonly DirectProperty<Window, Thickness> OffScreenMarginProperty =
+            AvaloniaProperty.RegisterDirect<Window, Thickness>(nameof(OffScreenMargin),
+                o => o.OffScreenMargin);
+
         /// <summary>
         /// Defines the <see cref="SystemDecorations"/> property.
         /// </summary>
@@ -164,6 +200,21 @@ namespace Avalonia.Controls
             WindowStateProperty.Changed.AddClassHandler<Window>(
                 (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
 
+            ExtendClientAreaToDecorationsHintProperty.Changed.AddClassHandler<Window>(
+                (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaToDecorationsHint((bool)e.NewValue); });
+
+            ExtendClientAreaChromeHintsProperty.Changed.AddClassHandler<Window>(
+                (w, e) =>
+                {
+                    if (w.PlatformImpl != null)
+                    {
+                        w.PlatformImpl.SetExtendClientAreaChromeHints((ExtendClientAreaChromeHints)e.NewValue);
+                    }
+                });
+
+            ExtendClientAreaTitleBarHeightHintProperty.Changed.AddClassHandler<Window>(
+                (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.SetExtendClientAreaTitleBarHeightHint((double)e.NewValue); });
+
             MinWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
             MinHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
             MaxWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
@@ -189,6 +240,7 @@ namespace Avalonia.Controls
             impl.GotInputWhenDisabled = OnGotInputWhenDisabled;
             impl.WindowStateChanged = HandleWindowStateChanged;
             _maxPlatformClientSize = PlatformImpl?.MaxAutoSizeHint ?? default(Size);
+            impl.ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged;            
             this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
 
             PlatformImpl?.ShowTaskbarIcon(ShowInTaskbar);
@@ -237,6 +289,66 @@ namespace Avalonia.Controls
             }
         }
 
+        /// <summary>
+        /// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border).
+        /// </summary>
+        public bool ExtendClientAreaToDecorationsHint
+        {
+            get { return GetValue(ExtendClientAreaToDecorationsHintProperty); }
+            set { SetValue(ExtendClientAreaToDecorationsHintProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets or Sets the <see cref="Avalonia.Platform.ExtendClientAreaChromeHints"/> that control
+        /// how the chrome looks when the client area is extended.
+        /// </summary>
+        public ExtendClientAreaChromeHints ExtendClientAreaChromeHints
+        {
+            get => GetValue(ExtendClientAreaChromeHintsProperty);
+            set => SetValue(ExtendClientAreaChromeHintsProperty, value);
+        }
+
+        /// <summary>
+        /// Gets or Sets the TitlebarHeightHint for when the client area is extended.
+        /// A value of -1 will cause the titlebar to be auto sized to the OS default.
+        /// Any other positive value will cause the titlebar to assume that height.
+        /// </summary>
+        public double ExtendClientAreaTitleBarHeightHint
+        {
+            get => GetValue(ExtendClientAreaTitleBarHeightHintProperty);
+            set => SetValue(ExtendClientAreaTitleBarHeightHintProperty, value);
+        }        
+
+        /// <summary>
+        /// Gets if the ClientArea is Extended into the Window Decorations.
+        /// </summary>
+        public bool IsExtendedIntoWindowDecorations
+        {
+            get => _isExtendedIntoWindowDecorations;
+            private set => SetAndRaise(IsExtendedIntoWindowDecorationsProperty, ref _isExtendedIntoWindowDecorations, value);
+        }        
+
+        /// <summary>
+        /// Gets the WindowDecorationMargin.
+        /// This tells you the thickness around the window that is used by borders and the titlebar.
+        /// </summary>
+        public Thickness WindowDecorationMargin
+        {
+            get => _windowDecorationMargin;
+            private set => SetAndRaise(WindowDecorationMarginProperty, ref _windowDecorationMargin, value);
+        }        
+
+        /// <summary>
+        /// Gets the window margin that is hidden off the screen area.
+        /// This is generally only the case on Windows when in Maximized where the window border
+        /// is hidden off the screen. This Margin may be used to ensure user content doesnt overlap this space.
+        /// </summary>
+        public Thickness OffScreenMargin
+        {
+            get => _offScreenMargin;
+            private set => SetAndRaise(OffScreenMarginProperty, ref _offScreenMargin, value);
+        }
+
         /// <summary>
         /// Sets the system decorations (title bar, border, etc)
         /// </summary>
@@ -435,6 +547,27 @@ namespace Avalonia.Controls
             }
         }
 
+        protected virtual void ExtendClientAreaToDecorationsChanged(bool isExtended)
+        {
+            IsExtendedIntoWindowDecorations = isExtended;
+            WindowDecorationMargin = PlatformImpl.ExtendedMargins;
+            OffScreenMargin = PlatformImpl.OffScreenMargin;
+
+            if (PlatformImpl.NeedsManagedDecorations)
+            {
+                if (_managedTitleBar == null)
+                {
+                    _managedTitleBar = new TitleBar(this);
+                    _managedTitleBar.Attach();
+                }                
+            }
+            else
+            {
+                _managedTitleBar?.Detach();
+                _managedTitleBar = null;
+            }
+        }
+
         /// <summary>
         /// Hides the window but does not close it.
         /// </summary>

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

@@ -78,7 +78,17 @@ namespace Avalonia.DesignerSupport.Remote
         }
 
         public IScreenImpl Screen { get; } = new ScreenStub();
-        public Action GotInputWhenDisabled { get; set; }
+        public Action GotInputWhenDisabled { get; set; }        
+        
+        public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
+
+        public Thickness ExtendedMargins { get; } = new Thickness();
+
+        public bool IsClientAreaExtendedToDecorations { get; }
+
+        public Thickness OffScreenMargin { get; } = new Thickness();
+
+        public bool NeedsManagedDecorations => false;
 
         public void Activate()
         {
@@ -119,5 +129,17 @@ namespace Avalonia.DesignerSupport.Remote
         public void SetEnabled(bool enable)
         {
         }
+
+        public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
+        {            
+        }
+
+        public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
+        {            
+        }
+
+        public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
+        {            
+        }
     }
 }

+ 23 - 1
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -38,7 +38,13 @@ namespace Avalonia.DesignerSupport.Remote
         public WindowState WindowState { get; set; }
         public Action<WindowState> WindowStateChanged { get; set; }
 
-        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }        
+
+        public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
+
+        public Thickness ExtendedMargins { get; } = new Thickness();
+
+        public Thickness OffScreenMargin { get; } = new Thickness();
 
         public WindowStub(IWindowImpl parent = null)
         {
@@ -141,6 +147,18 @@ namespace Avalonia.DesignerSupport.Remote
         {
         }
 
+        public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
+        {
+        }
+
+        public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
+        {
+        }
+
+        public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
+        {
+        }
+
         public IPopupPositioner PopupPositioner { get; }
 
         public Action GotInputWhenDisabled { get; set; }
@@ -152,6 +170,10 @@ namespace Avalonia.DesignerSupport.Remote
         }
 
         public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
+        public bool IsClientAreaExtendedToDecorations { get; }
+
+        public bool NeedsManagedDecorations => false;
     }
 
     class ClipboardStub : IClipboard

+ 9 - 1
src/Avalonia.Diagnostics/Diagnostics/DevTools.cs

@@ -45,7 +45,15 @@ namespace Avalonia.Diagnostics
 
                 window.Closed += DevToolsClosed;
                 s_open.Add(root, window);
-                window.Show();
+
+                if (root is Window inspectedWindow)
+                {
+                    window.Show(inspectedWindow);
+                }
+                else
+                {
+                    window.Show();
+                }
             }
 
             return Disposable.Create(() => window?.Close());

+ 28 - 1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/LogicalTreeNode.cs

@@ -1,3 +1,4 @@
+using System;
 using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
@@ -9,7 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public LogicalTreeNode(ILogical logical, TreeNode parent)
             : base((Control)logical, parent)
         {
-            Children = logical.LogicalChildren.CreateDerivedList(x => new LogicalTreeNode(x, this));
+            Children = new LogicalTreeNodeCollection(this, logical);
         }
 
         public static LogicalTreeNode[] Create(object control)
@@ -17,5 +18,31 @@ namespace Avalonia.Diagnostics.ViewModels
             var logical = control as ILogical;
             return logical != null ? new[] { new LogicalTreeNode(logical, null) } : null;
         }
+
+        internal class LogicalTreeNodeCollection : TreeNodeCollection
+        {
+            private readonly ILogical _control;
+            private IDisposable _subscription;
+
+            public LogicalTreeNodeCollection(TreeNode owner, ILogical control)
+                : base(owner)
+            {
+                _control = control;
+            }
+
+            public override void Dispose()
+            {
+                base.Dispose();
+                _subscription?.Dispose();
+            }
+
+            protected override void Initialize(AvaloniaList<TreeNode> nodes)
+            {
+                _subscription = _control.LogicalChildren.ForEachItem(
+                    (i, item) => nodes.Insert(i, new LogicalTreeNode(item, Owner)),
+                    (i, item) => nodes.RemoveAt(i),
+                    () => nodes.Clear());
+            }
+        }
     }
 }

+ 14 - 9
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@@ -1,4 +1,5 @@
 using System;
+using System.ComponentModel;
 using Avalonia.Controls;
 using Avalonia.Diagnostics.Models;
 using Avalonia.Input;
@@ -12,6 +13,7 @@ namespace Avalonia.Diagnostics.ViewModels
         private readonly TreePageViewModel _logicalTree;
         private readonly TreePageViewModel _visualTree;
         private readonly EventsPageViewModel _events;
+        private readonly IDisposable _pointerOverSubscription;
         private ViewModelBase _content;
         private int _selectedTab;
         private string _focusedControl;
@@ -25,16 +27,9 @@ namespace Avalonia.Diagnostics.ViewModels
             _events = new EventsPageViewModel(root);
 
             UpdateFocusedControl();
-            KeyboardDevice.Instance.PropertyChanged += (s, e) =>
-            {
-                if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
-                {
-                    UpdateFocusedControl();
-                }
-            };
-
+            KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged;
             SelectedTab = 0;
-            root.GetObservable(TopLevel.PointerOverElementProperty)
+            _pointerOverSubscription = root.GetObservable(TopLevel.PointerOverElementProperty)
                 .Subscribe(x => PointerOverElement = x?.GetType().Name);
             Console = new ConsoleViewModel(UpdateConsoleContext);
         }
@@ -129,6 +124,8 @@ namespace Avalonia.Diagnostics.ViewModels
 
         public void Dispose()
         {
+            KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged;
+            _pointerOverSubscription.Dispose();
             _logicalTree.Dispose();
             _visualTree.Dispose();
         }
@@ -137,5 +134,13 @@ namespace Avalonia.Diagnostics.ViewModels
         {
             FocusedControl = KeyboardDevice.Instance.FocusedElement?.GetType().Name;
         }
+
+        private void KeyboardPropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement))
+            {
+                UpdateFocusedControl();
+            }
+        }
     }
 }

+ 10 - 4
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNode.cs

@@ -3,15 +3,15 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Reactive;
 using System.Reactive.Linq;
-using Avalonia.Collections;
 using Avalonia.Controls;
 using Avalonia.LogicalTree;
 using Avalonia.VisualTree;
 
 namespace Avalonia.Diagnostics.ViewModels
 {
-    internal class TreeNode : ViewModelBase
+    internal class TreeNode : ViewModelBase, IDisposable
     {
+        private IDisposable _classesSubscription;
         private string _classes;
         private bool _isExpanded;
 
@@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels
                         x => control.Classes.CollectionChanged -= x)
                     .TakeUntil(removed);
 
-                classesChanged.Select(_ => Unit.Default)
+                _classesSubscription = classesChanged.Select(_ => Unit.Default)
                     .StartWith(Unit.Default)
                     .Subscribe(_ =>
                     {
@@ -49,7 +49,7 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
-        public IAvaloniaReadOnlyList<TreeNode> Children
+        public TreeNodeCollection Children
         {
             get;
             protected set;
@@ -104,6 +104,12 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
+        public void Dispose()
+        {
+            _classesSubscription.Dispose();
+            Children.Dispose();
+        }
+
         private static int IndexOf(IReadOnlyList<TreeNode> collection, TreeNode item)
         {
             var count = collection.Count;

+ 78 - 0
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreeNodeCollection.cs

@@ -0,0 +1,78 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Avalonia.Collections;
+
+namespace Avalonia.Diagnostics.ViewModels
+{
+    internal abstract class TreeNodeCollection : IAvaloniaReadOnlyList<TreeNode>, IDisposable
+    {
+        private AvaloniaList<TreeNode> _inner;
+
+        public TreeNodeCollection(TreeNode owner) => Owner = owner;
+
+        public TreeNode this[int index]
+        {
+            get
+            {
+                EnsureInitialized();
+                return _inner[index];
+            }
+        }
+
+        public int Count
+        {
+            get
+            {
+                EnsureInitialized();
+                return _inner.Count;
+            }
+        }
+
+        protected TreeNode Owner { get; }
+
+        public event NotifyCollectionChangedEventHandler CollectionChanged
+        {
+            add => _inner.CollectionChanged += value;
+            remove => _inner.CollectionChanged -= value;
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged
+        {
+            add => _inner.PropertyChanged += value;
+            remove => _inner.PropertyChanged -= value;
+        }
+
+        public virtual void Dispose()
+        {
+            if (_inner is object)
+            {
+                foreach (var node in _inner)
+                {
+                    node.Dispose();
+                }
+            }
+        }
+
+        public IEnumerator<TreeNode> GetEnumerator()
+        {
+            EnsureInitialized();
+            return _inner.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+        protected abstract void Initialize(AvaloniaList<TreeNode> nodes);
+
+        private void EnsureInitialized()
+        {
+            if (_inner is null)
+            {
+                _inner = new AvaloniaList<TreeNode>();
+                Initialize(_inner);
+            }
+        }
+    }
+}

+ 9 - 1
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@@ -62,7 +62,15 @@ namespace Avalonia.Diagnostics.ViewModels
             }
         }
 
-        public void Dispose() => _details?.Dispose();
+        public void Dispose()
+        {
+            foreach (var node in Nodes)
+            {
+                node.Dispose();
+            }
+
+            _details?.Dispose();
+        }
 
         public TreeNode FindNode(IControl control)
         {

+ 27 - 10
src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs

@@ -1,3 +1,4 @@
+using System;
 using Avalonia.Collections;
 using Avalonia.Styling;
 using Avalonia.VisualTree;
@@ -9,16 +10,7 @@ namespace Avalonia.Diagnostics.ViewModels
         public VisualTreeNode(IVisual visual, TreeNode parent)
             : base(visual, parent)
         {
-            var host = visual as IVisualTreeHost;
-
-            if (host?.Root == null)
-            {
-                Children = visual.VisualChildren.CreateDerivedList(x => new VisualTreeNode(x, this));
-            }
-            else
-            {
-                Children = new AvaloniaList<VisualTreeNode>(new[] { new VisualTreeNode(host.Root, this) });
-            }
+            Children = new VisualTreeNodeCollection(this, visual);
 
             if ((Visual is IStyleable styleable))
             {
@@ -33,5 +25,30 @@ namespace Avalonia.Diagnostics.ViewModels
             var visual = control as IVisual;
             return visual != null ? new[] { new VisualTreeNode(visual, null) } : null;
         }
+
+        internal class VisualTreeNodeCollection : TreeNodeCollection
+        {
+            private readonly IVisual _control;
+            private IDisposable _subscription;
+
+            public VisualTreeNodeCollection(TreeNode owner, IVisual control)
+                : base(owner)
+            {
+                _control = control;
+            }
+
+            public override void Dispose()
+            {
+                _subscription?.Dispose();
+            }
+
+            protected override void Initialize(AvaloniaList<TreeNode> nodes)
+            {
+                _subscription = _control.VisualChildren.ForEachItem(
+                    (i, item) => nodes.Insert(i, new VisualTreeNode(item, Owner)),
+                    (i, item) => nodes.RemoveAt(i),
+                    () => nodes.Clear());
+            }
+        }
     }
 }

+ 20 - 2
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs

@@ -14,8 +14,8 @@ namespace Avalonia.Diagnostics.Views
 {
     internal class MainWindow : Window, IStyleHost
     {
+        private readonly IDisposable _keySubscription;
         private TopLevel _root;
-        private IDisposable _keySubscription;
 
         public MainWindow()
         {
@@ -33,8 +33,22 @@ namespace Avalonia.Diagnostics.Views
             {
                 if (_root != value)
                 {
+                    if (_root != null)
+                    {
+                        _root.Closed -= RootClosed;
+                    }
+
                     _root = value;
-                    DataContext = new MainViewModel(value);
+
+                    if (_root != null)
+                    {
+                        _root.Closed += RootClosed;
+                        DataContext = new MainViewModel(value);
+                    }
+                    else
+                    {
+                        DataContext = null;
+                    }
                 }
             }
         }
@@ -45,6 +59,8 @@ namespace Avalonia.Diagnostics.Views
         {
             base.OnClosed(e);
             _keySubscription.Dispose();
+            _root.Closed -= RootClosed;
+            _root = null;
             ((MainViewModel)DataContext)?.Dispose();
         }
 
@@ -70,5 +86,7 @@ namespace Avalonia.Diagnostics.Views
                 }
             }
         }
+
+        private void RootClosed(object sender, EventArgs e) => Close();
     }
 }

+ 6 - 1
src/Avalonia.Input/InputElement.cs

@@ -158,6 +158,7 @@ namespace Avalonia.Input
 
         private bool _isEffectivelyEnabled = true;
         private bool _isFocused;
+        private bool _isFocusVisible;
         private bool _isPointerOver;
         private GestureRecognizerCollection _gestureRecognizers;
 
@@ -427,7 +428,9 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnGotFocus(GotFocusEventArgs e)
         {
-            IsFocused = e.Source == this;
+            var isFocused = e.Source == this;
+            _isFocusVisible = isFocused && (e.NavigationMethod == NavigationMethod.Directional || e.NavigationMethod == NavigationMethod.Tab);
+            IsFocused = isFocused;
         }
 
         /// <summary>
@@ -436,6 +439,7 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnLostFocus(RoutedEventArgs e)
         {
+            _isFocusVisible = false;
             IsFocused = false;
         }
 
@@ -602,6 +606,7 @@ namespace Avalonia.Input
             if (isFocused.HasValue)
             {
                 PseudoClasses.Set(":focus", isFocused.Value);
+                PseudoClasses.Set(":focus-visible", _isFocusVisible);
             }
             
             if (isPointerOver.HasValue)

+ 3 - 1
src/Avalonia.Input/Navigation/TabNavigation.cs

@@ -219,7 +219,9 @@ namespace Avalonia.Input.Navigation
 
             if (parent != null)
             {
-                if (direction == NavigationDirection.Previous && parent.CanFocus())
+                if (direction == NavigationDirection.Previous &&
+                    parent.CanFocus() && 
+                    KeyboardNavigation.GetIsTabStop((InputElement) parent))
                 {
                     return parent;
                 }

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

@@ -106,8 +106,6 @@ namespace Avalonia.Layout
 
             if (!_running)
             {
-                _running = true;
-
                 Stopwatch? stopwatch = null;
 
                 const LogEventLevel timingLogLevel = LogEventLevel.Information;
@@ -128,15 +126,24 @@ namespace Avalonia.Layout
                 _toMeasure.BeginLoop(MaxPasses);
                 _toArrange.BeginLoop(MaxPasses);
 
-                for (var pass = 0; pass < MaxPasses; ++pass)
+                try
                 {
-                    InnerLayoutPass();
+                    _running = true;
 
-                    if (!RaiseEffectiveViewportChanged())
+                    for (var pass = 0; pass < MaxPasses; ++pass)
                     {
-                        break;
+                        InnerLayoutPass();
+
+                        if (!RaiseEffectiveViewportChanged())
+                        {
+                            break;
+                        }
                     }
                 }
+                finally
+                {
+                    _running = false;
+                }
 
                 _toMeasure.EndLoop();
                 _toArrange.EndLoop();
@@ -221,23 +228,16 @@ namespace Avalonia.Layout
 
         private void InnerLayoutPass()
         {
-            try
+            for (var pass = 0; pass < MaxPasses; ++pass)
             {
-                for (var pass = 0; pass < MaxPasses; ++pass)
-                {
-                    ExecuteMeasurePass();
-                    ExecuteArrangePass();
+                ExecuteMeasurePass();
+                ExecuteArrangePass();
 
-                    if (_toMeasure.Count == 0)
-                    {
-                        break;
-                    }
+                if (_toMeasure.Count == 0)
+                {
+                    break;
                 }
             }
-            finally
-            {
-                _running = false;
-            }
         }
 
         private void ExecuteMeasurePass()
@@ -362,7 +362,7 @@ namespace Avalonia.Layout
                 }
             }
 
-            return startCount != _toMeasure.Count + _toMeasure.Count;
+            return startCount != _toMeasure.Count + _toArrange.Count;
         }
 
         private Rect CalculateEffectiveViewport(IVisual control)

+ 4 - 5
src/Avalonia.Native/NativeControlHostImpl.cs

@@ -114,19 +114,18 @@ namespace Avalonia.Native
 
             public bool IsCompatibleWith(INativeControlHostImpl host) => host is NativeControlHostImpl;
 
-            public void Hide()
+            public void HideWithSize(Size size)
             {
-                _native?.Hide();
+                _native.HideWithSize(Math.Max(1, (float)size.Width), Math.Max(1, (float)size.Height));
             }
             
-            public void ShowInBounds(TransformedBounds transformedBounds)
+            public void ShowInBounds(Rect bounds)
             {
                 if (_attachedTo == null)
                     throw new InvalidOperationException("Native control isn't attached to a toplevel");
-                var bounds = transformedBounds.Bounds.TransformToAABB(transformedBounds.Transform);
                 bounds = new Rect(bounds.X, bounds.Y, Math.Max(1, bounds.Width),
                     Math.Max(1, bounds.Height));
-                _native.MoveTo((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
+                _native.ShowInBounds((float) bounds.X, (float) bounds.Y, (float) bounds.Width, (float) bounds.Height);
             }
 
             public void InitWithChild(IPlatformHandle handle) 

+ 12 - 0
src/Avalonia.Native/PopupImpl.cs

@@ -10,6 +10,7 @@ namespace Avalonia.Native
         private readonly IAvaloniaNativeFactory _factory;
         private readonly AvaloniaNativePlatformOptions _opts;
         private readonly GlPlatformFeature _glFeature;
+        private readonly IWindowBaseImpl _parent;
 
         public PopupImpl(IAvaloniaNativeFactory factory,
             AvaloniaNativePlatformOptions opts,
@@ -19,6 +20,7 @@ namespace Avalonia.Native
             _factory = factory;
             _opts = opts;
             _glFeature = glFeature;
+            _parent = parent;
             using (var e = new PopupEvents(this))
             {
                 var context = _opts.UseGpu ? glFeature?.DeferredContext : null;
@@ -58,6 +60,16 @@ namespace Avalonia.Native
             }
         }
 
+        public override void Show()
+        {
+            var parent = _parent;
+            while (parent is PopupImpl p) 
+                parent = p._parent;
+            if (parent is WindowImpl w)
+                w.Native.TakeFocusFromChildren();
+            base.Show();
+        }
+
         public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, _glFeature, this);
 
         public void SetWindowManagerAddShadowHint(bool enabled)

+ 85 - 1
src/Avalonia.Native/WindowImpl.cs

@@ -1,6 +1,8 @@
 using System;
 using Avalonia.Controls;
 using Avalonia.Controls.Platform;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
 using Avalonia.Native.Interop;
 using Avalonia.OpenGL;
 using Avalonia.Platform;
@@ -14,6 +16,8 @@ namespace Avalonia.Native
         private readonly AvaloniaNativePlatformOptions _opts;
         private readonly GlPlatformFeature _glFeature;
         IAvnWindow _native;
+        private double _extendTitleBarHeight = -1;
+
         internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
             GlPlatformFeature glFeature) : base(opts, glFeature)
         {
@@ -50,6 +54,8 @@ namespace Avalonia.Native
 
             void IAvnWindowEvents.WindowStateChanged(AvnWindowState state)
             {
+                _parent.InvalidateExtendedMargins();
+
                 _parent.WindowStateChanged?.Invoke((WindowState)state);
             }
 
@@ -96,7 +102,85 @@ namespace Avalonia.Native
             }
         }
 
-        public Action<WindowState> WindowStateChanged { get; set; }
+        public Action<WindowState> WindowStateChanged { get; set; }        
+
+        public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
+
+        public Thickness ExtendedMargins { get; private set; }
+
+        public Thickness OffScreenMargin { get; } = new Thickness();
+
+        private bool _isExtended;
+        public bool IsClientAreaExtendedToDecorations => _isExtended;
+
+        protected override bool ChromeHitTest (RawPointerEventArgs e)
+        {
+            if(_isExtended)
+            {
+                if(e.Type == RawPointerEventType.LeftButtonDown)
+                {
+                    var visual = (_inputRoot as Window).Renderer.HitTestFirst(e.Position, _inputRoot as Window, x =>
+                            {
+                                if (x is IInputElement ie && !ie.IsHitTestVisible)
+                                {
+                                    return false;
+                                }
+                                return true;
+                            });
+
+                    if(visual == null)
+                    {
+                        _native.BeginMoveDrag();
+                    }
+                }
+            }
+
+            return false;
+        }
+        
+        private void InvalidateExtendedMargins()
+        {
+            if (WindowState ==  WindowState.FullScreen)
+            {
+                ExtendedMargins = new Thickness();
+            }
+            else
+            {
+                ExtendedMargins = _isExtended ? new Thickness(0, _extendTitleBarHeight == -1 ? _native.GetExtendTitleBarHeight() : _extendTitleBarHeight, 0, 0) : new Thickness();
+            }
+
+            ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended);
+        }
+
+        /// <inheritdoc/>
+        public void SetExtendClientAreaToDecorationsHint(bool extendIntoClientAreaHint)
+        {
+            _isExtended = extendIntoClientAreaHint;
+
+            _native.SetExtendClientArea(extendIntoClientAreaHint);
+
+            InvalidateExtendedMargins();
+        }
+
+        /// <inheritdoc/>
+        public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
+        {   
+            _native.SetExtendClientAreaHints ((AvnExtendClientAreaChromeHints)hints);
+        }
+
+        /// <inheritdoc/>
+        public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
+        {
+            _extendTitleBarHeight = titleBarHeight;
+            _native.SetExtendTitleBarHeight(titleBarHeight);
+
+            ExtendedMargins = _isExtended ? new Thickness(0, titleBarHeight == -1 ? _native.GetExtendTitleBarHeight() : titleBarHeight, 0, 0) : new Thickness();
+
+            ExtendClientAreaToDecorationsChanged?.Invoke(_isExtended);
+        }
+
+        /// <inheritdoc/>
+        public bool NeedsManagedDecorations => false;
 
         public void ShowTaskbarIcon(bool value)
         {

+ 13 - 3
src/Avalonia.Native/WindowImplBase.cs

@@ -46,7 +46,7 @@ namespace Avalonia.Native
     public abstract class WindowBaseImpl : IWindowBaseImpl,
         IFramebufferPlatformSurface, ITopLevelImplWithNativeControlHost
     {
-        IInputRoot _inputRoot;
+        protected IInputRoot _inputRoot;
         IAvnWindowBase _native;
         private object _syncRoot = new object();
         private bool _deferredRendering = false;
@@ -266,6 +266,11 @@ namespace Avalonia.Native
             return args.Handled;
         }
 
+        protected virtual bool ChromeHitTest(RawPointerEventArgs e)
+        {
+            return false;
+        }
+
         public void RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
         {
             Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
@@ -277,7 +282,12 @@ namespace Avalonia.Native
                     break;
 
                 default:
-                    Input?.Invoke(new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (RawInputModifiers)modifiers));
+                    var e = new RawPointerEventArgs(_mouse, timeStamp, _inputRoot, (RawPointerEventType)type, point.ToAvaloniaPoint(), (RawInputModifiers)modifiers);
+                    
+                    if(!ChromeHitTest(e))
+                    {
+                        Input?.Invoke(e);
+                    }
                     break;
             }
         }
@@ -319,7 +329,7 @@ namespace Avalonia.Native
         }
 
 
-        public void Show()
+        public virtual void Show()
         {
             _native.Show();
         }

+ 0 - 5
src/Avalonia.Styling/IStyledElement.cs

@@ -17,11 +17,6 @@ namespace Avalonia
         /// </summary>
         event EventHandler Initialized;
 
-        /// <summary>
-        /// Raised when resources on the element are changed.
-        /// </summary>
-        event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
-
         /// <summary>
         /// Gets a value that indicates whether the element has finished initialization.
         /// </summary>

+ 0 - 1
src/Avalonia.Styling/StyledElement.cs

@@ -67,7 +67,6 @@ namespace Avalonia
         private List<IStyleInstance>? _appliedStyles;
         private ITemplatedControl? _templatedParent;
         private bool _dataContextUpdating;
-        private bool _notifyingResourcesChanged;
 
         /// <summary>
         /// Initializes static members of the <see cref="StyledElement"/> class.

+ 0 - 1
src/Avalonia.Styling/Styling/Styles.cs

@@ -21,7 +21,6 @@ namespace Avalonia.Styling
         private IResourceHost? _owner;
         private IResourceDictionary? _resources;
         private Dictionary<Type, List<IStyle>?>? _cache;
-        private bool _notifyingResourcesChanged;
 
         public Styles()
         {

+ 69 - 0
src/Avalonia.Themes.Default/CaptionButtons.xaml

@@ -0,0 +1,69 @@
+<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Style Selector="CaptionButtons">
+    <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>    
+    <Setter Property="MaxHeight" Value="30" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal">
+          <StackPanel.Styles>
+            <Style Selector="Panel">
+              <Setter Property="Width" Value="45" />
+              <Setter Property="Background" Value="Transparent" />
+            </Style>
+            <Style Selector="Panel:pointerover">
+              <Setter Property="Background" Value="#7F7f7f7f" />
+            </Style>
+            <Style Selector="Panel#PART_CloseButton:pointerover">
+              <Setter Property="Background" Value="#7FFF0000" />
+            </Style>
+            <Style Selector="Viewbox">
+              <Setter Property="Width" Value="11" />
+              <Setter Property="Margin" Value="2" />
+            </Style>
+          </StackPanel.Styles>
+          <Panel x:Name="PART_FullScreenButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" />
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_MinimiseButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" />
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_RestoreButton">
+            <Viewbox>
+              <Viewbox.RenderTransform>
+                <RotateTransform Angle="-90" />
+              </Viewbox.RenderTransform>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/>
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_CloseButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" />
+            </Viewbox>
+          </Panel>
+        </StackPanel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  <Style Selector="CaptionButtons Panel#PART_RestoreButton Path">
+    <Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" />
+  </Style>
+  <Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
+    <Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
+  </Style>
+  <Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
+    <Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" />
+  </Style>
+  <Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path">
+    <Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" />
+  </Style>
+  <Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+ </Styles>

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

@@ -9,6 +9,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Default.Button.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.Carousel.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.CheckBox.xaml?assembly=Avalonia.Themes.Default"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Default.CaptionButtons.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ComboBox.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ComboBoxItem.xaml?assembly=Avalonia.Themes.Default"/>
   <StyleInclude Source="resm:Avalonia.Themes.Default.ContentControl.xaml?assembly=Avalonia.Themes.Default"/>

+ 10 - 2
src/Avalonia.Themes.Default/ProgressBar.xaml

@@ -1,4 +1,12 @@
 <Styles xmlns="https://github.com/avaloniaui">
+  <Design.PreviewWith>
+    <Border Padding="20">
+      <StackPanel>
+        <ProgressBar VerticalAlignment="Center" IsIndeterminate="True" />
+        <ProgressBar HorizontalAlignment="Left" IsIndeterminate="True" Orientation="Vertical" />
+      </StackPanel>
+    </Border>
+  </Design.PreviewWith>
   <Style Selector="ProgressBar">
     <Setter Property="Background" Value="{DynamicResource ThemeAccentBrush4}"/>
     <Setter Property="Foreground" Value="{DynamicResource ThemeAccentBrush}"/>
@@ -69,11 +77,11 @@
                  Easing="LinearEasing">
         <KeyFrame Cue="0%">
           <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+                  Value="{Binding TemplateProperties.IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
         </KeyFrame>
         <KeyFrame Cue="100%">
           <Setter Property="TranslateTransform.Y"
-                  Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
+                  Value="{Binding TemplateProperties.IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" />
         </KeyFrame>
       </Animation>
     </Style.Animations>

+ 6 - 3
src/Avalonia.Themes.Default/Slider.xaml

@@ -87,7 +87,10 @@
         </ControlTemplate>
     </Setter>
   </Style>
-    <Style Selector="Slider:disabled /template/ Grid#grid">
-        <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
-    </Style>
+  <Style Selector="Slider /template/ TickBar">
+    <Setter Property="Ticks" Value="{TemplateBinding Ticks}" />
+  </Style>
+  <Style Selector="Slider:disabled /template/ Grid#grid">
+    <Setter Property="Opacity" Value="{DynamicResource ThemeDisabledOpacity}" />
+  </Style>
 </Styles>

+ 53 - 0
src/Avalonia.Themes.Default/TitleBar.xaml

@@ -0,0 +1,53 @@
+<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Design.PreviewWith>
+    <Border>
+      <TitleBar Background="SkyBlue" Height="30" Width="300" Foreground="Black" />
+    </Border>
+  </Design.PreviewWith>
+  <Style Selector="TitleBar">
+    <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>    
+    <Setter Property="VerticalAlignment" Value="Top" />
+    <Setter Property="HorizontalAlignment" Value="Stretch" />
+    <Setter Property="Background" Value="Transparent" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="Stretch">
+          <Panel x:Name="PART_MouseTracker" Height="1" VerticalAlignment="Top" />
+          <Panel x:Name="PART_Container">
+            <Border x:Name="PART_Background" Background="{TemplateBinding Background}" />
+            <CaptionButtons x:Name="PART_CaptionButtons" VerticalAlignment="Top" HorizontalAlignment="Right" Foreground="{TemplateBinding Foreground}" MaxHeight="30" />
+          </Panel>
+        </Panel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="TitleBar:fullscreen">
+    <Setter Property="Background" Value="{DynamicResource SystemAccentColor}" />
+  </Style>
+
+  <Style Selector="TitleBar /template/ Border#PART_Background">
+    <Setter Property="IsHitTestVisible" Value="False" />
+  </Style>
+
+  <Style Selector="TitleBar:fullscreen /template/ Border#PART_Background">
+    <Setter Property="IsHitTestVisible" Value="True" />
+  </Style>
+
+  <Style Selector="TitleBar:fullscreen /template/ Panel#PART_MouseTracker">
+    <Setter Property="Background" Value="Transparent" />
+  </Style>
+
+  <Style Selector="TitleBar:fullscreen /template/ Panel#PART_Container">
+    <Setter Property="RenderTransform" Value="translateY(-30px)" />
+    <Setter Property="Transitions">
+      <Transitions>
+        <TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" />
+      </Transitions>
+    </Setter>
+  </Style>
+
+  <Style Selector="TitleBar:fullscreen:pointerover /template/ Panel#PART_Container">
+    <Setter Property="RenderTransform" Value="none" />
+  </Style>
+</Styles>

+ 3 - 1
src/Avalonia.Themes.Default/Window.xaml

@@ -5,7 +5,9 @@
   <Setter Property="Template">
     <ControlTemplate>
       <Panel>
-        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        <Panel IsHitTestVisible="False" Margin="{TemplateBinding OffScreenMargin}">
+          <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        </Panel>
         <Border Background="{TemplateBinding Background}">
           <VisualLayerManager>
             <ContentPresenter Name="PART_ContentPresenter"

+ 7 - 36
src/Avalonia.Themes.Fluent/Accents/FluentBaseDark.xaml

@@ -2,35 +2,8 @@
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:sys="clr-namespace:System;assembly=netstandard">
   <Style.Resources>
-    <Color x:Key="SystemAccentColor">#FF0078D7</Color>
-    <Color x:Key="SystemAltHighColor">#FF000000</Color>
-    <Color x:Key="SystemAltLowColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumHighColor">#FF000000</Color>
-    <Color x:Key="SystemAltMediumLowColor">#FF000000</Color>
-    <Color x:Key="SystemBaseHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemBaseLowColor">#FF333333</Color>
-    <Color x:Key="SystemBaseMediumColor">#FF9A9A9A</Color>
-    <Color x:Key="SystemBaseMediumHighColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemBaseMediumLowColor">#FF676767</Color>
-    <Color x:Key="SystemChromeAltLowColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackLowColor">#FFB4B4B4</Color>
-    <Color x:Key="SystemChromeBlackMediumColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackMediumLowColor">#FF000000</Color>
-    <Color x:Key="SystemChromeDisabledHighColor">#FF333333</Color>
-    <Color x:Key="SystemChromeGrayColor">#FF808080</Color>
-    <Color x:Key="SystemChromeHighColor">#FF808080</Color>
-    <Color x:Key="SystemChromeLowColor">#FF151515</Color>
-    <Color x:Key="SystemChromeMediumColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemChromeMediumLowColor">#FF2C2C2C</Color>
-    <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemListLowColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemListMediumColor">#FF333333</Color>
-    <Color x:Key="SystemChromeAltMediumHighColor">#CC000000</Color>
-    <Color x:Key="SystemChromeAltHighColor">#FF333333</Color>
-    <Color x:Key="SystemRevealListLowColor">#FF1D1D1D</Color>
-    <Color x:Key="SystemRevealListMediumColor">#FF333333</Color>
+    <Color x:Key="SystemRevealListLowColor">#18FFFFFF</Color>
+    <Color x:Key="SystemRevealListMediumColor">#30FFFFFF</Color>
     <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
     <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
     <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@@ -67,13 +40,6 @@
     <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
     <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
     
-    <!-- Override system generated accent colors -->
-    <Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
-    <Color x:Key="SystemAccentColorDark2">#FF004275</Color>
-    <Color x:Key="SystemAccentColorDark3">#FF002642</Color>
-    <Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
-    <Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
-    <Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
     <Color x:Key="RegionColor">#FF000000</Color>
     <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
     
@@ -218,6 +184,11 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99000000" />
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
+    <!-- BaseResources for ProgressBar.xaml -->
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
 
     <!-- BaseResources for TextBox.xaml -->
     <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />

+ 8 - 37
src/Avalonia.Themes.Fluent/Accents/FluentBaseLight.xaml

@@ -2,36 +2,8 @@
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
   xmlns:sys="clr-namespace:System;assembly=netstandard">
   <Style.Resources>
-    <Color x:Key="SystemAccentColor">#FF0078D7</Color>
-    <Color x:Key="SystemAltHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltLowColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumHighColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemAltMediumLowColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemBaseHighColor">#FF000000</Color>
-    <Color x:Key="SystemBaseLowColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemBaseMediumColor">#FF898989</Color>
-    <Color x:Key="SystemBaseMediumHighColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemBaseMediumLowColor">#FF737373</Color>
-    <Color x:Key="SystemChromeAltLowColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemChromeBlackHighColor">#FF000000</Color>
-    <Color x:Key="SystemChromeBlackLowColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeBlackMediumColor">#FF5D5D5D</Color>
-    <Color x:Key="SystemChromeBlackMediumLowColor">#FF898989</Color>
-    <Color x:Key="SystemChromeDisabledHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeDisabledLowColor">#FF898989</Color>
-    <Color x:Key="SystemChromeGrayColor">#FF737373</Color>
-    <Color x:Key="SystemChromeHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeLowColor">#FFECECEC</Color>
-    <Color x:Key="SystemChromeMediumColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemChromeMediumLowColor">#FFECECEC</Color>
-    <Color x:Key="SystemChromeWhiteColor">#FFFFFFFF</Color>
-    <Color x:Key="SystemListLowColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemListMediumColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemChromeAltMediumHighColor">#CCFFFFFF</Color>
-    <Color x:Key="SystemChromeAltHighColor">#FFCCCCCC</Color>
-    <Color x:Key="SystemRevealListLowColor">#FFE6E6E6</Color>
-    <Color x:Key="SystemRevealListMediumColor">#FFCCCCCC</Color>
+    <Color x:Key="SystemRevealListLowColor">#17000000</Color>
+    <Color x:Key="SystemRevealListMediumColor">#2E000000</Color>
     <!--<AcrylicBrush x:Key="SystemControlAcrylicWindowBrush" BackgroundSource="HostBackdrop" TintColor="{ThemeResource SystemChromeAltHighColor}" TintOpacity="0.8" FallbackColor="{ThemeResource SystemChromeMediumColor}" />-->
     <!--<RevealBackgroundBrush x:Key="SystemControlTransparentRevealBackgroundBrush" TargetTheme="Dark" Color="Transparent" FallbackColor="Transparent" />-->
     <SolidColorBrush x:Key="SystemControlTransparentRevealBackgroundBrush" Color="Transparent" />
@@ -68,13 +40,6 @@
     <Thickness x:Key="ComboBoxItemRevealBorderThemeThickness">1,1,1,1</Thickness>
     <x:Double x:Key="PersonPictureEllipseBadgeStrokeThickness">1</x:Double>
     
-    <!-- Override system generated accent colors -->
-    <Color x:Key="SystemAccentColorDark1">#FF005A9E</Color>
-    <Color x:Key="SystemAccentColorDark2">#FF004275</Color>
-    <Color x:Key="SystemAccentColorDark3">#FF002642</Color>
-    <Color x:Key="SystemAccentColorLight1">#FF429CE3</Color>
-    <Color x:Key="SystemAccentColorLight2">#FF76B9ED</Color>
-    <Color x:Key="SystemAccentColorLight3">#FFA6D8FF</Color>
     <!--<RevealBackgroundBrush x:Key="SystemControlHighlightListLowRevealBackgroundBrush" TargetTheme="Light" Color="{ThemeResource SystemRevealListMediumColor}" FallbackColor="{ StaticResource SystemListMediumColor}" />-->
     <Color x:Key="RegionColor">#FFFFFFFF</Color>
     <SolidColorBrush x:Key="RegionBrush" Color="{StaticResource RegionColor}" />
@@ -222,6 +187,12 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
     
+    <!-- BaseResources for ProgressBar.xaml -->
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
+
     <!-- BaseResources for TextBox.xaml -->
     <StaticResource x:Key="TextControlForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
     <StaticResource x:Key="TextControlForegroundPointerOver" ResourceKey="SystemControlForegroundBaseHighBrush" />

+ 72 - 0
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesDark.xaml

@@ -336,6 +336,15 @@
     <StaticResource x:Key="MenuFlyoutSubItemRevealBorderBrushDisabled" ResourceKey="SystemControlTransparentBrush" />-->
     <!--<Thickness x:Key="LanguageSwitcherMenuFlyoutItemPlaceholderThemeThickness">44,0,0,0</Thickness>-->
 
+    <!-- Resources for ProgressBar.xaml -->
+    <x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
+    <x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
+    <Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>        
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#59FFFFFF" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF5B2EC5" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF8A57FF" />
+    
     <!-- Resources for TextBox.xaml -->
     <SolidColorBrush x:Key="TextBoxForegroundHeaderThemeBrush" Color="#FFFFFFFF" />
     <SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#AB000000" />
@@ -658,6 +667,67 @@
     <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
     <Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
 
+    <!-- Resources for DatePicker.xaml-->
+    <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="DatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="DatePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="DatePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
+
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackground" ResourceKey="SystemControlTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightListLowBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPressed" ResourceKey="SystemControlHighlightListMediumBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPressed" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    
+    <!-- Resources for TimePicker.xaml -->
+    <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="TimePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="TimePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
+    <SolidColorBrush x:Key="TimePickerHeaderForegroundThemeBrush" Color="#FFFFFFFF" />
+    <SolidColorBrush x:Key="TimePickerForegroundThemeBrush" Color="#FF000000" />
+    
     <!-- Resources for Pivot.xaml -->
     <FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
     <FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
@@ -819,5 +889,7 @@
     <StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" />
     <Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness>
     <x:Double x:Key="TreeViewItemMinHeight">32</x:Double>
+    <!-- Resources for SplitView.xaml -->
+    <StaticResource x:Key="SplitViewLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
   </Style.Resources>
 </Style>

+ 71 - 0
src/Avalonia.Themes.Fluent/Accents/FluentControlResourcesLight.xaml

@@ -248,6 +248,14 @@
     <SolidColorBrush x:Key="ListBoxItemSelectedDisabledForegroundThemeBrush" Color="#99FFFFFF" />
     <SolidColorBrush x:Key="ListBoxItemSelectedForegroundThemeBrush" Color="White" />
     <SolidColorBrush x:Key="ListBoxItemSelectedPointerOverBackgroundThemeBrush" Color="#FF5F37BE" />
+    <!-- Resources for ProgressBar.xaml -->
+    <x:Double x:Key="ProgressBarIndicatorPauseOpacity">0.6</x:Double>
+    <x:Double x:Key="ProgressBarThemeMinHeight">4</x:Double>
+    <Thickness x:Key="ProgressBarBorderThemeThickness">0</Thickness>
+    <SolidColorBrush x:Key="ProgressBarBackgroundThemeBrush" Color="#30000000" />
+    <SolidColorBrush x:Key="ProgressBarBorderThemeBrush" Color="Transparent" />
+    <SolidColorBrush x:Key="ProgressBarForegroundThemeBrush" Color="#FF4617B4" />
+    <SolidColorBrush x:Key="ProgressBarIndeterminateForegroundThemeBrush" Color="#FF4617B4" />
 
     <!-- Resources for MenuFlyout.xaml (Menu, ContextMenu, etc) -->
     <x:Double x:Key="MenuFlyoutSeparatorThemeHeight">1</x:Double>
@@ -656,6 +664,67 @@
     <SolidColorBrush x:Key="ToolTipForegroundThemeBrush" Color="#FF666666" />
     <Thickness x:Key="ToolTipBorderThemePadding">8,5,8,7</Thickness>
 
+    <!-- Resources for DatePicker.xaml-->
+    <StaticResource x:Key="DatePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="DatePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="DatePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="DatePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="DatePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="DatePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="DatePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
+
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackground" ResourceKey="SystemControlTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPointerOver" ResourceKey="SystemControlHighlightListLowBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBackgroundPressed" ResourceKey="SystemControlHighlightListMediumBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrush" ResourceKey="SystemControlForegroundTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonBorderBrushPressed" ResourceKey="SystemControlHighlightTransparentBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPointerOver" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="DateTimePickerFlyoutButtonForegroundPressed" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    
+    <!-- Resources for TimePicker.xaml -->
+    <StaticResource x:Key="TimePickerSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerSpacerFillDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="TimePickerHeaderForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="TimePickerHeaderForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrush" ResourceKey="SystemControlForegroundBaseMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushPointerOver" ResourceKey="SystemControlHighlightBaseMediumHighBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushPressed" ResourceKey="SystemControlHighlightBaseMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBorderBrushDisabled" ResourceKey="SystemControlDisabledBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackground" ResourceKey="SystemControlBackgroundAltMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundPointerOver" ResourceKey="SystemControlPageBackgroundAltMediumBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundPressed" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundDisabled" ResourceKey="SystemControlBackgroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerButtonBackgroundFocused" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="TimePickerButtonForeground" ResourceKey="SystemControlForegroundBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundPointerOver" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundPressed" ResourceKey="SystemControlHighlightBaseHighBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundDisabled" ResourceKey="SystemControlDisabledBaseMediumLowBrush" />
+    <StaticResource x:Key="TimePickerButtonForegroundFocused" ResourceKey="SystemControlHighlightAltBaseHighBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterBackground" ResourceKey="SystemControlTransientBackgroundBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterBorderBrush" ResourceKey="SystemControlTransientBorderBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterSpacerFill" ResourceKey="SystemControlForegroundBaseLowBrush" />
+    <StaticResource x:Key="TimePickerFlyoutPresenterHighlightFill" ResourceKey="SystemControlHighlightListAccentLowBrush" />
+    <StaticResource x:Key="TimePickerLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
+    <SolidColorBrush x:Key="TimePickerForegroundThemeBrush" Color="#FF000000" />
+    <SolidColorBrush x:Key="TimePickerHeaderForegroundThemeBrush" Color="#FF000000" />
+    
     <!-- Resources for Pivot.xaml -->
     <FontFamily x:Key="PivotHeaderItemFontFamily">XamlAutoFontFamily</FontFamily>
     <FontFamily x:Key="PivotTitleFontFamily">XamlAutoFontFamily</FontFamily>
@@ -817,5 +886,7 @@
     <StaticResource x:Key="TreeViewItemCheckGlyphSelected" ResourceKey="SystemControlForegroundBaseMediumHighBrush" />
     <Thickness x:Key="TreeViewItemBorderThemeThickness">1</Thickness>
     <x:Double x:Key="TreeViewItemMinHeight">32</x:Double>
+    <!-- Resources for SplitView.xaml -->
+    <StaticResource x:Key="SplitViewLightDismissOverlayBackground" ResourceKey="SystemControlPageBackgroundMediumAltMediumBrush" />
   </Style.Resources>
 </Style>

+ 68 - 0
src/Avalonia.Themes.Fluent/CaptionButtons.xaml

@@ -0,0 +1,68 @@
+<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Style Selector="CaptionButtons">    
+    <Setter Property="MaxHeight" Value="30" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <StackPanel Spacing="2" Margin="0 0 7 0" VerticalAlignment="Stretch" TextBlock.FontSize="10" Orientation="Horizontal">
+          <StackPanel.Styles>
+            <Style Selector="Panel">
+              <Setter Property="Width" Value="45" />
+              <Setter Property="Background" Value="Transparent" />
+            </Style>
+            <Style Selector="Panel:pointerover">
+              <Setter Property="Background" Value="#7F7f7f7f" />
+            </Style>
+            <Style Selector="Panel#PART_CloseButton:pointerover">
+              <Setter Property="Background" Value="#7FFF0000" />
+            </Style>
+            <Style Selector="Viewbox">
+              <Setter Property="Width" Value="11" />
+              <Setter Property="Margin" Value="2" />
+            </Style>
+          </StackPanel.Styles>
+          <Panel x:Name="PART_FullScreenButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" />
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_MinimiseButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M2048 1229v-205h-2048v205h2048z" />
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_RestoreButton">
+            <Viewbox>
+              <Viewbox.RenderTransform>
+                <RotateTransform Angle="-90" />
+              </Viewbox.RenderTransform>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}"/>
+            </Viewbox>
+          </Panel>
+
+          <Panel x:Name="PART_CloseButton">
+            <Viewbox>
+              <Path Stretch="UniformToFill" Fill="{TemplateBinding Foreground}" Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" />
+            </Viewbox>
+          </Panel>
+        </StackPanel>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  <Style Selector="CaptionButtons Panel#PART_RestoreButton Path">
+    <Setter Property="Data" Value="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" />
+  </Style>
+  <Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
+    <Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
+  </Style>
+  <Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
+    <Setter Property="Data" Value="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" />
+  </Style>
+  <Style Selector="CaptionButtons:fullscreen Panel#PART_FullScreenButton Path">
+    <Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" />
+  </Style>
+  <Style Selector="CaptionButtons:fullscreen Panel#PART_RestoreButton, CaptionButtons:fullscreen Panel#PART_MinimiseButton">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+ </Styles>

+ 338 - 0
src/Avalonia.Themes.Fluent/DatePicker.xaml

@@ -0,0 +1,338 @@
+<!--
+// (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"
+        xmlns:sys="clr-namespace:System;assembly=netstandard">
+  <Styles.Resources>
+    <Thickness x:Key="DatePickerTopHeaderMargin">0,0,0,4</Thickness>
+    <x:Double x:Key="DatePickerFlyoutPresenterHighlightHeight">40</x:Double>
+    <x:Double x:Key="DatePickerFlyoutPresenterItemHeight">40</x:Double>
+    <x:Double x:Key="DatePickerFlyoutPresenterAcceptDismissHostGridHeight">41</x:Double>
+    <x:Double x:Key="DatePickerThemeMinWidth">296</x:Double>
+    <x:Double x:Key="DatePickerThemeMaxWidth">456</x:Double>
+    <Thickness x:Key="DatePickerFlyoutPresenterItemPadding">0,3,0,6</Thickness>
+    <Thickness x:Key="DatePickerFlyoutPresenterMonthPadding">9,3,0,6</Thickness>
+    <Thickness x:Key="DatePickerHostPadding">0,3,0,6</Thickness>
+    <Thickness x:Key="DatePickerHostMonthPadding">9,3,0,6</Thickness>
+    <x:Double x:Key="DatePickerSpacerThemeWidth">1</x:Double>
+  </Styles.Resources>
+
+  <!-- Styles for the items displayed in the selectors -->
+  <Style Selector="ListBoxItem.DateTimePickerItem">
+    <Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterItemPadding}"/>
+    <Setter Property="VerticalContentAlignment" Value="Center" />
+    <Setter Property="HorizontalContentAlignment" Value="Center" />
+  </Style>
+  <Style Selector="ListBoxItem.DateTimePickerItem:selected">
+    <Setter Property="IsHitTestVisible" Value="False"/>
+  </Style>
+  <Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ Rectangle#PressedBackground">
+    <Setter Property="Fill" Value="Transparent"/>
+  </Style>
+  <Style Selector="ListBoxItem.DateTimePickerItem:selected /template/ ContentPresenter">
+    <Setter Property="Background" Value="Transparent" />
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
+  </Style>
+  <Style Selector="ListBoxItem.DateTimePickerItem.MonthItem">
+    <Setter Property="Padding" Value="{DynamicResource DatePickerFlyoutPresenterMonthPadding}"/>
+    <Setter Property="VerticalContentAlignment" Value="Center" />
+    <Setter Property="HorizontalContentAlignment" Value="Left" />
+  </Style>
+
+
+  <!-- This is used for both the accept/dismiss & repeatbuttons in the presenter-->
+  <Style Selector=":is(Button).DateTimeFlyoutButtonStyle">
+    <Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackground}" />
+    <Setter Property="HorizontalContentAlignment" Value="Center"/>
+    <Setter Property="VerticalContentAlignment" Value="Center"/>
+    <Setter Property="Template">
+      <ControlTemplate>
+        <!-- 
+                The background is doubled here for the loopingselector up/down repeat buttons 
+                that appear opaque. Not sure how MS does it though I suspect this is it
+                but source isn't MIT yet, so this is my solution -->
+        <Border Background="{TemplateBinding Background}">
+          <ContentPresenter x:Name="ContentPresenter"
+                  Background="{TemplateBinding Background}"
+                  BorderBrush="{DynamicResource DateTimePickerFlyoutButtonBorderBrush}"
+                  BorderThickness="{DynamicResource DateTimeFlyoutButtonBorderThickness}"
+                  Content="{TemplateBinding Content}"
+                  TextBlock.Foreground="{DynamicResource SystemControlHighlightAltBaseHighBrush}"
+                  ContentTemplate="{TemplateBinding ContentTemplate}"
+                  Padding="{TemplateBinding Padding}"
+                  HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                  VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
+                  CornerRadius="{DynamicResource ControlCornerRadius}"/>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pointerover /template/ ContentPresenter">
+    <Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPointerOver}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPointerOver}"/>
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPointerOver}"/>
+  </Style>
+
+  <Style Selector=":is(Button).DateTimeFlyoutButtonStyle:pressed /template/ ContentPresenter">
+    <Setter Property="Background" Value="{DynamicResource DateTimePickerFlyoutButtonBackgroundPressed}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource DateTimePickerFlyoutButtonBorderBrushPressed}"/>
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DateTimePickerFlyoutButtonForegroundPressed}"/>
+  </Style>
+
+
+  <Style Selector="RepeatButton.UpButton">
+    <Setter Property="VerticalAlignment" Value="Top"/>
+    <Setter Property="Height" Value="22" />
+    <Setter Property="HorizontalAlignment" Value="Stretch" />
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" />
+    <Setter Property="Content">
+      <Template>
+        <Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center">
+          <Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,9 L 9,0 L 18,9"/>
+        </Viewbox>
+      </Template>
+    </Setter>
+  </Style>
+  <Style Selector="RepeatButton.DownButton">
+    <Setter Property="VerticalAlignment" Value="Bottom"/>
+    <Setter Property="Height" Value="22" />
+    <Setter Property="HorizontalAlignment" Value="Stretch" />
+    <Setter Property="Focusable" Value="False" />
+    <Setter Property="Background" Value="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" />
+    <Setter Property="Content">
+      <Template>
+        <Viewbox Height="10" Width="10" HorizontalAlignment="Center" VerticalAlignment="Center">
+          <Path Stroke="{Binding $parent[RepeatButton].Foreground}" StrokeThickness="1" Data="M 0,0 L 9,9 L 18,0"/>
+        </Viewbox>
+      </Template>
+    </Setter>
+  </Style>
+
+  <Style Selector="DatePicker">
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
+    <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
+    <Setter Property="Foreground" Value="{DynamicResource DatePickerButtonForeground}" />
+    <Setter Property="Background" Value="{DynamicResource DatePickerButtonBackground}"/>
+    <Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrush}"/>
+    <Setter Property="BorderThickness" Value="1"/>
+    <Setter Property="HorizontalAlignment" Value="Left" />
+    <Setter Property="VerticalAlignment" Value="Center" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Grid Name="LayoutRoot" Margin="{TemplateBinding Padding}" RowDefinitions="Auto,*">
+          <ContentPresenter Name="HeaderContentPresenter" Grid.Row="0"
+                            Content="{TemplateBinding Header}"
+                            ContentTemplate="{TemplateBinding HeaderTemplate}"
+                            Margin="{DynamicResource DatePickerTopHeaderMargin}"
+                            MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
+                            HorizontalAlignment="Stretch"
+                            VerticalAlignment="Top"/>
+
+          <Button Name="FlyoutButton" Grid.Row="1"
+                  Foreground="{TemplateBinding Foreground}"
+                  Background="{TemplateBinding Background}"
+                  BorderBrush="{TemplateBinding BorderBrush}"
+                  BorderThickness="{TemplateBinding BorderThickness}"
+                  IsEnabled="{TemplateBinding IsEnabled}"
+                  MinWidth="{StaticResource DatePickerThemeMinWidth}"
+                  MaxWidth="{StaticResource DatePickerThemeMaxWidth}"
+                  HorizontalAlignment="Stretch"
+                  VerticalAlignment="Stretch"
+                  HorizontalContentAlignment="Stretch"
+                  VerticalContentAlignment="Stretch"
+                  TemplatedControl.IsTemplateFocusTarget="True">
+            <Button.Template>
+              <ControlTemplate>
+                <ContentPresenter Name="ContentPresenter"
+                                  BorderBrush="{TemplateBinding BorderBrush}"
+                                  Background="{TemplateBinding Background}"
+                                  BorderThickness="{TemplateBinding BorderThickness}"
+                                  Content="{TemplateBinding Content}"
+                                  TextBlock.Foreground="{TemplateBinding Foreground}"
+                                  HorizontalContentAlignment="Stretch"
+                                  VerticalContentAlignment="Stretch"
+                                  CornerRadius="{DynamicResource ControlCornerRadius}"/>
+              </ControlTemplate>
+            </Button.Template>
+            <Grid Name="ButtonContentGrid" ColumnDefinitions="78*,Auto,132*,Auto,78*">
+              <TextBlock Name="DayText" Text="day" HorizontalAlignment="Center"
+                         Padding="{DynamicResource DatePickerHostPadding}"
+                         FontFamily="{TemplateBinding FontFamily}"
+                         FontWeight="{TemplateBinding FontWeight}"
+                         FontSize="{TemplateBinding FontSize}"/>
+              <TextBlock Name="MonthText" Text="month" TextAlignment="Left"
+                         Padding="{DynamicResource DatePickerHostMonthPadding}"
+                         FontFamily="{TemplateBinding FontFamily}"
+                         FontWeight="{TemplateBinding FontWeight}"
+                         FontSize="{TemplateBinding FontSize}"/>
+              <TextBlock Name="YearText" Text="year" HorizontalAlignment="Center"
+                         Padding="{DynamicResource DatePickerHostPadding}"
+                         FontFamily="{TemplateBinding FontFamily}"
+                         FontWeight="{TemplateBinding FontWeight}"
+                         FontSize="{TemplateBinding FontSize}"/>
+              <Rectangle x:Name="FirstSpacer"
+                    Fill="{DynamicResource DatePickerSpacerFill}"
+                    HorizontalAlignment="Center"
+                    Width="1"
+                    Grid.Column="1" />
+              <Rectangle x:Name="SecondSpacer"
+                    Fill="{DynamicResource DatePickerSpacerFill}"
+                    HorizontalAlignment="Center"
+                    Width="1"
+                    Grid.Column="3" />
+            </Grid>
+          </Button>
+
+          <Popup Name="Popup" WindowManagerAddShadowHint="False"
+                 StaysOpen="False" PlacementTarget="{TemplateBinding}"
+                 PlacementMode="Bottom">
+            <DatePickerPresenter Name="PickerPresenter" />
+          </Popup>
+
+        </Grid>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+  <Style Selector="DatePicker /template/ ContentPresenter#HeaderContentPresenter">
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerHeaderForeground}"/>
+  </Style>
+  <Style Selector="DatePicker:disabled /template/ Rectangle">
+    <Setter Property="Fill" Value="{DynamicResource DatePickerSpacerFillDisabled}"/>
+  </Style>
+
+  <Style Selector="DatePicker /template/ Button#FlyoutButton:pointerover /template/ ContentPresenter">
+    <Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPointerOver}"/>
+    <Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPointerOver}"/>
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPointerOver}"/>
+  </Style>
+
+  <Style Selector="DatePicker /template/ Button#FlyoutButton:pressed /template/ ContentPresenter">
+    <Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushPressed}"/>
+    <Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundPressed}"/>
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundPressed}"/>
+  </Style>
+
+  <Style Selector="DatePicker /template/ Button#FlyoutButton:disabled /template/ ContentPresenter">
+    <Setter Property="BorderBrush" Value="{DynamicResource DatePickerButtonBorderBrushDisabled}"/>
+    <Setter Property="Background" Value="{DynamicResource DatePickerButtonBackgroundDisabled}"/>
+    <Setter Property="TextBlock.Foreground" Value="{DynamicResource DatePickerButtonForegroundDisabled}"/>
+  </Style>
+
+  <!-- Changes foreground for watermark text when SelectedDate is null-->
+  <Style Selector="DatePicker:hasnodate /template/ Button#FlyoutButton TextBlock">
+    <Setter Property="Foreground" Value="{DynamicResource TextControlPlaceholderForeground}"/>
+  </Style>
+
+  <!--WinUI: DatePickerFlyoutPresenter-->
+  <Style Selector="DatePickerPresenter">
+    <Setter Property="Width" Value="296" />
+    <Setter Property="MinWidth" Value="296" />
+    <Setter Property="MaxHeight" Value="398" />
+    <Setter Property="FontFamily" Value="{DynamicResource ContentControlThemeFontFamily}" />
+    <Setter Property="FontWeight" Value="Normal" />
+    <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
+    <Setter Property="Background" Value="{DynamicResource DatePickerFlyoutPresenterBackground}" />
+    <Setter Property="BorderBrush" Value="{DynamicResource DatePickerFlyoutPresenterBorderBrush}" />
+    <Setter Property="BorderThickness" Value="{DynamicResource DateTimeFlyoutBorderThickness}" />
+    <Setter Property="Template">
+      <ControlTemplate>
+        <Border Name="Background" Background="{TemplateBinding Background}"
+                BorderBrush="{TemplateBinding BorderBrush}"
+                BorderThickness="{TemplateBinding BorderThickness}"
+                Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
+                MaxHeight="398" CornerRadius="{DynamicResource OverlayCornerRadius}">
+          <Grid Name="ContentRoot" RowDefinitions="*,Auto">
+            <Grid Name="PickerContainer">
+              <!--Column Definitions set in code, ignore here-->
+              <Panel Name="MonthHost">
+                <ScrollViewer HorizontalScrollBarVisibility="Disabled"
+                              VerticalScrollBarVisibility="Hidden">
+                  <DateTimePickerPanel Name="MonthSelector"
+                                      PanelType="Month"
+                                      ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
+                                      ShouldLoop="True" />
+                </ScrollViewer>
+                <RepeatButton Name="MonthUpButton"
+                              Classes="DateTimeFlyoutButtonStyle UpButton"/>
+                <RepeatButton Name="MonthDownButton"
+                              Classes="DateTimeFlyoutButtonStyle DownButton"/>
+              </Panel>
+              <Panel Name="DayHost">
+                <ScrollViewer HorizontalScrollBarVisibility="Disabled"
+                              VerticalScrollBarVisibility="Hidden">
+                  <DateTimePickerPanel Name="DaySelector"
+                                      PanelType="Day"
+                                      ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
+                                      ShouldLoop="True" />
+                </ScrollViewer>
+                <RepeatButton Name="DayUpButton"
+                              Classes="DateTimeFlyoutButtonStyle UpButton"/>
+                <RepeatButton Name="DayDownButton"
+                              Classes="DateTimeFlyoutButtonStyle DownButton"/>
+              </Panel>
+              <Panel Name="YearHost">
+                <ScrollViewer HorizontalScrollBarVisibility="Disabled"
+                              VerticalScrollBarVisibility="Hidden">
+                  <DateTimePickerPanel Name="YearSelector"
+                                      PanelType="Year"
+                                      ItemHeight="{DynamicResource DatePickerFlyoutPresenterItemHeight}"
+                                      ShouldLoop="False" />
+                </ScrollViewer>
+                <RepeatButton Name="YearUpButton"
+                              Classes="DateTimeFlyoutButtonStyle UpButton"/>
+                <RepeatButton Name="YearDownButton"
+                              Classes="DateTimeFlyoutButtonStyle DownButton"/>
+              </Panel>
+              <Rectangle Name="HighlightRect" IsHitTestVisible="False" ZIndex="-1"
+                         Fill="{DynamicResource DatePickerFlyoutPresenterHighlightFill}"
+                         Grid.Column="0" Grid.ColumnSpan="5" VerticalAlignment="Center"
+                         Height="{DynamicResource DatePickerFlyoutPresenterHighlightHeight}" />
+              <Rectangle Name="FirstSpacer"
+                         Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
+                         HorizontalAlignment="Center"
+                         Width="{DynamicResource DatePickerSpacerThemeWidth}"
+                         Grid.Column="1" />
+              <Rectangle Name="SecondSpacer"
+                         Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
+                         HorizontalAlignment="Center"
+                         Width="{DynamicResource DatePickerSpacerThemeWidth}"
+                         Grid.Column="3" />
+            </Grid>
+            <Grid Grid.Row="1" Height="{DynamicResource DatePickerFlyoutPresenterAcceptDismissHostGridHeight}"
+                  Name="AcceptDismissGrid" ColumnDefinitions="*,*">
+              <Rectangle Height="{DynamicResource DatePickerSpacerThemeWidth}" VerticalAlignment="Top"
+                         Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
+                         Grid.ColumnSpan="2"/>
+              <Button Name="AcceptButton" Grid.Column="0" Classes="DateTimeFlyoutButtonStyle"
+                          HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+                <Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
+                      StrokeThickness="0.75" Data="M0.5,8.5 5,13.5 15.5,3" />
+              </Button>
+              <Button Name="DismissButton" Grid.Column="1" Classes="DateTimeFlyoutButtonStyle"
+                         FontSize="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
+                <Path Stroke="{Binding $parent[Button].Foreground}" StrokeLineCap="Round"
+                      StrokeThickness="0.75" Data="M2,2 14,14 M2,14 14 2" />
+              </Button>
+            </Grid>
+          </Grid>
+        </Border>
+      </ControlTemplate>
+    </Setter>
+  </Style>
+
+  <Style Selector="DatePickerPresenter /template/ Panel RepeatButton">
+    <Setter Property="IsVisible" Value="False" />
+  </Style>
+
+  <Style Selector="DatePickerPresenter /template/ Panel:pointerover RepeatButton">
+    <Setter Property="IsVisible" Value="True" />
+  </Style>
+  
+</Styles>

+ 6 - 1
src/Avalonia.Themes.Fluent/FluentTheme.xaml

@@ -7,7 +7,8 @@
 
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.FocusAdorner.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.Button.xaml?assembly=Avalonia.Themes.Fluent"/>
-  <StyleInclude Source="resm:Avalonia.Themes.Fluent.Carousel.xaml?assembly=Avalonia.Themes.Fluent"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.CaptionButtons.xaml?assembly=Avalonia.Themes.Fluent"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.Carousel.xaml?assembly=Avalonia.Themes.Fluent"/>  
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.CheckBox.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.ComboBox.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.ComboBoxItem.xaml?assembly=Avalonia.Themes.Fluent"/>
@@ -36,6 +37,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.TextBox.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.ToggleButton.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.Expander.xaml?assembly=Avalonia.Themes.Fluent"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.TitleBar.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.TreeView.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.TreeViewItem.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.UserControl.xaml?assembly=Avalonia.Themes.Fluent"/>
@@ -53,4 +55,7 @@
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.NotificationCard.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.NativeMenuBar.xaml?assembly=Avalonia.Themes.Fluent"/>
   <StyleInclude Source="resm:Avalonia.Themes.Fluent.ToggleSwitch.xaml?assembly=Avalonia.Themes.Fluent"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.SplitView.xaml?assembly=Avalonia.Themes.Fluent"/>
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.DatePicker.xaml?assembly=Avalonia.Themes.Fluent"/>  
+  <StyleInclude Source="resm:Avalonia.Themes.Fluent.TimePicker.xaml?assembly=Avalonia.Themes.Fluent"/>
 </Styles>

+ 34 - 10
src/Avalonia.Themes.Fluent/FocusAdorner.xaml

@@ -1,10 +1,34 @@
-<Style xmlns="https://github.com/avaloniaui" Selector=":is(Control)">
-  <Setter Property="FocusAdorner">
-    <FocusAdornerTemplate>
-      <Rectangle Stroke="Black"
-                 StrokeThickness="1"
-                 StrokeDashArray="1,2"
-                 Margin="1"/>
-    </FocusAdornerTemplate>
-  </Setter>
-</Style>
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+  <Styles.Resources>
+    <Thickness x:Key="SystemControlFocusVisualMargin">0</Thickness>
+    <Thickness x:Key="SystemControlFocusVisualPrimaryThickness">2</Thickness>
+    <Thickness x:Key="SystemControlFocusVisualSecondaryThickness">1</Thickness>
+  </Styles.Resources>
+
+  <!--  HighVisibility FocusAdorner  -->
+  <Style Selector=":is(Control)">
+    <Setter Property="FocusAdorner">
+      <FocusAdornerTemplate>
+        <Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
+                BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
+                Margin="{StaticResource SystemControlFocusVisualMargin}">
+          <Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
+                  BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
+        </Border>
+      </FocusAdornerTemplate>
+    </Setter>
+  </Style>
+
+  <!--  DottedLine FocusAdorner  -->
+  <Style Selector="Window.DottedLineFocusAdorner :is(Control)">
+    <Setter Property="FocusAdorner">
+      <FocusAdornerTemplate>
+        <Rectangle Stroke="{StaticResource SystemControlFocusVisualPrimaryBrush}"
+                   StrokeThickness="1"
+                   StrokeDashArray="1,2"
+                   Margin="1" />
+      </FocusAdornerTemplate>
+    </Setter>
+  </Style>
+</Styles>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio