diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index 9c511f9eb0..801844a43c 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -83,9 +83,10 @@ namespace ControlCatalog if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected) { var topLevel = (TopLevel)this.GetVisualRoot()!; - topLevel.TransparencyLevelHint = selected; + 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); 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/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/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/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 6cc7ca13b6..a29f06a106 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; @@ -45,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) { @@ -68,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); } @@ -273,7 +276,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) { @@ -298,91 +312,64 @@ namespace Avalonia.Android.Platform.SkiaPlatform public double Scaling => RenderScaling; - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - if (TransparencyLevel != transparencyLevel) + if (_view.Context is not AvaloniaMainActivity activity) + return; + + foreach (var level in transparencyLevels) { - bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; - bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; - if (_view.Context is AvaloniaMainActivity activity) + if (!IsSupported(level)) + { + continue; + } + + if (level == TransparencyLevel) + { + return; + } + + if (level == WindowTransparencyLevel.None) + { + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(false); + } + + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.White)); + } + else if (level == WindowTransparencyLevel.Transparent) + { + if (OperatingSystem.IsAndroidVersionAtLeast(30)) + { + activity.SetTranslucent(true); + SetBlurBehind(activity, 0); + activity.Window.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); + } + } + else if (level == WindowTransparencyLevel.Blur) { - switch (transparencyLevel) + if (OperatingSystem.IsAndroidVersionAtLeast(31)) { - case WindowTransparencyLevel.AcrylicBlur: - case WindowTransparencyLevel.ForceAcrylicBlur: - case WindowTransparencyLevel.Mica: - case 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; - } - break; - case 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)); - break; - case 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; - } - break; + activity.SetTranslucent(true); + SetBlurBehind(activity, 120); + activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent)); } - TransparencyLevel = transparencyLevel; } + + TransparencyLevel = level; + 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) { if (featureType == typeof(IStorageProvider)) @@ -417,6 +404,31 @@ 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 SetBlurBehind(AvaloniaMainActivity activity, int radius) + { + if (radius == 0) + activity.Window?.ClearFlags(WindowManagerFlags.BlurBehind); + else + activity.Window?.AddFlags(WindowManagerFlags.BlurBehind); + + 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.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/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.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; 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/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index a9d8e4c9c7..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. @@ -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, }); @@ -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/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) diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs index 443cd406c9..f379120638 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs @@ -93,9 +93,9 @@ 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; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public IPopupImpl? CreatePopup() => null; 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/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/src/Avalonia.Controls/Platform/ITopLevelImpl.cs b/src/Avalonia.Controls/Platform/ITopLevelImpl.cs index 7468f41d14..35478053c4 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 transparencyLevels); /// /// Gets the current of the TopLevel. diff --git a/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs b/src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs index 8525f025a3..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 = ((Visual?)VisualRoot)?.GetVisualAt(point, x => x != this); - - if (hit is object) + if (VisualRoot is IInputElement ie && ie.InputHitTest(point, x => x != this) is Visual hit) { return !v.IsVisualAncestorOf(hit); } diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 7dcdaa2f8d..3dad162286 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -31,77 +31,157 @@ namespace Avalonia.Controls private double _containerAnimationEndPosition; 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); - + private double _indeterminateStartingOffset; + private double _indeterminateEndingOffset; + + /// + /// Defines the property. + /// + public static readonly DirectProperty + ContainerAnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationStartPosition), + p => p.ContainerAnimationStartPosition, + (p, o) => p.ContainerAnimationStartPosition = o); + + /// + /// Defines the property. + /// + public static readonly DirectProperty + ContainerAnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(ContainerAnimationEndPosition), + p => p.ContainerAnimationEndPosition, + (p, o) => p.ContainerAnimationEndPosition = o); + + /// + /// Defines the property. + /// + public static readonly DirectProperty + Container2AnimationStartPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationStartPosition), + p => p.Container2AnimationStartPosition, + (p, o) => p.Container2AnimationStartPosition = o); + + /// + /// Defines the property. + /// + public static readonly DirectProperty + Container2AnimationEndPositionProperty = + AvaloniaProperty.RegisterDirect( + nameof(Container2AnimationEndPosition), + 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 DirectProperty IndeterminateStartingOffsetProperty = + AvaloniaProperty.RegisterDirect( + nameof(IndeterminateStartingOffset), + p => p.IndeterminateStartingOffset, + (p, o) => p.IndeterminateStartingOffset = o); + + /// + /// Defines the property. + /// + 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. + /// + 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; - set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value); + set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, + 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); + set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, + value); } - - public double Container2Width + + /// + /// Used by to define the second indeterminate indicator's end position when animated. + /// + public double Container2AnimationEndPosition { - get => _container2Width; - set => SetAndRaise(Container2WidthProperty, ref _container2Width, value); + get => _container2AnimationEndPosition; + set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); } - public double ContainerWidth + /// + /// Used by to define the starting point of its indeterminate animation. + /// + public double IndeterminateStartingOffset { - get => _containerWidth; - set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value); + get => _indeterminateStartingOffset; + set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value); } - - public double Container2AnimationEndPosition + + /// + /// Used by to define the ending point of its indeterminate animation. + /// + public double IndeterminateEndingOffset { - get => _container2AnimationEndPosition; - set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); + get => _indeterminateEndingOffset; + set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value); } } @@ -131,7 +211,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 +221,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 /// @@ -162,30 +230,13 @@ namespace Avalonia.Controls /// public double Percentage { - get { return _percentage; } + get => _percentage; 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)); } /// @@ -251,6 +302,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,64 +346,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% - // Remove these properties when we switch to fluent as default and removed the old one. - SetCurrentValue(IndeterminateStartingOffsetProperty,-dim); - SetCurrentValue(IndeterminateEndingOffsetProperty,dim); + TemplateSettings.IndeterminateStartingOffset = -dim; + TemplateSettings.IndeterminateEndingOffset = dim; + } + else + { + var percent = Math.Abs(Maximum - Minimum) < double.Epsilon ? + 1.0 : + (Value - Minimum) / (Maximum - Minimum); - 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) - )); + // 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 { - double percent = Maximum == Minimum ? 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(AvaloniaPropertyChangedEventArgs e) - { - UpdateIndicator(); + + Percentage = percent * 100; + } } private void UpdatePseudoClasses( @@ -355,11 +401,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.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 9c88fb6844..ec6b3f7421 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; using Avalonia.Rendering.Composition; namespace Avalonia.Controls @@ -65,8 +67,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. @@ -173,7 +175,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; @@ -311,8 +313,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); } @@ -536,8 +541,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) @@ -632,27 +637,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; } @@ -682,7 +671,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.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs index e80690539a..a1fc0e885d 100644 --- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs +++ b/src/Avalonia.DesignerSupport/Remote/Stubs.cs @@ -177,13 +177,13 @@ namespace Avalonia.DesignerSupport.Remote public Action GotInputWhenDisabled { get; set; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { } public void SetWindowManagerAddShadowHint(bool enabled) { } - public WindowTransparencyLevel TransparencyLevel { get; private set; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public bool IsClientAreaExtendedToDecorations { get; } diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index e71fddea3f..760816643e 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -65,6 +65,7 @@ namespace Avalonia.Native private NativeControlHostImpl _nativeControlHost; private IStorageProvider _storageProvider; private PlatformBehaviorInhibition _platformBehaviorInhibition; + private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None; internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, AvaloniaNativeGlPlatformGraphics glFeature) @@ -479,26 +480,47 @@ namespace Avalonia.Native _native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - if (TransparencyLevel != transparencyLevel) + foreach (var level in transparencyLevels) { - if (transparencyLevel > WindowTransparencyLevel.Transparent) - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; + AvnWindowTransparencyMode? mode = null; - TransparencyLevel = transparencyLevel; + if (level == WindowTransparencyLevel.None) + mode = AvnWindowTransparencyMode.Opaque; + if (level == WindowTransparencyLevel.Transparent) + mode = AvnWindowTransparencyMode.Transparent; + else if (level == WindowTransparencyLevel.AcrylicBlur) + mode = AvnWindowTransparencyMode.Blur; - _native.SetTransparencyMode(transparencyLevel == WindowTransparencyLevel.None - ? AvnWindowTransparencyMode.Opaque - : transparencyLevel == WindowTransparencyLevel.Transparent - ? AvnWindowTransparencyMode.Transparent - : AvnWindowTransparencyMode.Blur); + if (mode.HasValue && level != TransparencyLevel) + { + _native?.SetTransparencyMode(mode.Value); + TransparencyLevel = level; + return; + } + } - TransparencyLevelChanged?.Invoke(TransparencyLevel); + // 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.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"> - + - + diff --git a/src/Avalonia.X11/TransparencyHelper.cs b/src/Avalonia.X11/TransparencyHelper.cs index 5ca2d1d337..3cc901e332 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,11 +12,23 @@ 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 WindowTransparencyLevel CurrentLevel => _currentLevel; + public Action? TransparencyLevelChanged { get; set; } + + public WindowTransparencyLevel CurrentLevel + { + get => _currentLevel; + set + { + if (_currentLevel != value) + { + _currentLevel = value; + TransparencyLevelChanged?.Invoke(value); + } + } + } public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals) { @@ -23,25 +38,53 @@ namespace Avalonia.X11 _globals.AddSubscriber(this); } - public void SetTransparencyRequest(WindowTransparencyLevel level) + public void SetTransparencyRequest(IReadOnlyList levels) + { + _requestedLevels = levels; + + foreach (var level in levels) + { + if (!IsSupported(level)) + continue; + + SetBlur(level == WindowTransparencyLevel.Blur); + CurrentLevel = level; + 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. + SetBlur(false); + CurrentLevel = _globals.IsCompositionEnabled ? + WindowTransparencyLevel.Transparent : + WindowTransparencyLevel.None; + } + + private bool IsSupported(WindowTransparencyLevel level) { - _requestedLevel = level; - UpdateTransparency(); + // 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() { - var newLevel = UpdateAtomsAndGetTransparency(); - if (newLevel != _currentLevel) - { - _currentLevel = newLevel; - TransparencyLevelChanged?.Invoke(newLevel); - } + SetTransparencyRequest(_requestedLevels ?? Array.Empty()); } - - private WindowTransparencyLevel UpdateAtomsAndGetTransparency() + + private void SetBlur(bool blur) { - if (_requestedLevel >= WindowTransparencyLevel.Blur) + if (blur) { if (!_blurAtomsAreSet) { @@ -59,15 +102,7 @@ namespace Avalonia.X11 _blurAtomsAreSet = false; } } - - if (!_globals.IsCompositionEnabled) - return WindowTransparencyLevel.None; - if (_requestedLevel >= WindowTransparencyLevel.Blur && CanBlur) - return WindowTransparencyLevel.Blur; - return WindowTransparencyLevel.Transparent; } - - private bool CanBlur => _globals.WmName == "KWin" && _globals.IsCompositionEnabled; public void Dispose() { diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 7df275b4c2..be2d754819 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(); @@ -1306,8 +1306,10 @@ namespace Avalonia.X11 public IPopupPositioner? PopupPositioner { get; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => - _transparencyHelper?.SetTransparencyRequest(transparencyLevel); + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) + { + _transparencyHelper?.SetTransparencyRequest(transparencyLevels); + } public void SetWindowManagerAddShadowHint(bool enabled) { diff --git a/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs b/src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs index f8fa0011f3..d05eff0000 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(); @@ -218,13 +217,8 @@ namespace Avalonia.Browser return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { - if (transparencyLevel == WindowTransparencyLevel.None - || transparencyLevel == WindowTransparencyLevel.Transparent) - { - TransparencyLevel = transparencyLevel; - } } public Size ClientSize => _clientSize; @@ -244,7 +238,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/Headless/Avalonia.Headless/HeadlessWindowImpl.cs b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs index 31c38c5171..3f486d98f6 100644 --- a/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs +++ b/src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs @@ -336,7 +336,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 a56368fca9..5843b5400e 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs @@ -72,9 +72,9 @@ 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; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } 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.Interop/Wpf/WpfTopLevelImpl.cs b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs index 7c7dfa7969..a74cc69c48 100644 --- a/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs +++ b/src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs @@ -241,9 +241,9 @@ 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; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } 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/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/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 793782fe1f..4260f90e9f 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 { @@ -97,6 +98,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(); @@ -180,6 +182,7 @@ namespace Avalonia.Win32 _storageProvider = new Win32StorageProvider(this); _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition); + _transparencyLevel = _isUsingComposition ? WindowTransparencyLevel.Transparent : WindowTransparencyLevel.None; s_instances.Add(this); } @@ -313,7 +316,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; @@ -344,82 +358,132 @@ namespace Avalonia.Win32 return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevels) { - TransparencyLevel = EnableBlur(transparencyLevel); - } + var windowsVersion = Win32Platform.WindowsVersion; + + foreach (var level in transparencyLevels) + { + if (!IsSupported(level, windowsVersion)) + continue; + if (level == TransparencyLevel) + return; + 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; + return; + } - private WindowTransparencyLevel EnableBlur(WindowTransparencyLevel transparencyLevel) - { - if (Win32Platform.WindowsVersion.Major >= 6) + // 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) { - 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); - } + SetTransparencyTransparent(windowsVersion); + TransparencyLevel = WindowTransparencyLevel.Transparent; } else { - return WindowTransparencyLevel.None; + TransparencyLevel = WindowTransparencyLevel.None; } } - 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; - var blurInfo = new DWM_BLURBEHIND(false); + // When composition is enabled, None is not supported because the backing visual always + // has an alpha channel + if (level == WindowTransparencyLevel.None) + return false; - if (transparencyLevel == WindowTransparencyLevel.Blur) + // 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; + + // Mica is supported on Windows >= 10.0.22000. + if (level == WindowTransparencyLevel.Mica) + return windowsVersion >= WinUiCompositionShared.MinHostBackdropVersion; + + 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); @@ -430,98 +494,18 @@ namespace Avalonia.Win32 data.Data = accentPtr; SetWindowCompositionAttribute(_hwnd, ref data); - Marshal.FreeHGlobal(accentPtr); - - if (transparencyLevel >= WindowTransparencyLevel.Blur) - { - Win7EnableBlur(transparencyLevel); - } - - return transparencyLevel; } - private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel) + private void SetUseHostBackdropBrush(bool useHostBackdropBrush) { - if (_isUsingComposition) - { - var effect = transparencyLevel switch - { - WindowTransparencyLevel.Mica => BlurEffect.Mica, - WindowTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic, - WindowTransparencyLevel.Blur => BlurEffect.Acrylic, - _ => 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; - } - - switch (transparencyLevel) - { - default: - case WindowTransparencyLevel.None: - accent.AccentState = AccentState.ACCENT_DISABLED; - break; - - case WindowTransparencyLevel.Transparent: - accent.AccentState = AccentState.ACCENT_ENABLE_TRANSPARENTGRADIENT; - break; - - case WindowTransparencyLevel.Blur: - accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; - break; - - case WindowTransparencyLevel.AcrylicBlur: - case WindowTransparencyLevel.ForceAcrylicBlur: // hack-force acrylic. - case WindowTransparencyLevel.Mica: - accent.AccentState = AccentState.ACCENT_ENABLE_ACRYLIC; - transparencyLevel = WindowTransparencyLevel.AcrylicBlur; - break; - } - - 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 479fc7f1c2..74cabb294d 100644 --- a/src/iOS/Avalonia.iOS/AvaloniaView.cs +++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs @@ -137,7 +137,7 @@ namespace Avalonia.iOS return null; } - public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) + public void SetTransparencyLevelHint(IReadOnlyList transparencyLevel) { // No-op } @@ -157,8 +157,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/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()); } - - } } diff --git a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs index 7f9735cac0..0c24a0d36e 100644 --- a/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs @@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests int openedCount = 0; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { openedCount++; }; @@ -168,7 +168,7 @@ namespace Avalonia.Controls.UnitTests bool opened = false; - sut.MenuOpened += (sender, args) => + sut.Opened += (sender, args) => { opened = true; }; @@ -221,7 +221,7 @@ namespace Avalonia.Controls.UnitTests int closedCount = 0; - sut.MenuClosed += (sender, args) => + sut.Closed += (sender, args) => { closedCount++; }; @@ -259,7 +259,7 @@ namespace Avalonia.Controls.UnitTests var tracker = 0; var c = new ContextMenu(); - c.ContextMenuClosing += (s, e) => + c.Closing += (s, e) => { tracker++; e.Cancel = true; @@ -431,7 +431,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); @@ -575,7 +575,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(); 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); + } + } +} diff --git a/tests/Avalonia.UnitTests/CompositorTestServices.cs b/tests/Avalonia.UnitTests/CompositorTestServices.cs index 5fcfec0437..0a85072af1 100644 --- a/tests/Avalonia.UnitTests/CompositorTestServices.cs +++ b/tests/Avalonia.UnitTests/CompositorTestServices.cs @@ -186,11 +186,11 @@ 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) { } - public WindowTransparencyLevel TransparencyLevel { get; } + public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) {