Jelajahi Sumber

Merge pull request #3962 from AvaloniaUI/feature/transparency-api

Window Transparency
danwalmsley 5 tahun lalu
induk
melakukan
cf5d96adcc
28 mengubah file dengan 784 tambahan dan 38 penghapusan
  1. 1 0
      native/Avalonia.Native/inc/avalonia-native.h
  2. 3 0
      native/Avalonia.Native/src/OSX/window.h
  3. 29 0
      native/Avalonia.Native/src/OSX/window.mm
  4. 7 1
      samples/ControlCatalog/MainView.xaml
  5. 7 0
      samples/ControlCatalog/MainView.xaml.cs
  6. 1 2
      samples/ControlCatalog/MainWindow.xaml
  7. 8 0
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  8. 16 0
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  9. 106 0
      src/Avalonia.Controls/TopLevel.cs
  10. 25 0
      src/Avalonia.Controls/WindowTransparencyLevel.cs
  11. 6 0
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  12. 25 0
      src/Avalonia.Native/WindowImplBase.cs
  13. 11 8
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  14. 10 7
      src/Avalonia.Themes.Default/OverlayPopupHost.xaml
  15. 10 7
      src/Avalonia.Themes.Default/PopupRoot.xaml
  16. 13 10
      src/Avalonia.Themes.Default/Window.xaml
  17. 82 0
      src/Avalonia.X11/TransparencyHelper.cs
  18. 2 0
      src/Avalonia.X11/X11Atoms.cs
  19. 182 0
      src/Avalonia.X11/X11Globals.cs
  20. 2 0
      src/Avalonia.X11/X11Platform.cs
  21. 16 0
      src/Avalonia.X11/X11Window.cs
  22. 8 0
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  23. 8 0
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  24. 106 0
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  25. 5 0
      src/Windows/Avalonia.Win32/Win32Platform.cs
  26. 84 1
      src/Windows/Avalonia.Win32/WindowImpl.cs
  27. 7 2
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  28. 4 0
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

+ 1 - 0
native/Avalonia.Native/inc/avalonia-native.h

@@ -258,6 +258,7 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
     virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
     virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
                                               IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) = 0;
+    virtual HRESULT SetBlurEnabled (bool enable) = 0;
 };
 
 AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase

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

@@ -3,6 +3,9 @@
 
 class WindowBaseImpl;
 
+@interface AutoFitContentVisualEffectView : NSVisualEffectView
+@end
+
 @interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
 -(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
 -(NSEvent* _Nonnull) lastMouseDownEvent;

+ 29 - 0
native/Avalonia.Native/src/OSX/window.mm

@@ -20,6 +20,7 @@ public:
         View = NULL;
         Window = NULL;
     }
+    NSVisualEffectView* VisualEffect;
     AvnView* View;
     AvnWindow* Window;
     ComPtr<IAvnWindowBaseEvents> BaseEvents;
@@ -47,6 +48,12 @@ public:
         
         [Window setStyleMask:NSWindowStyleMaskBorderless];
         [Window setBackingType:NSBackingStoreBuffered];
+        
+        VisualEffect = [AutoFitContentVisualEffectView new];
+        [VisualEffect setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+        [VisualEffect setMaterial:NSVisualEffectMaterialLight];
+        [VisualEffect setAutoresizesSubviews:true];
+        
         [Window setContentView: View];
     }
     
@@ -383,6 +390,18 @@ public:
         return *ppv == nil ? E_FAIL : S_OK;
     }
     
+    virtual HRESULT SetBlurEnabled (bool enable) override
+    {
+        [Window setContentView: enable ? VisualEffect : View];
+        
+        if(enable)
+        {
+            [VisualEffect addSubview:View];
+        }
+        
+        return S_OK;
+    }
+    
     virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
                                               IAvnClipboard* clipboard, IAvnDndResultCallback* cb,
                                               void* sourceHandle) override
@@ -911,6 +930,16 @@ protected:
 
 NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil];
 
+@implementation AutoFitContentVisualEffectView
+-(void)setFrameSize:(NSSize)newSize
+{
+    [super setFrameSize:newSize];
+    if([[self subviews] count] == 0)
+        return;
+    [[self subviews][0] setFrameSize: newSize];
+}
+@end
+
 @implementation AvnView
 {
     ComPtr<WindowBaseImpl> _parent;

+ 7 - 1
samples/ControlCatalog/MainView.xaml

@@ -2,7 +2,7 @@
         xmlns:pages="clr-namespace:ControlCatalog.Pages"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:Class="ControlCatalog.MainView"
-        Background="{DynamicResource ThemeBackgroundBrush}"
+        Background="Transparent"
         Foreground="{DynamicResource ThemeForegroundBrush}"
         FontSize="{DynamicResource FontSizeNormal}">
   <Grid>
@@ -70,6 +70,12 @@
             <ComboBoxItem>Light</ComboBoxItem>
             <ComboBoxItem>Dark</ComboBoxItem>
           </ComboBox>
+          <ComboBox x:Name="TransparencyLevels" SelectedIndex="0">
+            <ComboBoxItem>None</ComboBoxItem>
+            <ComboBoxItem>Transparent</ComboBoxItem>
+            <ComboBoxItem>Blur</ComboBoxItem>
+            <ComboBoxItem>AcrylicBlur</ComboBoxItem>
+          </ComboBox>
           <ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
         </StackPanel>
       </TabControl.Tag>

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

@@ -63,6 +63,13 @@ 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)

+ 1 - 2
samples/ControlCatalog/MainWindow.xaml

@@ -7,8 +7,7 @@
         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}">
-
+        x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}" Background="Transparent">
   <NativeMenu.Menu>
     <NativeMenu>
       <NativeMenuItem Header="File">

+ 8 - 0
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@@ -49,6 +49,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }
         public Action<double> ScalingChanged { get; set; }
+
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
 
         public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);
@@ -61,6 +64,11 @@ namespace Avalonia.Controls.Embedding.Offscreen
 
         public Action Closed { get; set; }
         public abstract IMouseDevice MouseDevice { get; }
+
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
+
+        public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
         public IPopupImpl CreatePopup() => null;
     }
 }

+ 16 - 0
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Rendering;
@@ -58,6 +59,11 @@ namespace Avalonia.Platform
         /// </summary>
         Action<double> ScalingChanged { get; set; }
 
+        /// <summary>
+        /// Gets or sets a method called when the toplevel's TransparencyLevel changes.
+        /// </summary>
+        Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         /// <summary>
         /// Creates a new renderer for the toplevel.
         /// </summary>
@@ -106,5 +112,15 @@ namespace Avalonia.Platform
         IMouseDevice MouseDevice { get; }
 
         IPopupImpl CreatePopup();
+
+        /// <summary>
+        /// Sets the <see cref="WindowTransparencyLevel"/> hint of the TopLevel.
+        /// </summary>
+        void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel);
+
+        /// <summary>
+        /// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel.
+        /// </summary>
+        WindowTransparencyLevel TransparencyLevel { get; }
     }
 }

+ 106 - 0
src/Avalonia.Controls/TopLevel.cs

@@ -6,6 +6,7 @@ using Avalonia.Input.Raw;
 using Avalonia.Layout;
 using Avalonia.Logging;
 using Avalonia.LogicalTree;
+using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Rendering;
 using Avalonia.Styling;
@@ -43,13 +44,35 @@ namespace Avalonia.Controls
         public static readonly StyledProperty<IInputElement> PointerOverElementProperty =
             AvaloniaProperty.Register<TopLevel, IInputElement>(nameof(IInputRoot.PointerOverElement));
 
+        /// <summary>
+        /// Defines the <see cref="TransparencyLevelHint"/> property.
+        /// </summary>
+        public static readonly StyledProperty<WindowTransparencyLevel> TransparencyLevelHintProperty =
+            AvaloniaProperty.Register<TopLevel, WindowTransparencyLevel>(nameof(TransparencyLevelHint), WindowTransparencyLevel.None);
+
+        /// <summary>
+        /// Defines the <see cref="ActualTransparencyLevel"/> property.
+        /// </summary>
+        public static readonly DirectProperty<TopLevel, WindowTransparencyLevel> ActualTransparencyLevelProperty =
+            AvaloniaProperty.RegisterDirect<TopLevel, WindowTransparencyLevel>(nameof(ActualTransparencyLevel), 
+                o => o.ActualTransparencyLevel, 
+                unsetValue: WindowTransparencyLevel.None);        
+
+        /// <summary>
+        /// Defines the <see cref="TransparencyBackgroundFallbackProperty"/> property.
+        /// </summary>
+        public static readonly StyledProperty<IBrush> TransparencyBackgroundFallbackProperty =
+            AvaloniaProperty.Register<TopLevel, IBrush>(nameof(TransparencyBackgroundFallback), Brushes.White);
+
         private readonly IInputManager _inputManager;
         private readonly IAccessKeyHandler _accessKeyHandler;
         private readonly IKeyboardNavigationHandler _keyboardNavigationHandler;
         private readonly IPlatformRenderInterface _renderInterface;
         private readonly IGlobalStyles _globalStyles;
         private Size _clientSize;
+        private WindowTransparencyLevel _actualTransparencyLevel;
         private ILayoutManager _layoutManager;
+        private Border _transparencyFallbackBorder;
 
         /// <summary>
         /// Initializes static members of the <see cref="TopLevel"/> class.
@@ -57,6 +80,16 @@ namespace Avalonia.Controls
         static TopLevel()
         {
             AffectsMeasure<TopLevel>(ClientSizeProperty);
+
+            TransparencyLevelHintProperty.Changed.AddClassHandler<TopLevel>(
+                (tl, e) => 
+                {
+                    if (tl.PlatformImpl != null)
+                    {
+                        tl.PlatformImpl.SetTransparencyLevelHint((WindowTransparencyLevel)e.NewValue);
+                        tl.HandleTransparencyLevelChanged(tl.PlatformImpl.TransparencyLevel);
+                    }
+                });
         }
 
         /// <summary>
@@ -85,6 +118,8 @@ namespace Avalonia.Controls
 
             PlatformImpl = impl;
 
+            _actualTransparencyLevel = PlatformImpl.TransparencyLevel;            
+
             dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current;
             var styler = TryGetService<IStyler>(dependencyResolver);
 
@@ -108,6 +143,7 @@ namespace Avalonia.Controls
             impl.Paint = HandlePaint;
             impl.Resized = HandleResized;
             impl.ScalingChanged = HandleScalingChanged;
+            impl.TransparencyLevelChanged = HandleTransparencyLevelChanged;
 
             _keyboardNavigationHandler?.SetOwner(this);
             _accessKeyHandler?.SetOwner(this);
@@ -155,6 +191,34 @@ namespace Avalonia.Controls
             protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); }
         }
 
+        /// <summary>
+        /// Gets or sets the <see cref="WindowTransparencyLevel"/> that the TopLevel should use when possible.
+        /// </summary>
+        public WindowTransparencyLevel TransparencyLevelHint
+        {
+            get { return GetValue(TransparencyLevelHintProperty); }
+            set { SetValue(TransparencyLevelHintProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets the acheived <see cref="WindowTransparencyLevel"/> that the platform was able to provide.
+        /// </summary>
+        public WindowTransparencyLevel ActualTransparencyLevel
+        {
+            get => _actualTransparencyLevel;
+            private set => SetAndRaise(ActualTransparencyLevelProperty, ref _actualTransparencyLevel, value);
+        }        
+
+        /// <summary>
+        /// Gets or sets the <see cref="IBrush"/> that transparency will blend with when transparency is not supported.
+        /// By default this is a solid white brush.
+        /// </summary>
+        public IBrush TransparencyBackgroundFallback
+        {
+            get => GetValue(TransparencyBackgroundFallbackProperty);
+            set => SetValue(TransparencyBackgroundFallbackProperty, value);
+        }
+
         public ILayoutManager LayoutManager
         {
             get
@@ -312,6 +376,39 @@ namespace Avalonia.Controls
             LayoutHelper.InvalidateSelfAndChildrenMeasure(this);
         }
 
+        private bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received)
+        {
+            if(requested == received)
+            {
+                return true;
+            }
+            else if(requested >= WindowTransparencyLevel.Blur && received >= WindowTransparencyLevel.Blur)
+            {
+                return true;
+            }
+
+            return false;
+        }
+
+        protected virtual void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel)
+        {
+            if(_transparencyFallbackBorder != null)
+            {
+                if(transparencyLevel == WindowTransparencyLevel.None || 
+                    TransparencyLevelHint == WindowTransparencyLevel.None || 
+                    !TransparencyLevelsMatch(TransparencyLevelHint, transparencyLevel))
+                {
+                    _transparencyFallbackBorder.Background = TransparencyBackgroundFallback;
+                }
+                else
+                {
+                    _transparencyFallbackBorder.Background = Brushes.Transparent;
+                }
+            }
+
+            ActualTransparencyLevel = transparencyLevel;
+        }
+
         /// <inheritdoc/>
         protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
         {
@@ -321,6 +418,15 @@ namespace Avalonia.Controls
                 $"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
         }
 
+        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+        {
+            base.OnApplyTemplate(e);
+
+            _transparencyFallbackBorder = e.NameScope.Find<Border>("PART_TransparencyFallback");
+
+            HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel);
+        }
+
         /// <summary>
         /// Raises the <see cref="Opened"/> event.
         /// </summary>

+ 25 - 0
src/Avalonia.Controls/WindowTransparencyLevel.cs

@@ -0,0 +1,25 @@
+namespace Avalonia.Controls
+{
+    public enum WindowTransparencyLevel
+    {
+        /// <summary>
+        /// The window background is Black where nothing is drawn in the window.
+        /// </summary>
+        None,
+
+        /// <summary>
+        /// The window background is Transparent where nothing is drawn in the window.
+        /// </summary>
+        Transparent,
+
+        /// <summary>
+        /// The window background is a blur-behind where nothing is drawn in the window.
+        /// </summary>
+        Blur,
+
+        /// <summary>
+        /// The window background is a blur-behind with a high blur radius. This level may fallback to Blur.
+        /// </summary>
+        AcrylicBlur
+    }
+}

+ 6 - 0
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@@ -37,6 +37,8 @@ namespace Avalonia.DesignerSupport.Remote
         public WindowState WindowState { get; set; }
         public Action<WindowState> WindowStateChanged { get; set; }
 
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         public WindowStub(IWindowImpl parent = null)
         {
             if (parent != null)
@@ -141,6 +143,10 @@ namespace Avalonia.DesignerSupport.Remote
         public IPopupPositioner PopupPositioner { get; }
 
         public Action GotInputWhenDisabled { get; set; }
+
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
+
+        public WindowTransparencyLevel TransparencyLevel { get; private set; }
     }
 
     class ClipboardStub : IClipboard

+ 25 - 0
src/Avalonia.Native/WindowImplBase.cs

@@ -369,6 +369,8 @@ namespace Avalonia.Native
 
         Action<double> ITopLevelImpl.ScalingChanged { get; set; }
 
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         public IScreenImpl Screen { get; private set; }
 
         // TODO
@@ -389,6 +391,29 @@ namespace Avalonia.Native
             _native.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
         }
 
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) 
+        {
+            if (TransparencyLevel != transparencyLevel)
+            {
+                if (transparencyLevel >= WindowTransparencyLevel.Blur)
+                {
+                    transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
+                }
+
+                if(transparencyLevel == WindowTransparencyLevel.None)
+                {
+                    transparencyLevel = WindowTransparencyLevel.Transparent;
+                }
+
+                TransparencyLevel = transparencyLevel;
+
+                _native.SetBlurEnabled(TransparencyLevel >= WindowTransparencyLevel.Blur);
+                TransparencyLevelChanged?.Invoke(TransparencyLevel);
+            }
+        }
+
+        public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent;
+
         public IPlatformHandle Handle { get; private set; }
     }
 }

+ 11 - 8
src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml

@@ -3,14 +3,17 @@
   <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
   <Setter Property="Template">
     <ControlTemplate>
-      <Border Background="{TemplateBinding Background}">
-        <VisualLayerManager>
-          <ContentPresenter Name="PART_ContentPresenter" 
-                            ContentTemplate="{TemplateBinding ContentTemplate}"
-                            Content="{TemplateBinding Content}" 
-                            Margin="{TemplateBinding Padding}"/>
-        </VisualLayerManager>
-      </Border>
+      <Panel>
+        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        <Border Background="{TemplateBinding Background}">
+          <VisualLayerManager>
+            <ContentPresenter Name="PART_ContentPresenter"
+                              ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
+                              Margin="{TemplateBinding Padding}"/>
+          </VisualLayerManager>
+        </Border>
+      </Panel>
     </ControlTemplate>
   </Setter>
 </Style>

+ 10 - 7
src/Avalonia.Themes.Default/OverlayPopupHost.xaml

@@ -2,13 +2,16 @@
   <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
   <Setter Property="Template">
     <ControlTemplate>
-      <VisualLayerManager IsPopup="True">
-        <ContentPresenter Name="PART_ContentPresenter"
-                          Background="{TemplateBinding Background}"
-                          ContentTemplate="{TemplateBinding ContentTemplate}"
-                          Content="{TemplateBinding Content}" 
-                          Padding="{TemplateBinding Padding}"/>
-      </VisualLayerManager>
+      <Panel>
+        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        <VisualLayerManager IsPopup="True">
+          <ContentPresenter Name="PART_ContentPresenter"
+                            Background="{TemplateBinding Background}"
+                            ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
+                            Padding="{TemplateBinding Padding}"/>
+        </VisualLayerManager>
+      </Panel>
     </ControlTemplate>
   </Setter>
 </Style>

+ 10 - 7
src/Avalonia.Themes.Default/PopupRoot.xaml

@@ -2,13 +2,16 @@
   <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
   <Setter Property="Template">
     <ControlTemplate>
-      <VisualLayerManager IsPopup="True">
-        <ContentPresenter Name="PART_ContentPresenter"
-                          Background="{TemplateBinding Background}"
-                          ContentTemplate="{TemplateBinding ContentTemplate}"
-                          Content="{TemplateBinding Content}" 
-                          Padding="{TemplateBinding Padding}"/>
-      </VisualLayerManager>
+      <Panel>
+        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        <VisualLayerManager IsPopup="True">
+          <ContentPresenter Name="PART_ContentPresenter"
+                            Background="{TemplateBinding Background}"
+                            ContentTemplate="{TemplateBinding ContentTemplate}"
+                            Content="{TemplateBinding Content}"
+                            Padding="{TemplateBinding Padding}"/>
+        </VisualLayerManager>
+      </Panel>
     </ControlTemplate>
   </Setter>
 </Style>

+ 13 - 10
src/Avalonia.Themes.Default/Window.xaml

@@ -4,16 +4,19 @@
   <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
   <Setter Property="Template">
     <ControlTemplate>
-      <Border Background="{TemplateBinding Background}">
-        <VisualLayerManager>
-          <ContentPresenter Name="PART_ContentPresenter"
-                            ContentTemplate="{TemplateBinding ContentTemplate}"
-                            Content="{TemplateBinding Content}"
-                            Margin="{TemplateBinding Padding}"
-                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
-                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>           
-        </VisualLayerManager>
-      </Border>
+      <Panel>
+        <Border Name="PART_TransparencyFallback" IsHitTestVisible="False" />
+        <Border Background="{TemplateBinding Background}">
+          <VisualLayerManager>
+            <ContentPresenter Name="PART_ContentPresenter"
+                              ContentTemplate="{TemplateBinding ContentTemplate}"
+                              Content="{TemplateBinding Content}"
+                              Margin="{TemplateBinding Padding}"
+                              HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
+                              VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>
+          </VisualLayerManager>
+        </Border>
+      </Panel>
     </ControlTemplate>
   </Setter>
 </Style>

+ 82 - 0
src/Avalonia.X11/TransparencyHelper.cs

@@ -0,0 +1,82 @@
+using System;
+using Avalonia.Controls;
+
+namespace Avalonia.X11
+{
+    class TransparencyHelper :  IDisposable, X11Globals.IGlobalsSubscriber
+    {
+        private readonly X11Info _x11;
+        private readonly IntPtr _window;
+        private readonly X11Globals _globals;
+        private WindowTransparencyLevel _currentLevel;
+        private WindowTransparencyLevel _requestedLevel;
+        private bool _isCompositing;
+        private bool _blurAtomsAreSet;
+        
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+        public WindowTransparencyLevel CurrentLevel => _currentLevel;
+
+        public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals)
+        {
+            _x11 = x11;
+            _window = window;
+            _globals = globals;
+            _globals.AddSubscriber(this);
+        }
+
+        public void SetTransparencyRequest(WindowTransparencyLevel level)
+        {
+            _requestedLevel = level;
+            UpdateTransparency();
+        }
+
+        private void UpdateTransparency()
+        {
+            var newLevel = UpdateAtomsAndGetTransparency();
+            if (newLevel != _currentLevel)
+            {
+                _currentLevel = newLevel;
+                TransparencyLevelChanged?.Invoke(newLevel);
+            }
+        }
+        
+        private WindowTransparencyLevel UpdateAtomsAndGetTransparency()
+        {
+            if (_requestedLevel >= WindowTransparencyLevel.Blur)
+            {
+                if (!_blurAtomsAreSet)
+                {
+                    IntPtr value = IntPtr.Zero;
+                    XLib.XChangeProperty(_x11.Display, _window, _x11.Atoms._KDE_NET_WM_BLUR_BEHIND_REGION,
+                        _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref value, 1);
+                    _blurAtomsAreSet = true;
+                }
+            }
+            else
+            {
+                if (_blurAtomsAreSet)
+                {
+                    XLib.XDeleteProperty(_x11.Display, _window, _x11.Atoms._KDE_NET_WM_BLUR_BEHIND_REGION);
+                    _blurAtomsAreSet = false;
+                }
+            }
+
+            if (!_globals.IsCompositionEnabled)
+                return WindowTransparencyLevel.None;
+            if (_requestedLevel >= WindowTransparencyLevel.Blur && CanBlur)
+                return WindowTransparencyLevel.Blur;
+            return WindowTransparencyLevel.Transparent;
+        }
+
+        private bool CanBlur => _globals.WmName == "KWin" && _globals.IsCompositionEnabled;
+        
+        public void Dispose()
+        {
+            _globals.RemoveSubscriber(this);
+        }
+
+        void X11Globals.IGlobalsSubscriber.WmChanged(string wmName) => UpdateTransparency();
+
+        void X11Globals.IGlobalsSubscriber.CompositionChanged(bool compositing) => UpdateTransparency();
+    }
+}

+ 2 - 0
src/Avalonia.X11/X11Atoms.cs

@@ -185,6 +185,8 @@ namespace Avalonia.X11
         public readonly IntPtr UTF8_STRING;
         public readonly IntPtr UTF16_STRING;
         public readonly IntPtr ATOM_PAIR;
+        public readonly IntPtr MANAGER;
+        public readonly IntPtr _KDE_NET_WM_BLUR_BEHIND_REGION;
 
 
         public X11Atoms(IntPtr display)

+ 182 - 0
src/Avalonia.X11/X11Globals.cs

@@ -0,0 +1,182 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using static Avalonia.X11.XLib;
+namespace Avalonia.X11
+{
+    unsafe class X11Globals
+    {
+        private readonly AvaloniaX11Platform _plat;
+        private readonly int _screenNumber;
+        private readonly X11Info _x11;
+        private readonly IntPtr _rootWindow;
+        private readonly IntPtr _compositingAtom;
+        private readonly List<IGlobalsSubscriber> _subscribers = new List<IGlobalsSubscriber>();
+        
+        private string _wmName;
+        private IntPtr _compositionAtomOwner;
+        private bool _isCompositionEnabled;
+
+        public X11Globals(AvaloniaX11Platform plat)
+        {
+            _plat = plat;
+            _x11 = plat.Info;
+            _screenNumber = XDefaultScreen(_x11.Display);
+            _rootWindow = XRootWindow(_x11.Display, _screenNumber);
+            plat.Windows[_rootWindow] = OnRootWindowEvent;
+            XSelectInput(_x11.Display, _rootWindow,
+                new IntPtr((int)(EventMask.StructureNotifyMask | EventMask.PropertyChangeMask)));
+            _compositingAtom = XInternAtom(_x11.Display, "_NET_WM_CM_S" + _screenNumber, false);
+            UpdateWmName();
+            UpdateCompositingAtomOwner();
+        }
+        
+        public string WmName
+        {
+            get => _wmName;
+            private set
+            {
+                if (_wmName != value)
+                {
+                    _wmName = value;
+                    // The collection might change during enumeration
+                    foreach (var s in _subscribers.ToList()) 
+                        s.WmChanged(value);
+                }
+            }
+        }
+
+        private IntPtr CompositionAtomOwner
+        {
+            get => _compositionAtomOwner;
+            set
+            {
+                if (_compositionAtomOwner != value)
+                {
+                    _compositionAtomOwner = value;
+                    IsCompositionEnabled = _compositionAtomOwner != IntPtr.Zero;
+                }
+            }
+        }
+
+        public bool IsCompositionEnabled
+        {
+            get => _isCompositionEnabled;
+            set
+            {
+                if (_isCompositionEnabled != value)
+                {
+                    _isCompositionEnabled = value;
+                    // The collection might change during enumeration
+                    foreach (var s in _subscribers.ToList()) 
+                        s.CompositionChanged(value);
+                }
+            }
+        }
+
+        IntPtr GetSupportingWmCheck(IntPtr window)
+        {
+            XGetWindowProperty(_x11.Display, _rootWindow, _x11.Atoms._NET_SUPPORTING_WM_CHECK,
+                IntPtr.Zero, new IntPtr(IntPtr.Size), false,
+                _x11.Atoms.XA_WINDOW, out IntPtr actualType, out int actualFormat, out IntPtr nitems,
+                out IntPtr bytesAfter, out IntPtr prop);
+            if (nitems.ToInt32() != 1)
+                return IntPtr.Zero;
+            try
+            {
+                if (actualType != _x11.Atoms.XA_WINDOW)
+                    return IntPtr.Zero;
+                return *(IntPtr*)prop.ToPointer();
+            }
+            finally
+            {
+                XFree(prop);
+            }
+        }
+
+        void UpdateCompositingAtomOwner()
+        {
+            // This procedure is described in https://tronche.com/gui/x/icccm/sec-2.html#s-2.8
+            
+            // Check the server-side selection owner
+            var newOwner = XGetSelectionOwner(_x11.Display, _compositingAtom);
+            while (CompositionAtomOwner != newOwner)
+            {
+                // We have a new owner, unsubscribe from the previous one first
+                if (CompositionAtomOwner != IntPtr.Zero)
+                {
+                    _plat.Windows.Remove(CompositionAtomOwner);
+                    XSelectInput(_x11.Display, CompositionAtomOwner, IntPtr.Zero);
+                }
+
+                // Set it as the current owner and select input
+                CompositionAtomOwner = newOwner;
+                if (CompositionAtomOwner != IntPtr.Zero)
+                {
+                    _plat.Windows[newOwner] = HandleCompositionAtomOwnerEvents;
+                    XSelectInput(_x11.Display, CompositionAtomOwner, new IntPtr((int)(EventMask.StructureNotifyMask)));
+                }
+                
+                // Check for the new owner again and repeat the procedure if it was changed between XGetSelectionOwner and XSelectInput call
+                newOwner = XGetSelectionOwner(_x11.Display, _compositingAtom);
+            }
+        }
+
+        private void HandleCompositionAtomOwnerEvents(XEvent ev)
+        {
+            if(ev.type == XEventName.DestroyNotify)
+                UpdateCompositingAtomOwner();
+        }
+        
+        void UpdateWmName() => WmName = GetWmName();
+
+        string GetWmName()
+        {
+            var wm = GetSupportingWmCheck(_rootWindow);
+            if (wm == IntPtr.Zero || wm != GetSupportingWmCheck(wm))
+                return null;
+            XGetWindowProperty(_x11.Display, wm, _x11.Atoms._NET_WM_NAME,
+                IntPtr.Zero, new IntPtr(0x7fffffff),
+                false, _x11.Atoms.UTF8_STRING, out var actualType, out var actualFormat,
+                out var nitems, out _, out var prop);
+            if (nitems == IntPtr.Zero)
+                return null;
+            try
+            {
+                if (actualFormat != 8)
+                    return null;
+                return Marshal.PtrToStringAnsi(prop, nitems.ToInt32());
+            }
+            finally
+            {
+                XFree(prop);
+            }
+        }
+        
+        private void OnRootWindowEvent(XEvent ev)
+        {
+            if (ev.type == XEventName.PropertyNotify)
+            {
+                if(ev.PropertyEvent.atom == _x11.Atoms._NET_SUPPORTING_WM_CHECK)
+                    UpdateWmName();
+            }
+
+            if (ev.type == XEventName.ClientMessage)
+            {
+                if(ev.ClientMessageEvent.message_type == _x11.Atoms.MANAGER
+                    && ev.ClientMessageEvent.ptr2 == _compositingAtom)
+                    UpdateCompositingAtomOwner();
+            }
+        }
+        
+        public interface IGlobalsSubscriber
+        {
+            void WmChanged(string wmName);
+            void CompositionChanged(bool compositing);
+        }
+
+        public void AddSubscriber(IGlobalsSubscriber subscriber) => _subscribers.Add(subscriber);
+        public void RemoveSubscriber(IGlobalsSubscriber subscriber) => _subscribers.Remove(subscriber);
+    }
+}

+ 2 - 0
src/Avalonia.X11/X11Platform.cs

@@ -26,6 +26,7 @@ namespace Avalonia.X11
         public IX11Screens X11Screens { get; private set; }
         public IScreenImpl Screens { get; private set; }
         public X11PlatformOptions Options { get; private set; }
+        public X11Globals Globals { get; private set; }
         public void Initialize(X11PlatformOptions options)
         {
             Options = options;
@@ -36,6 +37,7 @@ namespace Avalonia.X11
                 throw new Exception("XOpenDisplay failed");
             XError.Init();
             Info = new X11Info(Display, DeferredDisplay);
+            Globals = new X11Globals(this);
             //TODO: log
             if (options.UseDBusMenu)
                 DBusHelper.TryInitialize();

+ 16 - 0
src/Avalonia.X11/X11Window.cs

@@ -43,6 +43,7 @@ namespace Avalonia.X11
         private bool _wasMappedAtLeastOnce = false;
         private double? _scalingOverride;
         private bool _disabled;
+        private TransparencyHelper _transparencyHelper;
 
         public object SyncRoot { get; } = new object();
 
@@ -176,6 +177,9 @@ namespace Avalonia.X11
             UpdateSizeHints(null);
             _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
                 XNames.XNClientWindow, _handle, IntPtr.Zero);
+            _transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
+            _transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None);
+
             XFlush(_x11.Display);
             if(_popup)
                 PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
@@ -300,6 +304,13 @@ namespace Avalonia.X11
         public Action Activated { get; set; }
         public Func<bool> Closing { get; set; }
         public Action<WindowState> WindowStateChanged { get; set; }
+
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged
+        {
+            get => _transparencyHelper.TransparencyLevelChanged;
+            set => _transparencyHelper.TransparencyLevelChanged = value;
+        }
+
         public Action Closed { get; set; }
         public Action<PixelPoint> PositionChanged { get; set; }
 
@@ -1083,5 +1094,10 @@ namespace Avalonia.X11
 
         public IPopupPositioner PopupPositioner { get; }
         public ITopLevelNativeMenuExporter NativeMenuExporter { get; }
+
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) =>
+            _transparencyHelper.SetTransparencyRequest(transparencyLevel);
+
+        public WindowTransparencyLevel TransparencyLevel => _transparencyHelper.CurrentLevel;
     }
 }

+ 8 - 0
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.LinuxFramebuffer.Input;
@@ -70,6 +71,9 @@ namespace Avalonia.LinuxFramebuffer
         public Action<Rect> Paint { get; set; }
         public Action<Size> Resized { get; set; }
         public Action<double> ScalingChanged { get; set; }
+
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         public Action Closed { get; set; }
         public event Action LostFocus
         {
@@ -78,5 +82,9 @@ namespace Avalonia.LinuxFramebuffer
         }
 
         public Size ScaledSize => _outputBackend.PixelSize.ToSize(Scaling);
+
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
+
+        public WindowTransparencyLevel TransparencyLevel { get; private set; }
     }
 }

+ 8 - 0
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@@ -5,6 +5,7 @@ using System.Windows;
 using System.Windows.Input;
 using System.Windows.Interop;
 using System.Windows.Media;
+using Avalonia.Controls;
 using Avalonia.Controls.Embedding;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
@@ -236,6 +237,9 @@ namespace Avalonia.Win32.Interop.Wpf
         Action<Rect> ITopLevelImpl.Paint { get; set; }
         Action<Size> ITopLevelImpl.Resized { get; set; }
         Action<double> ITopLevelImpl.ScalingChanged { get; set; }
+
+        Action<WindowTransparencyLevel> ITopLevelImpl.TransparencyLevelChanged { get; set; }
+
         Action ITopLevelImpl.Closed { get; set; }
         public new event Action LostFocus;
 
@@ -248,5 +252,9 @@ namespace Avalonia.Win32.Interop.Wpf
         }
 
         public IPopupImpl CreatePopup() => null;
+
+        public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { }
+
+        public WindowTransparencyLevel TransparencyLevel { get; private set; }
     }
 }

+ 106 - 0
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@@ -1302,6 +1302,112 @@ namespace Avalonia.Win32.Interop
         [DllImport("dwmapi.dll")]
         public static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
 
+        [DllImport("dwmapi.dll")]
+        public static extern int DwmIsCompositionEnabled(out bool enabled);
+
+        [DllImport("dwmapi.dll")]
+        public static extern void DwmEnableBlurBehindWindow(IntPtr hwnd, ref DWM_BLURBEHIND blurBehind);
+
+        [Flags]
+        public enum DWM_BB
+        {
+            Enable = 1,
+            BlurRegion = 2,
+            TransitionMaximized = 4
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        public struct DWM_BLURBEHIND
+        {
+            public DWM_BB dwFlags;
+            public bool fEnable;
+            public IntPtr hRgnBlur;
+            public bool fTransitionOnMaximized;
+
+            public DWM_BLURBEHIND(bool enabled)
+            {
+                fEnable = enabled ? true : false;
+                hRgnBlur = IntPtr.Zero;
+                fTransitionOnMaximized = false;
+                dwFlags = DWM_BB.Enable;
+            }
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct RTL_OSVERSIONINFOEX
+        {
+            internal uint dwOSVersionInfoSize;
+            internal uint dwMajorVersion;
+            internal uint dwMinorVersion;
+            internal uint dwBuildNumber;
+            internal uint dwPlatformId;
+            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
+            internal string szCSDVersion;
+        }
+
+        [DllImport("ntdll")]
+        private static extern int RtlGetVersion(out RTL_OSVERSIONINFOEX lpVersionInformation);
+
+        internal static Version RtlGetVersion()
+        {
+            RTL_OSVERSIONINFOEX v = new RTL_OSVERSIONINFOEX();
+            v.dwOSVersionInfoSize = (uint)Marshal.SizeOf(v);
+            if (RtlGetVersion(out v) == 0)
+            {
+                return new Version((int)v.dwMajorVersion, (int)v.dwMinorVersion, (int)v.dwBuildNumber, (int)v.dwPlatformId);
+            }
+            else
+            {
+                throw new Exception("RtlGetVersion failed!");
+            }
+        }
+
+        [DllImport("user32.dll")]
+        internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct WindowCompositionAttributeData
+        {
+            public WindowCompositionAttribute Attribute;
+            public IntPtr Data;
+            public int SizeOfData;
+        }
+
+        internal enum WindowCompositionAttribute
+        {
+            // ...
+            WCA_ACCENT_POLICY = 19
+            // ...
+        }
+
+        internal enum AccentState
+        {
+            ACCENT_DISABLED = 0,
+            ACCENT_ENABLE_GRADIENT = 1,
+            ACCENT_ENABLE_TRANSPARENTGRADIENT = 2,
+            ACCENT_ENABLE_BLURBEHIND = 3,
+            ACCENT_ENABLE_ACRYLIC = 4, //1703 and above
+            ACCENT_ENABLE_HOSTBACKDROP = 5,        // RS5 1809
+            ACCENT_INVALID_STATE = 6
+        }
+
+        internal enum AccentFlags
+        {
+            DrawLeftBorder = 0x20,
+            DrawTopBorder = 0x40,
+            DrawRightBorder = 0x80,
+            DrawBottomBorder = 0x100,            
+        }
+
+        [StructLayout(LayoutKind.Sequential)]
+        internal struct AccentPolicy
+        {
+            public AccentState AccentState;
+            public int AccentFlags;
+            public int GradientColor;
+            public int AnimationId;
+        }
+
         [StructLayout(LayoutKind.Sequential)]
         internal struct MARGINS
         {

+ 5 - 0
src/Windows/Avalonia.Win32/Win32Platform.cs

@@ -58,6 +58,11 @@ namespace Avalonia.Win32
             CreateMessageWindow();
         }
 
+        /// <summary>
+        /// Gets the actual WindowsVersion. Same as the info returned from RtlGetVersion.
+        /// </summary>
+        public static Version WindowsVersion { get; } = RtlGetVersion();
+
         public static bool UseDeferredRendering => Options.UseDeferredRendering;
         internal static bool UseOverlayPopups => Options.OverlayPopups;
         public static Win32PlatformOptions Options { get; private set; }

+ 84 - 1
src/Windows/Avalonia.Win32/WindowImpl.cs

@@ -5,6 +5,7 @@ using System.Runtime.InteropServices;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
+using Avalonia.Media;
 using Avalonia.OpenGL;
 using Avalonia.Platform;
 using Avalonia.Rendering;
@@ -122,6 +123,8 @@ namespace Avalonia.Win32
 
         public Action<WindowState> WindowStateChanged { get; set; }
 
+        public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
+
         public Thickness BorderThickness
         {
             get
@@ -206,6 +209,81 @@ namespace Avalonia.Win32
             }
         }
 
+        public WindowTransparencyLevel TransparencyLevel { get; private set; }
+
+        public void SetTransparencyLevelHint (WindowTransparencyLevel transparencyLevel)
+        {
+            TransparencyLevel = EnableBlur(transparencyLevel);
+        }
+
+        private WindowTransparencyLevel EnableBlur(WindowTransparencyLevel transparencyLevel)
+        {
+            bool canUseTransparency = false;
+            bool canUseAcrylic = false;
+
+            if (Win32Platform.WindowsVersion.Major >= 10)
+            {
+                canUseTransparency = true;
+
+                if (Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628)
+                {
+                    canUseAcrylic = true;
+                }
+            }
+
+            if (!canUseTransparency || DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled)
+            {
+                return WindowTransparencyLevel.None;
+            }
+
+            var accent = new AccentPolicy();
+            var accentStructSize = Marshal.SizeOf(accent);
+
+            if(transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic)
+            {
+                transparencyLevel = WindowTransparencyLevel.Blur;
+            }
+
+            switch (transparencyLevel)
+            {
+                default:
+                case WindowTransparencyLevel.None:
+                    accent.AccentState = AccentState.ACCENT_DISABLED;
+                    break;
+
+                case WindowTransparencyLevel.Transparent:
+                    accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT;
+                    break;
+
+                case WindowTransparencyLevel.Blur:
+                    accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND;
+                    break;
+
+                case WindowTransparencyLevel.AcrylicBlur:
+                case (WindowTransparencyLevel.AcrylicBlur + 1): // hack-force acrylic.
+                    accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC;
+                    transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
+                    break;
+            }
+
+            accent.AccentFlags = 2;
+            accent.GradientColor = 0x00FFFFFF;
+
+            var accentPtr = Marshal.AllocHGlobal(accentStructSize);
+            Marshal.StructureToPtr(accent, accentPtr, false);
+
+            var data = new WindowCompositionAttributeData();
+            data.Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY;
+            data.SizeOfData = accentStructSize;
+            data.Data = accentPtr;
+
+            SetWindowCompositionAttribute(_hwnd, ref data);
+
+            Marshal.FreeHGlobal(accentPtr);            
+
+            return transparencyLevel;
+        }
+
         public IEnumerable<object> Surfaces => new object[] { Handle, _gl, _framebuffer };
 
         public PixelPoint Position
@@ -780,9 +858,14 @@ namespace Avalonia.Win32
 
                 if (!_isFullScreenActive)
                 {
+                    var margin = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0;
+
                     var margins = new MARGINS
                     {
-                        cyBottomHeight = newProperties.Decorations == SystemDecorations.BorderOnly ? 1 : 0
+                        cyBottomHeight = margin,
+                        cxRightWidth = margin,
+                        cxLeftWidth = margin,
+                        cyTopHeight = margin
                     };
 
                     DwmExtendFrameIntoClientArea(_hwnd, ref margins);

+ 7 - 2
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@@ -77,8 +77,13 @@ namespace Avalonia.Controls.UnitTests.Primitives
 
                 var templatedChild = ((Visual)target.Host).GetVisualChildren().Single();
                 
-                Assert.IsType<VisualLayerManager>(templatedChild);
-                var contentPresenter = templatedChild.VisualChildren.Single();
+                Assert.IsType<Panel>(templatedChild);
+
+                var visualLayerManager = templatedChild.GetVisualChildren().Skip(1).Single();
+
+                Assert.IsType<VisualLayerManager>(visualLayerManager);
+
+                var contentPresenter = visualLayerManager.VisualChildren.Single();
                 Assert.IsType<ContentPresenter>(contentPresenter);
                 
                 

+ 4 - 0
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@@ -275,6 +275,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 Assert.Equal(
                     new[]
                     {
+                        "Panel",
+                        "Border",
                         "VisualLayerManager",
                         "ContentPresenter",
                         "ContentPresenter",
@@ -289,6 +291,8 @@ namespace Avalonia.Controls.UnitTests.Primitives
                 Assert.Equal(
                     new object[]
                     {
+                        popupRoot,
+                        popupRoot,
                         popupRoot,
                         popupRoot,
                         target,