From b2d780506036965465f484a29624fb8919f0d6e2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 00:08:20 +0200 Subject: [PATCH 01/11] Replace WindowTransparencyLevel enum with list of structs The transparency level is not yet communicated to the backends though. Co-Authored-By: Max Katz --- samples/ControlCatalog/MainView.xaml.cs | 2 +- .../IntegrationTestApp/MainWindow.axaml.cs | 2 +- .../Platform/SkiaPlatform/TopLevelImpl.cs | 118 +++++++++--------- .../ExperimentalAcrylicBorder.cs | 22 +--- src/Avalonia.Controls/TopLevel.cs | 38 ++---- .../WindowTransparencyLevel.cs | 78 +++++++----- src/Avalonia.Native/WindowImplBase.cs | 6 +- src/Avalonia.X11/TransparencyHelper.cs | 8 +- .../AvaloniaXamlIlLanguageParseIntrinsics.cs | 16 ++- .../AvaloniaXamlIlWellKnownTypes.cs | 4 + src/Windows/Avalonia.Win32/TrayIconImpl.cs | 2 +- src/Windows/Avalonia.Win32/WindowImpl.cs | 54 ++++---- .../Xaml/WindowTests.cs | 26 ++++ 13 files changed, 211 insertions(+), 165 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Xaml/WindowTests.cs diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 9c511f9eb0..5e33952c68 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -83,7 +83,7 @@ namespace ControlCatalog if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected) { var topLevel = (TopLevel)this.GetVisualRoot()!; - topLevel.TransparencyLevelHint = selected; + topLevel.TransparencyLevelHint = new[] { selected }; if (selected != WindowTransparencyLevel.None) { diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 7130b3602a..689cfcb65d 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -136,7 +136,7 @@ namespace IntegrationTestApp Name = "TransparentWindow", SystemDecorations = SystemDecorations.None, Background = Brushes.Transparent, - TransparencyLevelHint = WindowTransparencyLevel.Transparent, + TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }, WindowStartupLocation = WindowStartupLocation.CenterOwner, Width = 200, Height = 200, diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index fae1aacf61..609b6cd519 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -307,78 +307,80 @@ namespace Avalonia.Android.Platform.SkiaPlatform bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; if (_view.Context is AvaloniaMainActivity activity) { - switch (transparencyLevel) + if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || + transparencyLevel == WindowTransparencyLevel.Mica || + transparencyLevel == WindowTransparencyLevel.None) { - case WindowTransparencyLevel.AcrylicBlur: - case WindowTransparencyLevel.ForceAcrylicBlur: - case WindowTransparencyLevel.Mica: - case WindowTransparencyLevel.None: - if (!isBelowR) + if (!isBelowR) + { + activity.SetTranslucent(false); + } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) { - activity.SetTranslucent(false); - } - if (isAboveR) - { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 0; + attr.BlurBehindRadius = 0; - activity.Window.Attributes = attr; - } + activity.Window.Attributes = attr; } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); - if(transparencyLevel != WindowTransparencyLevel.None) - { - return; - } - break; - case WindowTransparencyLevel.Transparent: - if (!isBelowR) - { - activity.SetTranslucent(true); - } - if (isAboveR) - { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + if (transparencyLevel != WindowTransparencyLevel.None) + { + return; + } + } - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 0; + if (transparencyLevel == WindowTransparencyLevel.Transparent) + { + if (!isBelowR) + { + activity.SetTranslucent(true); + } + if (isAboveR) + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + var attr = activity.Window?.Attributes; + if (attr != null) + { + attr.BlurBehindRadius = 0; - activity.Window.Attributes = attr; - } + activity.Window.Attributes = attr; } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - break; - case WindowTransparencyLevel.Blur: - if (isAboveR) - { - activity.SetTranslucent(true); - activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + } + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 120; + if (transparencyLevel == WindowTransparencyLevel.Blur) + { + if (isAboveR) + { + activity.SetTranslucent(true); + activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); - activity.Window.Attributes = attr; - } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - } - else + var attr = activity.Window?.Attributes; + if (attr != null) { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + attr.BlurBehindRadius = 120; - return; + activity.Window.Attributes = attr; } - break; + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + else + { + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + return; + } } + TransparencyLevel = transparencyLevel; } } diff --git a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs index bc17fe7237..be1a90258c 100644 --- a/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs +++ b/src/Avalonia.Controls/ExperimentalAcrylicBorder.cs @@ -56,22 +56,12 @@ namespace Avalonia.Controls { if (tl.PlatformImpl is null) return; - - switch (x) - { - case WindowTransparencyLevel.Transparent: - case WindowTransparencyLevel.None: - Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; - break; - - case WindowTransparencyLevel.Blur: - Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; - break; - - case WindowTransparencyLevel.AcrylicBlur: - Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel; - break; - } + if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None) + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; + else if (x == WindowTransparencyLevel.Blur) + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel; + else if (x == WindowTransparencyLevel.AcrylicBlur) + Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel; }); UpdateMaterialSubscription(); } diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 98c96e4974..189dbe5e62 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using Avalonia.Reactive; @@ -22,6 +23,7 @@ using Avalonia.Utilities; using Avalonia.Input.Platform; using System.Linq; using System.Threading.Tasks; +using Avalonia.Metadata; namespace Avalonia.Controls { @@ -64,8 +66,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty TransparencyLevelHintProperty = - AvaloniaProperty.Register(nameof(TransparencyLevelHint), WindowTransparencyLevel.None); + public static readonly StyledProperty> TransparencyLevelHintProperty = + AvaloniaProperty.Register>(nameof(TransparencyLevelHint), Array.Empty()); /// /// Defines the property. @@ -172,7 +174,7 @@ namespace Avalonia.Controls PlatformImpl = impl ?? throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); - _actualTransparencyLevel = PlatformImpl.TransparencyLevel; + _actualTransparencyLevel = PlatformImpl.TransparencyLevel; dependencyResolver ??= AvaloniaLocator.Current; @@ -310,8 +312,11 @@ namespace Avalonia.Controls /// /// Gets or sets the that the TopLevel should use when possible. + /// Accepts multiple values which are applied in a fallback order. + /// For instance, with "Mica, Blur" Mica will be applied only on platforms where it is possible, + /// and Blur will be used on the rest of them. Default value is an empty array or "None". /// - public WindowTransparencyLevel TransparencyLevelHint + public IReadOnlyList TransparencyLevelHint { get { return GetValue(TransparencyLevelHintProperty); } set { SetValue(TransparencyLevelHintProperty, value); } @@ -518,8 +523,8 @@ namespace Avalonia.Controls { if (PlatformImpl != null) { - PlatformImpl.SetTransparencyLevelHint(change.GetNewValue()); - HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); + ////PlatformImpl.SetTransparencyLevelHint( + //// change.GetNewValue>() ?? Array.Empty()); } } else if (change.Property == ActualThemeVariantProperty) @@ -610,27 +615,11 @@ namespace Avalonia.Controls ScalingChanged?.Invoke(this, EventArgs.Empty); } - private static bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received) - { - if(requested == received) - { - return true; - } - else if(requested >= WindowTransparencyLevel.Blur && received >= WindowTransparencyLevel.Blur) - { - return true; - } - - return false; - } - private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel) { - if(_transparencyFallbackBorder != null) + if (_transparencyFallbackBorder != null) { - if(transparencyLevel == WindowTransparencyLevel.None || - TransparencyLevelHint == WindowTransparencyLevel.None || - !TransparencyLevelsMatch(TransparencyLevelHint, transparencyLevel)) + if (transparencyLevel == WindowTransparencyLevel.None) { _transparencyFallbackBorder.Background = TransparencyBackgroundFallback; } @@ -660,7 +649,6 @@ namespace Avalonia.Controls return; _transparencyFallbackBorder = e.NameScope.Find("PART_TransparencyFallback"); - HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); } diff --git a/src/Avalonia.Controls/WindowTransparencyLevel.cs b/src/Avalonia.Controls/WindowTransparencyLevel.cs index d463f74a0e..bce6e21e68 100644 --- a/src/Avalonia.Controls/WindowTransparencyLevel.cs +++ b/src/Avalonia.Controls/WindowTransparencyLevel.cs @@ -1,35 +1,51 @@ -namespace Avalonia.Controls +using System.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Avalonia.Controls; + +public readonly record struct WindowTransparencyLevel { - public enum WindowTransparencyLevel + private readonly string _value; + + private WindowTransparencyLevel(string value) { - /// - /// The window background is Black where nothing is drawn in the window. - /// - None, - - /// - /// The window background is Transparent where nothing is drawn in the window. - /// - Transparent, - - /// - /// The window background is a blur-behind where nothing is drawn in the window. - /// - Blur, - - /// - /// The window background is a blur-behind with a high blur radius. This level may fallback to Blur. - /// - AcrylicBlur, - - /// - /// Force acrylic on some incompatible versions of Windows 10. - /// - ForceAcrylicBlur, - - /// - /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 - /// - Mica + _value = value; + } + + /// + /// The window background is Black where nothing is drawn in the window. + /// + public static WindowTransparencyLevel None { get; } = new(nameof(None)); + + /// + /// The window background is Transparent where nothing is drawn in the window. + /// + public static WindowTransparencyLevel Transparent { get; } = new(nameof(Transparent)); + + /// + /// The window background is a blur-behind where nothing is drawn in the window. + /// + public static WindowTransparencyLevel Blur { get; } = new(nameof(Blur)); + + /// + /// The window background is a blur-behind with a high blur radius. This level may fallback to Blur. + /// + public static WindowTransparencyLevel AcrylicBlur { get; } = new(nameof(AcrylicBlur)); + + /// + /// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11 + /// + public static WindowTransparencyLevel Mica { get; } = new(nameof(Mica)); + + public override string ToString() + { + return _value; } } + +public class WindowTransparencyLevelCollection : ReadOnlyCollection +{ + public WindowTransparencyLevelCollection(IList list) : base(list) + { + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index b802b1db71..c065346377 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -489,8 +489,12 @@ namespace Avalonia.Native { if (TransparencyLevel != transparencyLevel) { - if (transparencyLevel > WindowTransparencyLevel.Transparent) + if (transparencyLevel == WindowTransparencyLevel.Blur || + transparencyLevel == WindowTransparencyLevel.AcrylicBlur || + transparencyLevel == WindowTransparencyLevel.Mica) + { transparencyLevel = WindowTransparencyLevel.AcrylicBlur; + } TransparencyLevel = transparencyLevel; diff --git a/src/Avalonia.X11/TransparencyHelper.cs b/src/Avalonia.X11/TransparencyHelper.cs index 5ca2d1d337..b4f8ca5aed 100644 --- a/src/Avalonia.X11/TransparencyHelper.cs +++ b/src/Avalonia.X11/TransparencyHelper.cs @@ -41,7 +41,11 @@ namespace Avalonia.X11 private WindowTransparencyLevel UpdateAtomsAndGetTransparency() { - if (_requestedLevel >= WindowTransparencyLevel.Blur) + var blur = _requestedLevel == WindowTransparencyLevel.Blur || + _requestedLevel == WindowTransparencyLevel.AcrylicBlur || + _requestedLevel == WindowTransparencyLevel.Mica; + + if (blur) { if (!_blurAtomsAreSet) { @@ -62,7 +66,7 @@ namespace Avalonia.X11 if (!_globals.IsCompositionEnabled) return WindowTransparencyLevel.None; - if (_requestedLevel >= WindowTransparencyLevel.Blur && CanBlur) + if (blur && CanBlur) return WindowTransparencyLevel.Blur; return WindowTransparencyLevel.Transparent; } diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs index 819e721b36..df1c4aa6d6 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs @@ -274,6 +274,19 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions } } + if (type.Equals(types.WindowTransparencyLevel)) + { + foreach (var property in types.WindowTransparencyLevel.Properties) + { + if (property.PropertyType == types.WindowTransparencyLevel && property.Name.Equals(text, StringComparison.OrdinalIgnoreCase)) + { + result = new XamlStaticOrTargetedReturnMethodCallNode(node, property.Getter, Enumerable.Empty()); + + return true; + } + } + } + if (type.Equals(types.Uri)) { var uriText = text.Trim(); @@ -385,7 +398,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions result = new AvaloniaXamlIlArrayConstantAstNode(node, elementType.MakeArrayType(1), elementType, nodes); return true; } - else if (type == context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType)) + else if (type == context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType) || + type == types.IReadOnlyListOfT.MakeGenericType(elementType)) { var listType = context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType); result = new AvaloniaXamlIlArrayConstantAstNode(node, listType, elementType, nodes); diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index d160d4a277..05fe4bd2b8 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -117,6 +117,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlConstructor UriConstructor { get; } public IXamlType Style { get; } public IXamlType ControlTheme { get; } + public IXamlType WindowTransparencyLevel { get; } + public IXamlType IReadOnlyListOfT { get; } public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) { @@ -199,6 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); FontFamilyConstructorUriName = FontFamily.GetConstructor(new List { Uri, XamlIlTypes.String }); ThemeVariant = cfg.TypeSystem.GetType("Avalonia.Styling.ThemeVariant"); + WindowTransparencyLevel = cfg.TypeSystem.GetType("Avalonia.Controls.WindowTransparencyLevel"); (IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount) { @@ -260,6 +263,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind }); Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); + IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); } } diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index 5069add9fd..f2023d37ac 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -147,7 +147,7 @@ namespace Avalonia.Win32 SystemDecorations = SystemDecorations.None, SizeToContent = SizeToContent.WidthAndHeight, Background = null, - TransparencyLevelHint = WindowTransparencyLevel.Transparent, + TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent }, Content = new TrayIconMenuFlyoutPresenter() { ItemsSource = menuItems diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 9217f42952..b8eb37bb33 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -435,7 +435,9 @@ namespace Avalonia.Win32 Marshal.FreeHGlobal(accentPtr); - if (transparencyLevel >= WindowTransparencyLevel.Blur) + if (transparencyLevel == WindowTransparencyLevel.Blur || + transparencyLevel == WindowTransparencyLevel.AcrylicBlur || + transparencyLevel == WindowTransparencyLevel.Mica) { Win7EnableBlur(transparencyLevel); } @@ -447,13 +449,14 @@ namespace Avalonia.Win32 { if (_isUsingComposition) { - var effect = transparencyLevel switch - { - WindowTransparencyLevel.Mica => BlurEffect.Mica, - WindowTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic, - WindowTransparencyLevel.Blur => BlurEffect.Acrylic, - _ => BlurEffect.None - }; + BlurEffect effect; + + if (transparencyLevel == WindowTransparencyLevel.Mica) + effect = BlurEffect.Mica; + else if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) + effect = BlurEffect.Acrylic; + else + effect = BlurEffect.None; if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion) { @@ -485,27 +488,22 @@ namespace Avalonia.Win32 transparencyLevel = WindowTransparencyLevel.Blur; } - switch (transparencyLevel) + if (transparencyLevel == WindowTransparencyLevel.Transparent) + { + accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; + } + else if (transparencyLevel == WindowTransparencyLevel.Blur) + { + accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; + } + else if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || transparencyLevel == WindowTransparencyLevel.Mica) + { + accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; + transparencyLevel = WindowTransparencyLevel.AcrylicBlur; + } + else { - 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.ForceAcrylicBlur: // hack-force acrylic. - case WindowTransparencyLevel.Mica: - accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - break; + accent.AccentState = AccentState.ACCENT_DISABLED; } accent.AccentFlags = 2; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/WindowTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/WindowTests.cs new file mode 100644 index 0000000000..a48776bfee --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/WindowTests.cs @@ -0,0 +1,26 @@ +using Avalonia.Controls; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Xaml +{ + public class WindowTests : XamlTestBase + { + [Fact] + public void Can_Specify_TransparencyLevelHint() + { + using var app = UnitTestApplication.Start(TestServices.MockWindowingPlatform); + var xaml = @""; + + var target = AvaloniaRuntimeXamlLoader.Parse(xaml); + + Assert.Equal( + new[] + { + WindowTransparencyLevel.Blur, + WindowTransparencyLevel.Transparent, + WindowTransparencyLevel.None, + }, target.TransparencyLevelHint); + } + } +} From 8845f239b609c2b0c22e286902499beb10f70c2c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 13:54:12 +0200 Subject: [PATCH 02/11] Implement transparency level hint list on win32. Other backends have the relevant code commented-out still. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 168 ++++++------ .../Offscreen/OffscreenTopLevelImpl.cs | 2 +- .../Platform/ITopLevelImpl.cs | 2 +- src/Avalonia.Controls/TopLevel.cs | 4 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Avalonia.Native/WindowImplBase.cs | 42 +-- src/Avalonia.X11/X11Window.cs | 6 +- .../Avalonia.Browser/BrowserTopLevelImpl.cs | 12 +- .../Avalonia.Headless/HeadlessWindowImpl.cs | 2 +- .../FramebufferToplevelImpl.cs | 2 +- .../Wpf/WpfTopLevelImpl.cs | 2 +- .../Avalonia.Win32/PlatformConstants.cs | 7 +- .../Composition/WinUiCompositionShared.cs | 1 + src/Windows/Avalonia.Win32/WindowImpl.cs | 244 ++++++++---------- src/iOS/Avalonia.iOS/AvaloniaView.cs | 2 +- .../CompositorTestServices.cs | 2 +- 16 files changed, 232 insertions(+), 268 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 609b6cd519..144e2d843a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -299,91 +299,91 @@ namespace Avalonia.Android.Platform.SkiaPlatform public double Scaling => RenderScaling; - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { - if (TransparencyLevel != transparencyLevel) - { - bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; - bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; - if (_view.Context is AvaloniaMainActivity activity) - { - if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || - transparencyLevel == WindowTransparencyLevel.Mica || - transparencyLevel == WindowTransparencyLevel.None) - { - if (!isBelowR) - { - activity.SetTranslucent(false); - } - if (isAboveR) - { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 0; - - activity.Window.Attributes = attr; - } - } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); - - if (transparencyLevel != WindowTransparencyLevel.None) - { - return; - } - } - - if (transparencyLevel == WindowTransparencyLevel.Transparent) - { - if (!isBelowR) - { - activity.SetTranslucent(true); - } - if (isAboveR) - { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 0; - - activity.Window.Attributes = attr; - } - } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - } - - if (transparencyLevel == WindowTransparencyLevel.Blur) - { - if (isAboveR) - { - activity.SetTranslucent(true); - activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); - - var attr = activity.Window?.Attributes; - if (attr != null) - { - attr.BlurBehindRadius = 120; - - activity.Window.Attributes = attr; - } - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - } - else - { - activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); - - return; - } - } - - TransparencyLevel = transparencyLevel; - } - } + ////if (TransparencyLevel != transparencyLevel) + ////{ + //// bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; + //// bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; + //// if (_view.Context is AvaloniaMainActivity activity) + //// { + //// if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || + //// transparencyLevel == WindowTransparencyLevel.Mica || + //// transparencyLevel == WindowTransparencyLevel.None) + //// { + //// if (!isBelowR) + //// { + //// activity.SetTranslucent(false); + //// } + //// if (isAboveR) + //// { + //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + //// var attr = activity.Window?.Attributes; + //// if (attr != null) + //// { + //// attr.BlurBehindRadius = 0; + + //// activity.Window.Attributes = attr; + //// } + //// } + //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + //// if (transparencyLevel != WindowTransparencyLevel.None) + //// { + //// return; + //// } + //// } + + //// if (transparencyLevel == WindowTransparencyLevel.Transparent) + //// { + //// if (!isBelowR) + //// { + //// activity.SetTranslucent(true); + //// } + //// if (isAboveR) + //// { + //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + + //// var attr = activity.Window?.Attributes; + //// if (attr != null) + //// { + //// attr.BlurBehindRadius = 0; + + //// activity.Window.Attributes = attr; + //// } + //// } + //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + //// } + + //// if (transparencyLevel == WindowTransparencyLevel.Blur) + //// { + //// if (isAboveR) + //// { + //// activity.SetTranslucent(true); + //// activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + + //// var attr = activity.Window?.Attributes; + //// if (attr != null) + //// { + //// attr.BlurBehindRadius = 120; + + //// activity.Window.Attributes = attr; + //// } + //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + //// } + //// else + //// { + //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); + + //// return; + //// } + //// } + + //// TransparencyLevel = transparencyLevel; + //// } + ////} } public virtual object TryGetFeature(Type featureType) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 387357dddd..28b43e8397 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -89,7 +89,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public Action? LostFocus { get; set; } public abstract IMouseDevice MouseDevice { get; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } public WindowTransparencyLevel TransparencyLevel { get; private set; } diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index bb6b2304af..02b96d0628 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -117,7 +117,7 @@ namespace Avalonia.Platform /// /// Sets the hint of the TopLevel. /// - void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel); + void SetTransparencyLevelHint(IReadOnlyList transparencyLevel); /// /// Gets the current of the TopLevel. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 189dbe5e62..ca12c302f7 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -523,8 +523,8 @@ namespace Avalonia.Controls { if (PlatformImpl != null) { - ////PlatformImpl.SetTransparencyLevelHint( - //// change.GetNewValue>() ?? Array.Empty()); + PlatformImpl.SetTransparencyLevelHint( + change.GetNewValue>() ?? Array.Empty()); } } else if (change.Property == ActualThemeVariantProperty) diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index f6f5c185e9..28d1b147fd 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -179,7 +179,7 @@ namespace Avalonia.DesignerSupport.Remote public Action GotInputWhenDisabled { get; set; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } public void SetWindowManagerAddShadowHint(bool enabled) { diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index c065346377..c8198f0da2 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -485,27 +485,27 @@ 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 || - transparencyLevel == WindowTransparencyLevel.Mica) - { - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - } - - TransparencyLevel = transparencyLevel; - - _native.SetTransparencyMode(transparencyLevel == WindowTransparencyLevel.None - ? AvnWindowTransparencyMode.Opaque - : transparencyLevel == WindowTransparencyLevel.Transparent - ? AvnWindowTransparencyMode.Transparent - : AvnWindowTransparencyMode.Blur); - - TransparencyLevelChanged?.Invoke(TransparencyLevel); - } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) + { + ////if (TransparencyLevel != transparencyLevel) + ////{ + //// if (transparencyLevel == WindowTransparencyLevel.Blur || + //// transparencyLevel == WindowTransparencyLevel.AcrylicBlur || + //// transparencyLevel == WindowTransparencyLevel.Mica) + //// { + //// transparencyLevel = WindowTransparencyLevel.AcrylicBlur; + //// } + + //// TransparencyLevel = transparencyLevel; + + //// _native.SetTransparencyMode(transparencyLevel == WindowTransparencyLevel.None + //// ? AvnWindowTransparencyMode.Opaque + //// : transparencyLevel == WindowTransparencyLevel.Transparent + //// ? AvnWindowTransparencyMode.Transparent + //// : AvnWindowTransparencyMode.Blur); + + //// TransparencyLevelChanged?.Invoke(TransparencyLevel); + ////} } public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None; diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 0a535d2f57..58b59a6b56 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -1307,8 +1307,10 @@ namespace Avalonia.X11 public IPopupPositioner? PopupPositioner { get; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => - _transparencyHelper?.SetTransparencyRequest(transparencyLevel); + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) + { + ////_transparencyHelper?.SetTransparencyRequest(transparencyLevel); + } public void SetWindowManagerAddShadowHint(bool enabled) { diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index 8456dc92d0..c243a2a28b 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -223,13 +223,13 @@ namespace Avalonia.Browser return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { - if (transparencyLevel == WindowTransparencyLevel.None - || transparencyLevel == WindowTransparencyLevel.Transparent) - { - TransparencyLevel = transparencyLevel; - } + ////if (transparencyLevel == WindowTransparencyLevel.None + //// || transparencyLevel == WindowTransparencyLevel.Transparent) + ////{ + //// TransparencyLevel = transparencyLevel; + ////} } public Size ClientSize => _clientSize; diff --git a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 93f92d46f8..edf8275b1a 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -337,7 +337,7 @@ namespace Avalonia.Headless } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index ccc8cab8ae..79408cd604 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -75,7 +75,7 @@ using Avalonia.Rendering.Composition; public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling); - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } public WindowTransparencyLevel TransparencyLevel { get; private set; } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 8025779c90..64f4352c25 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -247,7 +247,7 @@ namespace Avalonia.Win32.Interop.Wpf public IPopupImpl CreatePopup() => null; - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } public WindowTransparencyLevel TransparencyLevel { get; private set; } diff --git a/src/Windows/Avalonia.Win32/PlatformConstants.cs b/src/Windows/Avalonia.Win32/PlatformConstants.cs index 48dd9f45da..bed78501f3 100644 --- a/src/Windows/Avalonia.Win32/PlatformConstants.cs +++ b/src/Windows/Avalonia.Win32/PlatformConstants.cs @@ -2,12 +2,13 @@ using System; namespace Avalonia.Win32 { - public static class PlatformConstants + internal static class PlatformConstants { public const string WindowHandleType = "HWND"; public const string CursorHandleType = "HCURSOR"; - internal static readonly Version Windows8 = new Version(6, 2); - internal static readonly Version Windows7 = new Version(6, 1); + public static readonly Version Windows10 = new Version(10, 0); + public static readonly Version Windows8 = new Version(6, 2); + public static readonly Version Windows7 = new Version(6, 1); } } diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs index 602d0a3f8a..f17805fba3 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs @@ -12,6 +12,7 @@ internal class WinUiCompositionShared : IDisposable public ICompositionBrush? MicaBrush { get; } public object SyncRoot { get; } = new(); + public static readonly Version MinAcrylicVersion = new(10, 0, 15063); public static readonly Version MinHostBackdropVersion = new(10, 0, 22000); public WinUiCompositionShared(ICompositor compositor) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index b8eb37bb33..dc9cc7f50c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; @@ -25,6 +25,7 @@ using Avalonia.Win32.WinRT.Composition; using Avalonia.Win32.WinRT; using static Avalonia.Win32.Interop.UnmanagedMethods; using Avalonia.Input.Platform; +using System.Diagnostics; namespace Avalonia.Win32 { @@ -346,82 +347,119 @@ namespace Avalonia.Win32 return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - TransparencyLevel = EnableBlur(transparencyLevel); - } + var windowsVersion = Win32Platform.WindowsVersion; - private WindowTransparencyLevel EnableBlur(WindowTransparencyLevel transparencyLevel) - { - if (Win32Platform.WindowsVersion.Major >= 6) - { - if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) - { - return WindowTransparencyLevel.None; - } - else if (Win32Platform.WindowsVersion.Major >= 10) - { - return Win10EnableBlur(transparencyLevel); - } - else if (Win32Platform.WindowsVersion.Minor >= 2) - { - return Win8xEnableBlur(transparencyLevel); - } - else - { - return Win7EnableBlur(transparencyLevel); - } - } - else + foreach (var level in transparencyLevels) { - return WindowTransparencyLevel.None; + if (!IsSupported(level, windowsVersion)) + continue; + + if (level == WindowTransparencyLevel.Transparent) + SetTransparencyTransparent(windowsVersion); + else if (level == WindowTransparencyLevel.Blur) + SetTransparencyBlur(windowsVersion); + else if (level == WindowTransparencyLevel.AcrylicBlur) + SetTransparencyAcrylicBlur(windowsVersion); + else if (level == WindowTransparencyLevel.Mica) + SetTransparencyMica(windowsVersion); + + TransparencyLevel = level; + break; } } - private WindowTransparencyLevel Win7EnableBlur(WindowTransparencyLevel transparencyLevel) + private bool IsSupported(WindowTransparencyLevel level, Version windowsVersion) { - if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) - { - transparencyLevel = WindowTransparencyLevel.Blur; - } + // Only None is suppported when composition is disabled. + if (!_isUsingComposition) + return level == WindowTransparencyLevel.None; + + // When composition is enabled, None is not supported because the backing visual always + // has an alpha channel + if (level == WindowTransparencyLevel.None) + return false; + + // Transparent only supported on Windows 8+. + if (level == WindowTransparencyLevel.Transparent) + return windowsVersion >= PlatformConstants.Windows8; + + // Blur only supported on Windows 8 and lower. + if (level == WindowTransparencyLevel.Blur) + return windowsVersion < PlatformConstants.Windows10; + + // Acrylic is supported on Windows >= 10.0.15063. + if (level == WindowTransparencyLevel.AcrylicBlur) + return windowsVersion >= WinUiCompositionShared.MinAcrylicVersion; - var blurInfo = new DWM_BLURBEHIND(false); + // Mica is supported on Windows >= 10.0.22000. + if (level == WindowTransparencyLevel.Mica) + return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion; - if (transparencyLevel == WindowTransparencyLevel.Blur) + return false; + } + + private void SetTransparencyTransparent(Version windowsVersion) + { + // Transparent only supported with composition on Windows 8+. + if (!_isUsingComposition || windowsVersion < PlatformConstants.Windows8) + return; + + if (windowsVersion < PlatformConstants.Windows10) { - blurInfo = new DWM_BLURBEHIND(true); + // Some of the AccentState Enum's values have different meanings on Windows 8.x than on + // Windows 10, hence using ACCENT_ENABLE_BLURBEHIND to disable blurbehind ¯\_(ツ)_/¯. + // Hey, I'm just porting what was here before. + SetAccentState(AccentState.ACCENT_ENABLE_BLURBEHIND); + var blurInfo = new DWM_BLURBEHIND(false); + DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); } + SetUseHostBackdropBrush(false); + _blurHost?.SetBlur(BlurEffect.None); + } + + private void SetTransparencyBlur(Version windowsVersion) + { + // Blur only supported with composition on Windows 8 and lower. + if (!_isUsingComposition || windowsVersion >= PlatformConstants.Windows10) + return; + + // Some of the AccentState Enum's values have different meanings on Windows 8.x than on + // Windows 10. + SetAccentState(AccentState.ACCENT_DISABLED); + var blurInfo = new DWM_BLURBEHIND(true); DwmEnableBlurBehindWindow(_hwnd, ref blurInfo); + } - if (transparencyLevel == WindowTransparencyLevel.Transparent) - { - return WindowTransparencyLevel.None; - } - else - { - return transparencyLevel; - } + private void SetTransparencyAcrylicBlur(Version windowsVersion) + { + // Acrylic blur only supported with composition on Windows >= 10.0.15063. + if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinAcrylicVersion) + return; + + SetUseHostBackdropBrush(true); + _blurHost?.SetBlur(BlurEffect.Acrylic); } - private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel) + private void SetTransparencyMica(Version windowsVersion) { - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(); + // Mica only supported with composition on Windows >= 10.0.22000. + if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion) + return; - if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) - { - transparencyLevel = WindowTransparencyLevel.Blur; - } + SetUseHostBackdropBrush(false); + _blurHost?.SetBlur(BlurEffect.Mica); + } - if (transparencyLevel == WindowTransparencyLevel.Transparent) - { - accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; - } - else - { - accent.AccentState = AccentState.ACCENT_DISABLED; - } + private void SetAccentState(AccentState state) + { + var accent = new AccentPolicy(); + var accentStructSize = Marshal.SizeOf(accent); + + //Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10 + accent.AccentState = state; var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false); @@ -432,96 +470,18 @@ namespace Avalonia.Win32 data.Data = accentPtr; SetWindowCompositionAttribute(_hwnd, ref data); - Marshal.FreeHGlobal(accentPtr); - - if (transparencyLevel == WindowTransparencyLevel.Blur || - transparencyLevel == WindowTransparencyLevel.AcrylicBlur || - transparencyLevel == WindowTransparencyLevel.Mica) - { - Win7EnableBlur(transparencyLevel); - } - - return transparencyLevel; } - private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel) + private void SetUseHostBackdropBrush(bool useHostBackdropBrush) { - if (_isUsingComposition) - { - BlurEffect effect; - - if (transparencyLevel == WindowTransparencyLevel.Mica) - effect = BlurEffect.Mica; - else if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) - effect = BlurEffect.Acrylic; - else - effect = BlurEffect.None; - - if (Win32Platform.WindowsVersion >= WinUiCompositionShared.MinHostBackdropVersion) - { - unsafe - { - int pvUseBackdropBrush = effect == BlurEffect.Acrylic ? 1 : 0; - DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); - } - } - - if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion && effect == BlurEffect.Mica) - { - effect = BlurEffect.Acrylic; - } - - _blurHost?.SetBlur(effect); + if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion) + return; - return transparencyLevel; - } - else + unsafe { - bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; - - var accent = new AccentPolicy(); - var accentStructSize = Marshal.SizeOf(); - - if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur && !canUseAcrylic) - { - transparencyLevel = WindowTransparencyLevel.Blur; - } - - if (transparencyLevel == WindowTransparencyLevel.Transparent) - { - accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; - } - else if (transparencyLevel == WindowTransparencyLevel.Blur) - { - accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; - } - else if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || transparencyLevel == WindowTransparencyLevel.Mica) - { - accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - } - else - { - accent.AccentState = AccentState.ACCENT_DISABLED; - } - - accent.AccentFlags = 2; - accent.GradientColor = 0x01000000; - - 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; + var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0; + DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int)); } } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 6ca0cf7ace..605955e44f 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -139,7 +139,7 @@ namespace Avalonia.iOS return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { // No-op } diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index de7cbc873c..734baba54f 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -192,7 +192,7 @@ public class CompositorTestServices : IDisposable public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IPopupImpl CreatePopup() => throw new NotImplementedException(); - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } From 098f37d3cf31706d0dc367355fb972907d69846a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 14:54:04 +0200 Subject: [PATCH 03/11] Implement transparency level hint list on Android. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 155 ++++++++---------- .../Platform/ITopLevelImpl.cs | 2 +- 2 files changed, 71 insertions(+), 86 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 144e2d843a..151227dba4 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.Versioning; using Android.App; using Android.Content; using Android.Graphics; @@ -299,93 +300,36 @@ namespace Avalonia.Android.Platform.SkiaPlatform public double Scaling => RenderScaling; - public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - ////if (TransparencyLevel != transparencyLevel) - ////{ - //// bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; - //// bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; - //// if (_view.Context is AvaloniaMainActivity activity) - //// { - //// if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur || - //// transparencyLevel == WindowTransparencyLevel.Mica || - //// transparencyLevel == WindowTransparencyLevel.None) - //// { - //// if (!isBelowR) - //// { - //// activity.SetTranslucent(false); - //// } - //// if (isAboveR) - //// { - //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - - //// var attr = activity.Window?.Attributes; - //// if (attr != null) - //// { - //// attr.BlurBehindRadius = 0; - - //// activity.Window.Attributes = attr; - //// } - //// } - //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); - - //// if (transparencyLevel != WindowTransparencyLevel.None) - //// { - //// return; - //// } - //// } - - //// if (transparencyLevel == WindowTransparencyLevel.Transparent) - //// { - //// if (!isBelowR) - //// { - //// activity.SetTranslucent(true); - //// } - //// if (isAboveR) - //// { - //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - - //// var attr = activity.Window?.Attributes; - //// if (attr != null) - //// { - //// attr.BlurBehindRadius = 0; - - //// activity.Window.Attributes = attr; - //// } - //// } - //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - //// } - - //// if (transparencyLevel == WindowTransparencyLevel.Blur) - //// { - //// if (isAboveR) - //// { - //// activity.SetTranslucent(true); - //// activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); - - //// var attr = activity.Window?.Attributes; - //// if (attr != null) - //// { - //// attr.BlurBehindRadius = 120; - - //// activity.Window.Attributes = attr; - //// } - //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - //// } - //// else - //// { - //// activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); - //// activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.White)); - - //// return; - //// } - //// } - - //// TransparencyLevel = transparencyLevel; - //// } - ////} + if (_view.Context is not AvaloniaMainActivity activity) + return; + + foreach (var level in transparencyLevels) + { + if (!IsSupported(level)) + { + continue; + } + + if (level == WindowTransparencyLevel.None && OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(false); + } + else if (level == WindowTransparencyLevel.Transparent) + { + SetTransparencyTransparent(activity); + } + else if (level == WindowTransparencyLevel.Blur) + { + SetTransparencyBlur(activity); + } + + TransparencyLevel = level; + break; + } } - + public virtual object TryGetFeature(Type featureType) { if (featureType == typeof(IStorageProvider)) @@ -420,6 +364,47 @@ namespace Avalonia.Android.Platform.SkiaPlatform return null; } + + private static bool IsSupported(WindowTransparencyLevel level) + { + if (level == WindowTransparencyLevel.None) + return true; + if (level == WindowTransparencyLevel.Transparent) + return OperatingSystem.IsAndroidVersionAtLeast(30); + if (level == WindowTransparencyLevel.Blur) + return OperatingSystem.IsAndroidVersionAtLeast(31); + return false; + } + + private static void SetTransparencyTransparent(AvaloniaMainActivity activity) + { + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(true); + SetBlurBehindRadius(activity, 0); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + } + + private static void SetTransparencyBlur(AvaloniaMainActivity activity) + { + if (OperatingSystem.IsAndroidVersionAtLeast(31)) + { + activity.SetTranslucent(true); + activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + SetBlurBehindRadius(activity, 120); + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + } + + private static void SetBlurBehindRadius(AvaloniaMainActivity activity, int radius) + { + if (OperatingSystem.IsAndroidVersionAtLeast(31) && activity.Window?.Attributes is { } attr) + { + attr.BlurBehindRadius = radius; + activity.Window.Attributes = attr; + } + } } internal class AvaloniaInputConnection : BaseInputConnection diff --git a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 02b96d0628..5b25e0e551 100644 --- a/src/Avalonia.Controls/Platform/ITopLevelImpl.cs +++ b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs @@ -117,7 +117,7 @@ namespace Avalonia.Platform /// /// Sets the hint of the TopLevel. /// - void SetTransparencyLevelHint(IReadOnlyList transparencyLevel); + void SetTransparencyLevelHint(IReadOnlyList transparencyLevels); /// /// Gets the current of the TopLevel. From 9715d71bd97a81f1f9cd059e5a869c5ed7803d45 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 15:18:07 +0200 Subject: [PATCH 04/11] Implement transparency level hint list on X11. --- src/Avalonia.X11/TransparencyHelper.cs | 77 ++++++++++++++++++-------- src/Avalonia.X11/X11Window.cs | 6 +- 2 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.X11/TransparencyHelper.cs b/src/Avalonia.X11/TransparencyHelper.cs index b4f8ca5aed..01dccbf12a 100644 --- a/src/Avalonia.X11/TransparencyHelper.cs +++ b/src/Avalonia.X11/TransparencyHelper.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using Avalonia.Controls; +#nullable enable + namespace Avalonia.X11 { internal class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber @@ -9,10 +12,10 @@ namespace Avalonia.X11 private readonly IntPtr _window; private readonly X11Globals _globals; private WindowTransparencyLevel _currentLevel; - private WindowTransparencyLevel _requestedLevel; + private IReadOnlyList? _requestedLevels; private bool _blurAtomsAreSet; - public Action TransparencyLevelChanged { get; set; } + public Action? TransparencyLevelChanged { get; set; } public WindowTransparencyLevel CurrentLevel => _currentLevel; public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals) @@ -23,28 +26,62 @@ namespace Avalonia.X11 _globals.AddSubscriber(this); } - public void SetTransparencyRequest(WindowTransparencyLevel level) + public void SetTransparencyRequest(IReadOnlyList levels) { - _requestedLevel = level; - UpdateTransparency(); - } + WindowTransparencyLevel? newLevel = null; + _requestedLevels = levels; + + foreach (var level in levels) + { + if (!IsSupported(level)) + continue; + + SetBlur(level == WindowTransparencyLevel.Blur); + newLevel = level; + break; + } + + // If no matching transparency level was found, revert to Transparent or None depending + // on whether composition is enabled. + if (!newLevel.HasValue) + { + newLevel = _globals.IsCompositionEnabled ? + WindowTransparencyLevel.Transparent : + WindowTransparencyLevel.None; + SetBlur(false); + } - private void UpdateTransparency() - { - var newLevel = UpdateAtomsAndGetTransparency(); if (newLevel != _currentLevel) { - _currentLevel = newLevel; - TransparencyLevelChanged?.Invoke(newLevel); + _currentLevel = newLevel.Value; + TransparencyLevelChanged?.Invoke(newLevel.Value); } } - - private WindowTransparencyLevel UpdateAtomsAndGetTransparency() + + private bool IsSupported(WindowTransparencyLevel level) { - var blur = _requestedLevel == WindowTransparencyLevel.Blur || - _requestedLevel == WindowTransparencyLevel.AcrylicBlur || - _requestedLevel == WindowTransparencyLevel.Mica; + // None is suppported when composition is disabled. + if (level == WindowTransparencyLevel.None) + return !_globals.IsCompositionEnabled; + + // Transparent is suppported when composition is enabled. + if (level == WindowTransparencyLevel.Transparent) + return _globals.IsCompositionEnabled; + + // Blur is supported when composition is enabled and KWin is used. + if (level == WindowTransparencyLevel.Blur) + return _globals.IsCompositionEnabled && _globals.WmName == "KWin"; + + return false; + } + private void UpdateTransparency() + { + SetTransparencyRequest(_requestedLevels ?? Array.Empty()); + } + + private void SetBlur(bool blur) + { if (blur) { if (!_blurAtomsAreSet) @@ -63,15 +100,7 @@ namespace Avalonia.X11 _blurAtomsAreSet = false; } } - - if (!_globals.IsCompositionEnabled) - return WindowTransparencyLevel.None; - if (blur && CanBlur) - return WindowTransparencyLevel.Blur; - return WindowTransparencyLevel.Transparent; } - - private bool CanBlur => _globals.WmName == "KWin" && _globals.IsCompositionEnabled; public void Dispose() { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 58b59a6b56..7da3d31dc2 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -196,7 +196,7 @@ namespace Avalonia.X11 _rawEventGrouper = new RawEventGrouper(DispatchInput, platform.EventGrouperDispatchQueue); _transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals); - _transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None); + _transparencyHelper.SetTransparencyRequest(Array.Empty()); CreateIC(); @@ -1307,9 +1307,9 @@ namespace Avalonia.X11 public IPopupPositioner? PopupPositioner { get; } - public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - ////_transparencyHelper?.SetTransparencyRequest(transparencyLevel); + _transparencyHelper?.SetTransparencyRequest(transparencyLevels); } public void SetWindowManagerAddShadowHint(bool enabled) From 0218a0d4ac602c4bf3aba79358c987089c970457 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 15:25:47 +0200 Subject: [PATCH 05/11] Implement transparency level hint list on macOS. --- src/Avalonia.Native/WindowImplBase.cs | 42 +++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index c8198f0da2..0fe0c5e7c0 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -485,27 +485,27 @@ namespace Avalonia.Native _native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); } - public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) - { - ////if (TransparencyLevel != transparencyLevel) - ////{ - //// if (transparencyLevel == WindowTransparencyLevel.Blur || - //// transparencyLevel == WindowTransparencyLevel.AcrylicBlur || - //// transparencyLevel == WindowTransparencyLevel.Mica) - //// { - //// transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - //// } - - //// TransparencyLevel = transparencyLevel; - - //// _native.SetTransparencyMode(transparencyLevel == WindowTransparencyLevel.None - //// ? AvnWindowTransparencyMode.Opaque - //// : transparencyLevel == WindowTransparencyLevel.Transparent - //// ? AvnWindowTransparencyMode.Transparent - //// : AvnWindowTransparencyMode.Blur); - - //// TransparencyLevelChanged?.Invoke(TransparencyLevel); - ////} + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) + { + foreach (var level in transparencyLevels) + { + AvnWindowTransparencyMode? mode = null; + + if (level == WindowTransparencyLevel.None) + mode = AvnWindowTransparencyMode.Opaque; + if (level == WindowTransparencyLevel.Transparent) + mode = AvnWindowTransparencyMode.Transparent; + else if (level == WindowTransparencyLevel.AcrylicBlur) + mode = AvnWindowTransparencyMode.Blur; + + if (mode.HasValue) + { + _native?.SetTransparencyMode(mode.Value); + TransparencyLevel = level; + TransparencyLevelChanged?.Invoke(TransparencyLevel); + break; + } + } } public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None; From ac786d098b98399901140d7878822c085e1345e1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 15:33:03 +0200 Subject: [PATCH 06/11] Tweaked android logic. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 151227dba4..086de8f033 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -312,17 +312,32 @@ namespace Avalonia.Android.Platform.SkiaPlatform continue; } - if (level == WindowTransparencyLevel.None && OperatingSystem.IsAndroidVersionAtLeast(30)) + if (level == WindowTransparencyLevel.None) { - activity.SetTranslucent(false); + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(false); + } + + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White)); } else if (level == WindowTransparencyLevel.Transparent) { - SetTransparencyTransparent(activity); + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(true); + SetBlurBehind(activity, 0); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } } else if (level == WindowTransparencyLevel.Blur) { - SetTransparencyBlur(activity); + if (OperatingSystem.IsAndroidVersionAtLeast(31)) + { + activity.SetTranslucent(true); + SetBlurBehind(activity, 120); + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } } TransparencyLevel = level; @@ -376,29 +391,13 @@ namespace Avalonia.Android.Platform.SkiaPlatform return false; } - private static void SetTransparencyTransparent(AvaloniaMainActivity activity) + private static void SetBlurBehind(AvaloniaMainActivity activity, int radius) { - if (OperatingSystem.IsAndroidVersionAtLeast(30)) - { - activity.SetTranslucent(true); - SetBlurBehindRadius(activity, 0); - activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - } - } - - private static void SetTransparencyBlur(AvaloniaMainActivity activity) - { - if (OperatingSystem.IsAndroidVersionAtLeast(31)) - { - activity.SetTranslucent(true); + if (radius == 0) + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + else activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); - SetBlurBehindRadius(activity, 120); - activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); - } - } - private static void SetBlurBehindRadius(AvaloniaMainActivity activity, int radius) - { if (OperatingSystem.IsAndroidVersionAtLeast(31) && activity.Window?.Attributes is { } attr) { attr.BlurBehindRadius = radius; From 4954228f149af932993831debc4199255fbed584 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 25 May 2023 22:56:06 +0200 Subject: [PATCH 07/11] Fix display of unsupported transparency levels. Only make the window brush transparent when we requested a transparent window and were given one, otherwise we display a transparent brush for e.g. `None` because `None` is not available when using composition. --- samples/ControlCatalog/MainView.xaml.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 5e33952c68..801844a43c 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -85,7 +85,8 @@ namespace ControlCatalog var topLevel = (TopLevel)this.GetVisualRoot()!; topLevel.TransparencyLevelHint = new[] { selected }; - if (selected != WindowTransparencyLevel.None) + if (topLevel.ActualTransparencyLevel != WindowTransparencyLevel.None && + topLevel.ActualTransparencyLevel == selected) { var transparentBrush = new ImmutableSolidColorBrush(Colors.White, 0); var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2); From e50816008ec7f517a73d5c72539d1cf8f965e448 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 25 May 2023 23:17:21 +0200 Subject: [PATCH 08/11] Set default transparency level. If no matching transparency level was found, use the default on all platforms. Raise `TransparencyLevelChanged` on all platforms. --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 30 +++++++++++++- src/Avalonia.Native/WindowImplBase.cs | 27 ++++++++++--- src/Avalonia.X11/TransparencyHelper.cs | 40 ++++++++++--------- src/Windows/Avalonia.Win32/WindowImpl.cs | 32 +++++++++++++-- 4 files changed, 100 insertions(+), 29 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 086de8f033..6f9fdf3569 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -46,6 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly AndroidInsetsManager _insetsManager; private readonly ClipboardImpl _clipboard; private ViewImpl _view; + private WindowTransparencyLevel _transparencyLevel; public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) { @@ -69,6 +70,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context); + _transparencyLevel = WindowTransparencyLevel.None; _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService); } @@ -275,7 +277,18 @@ namespace Avalonia.Android.Platform.SkiaPlatform public Action LostFocus { get; set; } public Action TransparencyLevelChanged { get; set; } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel + { + get => _transparencyLevel; + private set + { + if (_transparencyLevel != value) + { + _transparencyLevel = value; + TransparencyLevelChanged?.Invoke(value); + } + } + } public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { @@ -312,6 +325,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform continue; } + if (level == TransparencyLevel) + { + return; + } + if (level == WindowTransparencyLevel.None) { if (OperatingSystem.IsAndroidVersionAtLeast(30)) @@ -341,8 +359,16 @@ namespace Avalonia.Android.Platform.SkiaPlatform } TransparencyLevel = level; - break; + return; } + + // If we get here, we didn't find a supported level. Use the default of None. + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(false); + } + + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White)); } public virtual object TryGetFeature(Type featureType) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 0fe0c5e7c0..dfe30004b0 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -59,12 +59,12 @@ namespace Avalonia.Native private readonly IKeyboardDevice _keyboard; private readonly ICursorFactory _cursorFactory; private Size _savedLogicalSize; - private Size _lastRenderedLogicalSize; private double _savedScaling; private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; private IStorageProvider _storageProvider; private PlatformBehaviorInhibition _platformBehaviorInhibition; + private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativeGlPlatformGraphics glFeature) @@ -498,17 +498,34 @@ namespace Avalonia.Native else if (level == WindowTransparencyLevel.AcrylicBlur) mode = AvnWindowTransparencyMode.Blur; - if (mode.HasValue) + if (mode.HasValue && level != TransparencyLevel) { _native?.SetTransparencyMode(mode.Value); TransparencyLevel = level; - TransparencyLevelChanged?.Invoke(TransparencyLevel); - break; + return; } } + + // If we get here, we didn't find a supported level. Use the default of None. + if (TransparencyLevel != WindowTransparencyLevel.None) + { + _native?.SetTransparencyMode(AvnWindowTransparencyMode.Opaque); + TransparencyLevel = WindowTransparencyLevel.None; + } } - public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.None; + public WindowTransparencyLevel TransparencyLevel + { + get => _transparencyLevel; + private set + { + if (_transparencyLevel != value) + { + _transparencyLevel = value; + TransparencyLevelChanged?.Invoke(value); + } + } + } public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { diff --git a/src/Avalonia.X11/TransparencyHelper.cs b/src/Avalonia.X11/TransparencyHelper.cs index 01dccbf12a..3cc901e332 100644 --- a/src/Avalonia.X11/TransparencyHelper.cs +++ b/src/Avalonia.X11/TransparencyHelper.cs @@ -16,7 +16,19 @@ namespace Avalonia.X11 private bool _blurAtomsAreSet; public Action? TransparencyLevelChanged { get; set; } - public WindowTransparencyLevel CurrentLevel => _currentLevel; + + public WindowTransparencyLevel CurrentLevel + { + get => _currentLevel; + set + { + if (_currentLevel != value) + { + _currentLevel = value; + TransparencyLevelChanged?.Invoke(value); + } + } + } public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals) { @@ -28,7 +40,6 @@ namespace Avalonia.X11 public void SetTransparencyRequest(IReadOnlyList levels) { - WindowTransparencyLevel? newLevel = null; _requestedLevels = levels; foreach (var level in levels) @@ -37,25 +48,16 @@ namespace Avalonia.X11 continue; SetBlur(level == WindowTransparencyLevel.Blur); - newLevel = level; - break; - } - - // If no matching transparency level was found, revert to Transparent or None depending - // on whether composition is enabled. - if (!newLevel.HasValue) - { - newLevel = _globals.IsCompositionEnabled ? - WindowTransparencyLevel.Transparent : - WindowTransparencyLevel.None; - SetBlur(false); + CurrentLevel = level; + return; } - if (newLevel != _currentLevel) - { - _currentLevel = newLevel.Value; - TransparencyLevelChanged?.Invoke(newLevel.Value); - } + // If we get here, we didn't find a supported level. Use the defualt of Transparent or + // None, depending on whether composition is enabled. + SetBlur(false); + CurrentLevel = _globals.IsCompositionEnabled ? + WindowTransparencyLevel.Transparent : + WindowTransparencyLevel.None; } private bool IsSupported(WindowTransparencyLevel level) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index dc9cc7f50c..cb989cbc50 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -99,6 +99,7 @@ namespace Avalonia.Win32 private bool _hiddenWindowIsParent; private uint _langid; private bool _ignoreWmChar; + private WindowTransparencyLevel _transparencyLevel; private const int MaxPointerHistorySize = 512; private static readonly PooledList s_intermediatePointsPooledList = new(); @@ -183,6 +184,7 @@ namespace Avalonia.Win32 _storageProvider = new Win32StorageProvider(this); _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition); + _transparencyLevel = _isUsingComposition ? WindowTransparencyLevel.Transparent : WindowTransparencyLevel.None; s_instances.Add(this); } @@ -316,7 +318,18 @@ namespace Avalonia.Win32 } } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel + { + get => _transparencyLevel; + private set + { + if (_transparencyLevel != value) + { + _transparencyLevel = value; + TransparencyLevelChanged?.Invoke(value); + } + } + } protected IntPtr Hwnd => _hwnd; @@ -355,7 +368,8 @@ namespace Avalonia.Win32 { if (!IsSupported(level, windowsVersion)) continue; - + if (level == TransparencyLevel) + return; if (level == WindowTransparencyLevel.Transparent) SetTransparencyTransparent(windowsVersion); else if (level == WindowTransparencyLevel.Blur) @@ -366,7 +380,19 @@ namespace Avalonia.Win32 SetTransparencyMica(windowsVersion); TransparencyLevel = level; - break; + return; + } + + // If we get here, we didn't find a supported level. Use the defualt of Transparent or + // None, depending on whether composition is enabled. + if (_isUsingComposition) + { + SetTransparencyTransparent(windowsVersion); + TransparencyLevel = WindowTransparencyLevel.Transparent; + } + else + { + TransparencyLevel = WindowTransparencyLevel.None; } } From 703e24ed57f8f29d8a1a205d609e8deafb528d14 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 25 May 2023 23:21:25 +0200 Subject: [PATCH 09/11] Make stub impls return None transparency. --- .../Embedding/Offscreen/OffscreenTopLevelImpl.cs | 2 +- src/Avalonia.DesignerSupport/Remote/Stubs.cs | 2 +- src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs | 7 +------ .../Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs | 2 +- src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs | 2 +- src/iOS/Avalonia.iOS/AvaloniaView.cs | 4 ++-- tests/Avalonia.UnitTests/CompositorTestServices.cs | 2 +- 7 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 28b43e8397..c267bced4a 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -91,7 +91,7 @@ namespace Avalonia.Controls.Embedding.Offscreen public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public IPopupImpl? CreatePopup() => null; diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index 28d1b147fd..a58b836667 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -185,7 +185,7 @@ namespace Avalonia.DesignerSupport.Remote { } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public bool IsClientAreaExtendedToDecorations { get; } diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index c243a2a28b..e9ee191bb2 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -225,11 +225,6 @@ namespace Avalonia.Browser public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { - ////if (transparencyLevel == WindowTransparencyLevel.None - //// || transparencyLevel == WindowTransparencyLevel.Transparent) - ////{ - //// TransparencyLevel = transparencyLevel; - ////} } public Size ClientSize => _clientSize; @@ -249,7 +244,7 @@ namespace Avalonia.Browser public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard; - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { // not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android. diff --git a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs index 79408cd604..19c7d030af 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -77,7 +77,7 @@ using Avalonia.Rendering.Composition; public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } diff --git a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 64f4352c25..24bdd41fb7 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -249,7 +249,7 @@ namespace Avalonia.Win32.Interop.Wpf public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs index 605955e44f..c3e626343c 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -159,8 +159,8 @@ namespace Avalonia.iOS // legacy no-op public IMouseDevice MouseDevice { get; } = new MouseDevice(); - public WindowTransparencyLevel TransparencyLevel { get; } - + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { // TODO adjust status bar depending on full screen mode. diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 734baba54f..62e863d4f0 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -196,7 +196,7 @@ public class CompositorTestServices : IDisposable { } - public WindowTransparencyLevel TransparencyLevel { get; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { From 7ee645bd7e6d9b0bbec29df6badfa8fb95b4ebb9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 May 2023 10:00:36 +0200 Subject: [PATCH 10/11] Fix compile error. Accidentally removed this line - it's unused but unrelated to this PR so putting it back. --- src/Avalonia.Native/WindowImplBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index dfe30004b0..e1819ffddc 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -59,6 +59,7 @@ namespace Avalonia.Native private readonly IKeyboardDevice _keyboard; private readonly ICursorFactory _cursorFactory; private Size _savedLogicalSize; + private Size _lastRenderedLogicalSize; private double _savedScaling; private GlPlatformSurface _glSurface; private NativeControlHostImpl _nativeControlHost; From f4beb593f5fc6dd4764c37c050c3b8f73aba428b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 26 May 2023 12:31:38 +0200 Subject: [PATCH 11/11] Fix compile error. --- src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index e9ee191bb2..eed64e59bf 100644 --- a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs +++ b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs @@ -39,7 +39,6 @@ namespace Avalonia.Browser { Surfaces = Enumerable.Empty(); _avaloniaView = avaloniaView; - TransparencyLevel = WindowTransparencyLevel.None; AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1); _touchDevice = new TouchDevice(); _penDevice = new PenDevice();