Browse Source

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

Window Transparency
pull/3988/head
danwalmsley 6 years ago
committed by GitHub
parent
commit
cf5d96adcc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      native/Avalonia.Native/inc/avalonia-native.h
  2. 3
      native/Avalonia.Native/src/OSX/window.h
  3. 29
      native/Avalonia.Native/src/OSX/window.mm
  4. 8
      samples/ControlCatalog/MainView.xaml
  5. 7
      samples/ControlCatalog/MainView.xaml.cs
  6. 3
      samples/ControlCatalog/MainWindow.xaml
  7. 8
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  8. 16
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  9. 106
      src/Avalonia.Controls/TopLevel.cs
  10. 25
      src/Avalonia.Controls/WindowTransparencyLevel.cs
  11. 6
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  12. 25
      src/Avalonia.Native/WindowImplBase.cs
  13. 19
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  14. 17
      src/Avalonia.Themes.Default/OverlayPopupHost.xaml
  15. 17
      src/Avalonia.Themes.Default/PopupRoot.xaml
  16. 23
      src/Avalonia.Themes.Default/Window.xaml
  17. 82
      src/Avalonia.X11/TransparencyHelper.cs
  18. 2
      src/Avalonia.X11/X11Atoms.cs
  19. 182
      src/Avalonia.X11/X11Globals.cs
  20. 2
      src/Avalonia.X11/X11Platform.cs
  21. 16
      src/Avalonia.X11/X11Window.cs
  22. 8
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  23. 8
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  24. 106
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  25. 5
      src/Windows/Avalonia.Win32/Win32Platform.cs
  26. 85
      src/Windows/Avalonia.Win32/WindowImpl.cs
  27. 9
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  28. 4
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

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

8
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
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)

3
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
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
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
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
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
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
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; }
}
}

19
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>

17
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>

17
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>

23
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
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
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
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
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
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
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
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
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
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; }

85
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);

9
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
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,

Loading…
Cancel
Save