From b2d780506036965465f484a29624fb8919f0d6e2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 24 May 2023 00:08:20 +0200 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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(); From 3b3b5145ee941c9dad0e9c318b081b74e0ea8287 Mon Sep 17 00:00:00 2001 From: Yoh Deadfall Date: Fri, 26 May 2023 17:34:10 +0300 Subject: [PATCH 12/22] Ignore non hittable elements on hit test --- src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 8525f025a3..5c350688a4 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -50,7 +50,7 @@ namespace Avalonia.Controls.Primitives { if (InputPassThroughElement is Visual v) { - var hit = ((Visual?)VisualRoot)?.GetVisualAt(point, x => x != this); + var hit = ((IInputElement?)VisualRoot)?.InputHitTest(point, x => x != this) as Visual; if (hit is object) { From 9eeebbc99db0f525de0133c30785dabed9903bf3 Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 28 May 2023 13:03:04 -0400 Subject: [PATCH 13/22] Rename MenuBase Opened/Closed events removing type prefix --- src/Avalonia.Base/Input/AccessKeyHandler.cs | 4 +-- src/Avalonia.Base/Input/IMainMenu.cs | 2 +- src/Avalonia.Controls/ContextMenu.cs | 4 +-- src/Avalonia.Controls/Menu.cs | 4 +-- src/Avalonia.Controls/MenuBase.cs | 28 +++++++++---------- .../Platform/DefaultMenuInteractionHandler.cs | 4 +-- .../ContextMenuTests.cs | 8 +++--- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Base/Input/AccessKeyHandler.cs b/src/Avalonia.Base/Input/AccessKeyHandler.cs index 2b8786089f..2e0268a644 100644 --- a/src/Avalonia.Base/Input/AccessKeyHandler.cs +++ b/src/Avalonia.Base/Input/AccessKeyHandler.cs @@ -65,14 +65,14 @@ namespace Avalonia.Input { if (_mainMenu != null) { - _mainMenu.MenuClosed -= MainMenuClosed; + _mainMenu.Closed -= MainMenuClosed; } _mainMenu = value; if (_mainMenu != null) { - _mainMenu.MenuClosed += MainMenuClosed; + _mainMenu.Closed += MainMenuClosed; } } } diff --git a/src/Avalonia.Base/Input/IMainMenu.cs b/src/Avalonia.Base/Input/IMainMenu.cs index cc1b88d4c3..02437b6367 100644 --- a/src/Avalonia.Base/Input/IMainMenu.cs +++ b/src/Avalonia.Base/Input/IMainMenu.cs @@ -26,6 +26,6 @@ namespace Avalonia.Input /// /// Occurs when the main menu closes. /// - event EventHandler? MenuClosed; + event EventHandler? Closed; } } diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a9d8e4c9c7..281ed5c6fb 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -353,7 +353,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuOpenedEvent, + RoutedEvent = OpenedEvent, Source = this, }); } @@ -394,7 +394,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuClosedEvent, + RoutedEvent = ClosedEvent, Source = this, }); diff --git a/src/Avalonia.Controls/Menu.cs b/src/Avalonia.Controls/Menu.cs index fe4d42c603..6a8cf2b515 100644 --- a/src/Avalonia.Controls/Menu.cs +++ b/src/Avalonia.Controls/Menu.cs @@ -60,7 +60,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuClosedEvent, + RoutedEvent = ClosedEvent, Source = this, }); } @@ -77,7 +77,7 @@ namespace Avalonia.Controls RaiseEvent(new RoutedEventArgs { - RoutedEvent = MenuOpenedEvent, + RoutedEvent = OpenedEvent, Source = this, }); } diff --git a/src/Avalonia.Controls/MenuBase.cs b/src/Avalonia.Controls/MenuBase.cs index 7fc804a338..e57fadffee 100644 --- a/src/Avalonia.Controls/MenuBase.cs +++ b/src/Avalonia.Controls/MenuBase.cs @@ -25,16 +25,16 @@ namespace Avalonia.Controls o => o.IsOpen); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent MenuOpenedEvent = - RoutedEvent.Register(nameof(MenuOpened), RoutingStrategies.Bubble); + public static readonly RoutedEvent OpenedEvent = + RoutedEvent.Register(nameof(Opened), RoutingStrategies.Bubble); /// - /// Defines the event. + /// Defines the event. /// - public static readonly RoutedEvent MenuClosedEvent = - RoutedEvent.Register(nameof(MenuClosed), RoutingStrategies.Bubble); + public static readonly RoutedEvent ClosedEvent = + RoutedEvent.Register(nameof(Closed), RoutingStrategies.Bubble); private bool _isOpen; @@ -68,8 +68,8 @@ namespace Avalonia.Controls /// public bool IsOpen { - get { return _isOpen; } - protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } + get => _isOpen; + protected set => SetAndRaise(IsOpenProperty, ref _isOpen, value); } /// @@ -105,19 +105,19 @@ namespace Avalonia.Controls /// /// Occurs when a is opened. /// - public event EventHandler? MenuOpened + public event EventHandler? Opened { - add { AddHandler(MenuOpenedEvent, value); } - remove { RemoveHandler(MenuOpenedEvent, value); } + add => AddHandler(OpenedEvent, value); + remove => RemoveHandler(OpenedEvent, value); } /// /// Occurs when a is closed. /// - public event EventHandler? MenuClosed + public event EventHandler? Closed { - add { AddHandler(MenuClosedEvent, value); } - remove { RemoveHandler(MenuClosedEvent, value); } + add => AddHandler(ClosedEvent, value); + remove => RemoveHandler(ClosedEvent, value); } /// diff --git a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs index 79a3038edf..6a76855d85 100644 --- a/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs +++ b/src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs @@ -290,7 +290,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed += PointerPressed; Menu.PointerReleased += PointerReleased; Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened); + Menu.AddHandler(MenuBase.OpenedEvent, MenuOpened); Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); @@ -326,7 +326,7 @@ namespace Avalonia.Controls.Platform Menu.PointerPressed -= PointerPressed; Menu.PointerReleased -= PointerReleased; Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); - Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened); + Menu.RemoveHandler(MenuBase.OpenedEvent, MenuOpened); Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 803cc793fd..fda3379976 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -35,7 +35,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -138,7 +138,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -167,7 +167,7 @@ namespace Avalonia.Controls.UnitTests bool opened = false; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { opened = true; }; @@ -220,7 +220,7 @@ namespace Avalonia.Controls.UnitTests int closedCount = 0; - sut.MenuClosed += (sender, args) => + sut.Closed += (sender, args) => { closedCount++; }; From 04c88b09fbd975e67d6457ef5e5c640a0934758c Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 28 May 2023 13:07:59 -0400 Subject: [PATCH 14/22] Rename ContextMenu Opening/Closing events removing type prefix --- samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs | 4 ++-- src/Avalonia.Controls/ContextMenu.cs | 8 ++++---- tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs index 96fcb54650..664264956f 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs @@ -19,8 +19,8 @@ namespace ControlCatalog.Pages customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); var cancellableContextBorder = this.Get("CancellableContextBorder"); - cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing; - cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening; + cancellableContextBorder.ContextMenu!.Closing += ContextFlyoutPage_Closing; + cancellableContextBorder.ContextMenu!.Opening += ContextFlyoutPage_Opening; } private ContextPageViewModel? _model; diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 281ed5c6fb..3dc1514667 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -196,14 +196,14 @@ namespace Avalonia.Controls /// /// property is changing from false to true. /// - public event CancelEventHandler? ContextMenuOpening; + public event CancelEventHandler? Opening; /// /// Occurs when the value of the /// /// property is changing from true to false. /// - public event CancelEventHandler? ContextMenuClosing; + public event CancelEventHandler? Closing; /// /// Called when the property changes on a control. @@ -446,14 +446,14 @@ namespace Avalonia.Controls private bool CancelClosing() { var eventArgs = new CancelEventArgs(); - ContextMenuClosing?.Invoke(this, eventArgs); + Closing?.Invoke(this, eventArgs); return eventArgs.Cancel; } private bool CancelOpening() { var eventArgs = new CancelEventArgs(); - ContextMenuOpening?.Invoke(this, eventArgs); + Opening?.Invoke(this, eventArgs); return eventArgs.Cancel; } } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index fda3379976..429572f3ea 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -258,7 +258,7 @@ namespace Avalonia.Controls.UnitTests var tracker = 0; var c = new ContextMenu(); - c.ContextMenuClosing += (s, e) => + c.Closing += (s, e) => { tracker++; e.Cancel = true; @@ -430,7 +430,7 @@ namespace Avalonia.Controls.UnitTests }; new Window { Content = target }; - sut.ContextMenuOpening += (c, e) => { eventCalled = true; e.Cancel = true; }; + sut.Opening += (c, e) => { eventCalled = true; e.Cancel = true; }; _mouse.Click(target, MouseButton.Right); @@ -574,7 +574,7 @@ namespace Avalonia.Controls.UnitTests var window = PreparedWindow(target); var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); - sut.ContextMenuClosing += (c, e) => { eventCalled = true; e.Cancel = true; }; + sut.Closing += (c, e) => { eventCalled = true; e.Cancel = true; }; window.Show(); From df22fb29bc5125aee5e9ceb498145eed8ae1036b Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Mon, 29 May 2023 19:39:33 +0800 Subject: [PATCH 15/22] move IndeterminateStarting/EndingOffset to TemplateSettings. --- src/Avalonia.Controls/ProgressBar.cs | 141 ++++++++---------- .../Controls/ProgressBar.xaml | 8 +- 2 files changed, 70 insertions(+), 79 deletions(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 7dcdaa2f8d..47c51fb839 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -32,29 +32,33 @@ namespace Avalonia.Controls private double _container2AnimationStartPosition; private double _container2AnimationEndPosition; - public static readonly DirectProperty ContainerAnimationStartPositionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ContainerAnimationStartPosition), - p => p.ContainerAnimationStartPosition, - (p, o) => p.ContainerAnimationStartPosition = o, 0d); - - public static readonly DirectProperty ContainerAnimationEndPositionProperty = - AvaloniaProperty.RegisterDirect( - nameof(ContainerAnimationEndPosition), - p => p.ContainerAnimationEndPosition, - (p, o) => p.ContainerAnimationEndPosition = o, 0d); - - public static readonly DirectProperty Container2AnimationStartPositionProperty = - AvaloniaProperty.RegisterDirect( - nameof(Container2AnimationStartPosition), - p => p.Container2AnimationStartPosition, - (p, o) => p.Container2AnimationStartPosition = o, 0d); - - public static readonly DirectProperty Container2AnimationEndPositionProperty = - AvaloniaProperty.RegisterDirect( - nameof(Container2AnimationEndPosition), - p => p.Container2AnimationEndPosition, - (p, o) => p.Container2AnimationEndPosition = o); + public static readonly DirectProperty + ContainerAnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationStartPosition), + p => p.ContainerAnimationStartPosition, + (p, o) => p.ContainerAnimationStartPosition = o); + + public static readonly DirectProperty + ContainerAnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationEndPosition), + p => p.ContainerAnimationEndPosition, + (p, o) => p.ContainerAnimationEndPosition = o); + + public static readonly DirectProperty + Container2AnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationStartPosition), + p => p.Container2AnimationStartPosition, + (p, o) => p.Container2AnimationStartPosition = o); + + public static readonly DirectProperty + Container2AnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationEndPosition), + p => p.Container2AnimationEndPosition, + (p, o) => p.Container2AnimationEndPosition = o); public static readonly DirectProperty Container2WidthProperty = AvaloniaProperty.RegisterDirect( @@ -68,10 +72,17 @@ namespace Avalonia.Controls p => p.ContainerWidth, (p, o) => p.ContainerWidth = o); + public static readonly StyledProperty IndeterminateStartingOffsetProperty = + AvaloniaProperty.Register(nameof(IndeterminateStartingOffset)); + + public static readonly StyledProperty IndeterminateEndingOffsetProperty = + AvaloniaProperty.Register(nameof(IndeterminateEndingOffset)); + public double ContainerAnimationStartPosition { get => _containerAnimationStartPosition; - set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value); + set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, + value); } public double ContainerAnimationEndPosition @@ -83,7 +94,8 @@ namespace Avalonia.Controls public double Container2AnimationStartPosition { get => _container2AnimationStartPosition; - set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value); + set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, + value); } public double Container2Width @@ -103,6 +115,18 @@ namespace Avalonia.Controls get => _container2AnimationEndPosition; set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); } + + public double IndeterminateStartingOffset + { + get => GetValue(IndeterminateStartingOffsetProperty); + set => SetValue(IndeterminateStartingOffsetProperty, value); + } + + public double IndeterminateEndingOffset + { + get => GetValue(IndeterminateEndingOffsetProperty); + set => SetValue(IndeterminateEndingOffsetProperty, value); + } } private double _percentage; @@ -131,7 +155,7 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly StyledProperty OrientationProperty = - AvaloniaProperty.Register(nameof(Orientation), Orientation.Horizontal); + AvaloniaProperty.Register(nameof(Orientation)); /// /// Defines the property. @@ -141,18 +165,6 @@ namespace Avalonia.Controls nameof(Percentage), o => o.Percentage); - /// - /// Defines the property. - /// - public static readonly StyledProperty IndeterminateStartingOffsetProperty = - AvaloniaProperty.Register(nameof(IndeterminateStartingOffset)); - - /// - /// Defines the property. - /// - public static readonly StyledProperty IndeterminateEndingOffsetProperty = - AvaloniaProperty.Register(nameof(IndeterminateEndingOffset)); - /// /// Gets the overall percentage complete of the progress /// @@ -166,26 +178,14 @@ namespace Avalonia.Controls private set { SetAndRaise(PercentageProperty, ref _percentage, value); } } - public double IndeterminateStartingOffset - { - get => GetValue(IndeterminateStartingOffsetProperty); - set => SetValue(IndeterminateStartingOffsetProperty, value); - } - - public double IndeterminateEndingOffset - { - get => GetValue(IndeterminateEndingOffsetProperty); - set => SetValue(IndeterminateEndingOffsetProperty, value); - } - static ProgressBar() { ValueProperty.OverrideMetadata(new(defaultBindingMode: BindingMode.OneWay)); - ValueProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); - MinimumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); - MaximumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); - IsIndeterminateProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); - OrientationProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + ValueProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); + MinimumProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); + MaximumProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); + IsIndeterminateProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); + OrientationProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); } /// @@ -305,22 +305,14 @@ namespace Avalonia.Controls TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% - // Remove these properties when we switch to fluent as default and removed the old one. - SetCurrentValue(IndeterminateStartingOffsetProperty,-dim); - SetCurrentValue(IndeterminateEndingOffsetProperty,dim); - - var padding = Padding; - var rectangle = new RectangleGeometry( - new Rect( - padding.Left, - padding.Top, - barSize.Width - (padding.Right + padding.Left), - barSize.Height - (padding.Bottom + padding.Top) - )); + TemplateSettings.IndeterminateStartingOffset = -dim; + TemplateSettings.IndeterminateEndingOffset = dim; } else { - double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); + var percent = Math.Abs(Maximum - Minimum) < double.Epsilon ? + 1.0 : + (Value - Minimum) / (Maximum - Minimum); // When the Orientation changed, the indicator's Width or Height should set to double.NaN. // Indicator size calculation should consider the ProgressBar's Padding property setting @@ -332,7 +324,8 @@ namespace Avalonia.Controls else { _indicator.Width = double.NaN; - _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * percent; + _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * + percent; } @@ -341,7 +334,7 @@ namespace Avalonia.Controls } } - private void UpdateIndicatorWhenPropChanged(AvaloniaPropertyChangedEventArgs e) + private void UpdateIndicatorWhenPropChanged() { UpdateIndicator(); } @@ -355,11 +348,9 @@ namespace Avalonia.Controls PseudoClasses.Set(":indeterminate", isIndeterminate.Value); } - if (o.HasValue) - { - PseudoClasses.Set(":vertical", o == Orientation.Vertical); - PseudoClasses.Set(":horizontal", o == Orientation.Horizontal); - } + if (!o.HasValue) return; + PseudoClasses.Set(":vertical", o == Orientation.Vertical); + PseudoClasses.Set(":horizontal", o == Orientation.Horizontal); } } } diff --git a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml index 4278c7ee28..e7ad33d04e 100644 --- a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml +++ b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml @@ -87,10 +87,10 @@ IterationCount="Infinite" Duration="0:0:3"> - + - + @@ -102,10 +102,10 @@ IterationCount="Infinite" Duration="0:0:3"> - + - + From 08a01d1fd8059b6faf547c9970c9d341df35579f Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Mon, 29 May 2023 20:36:23 +0800 Subject: [PATCH 16/22] Update ProgressBar.cs --- src/Avalonia.Controls/ProgressBar.cs | 89 ++++++++++++++-------------- 1 file changed, 43 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 47c51fb839..6bada2c6d8 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -174,18 +174,13 @@ namespace Avalonia.Controls /// public double Percentage { - get { return _percentage; } + get => _percentage; private set { SetAndRaise(PercentageProperty, ref _percentage, value); } } static ProgressBar() { ValueProperty.OverrideMetadata(new(defaultBindingMode: BindingMode.OneWay)); - ValueProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); - MinimumProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); - MaximumProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); - IsIndeterminateProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); - OrientationProperty.Changed.AddClassHandler((x, _) => x.UpdateIndicatorWhenPropChanged()); } /// @@ -251,6 +246,15 @@ namespace Avalonia.Controls { base.OnPropertyChanged(change); + if (change.Property == ValueProperty || + change.Property == MinimumProperty || + change.Property == MaximumProperty || + change.Property == IsIndeterminateProperty || + change.Property == OrientationProperty) + { + UpdateIndicator(); + } + if (change.Property == IsIndeterminateProperty) { UpdatePseudoClasses(change.GetNewValue(), null); @@ -286,57 +290,50 @@ namespace Avalonia.Controls // Gets the size of the parent indicator container var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size; - if (_indicator != null) + if (_indicator == null) return; + if (IsIndeterminate) { - if (IsIndeterminate) - { - // Pulled from ModernWPF. + // Pulled from ModernWPF. - var dim = Orientation == Orientation.Horizontal ? barSize.Width : barSize.Height; - var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar - var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar + var dim = Orientation == Orientation.Horizontal ? barSize.Width : barSize.Height; + var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar + var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar - TemplateSettings.ContainerWidth = barIndicatorWidth; - TemplateSettings.Container2Width = barIndicatorWidth2; + TemplateSettings.ContainerWidth = barIndicatorWidth; + TemplateSettings.Container2Width = barIndicatorWidth2; - TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% - TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% + TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% + TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% - TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% - TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% + TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% + TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% + + TemplateSettings.IndeterminateStartingOffset = -dim; + TemplateSettings.IndeterminateEndingOffset = dim; + } + else + { + var percent = Math.Abs(Maximum - Minimum) < double.Epsilon ? + 1.0 : + (Value - Minimum) / (Maximum - Minimum); - TemplateSettings.IndeterminateStartingOffset = -dim; - TemplateSettings.IndeterminateEndingOffset = dim; + // When the Orientation changed, the indicator's Width or Height should set to double.NaN. + // Indicator size calculation should consider the ProgressBar's Padding property setting + if (Orientation == Orientation.Horizontal) + { + _indicator.Width = (barSize.Width - _indicator.Margin.Left - _indicator.Margin.Right) * percent; + _indicator.Height = double.NaN; } else { - var percent = Math.Abs(Maximum - Minimum) < double.Epsilon ? - 1.0 : - (Value - Minimum) / (Maximum - Minimum); - - // When the Orientation changed, the indicator's Width or Height should set to double.NaN. - // Indicator size calculation should consider the ProgressBar's Padding property setting - if (Orientation == Orientation.Horizontal) - { - _indicator.Width = (barSize.Width - _indicator.Margin.Left - _indicator.Margin.Right) * percent; - _indicator.Height = double.NaN; - } - else - { - _indicator.Width = double.NaN; - _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * - percent; - } - - - Percentage = percent * 100; + _indicator.Width = double.NaN; + _indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) * + percent; } - } - } - private void UpdateIndicatorWhenPropChanged() - { - UpdateIndicator(); + + Percentage = percent * 100; + } } private void UpdatePseudoClasses( From d198bad34b641e9270550192555ff3e0b9a4cd94 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 29 May 2023 14:51:18 +0200 Subject: [PATCH 17/22] Make SingleSubscriberObservableBase internal. The last public dependency was `TemplateBinding` which can trivially be made to not inherit from it. --- src/Avalonia.Base/Data/TemplateBinding.cs | 37 ++++++++++++++++--- .../SingleSubscriberObservableBase.cs | 2 +- 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Data/TemplateBinding.cs b/src/Avalonia.Base/Data/TemplateBinding.cs index 09f04dfe70..0fd40cd09d 100644 --- a/src/Avalonia.Base/Data/TemplateBinding.cs +++ b/src/Avalonia.Base/Data/TemplateBinding.cs @@ -3,18 +3,21 @@ using System.Globalization; using Avalonia.Data.Converters; using Avalonia.Reactive; using Avalonia.Styling; +using Avalonia.Threading; namespace Avalonia.Data { /// /// A XAML binding to a property on a control's templated parent. /// - public class TemplateBinding : SingleSubscriberObservableBase, + public class TemplateBinding : IObservable, IBinding, IDescription, IAvaloniaSubject, - ISetterValue + ISetterValue, + IDisposable { + private IObserver? _observer; private bool _isSetterValue; private StyledElement? _target; private Type? _targetType; @@ -29,6 +32,28 @@ namespace Avalonia.Data Property = property; } + public IDisposable Subscribe(IObserver observer) + { + _ = observer ?? throw new ArgumentNullException(nameof(observer)); + Dispatcher.UIThread.VerifyAccess(); + + if (_observer != null) + { + throw new InvalidOperationException("The observable can only be subscribed once."); + } + + _observer = observer; + Subscribed(); + + return this; + } + + public virtual void Dispose() + { + Unsubscribed(); + _observer = null; + } + /// public InstancedBinding? Initiate( AvaloniaObject target, @@ -111,7 +136,7 @@ namespace Avalonia.Data /// void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true; - protected override void Subscribed() + private void Subscribed() { TemplatedParentChanged(); @@ -121,7 +146,7 @@ namespace Avalonia.Data } } - protected override void Unsubscribed() + private void Unsubscribed() { if (_target?.TemplatedParent is { } templatedParent) { @@ -147,12 +172,12 @@ namespace Avalonia.Data value = Converter.Convert(value, _targetType ?? typeof(object), ConverterParameter, CultureInfo.CurrentCulture); } - PublishNext(value); + _observer?.OnNext(value); _hasProducedValue = true; } else if (_hasProducedValue) { - PublishNext(AvaloniaProperty.UnsetValue); + _observer?.OnNext(AvaloniaProperty.UnsetValue); _hasProducedValue = false; } } diff --git a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs index 53a0b43c63..10d42214c2 100644 --- a/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs +++ b/src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs @@ -3,7 +3,7 @@ using Avalonia.Threading; namespace Avalonia.Reactive { - public abstract class SingleSubscriberObservableBase : IObservable, IDisposable + internal abstract class SingleSubscriberObservableBase : IObservable, IDisposable { private Exception? _error; private IObserver? _observer; From 5c32f033ee814b84f1be90d2b65bdc1c039fa049 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Mon, 29 May 2023 21:21:19 +0800 Subject: [PATCH 18/22] Add xml docs to ProgressBarTemplateSettings props --- src/Avalonia.Controls/ProgressBar.cs | 74 +++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 6bada2c6d8..337ec868d0 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -32,6 +32,9 @@ namespace Avalonia.Controls private double _container2AnimationStartPosition; private double _container2AnimationEndPosition; + /// + /// Defines the property. + /// public static readonly DirectProperty ContainerAnimationStartPositionProperty = AvaloniaProperty.RegisterDirect( @@ -39,6 +42,9 @@ namespace Avalonia.Controls p => p.ContainerAnimationStartPosition, (p, o) => p.ContainerAnimationStartPosition = o); + /// + /// Defines the property. + /// public static readonly DirectProperty ContainerAnimationEndPositionProperty = AvaloniaProperty.RegisterDirect( @@ -46,6 +52,9 @@ namespace Avalonia.Controls p => p.ContainerAnimationEndPosition, (p, o) => p.ContainerAnimationEndPosition = o); + /// + /// Defines the property. + /// public static readonly DirectProperty Container2AnimationStartPositionProperty = AvaloniaProperty.RegisterDirect( @@ -53,6 +62,9 @@ namespace Avalonia.Controls p => p.Container2AnimationStartPosition, (p, o) => p.Container2AnimationStartPosition = o); + /// + /// Defines the property. + /// public static readonly DirectProperty Container2AnimationEndPositionProperty = AvaloniaProperty.RegisterDirect( @@ -60,24 +72,57 @@ namespace Avalonia.Controls p => p.Container2AnimationEndPosition, (p, o) => p.Container2AnimationEndPosition = o); + /// + /// Defines the property. + /// public static readonly DirectProperty Container2WidthProperty = AvaloniaProperty.RegisterDirect( nameof(Container2Width), p => p.Container2Width, (p, o) => p.Container2Width = o); + /// + /// Defines the property. + /// public static readonly DirectProperty ContainerWidthProperty = AvaloniaProperty.RegisterDirect( nameof(ContainerWidth), p => p.ContainerWidth, (p, o) => p.ContainerWidth = o); + /// + /// Defines the property. + /// public static readonly StyledProperty IndeterminateStartingOffsetProperty = AvaloniaProperty.Register(nameof(IndeterminateStartingOffset)); + /// + /// Defines the property. + /// public static readonly StyledProperty IndeterminateEndingOffsetProperty = AvaloniaProperty.Register(nameof(IndeterminateEndingOffset)); + /// + /// Used by to define the first indeterminate indicator's width. + /// + public double ContainerWidth + { + get => _containerWidth; + set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value); + } + + /// + /// Used by to define the second indeterminate indicator's width. + /// + public double Container2Width + { + get => _container2Width; + set => SetAndRaise(Container2WidthProperty, ref _container2Width, value); + } + + /// + /// Used by to define the first indeterminate indicator's start position when animated. + /// public double ContainerAnimationStartPosition { get => _containerAnimationStartPosition; @@ -85,43 +130,46 @@ namespace Avalonia.Controls value); } + /// + /// Used by to define the first indeterminate indicator's end position when animated. + /// public double ContainerAnimationEndPosition { get => _containerAnimationEndPosition; set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value); } + /// + /// Used by to define the second indeterminate indicator's start position when animated. + /// public double Container2AnimationStartPosition { get => _container2AnimationStartPosition; set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value); } - - public double Container2Width - { - get => _container2Width; - set => SetAndRaise(Container2WidthProperty, ref _container2Width, value); - } - - public double ContainerWidth - { - get => _containerWidth; - set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value); - } - + + /// + /// Used by to define the second indeterminate indicator's end position when animated. + /// public double Container2AnimationEndPosition { get => _container2AnimationEndPosition; set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); } + /// + /// Used by to define the starting point of its indeterminate animation. + /// public double IndeterminateStartingOffset { get => GetValue(IndeterminateStartingOffsetProperty); set => SetValue(IndeterminateStartingOffsetProperty, value); } + /// + /// Used by to define the ending point of its indeterminate animation. + /// public double IndeterminateEndingOffset { get => GetValue(IndeterminateEndingOffsetProperty); From bc664b9b294e399994af17513106df40f250a4cf Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 29 May 2023 17:15:10 +0100 Subject: [PATCH 19/22] use pattern match to make more readable and eliminate an if. --- src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 5c350688a4..94ee99b019 100644 --- a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs +++ b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs @@ -46,13 +46,12 @@ namespace Avalonia.Controls.Primitives return manager?.LightDismissOverlayLayer; } + /// public bool HitTest(Point point) { if (InputPassThroughElement is Visual v) { - var hit = ((IInputElement?)VisualRoot)?.InputHitTest(point, x => x != this) as Visual; - - if (hit is object) + if (VisualRoot is IInputElement ie && ie.InputHitTest(point, x => x != this) is Visual hit) { return !v.IsVisualAncestorOf(hit); } From 4534bb0463ce22f676726e8c4978d31df5b65986 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Tue, 30 May 2023 00:44:56 +0800 Subject: [PATCH 20/22] make props to direct ones --- src/Avalonia.Controls/ProgressBar.cs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 337ec868d0..3dad162286 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -31,6 +31,8 @@ namespace Avalonia.Controls private double _containerAnimationEndPosition; private double _container2AnimationStartPosition; private double _container2AnimationEndPosition; + private double _indeterminateStartingOffset; + private double _indeterminateEndingOffset; /// /// Defines the property. @@ -93,14 +95,20 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty IndeterminateStartingOffsetProperty = - AvaloniaProperty.Register(nameof(IndeterminateStartingOffset)); - + public static readonly DirectProperty IndeterminateStartingOffsetProperty = + AvaloniaProperty.RegisterDirect( + nameof(IndeterminateStartingOffset), + p => p.IndeterminateStartingOffset, + (p, o) => p.IndeterminateStartingOffset = o); + /// /// Defines the property. /// - public static readonly StyledProperty IndeterminateEndingOffsetProperty = - AvaloniaProperty.Register(nameof(IndeterminateEndingOffset)); + public static readonly DirectProperty IndeterminateEndingOffsetProperty = + AvaloniaProperty.RegisterDirect( + nameof(IndeterminateEndingOffset), + p => p.IndeterminateEndingOffset, + (p, o) => p.IndeterminateEndingOffset = o); /// /// Used by to define the first indeterminate indicator's width. @@ -163,17 +171,17 @@ namespace Avalonia.Controls /// public double IndeterminateStartingOffset { - get => GetValue(IndeterminateStartingOffsetProperty); - set => SetValue(IndeterminateStartingOffsetProperty, value); + get => _indeterminateStartingOffset; + set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); } - + /// /// Used by to define the ending point of its indeterminate animation. /// public double IndeterminateEndingOffset { - get => GetValue(IndeterminateEndingOffsetProperty); - set => SetValue(IndeterminateEndingOffsetProperty, value); + get => _indeterminateEndingOffset; + set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value); } } From 40fbad3ec632e4ee6ac3c2eed8c0a72c74b045d5 Mon Sep 17 00:00:00 2001 From: dif-sam <41672086+dif-sam@users.noreply.github.com> Date: Mon, 29 May 2023 22:09:13 +0400 Subject: [PATCH 21/22] Changing the order during initialization the _daySelector Can fix the issue #11558 --- src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs index fb61ea679c..8ff8088a34 100644 --- a/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs +++ b/src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs @@ -367,11 +367,11 @@ namespace Avalonia.Controls var dt = Date; if (DayVisible) { + _daySelector.FormatDate = dt.Date; var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month); _daySelector.MaximumValue = maxDays; _daySelector.MinimumValue = 1; _daySelector.SelectedValue = dt.Day; - _daySelector.FormatDate = dt.Date; } if (MonthVisible) From f3cb243de5dfeef3b73ff979a4bb34a4059470e4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 29 May 2023 17:49:38 -0400 Subject: [PATCH 22/22] Fix android/ios --- src/Android/Avalonia.Android/AndroidPlatform.cs | 6 +----- src/Avalonia.Base/Rendering/Composition/Compositor.cs | 8 +++----- src/iOS/Avalonia.iOS/Platform.cs | 6 +----- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 2e22386ef8..d5d5f211e9 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -52,11 +52,7 @@ namespace Avalonia.Android EglPlatformGraphics.TryInitialize(); } - Compositor = new Compositor( - AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); - - + Compositor = new Compositor(AvaloniaLocator.Current.GetService()); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs index 0a4b7be15e..dde9dcd6fb 100644 --- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs +++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs @@ -66,16 +66,14 @@ namespace Avalonia.Rendering.Composition /// Creates a new compositor on a specified render loop that would use a particular GPU /// [PrivateApi] - public Compositor(IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false) + public Compositor(IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false) : this(RenderLoop.LocatorAutoInstance, gpu, useUiThreadForSynchronousCommits) { - } - - internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false) + + internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false) : this(loop, gpu, useUiThreadForSynchronousCommits, MediaContext.Instance, false) { - } internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu, diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs index 918c0caaa2..de664c93e0 100644 --- a/src/iOS/Avalonia.iOS/Platform.cs +++ b/src/iOS/Avalonia.iOS/Platform.cs @@ -47,12 +47,8 @@ namespace Avalonia.iOS .Bind().ToConstant(DispatcherImpl.Instance) .Bind().ToConstant(keyboard); - Compositor = new Compositor( - AvaloniaLocator.Current.GetRequiredService(), - AvaloniaLocator.Current.GetService()); + Compositor = new Compositor(AvaloniaLocator.Current.GetService()); } - - } }