Browse Source

Merge remote-tracking branch 'origin/master' into feature/borking-changes

pull/11557/head
Max Katz 3 years ago
parent
commit
aa78d2fc06
  1. 5
      samples/ControlCatalog/MainView.xaml.cs
  2. 4
      samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs
  3. 2
      samples/IntegrationTestApp/MainWindow.axaml.cs
  4. 6
      src/Android/Avalonia.Android/AndroidPlatform.cs
  5. 168
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  6. 37
      src/Avalonia.Base/Data/TemplateBinding.cs
  7. 4
      src/Avalonia.Base/Input/AccessKeyHandler.cs
  8. 2
      src/Avalonia.Base/Input/IMainMenu.cs
  9. 2
      src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs
  10. 8
      src/Avalonia.Base/Rendering/Composition/Compositor.cs
  11. 12
      src/Avalonia.Controls/ContextMenu.cs
  12. 2
      src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs
  13. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  14. 22
      src/Avalonia.Controls/ExperimentalAcrylicBorder.cs
  15. 4
      src/Avalonia.Controls/Menu.cs
  16. 28
      src/Avalonia.Controls/MenuBase.cs
  17. 4
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  18. 2
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  19. 5
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  20. 284
      src/Avalonia.Controls/ProgressBar.cs
  21. 38
      src/Avalonia.Controls/TopLevel.cs
  22. 78
      src/Avalonia.Controls/WindowTransparencyLevel.cs
  23. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  24. 46
      src/Avalonia.Native/WindowImplBase.cs
  25. 8
      src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml
  26. 81
      src/Avalonia.X11/TransparencyHelper.cs
  27. 8
      src/Avalonia.X11/X11Window.cs
  28. 10
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  29. 2
      src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs
  30. 4
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  31. 16
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  32. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  33. 4
      src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs
  34. 7
      src/Windows/Avalonia.Win32/PlatformConstants.cs
  35. 2
      src/Windows/Avalonia.Win32/TrayIconImpl.cs
  36. 1
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs
  37. 268
      src/Windows/Avalonia.Win32/WindowImpl.cs
  38. 6
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  39. 6
      src/iOS/Avalonia.iOS/Platform.cs
  40. 14
      tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs
  41. 26
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/WindowTests.cs
  42. 4
      tests/Avalonia.UnitTests/CompositorTestServices.cs

5
samples/ControlCatalog/MainView.xaml.cs

@ -83,9 +83,10 @@ namespace ControlCatalog
if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected) if (transparencyLevels.SelectedItem is WindowTransparencyLevel selected)
{ {
var topLevel = (TopLevel)this.GetVisualRoot()!; 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 transparentBrush = new ImmutableSolidColorBrush(Colors.White, 0);
var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2); var semiTransparentBrush = new ImmutableSolidColorBrush(Colors.Gray, 0.2);

4
samples/ControlCatalog/Pages/ContextMenuPage.xaml.cs

@ -19,8 +19,8 @@ namespace ControlCatalog.Pages
customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel); customContextRequestedBorder.AddHandler(ContextRequestedEvent, CustomContextRequested, RoutingStrategies.Tunnel);
var cancellableContextBorder = this.Get<Border>("CancellableContextBorder"); var cancellableContextBorder = this.Get<Border>("CancellableContextBorder");
cancellableContextBorder.ContextMenu!.ContextMenuClosing += ContextFlyoutPage_Closing; cancellableContextBorder.ContextMenu!.Closing += ContextFlyoutPage_Closing;
cancellableContextBorder.ContextMenu!.ContextMenuOpening += ContextFlyoutPage_Opening; cancellableContextBorder.ContextMenu!.Opening += ContextFlyoutPage_Opening;
} }
private ContextPageViewModel? _model; private ContextPageViewModel? _model;

2
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -136,7 +136,7 @@ namespace IntegrationTestApp
Name = "TransparentWindow", Name = "TransparentWindow",
SystemDecorations = SystemDecorations.None, SystemDecorations = SystemDecorations.None,
Background = Brushes.Transparent, Background = Brushes.Transparent,
TransparencyLevelHint = WindowTransparencyLevel.Transparent, TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
WindowStartupLocation = WindowStartupLocation.CenterOwner, WindowStartupLocation = WindowStartupLocation.CenterOwner,
Width = 200, Width = 200,
Height = 200, Height = 200,

6
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -52,11 +52,7 @@ namespace Avalonia.Android
EglPlatformGraphics.TryInitialize(); EglPlatformGraphics.TryInitialize();
} }
Compositor = new Compositor( Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
} }
} }

168
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Versioning;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Graphics; using Android.Graphics;
@ -45,6 +46,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly AndroidInsetsManager _insetsManager; private readonly AndroidInsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard; private readonly ClipboardImpl _clipboard;
private ViewImpl _view; private ViewImpl _view;
private WindowTransparencyLevel _transparencyLevel;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false) public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
{ {
@ -68,6 +70,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView); _nativeControlHost = new AndroidNativeControlHostImpl(avaloniaView);
_storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context); _storageProvider = new AndroidStorageProvider((Activity)avaloniaView.Context);
_transparencyLevel = WindowTransparencyLevel.None;
_systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService); _systemNavigationManager = new AndroidSystemNavigationManagerImpl(avaloniaView.Context as IActivityNavigationService);
} }
@ -273,7 +276,18 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public Action LostFocus { get; set; } public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } public Action<WindowTransparencyLevel> 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) public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{ {
@ -298,91 +312,64 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public double Scaling => RenderScaling; public double Scaling => RenderScaling;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
{ {
if (TransparencyLevel != transparencyLevel) if (_view.Context is not AvaloniaMainActivity activity)
return;
foreach (var level in transparencyLevels)
{ {
bool isBelowR = Build.VERSION.SdkInt < BuildVersionCodes.R; if (!IsSupported(level))
bool isAboveR = Build.VERSION.SdkInt > BuildVersionCodes.R; {
if (_view.Context is AvaloniaMainActivity activity) 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: activity.SetTranslucent(true);
case WindowTransparencyLevel.ForceAcrylicBlur: SetBlurBehind(activity, 120);
case WindowTransparencyLevel.Mica: activity.Window?.SetBackgroundDrawable(new ColorDrawable(Color.Transparent));
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;
} }
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) public virtual object TryGetFeature(Type featureType)
{ {
if (featureType == typeof(IStorageProvider)) if (featureType == typeof(IStorageProvider))
@ -417,6 +404,31 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return null; 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 internal class AvaloniaInputConnection : BaseInputConnection

37
src/Avalonia.Base/Data/TemplateBinding.cs

@ -3,18 +3,21 @@ using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.Threading;
namespace Avalonia.Data namespace Avalonia.Data
{ {
/// <summary> /// <summary>
/// A XAML binding to a property on a control's templated parent. /// A XAML binding to a property on a control's templated parent.
/// </summary> /// </summary>
public class TemplateBinding : SingleSubscriberObservableBase<object?>, public class TemplateBinding : IObservable<object?>,
IBinding, IBinding,
IDescription, IDescription,
IAvaloniaSubject<object?>, IAvaloniaSubject<object?>,
ISetterValue ISetterValue,
IDisposable
{ {
private IObserver<object?>? _observer;
private bool _isSetterValue; private bool _isSetterValue;
private StyledElement? _target; private StyledElement? _target;
private Type? _targetType; private Type? _targetType;
@ -29,6 +32,28 @@ namespace Avalonia.Data
Property = property; Property = property;
} }
public IDisposable Subscribe(IObserver<object?> 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;
}
/// <inheritdoc/> /// <inheritdoc/>
public InstancedBinding? Initiate( public InstancedBinding? Initiate(
AvaloniaObject target, AvaloniaObject target,
@ -111,7 +136,7 @@ namespace Avalonia.Data
/// <inheritdoc/> /// <inheritdoc/>
void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true; void ISetterValue.Initialize(SetterBase setter) => _isSetterValue = true;
protected override void Subscribed() private void Subscribed()
{ {
TemplatedParentChanged(); TemplatedParentChanged();
@ -121,7 +146,7 @@ namespace Avalonia.Data
} }
} }
protected override void Unsubscribed() private void Unsubscribed()
{ {
if (_target?.TemplatedParent is { } templatedParent) if (_target?.TemplatedParent is { } templatedParent)
{ {
@ -147,12 +172,12 @@ namespace Avalonia.Data
value = Converter.Convert(value, _targetType ?? typeof(object), ConverterParameter, CultureInfo.CurrentCulture); value = Converter.Convert(value, _targetType ?? typeof(object), ConverterParameter, CultureInfo.CurrentCulture);
} }
PublishNext(value); _observer?.OnNext(value);
_hasProducedValue = true; _hasProducedValue = true;
} }
else if (_hasProducedValue) else if (_hasProducedValue)
{ {
PublishNext(AvaloniaProperty.UnsetValue); _observer?.OnNext(AvaloniaProperty.UnsetValue);
_hasProducedValue = false; _hasProducedValue = false;
} }
} }

4
src/Avalonia.Base/Input/AccessKeyHandler.cs

@ -65,14 +65,14 @@ namespace Avalonia.Input
{ {
if (_mainMenu != null) if (_mainMenu != null)
{ {
_mainMenu.MenuClosed -= MainMenuClosed; _mainMenu.Closed -= MainMenuClosed;
} }
_mainMenu = value; _mainMenu = value;
if (_mainMenu != null) if (_mainMenu != null)
{ {
_mainMenu.MenuClosed += MainMenuClosed; _mainMenu.Closed += MainMenuClosed;
} }
} }
} }

2
src/Avalonia.Base/Input/IMainMenu.cs

@ -26,6 +26,6 @@ namespace Avalonia.Input
/// <summary> /// <summary>
/// Occurs when the main menu closes. /// Occurs when the main menu closes.
/// </summary> /// </summary>
event EventHandler<RoutedEventArgs>? MenuClosed; event EventHandler<RoutedEventArgs>? Closed;
} }
} }

2
src/Avalonia.Base/Reactive/SingleSubscriberObservableBase.cs

@ -3,7 +3,7 @@ using Avalonia.Threading;
namespace Avalonia.Reactive namespace Avalonia.Reactive
{ {
public abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable internal abstract class SingleSubscriberObservableBase<T> : IObservable<T>, IDisposable
{ {
private Exception? _error; private Exception? _error;
private IObserver<T>? _observer; private IObserver<T>? _observer;

8
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 /// Creates a new compositor on a specified render loop that would use a particular GPU
/// </summary> /// </summary>
[PrivateApi] [PrivateApi]
public Compositor(IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false) public Compositor(IPlatformGraphics? gpu, bool useUiThreadForSynchronousCommits = false)
: this(RenderLoop.LocatorAutoInstance, gpu, useUiThreadForSynchronousCommits) : 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) : this(loop, gpu, useUiThreadForSynchronousCommits, MediaContext.Instance, false)
{ {
} }
internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu, internal Compositor(IRenderLoop loop, IPlatformGraphics? gpu,

12
src/Avalonia.Controls/ContextMenu.cs

@ -196,14 +196,14 @@ namespace Avalonia.Controls
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" /> /// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
/// property is changing from false to true. /// property is changing from false to true.
/// </summary> /// </summary>
public event CancelEventHandler? ContextMenuOpening; public event CancelEventHandler? Opening;
/// <summary> /// <summary>
/// Occurs when the value of the /// Occurs when the value of the
/// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" /> /// <see cref="P:Avalonia.Controls.ContextMenu.IsOpen" />
/// property is changing from true to false. /// property is changing from true to false.
/// </summary> /// </summary>
public event CancelEventHandler? ContextMenuClosing; public event CancelEventHandler? Closing;
/// <summary> /// <summary>
/// Called when the <see cref="Control.ContextMenu"/> property changes on a control. /// Called when the <see cref="Control.ContextMenu"/> property changes on a control.
@ -353,7 +353,7 @@ namespace Avalonia.Controls
RaiseEvent(new RoutedEventArgs RaiseEvent(new RoutedEventArgs
{ {
RoutedEvent = MenuOpenedEvent, RoutedEvent = OpenedEvent,
Source = this, Source = this,
}); });
} }
@ -394,7 +394,7 @@ namespace Avalonia.Controls
RaiseEvent(new RoutedEventArgs RaiseEvent(new RoutedEventArgs
{ {
RoutedEvent = MenuClosedEvent, RoutedEvent = ClosedEvent,
Source = this, Source = this,
}); });
@ -446,14 +446,14 @@ namespace Avalonia.Controls
private bool CancelClosing() private bool CancelClosing()
{ {
var eventArgs = new CancelEventArgs(); var eventArgs = new CancelEventArgs();
ContextMenuClosing?.Invoke(this, eventArgs); Closing?.Invoke(this, eventArgs);
return eventArgs.Cancel; return eventArgs.Cancel;
} }
private bool CancelOpening() private bool CancelOpening()
{ {
var eventArgs = new CancelEventArgs(); var eventArgs = new CancelEventArgs();
ContextMenuOpening?.Invoke(this, eventArgs); Opening?.Invoke(this, eventArgs);
return eventArgs.Cancel; return eventArgs.Cancel;
} }
} }

2
src/Avalonia.Controls/DateTimePickers/DatePickerPresenter.cs

@ -367,11 +367,11 @@ namespace Avalonia.Controls
var dt = Date; var dt = Date;
if (DayVisible) if (DayVisible)
{ {
_daySelector.FormatDate = dt.Date;
var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month); var maxDays = _calendar.GetDaysInMonth(dt.Year, dt.Month);
_daySelector.MaximumValue = maxDays; _daySelector.MaximumValue = maxDays;
_daySelector.MinimumValue = 1; _daySelector.MinimumValue = 1;
_daySelector.SelectedValue = dt.Day; _daySelector.SelectedValue = dt.Day;
_daySelector.FormatDate = dt.Date;
} }
if (MonthVisible) if (MonthVisible)

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -93,9 +93,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action? LostFocus { get; set; } public Action? LostFocus { get; set; }
public abstract IMouseDevice MouseDevice { get; } public abstract IMouseDevice MouseDevice { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel) { }
public WindowTransparencyLevel TransparencyLevel { get; private set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public IPopupImpl? CreatePopup() => null; public IPopupImpl? CreatePopup() => null;

22
src/Avalonia.Controls/ExperimentalAcrylicBorder.cs

@ -56,22 +56,12 @@ namespace Avalonia.Controls
{ {
if (tl.PlatformImpl is null) if (tl.PlatformImpl is null)
return; return;
if (x == WindowTransparencyLevel.Transparent || x == WindowTransparencyLevel.None)
switch (x) Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel;
{ else if (x == WindowTransparencyLevel.Blur)
case WindowTransparencyLevel.Transparent: Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
case WindowTransparencyLevel.None: else if (x == WindowTransparencyLevel.AcrylicBlur)
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.TransparentLevel; Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
break;
case WindowTransparencyLevel.Blur:
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.BlurLevel;
break;
case WindowTransparencyLevel.AcrylicBlur:
Material.PlatformTransparencyCompensationLevel = tl.PlatformImpl.AcrylicCompensationLevels.AcrylicBlurLevel;
break;
}
}); });
UpdateMaterialSubscription(); UpdateMaterialSubscription();
} }

4
src/Avalonia.Controls/Menu.cs

@ -60,7 +60,7 @@ namespace Avalonia.Controls
RaiseEvent(new RoutedEventArgs RaiseEvent(new RoutedEventArgs
{ {
RoutedEvent = MenuClosedEvent, RoutedEvent = ClosedEvent,
Source = this, Source = this,
}); });
} }
@ -77,7 +77,7 @@ namespace Avalonia.Controls
RaiseEvent(new RoutedEventArgs RaiseEvent(new RoutedEventArgs
{ {
RoutedEvent = MenuOpenedEvent, RoutedEvent = OpenedEvent,
Source = this, Source = this,
}); });
} }

28
src/Avalonia.Controls/MenuBase.cs

@ -25,16 +25,16 @@ namespace Avalonia.Controls
o => o.IsOpen); o => o.IsOpen);
/// <summary> /// <summary>
/// Defines the <see cref="MenuOpened"/> event. /// Defines the <see cref="Opened"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuOpenedEvent = public static readonly RoutedEvent<RoutedEventArgs> OpenedEvent =
RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(MenuOpened), RoutingStrategies.Bubble); RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(Opened), RoutingStrategies.Bubble);
/// <summary> /// <summary>
/// Defines the <see cref="MenuClosed"/> event. /// Defines the <see cref="Closed"/> event.
/// </summary> /// </summary>
public static readonly RoutedEvent<RoutedEventArgs> MenuClosedEvent = public static readonly RoutedEvent<RoutedEventArgs> ClosedEvent =
RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(MenuClosed), RoutingStrategies.Bubble); RoutedEvent.Register<MenuBase, RoutedEventArgs>(nameof(Closed), RoutingStrategies.Bubble);
private bool _isOpen; private bool _isOpen;
@ -68,8 +68,8 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public bool IsOpen public bool IsOpen
{ {
get { return _isOpen; } get => _isOpen;
protected set { SetAndRaise(IsOpenProperty, ref _isOpen, value); } protected set => SetAndRaise(IsOpenProperty, ref _isOpen, value);
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -105,19 +105,19 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Occurs when a <see cref="Menu"/> is opened. /// Occurs when a <see cref="Menu"/> is opened.
/// </summary> /// </summary>
public event EventHandler<RoutedEventArgs>? MenuOpened public event EventHandler<RoutedEventArgs>? Opened
{ {
add { AddHandler(MenuOpenedEvent, value); } add => AddHandler(OpenedEvent, value);
remove { RemoveHandler(MenuOpenedEvent, value); } remove => RemoveHandler(OpenedEvent, value);
} }
/// <summary> /// <summary>
/// Occurs when a <see cref="Menu"/> is closed. /// Occurs when a <see cref="Menu"/> is closed.
/// </summary> /// </summary>
public event EventHandler<RoutedEventArgs>? MenuClosed public event EventHandler<RoutedEventArgs>? Closed
{ {
add { AddHandler(MenuClosedEvent, value); } add => AddHandler(ClosedEvent, value);
remove { RemoveHandler(MenuClosedEvent, value); } remove => RemoveHandler(ClosedEvent, value);
} }
/// <summary> /// <summary>

4
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -290,7 +290,7 @@ namespace Avalonia.Controls.Platform
Menu.PointerPressed += PointerPressed; Menu.PointerPressed += PointerPressed;
Menu.PointerReleased += PointerReleased; Menu.PointerReleased += PointerReleased;
Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); Menu.AddHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
Menu.AddHandler(MenuBase.MenuOpenedEvent, MenuOpened); Menu.AddHandler(MenuBase.OpenedEvent, MenuOpened);
Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.AddHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.AddHandler(MenuItem.PointerExitedItemEvent, PointerExited);
Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved); Menu.AddHandler(InputElement.PointerMovedEvent, PointerMoved);
@ -326,7 +326,7 @@ namespace Avalonia.Controls.Platform
Menu.PointerPressed -= PointerPressed; Menu.PointerPressed -= PointerPressed;
Menu.PointerReleased -= PointerReleased; Menu.PointerReleased -= PointerReleased;
Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed); Menu.RemoveHandler(AccessKeyHandler.AccessKeyPressedEvent, AccessKeyPressed);
Menu.RemoveHandler(MenuBase.MenuOpenedEvent, MenuOpened); Menu.RemoveHandler(MenuBase.OpenedEvent, MenuOpened);
Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered); Menu.RemoveHandler(MenuItem.PointerEnteredItemEvent, PointerEntered);
Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited); Menu.RemoveHandler(MenuItem.PointerExitedItemEvent, PointerExited);
Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved); Menu.RemoveHandler(InputElement.PointerMovedEvent, PointerMoved);

2
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -117,7 +117,7 @@ namespace Avalonia.Platform
/// <summary> /// <summary>
/// Sets the <see cref="WindowTransparencyLevel"/> hint of the TopLevel. /// Sets the <see cref="WindowTransparencyLevel"/> hint of the TopLevel.
/// </summary> /// </summary>
void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel); void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels);
/// <summary> /// <summary>
/// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel. /// Gets the current <see cref="WindowTransparencyLevel"/> of the TopLevel.

5
src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs

@ -46,13 +46,12 @@ namespace Avalonia.Controls.Primitives
return manager?.LightDismissOverlayLayer; return manager?.LightDismissOverlayLayer;
} }
/// <inheritdoc />
public bool HitTest(Point point) public bool HitTest(Point point)
{ {
if (InputPassThroughElement is Visual v) if (InputPassThroughElement is Visual v)
{ {
var hit = ((Visual?)VisualRoot)?.GetVisualAt(point, x => x != this); if (VisualRoot is IInputElement ie && ie.InputHitTest(point, x => x != this) is Visual hit)
if (hit is object)
{ {
return !v.IsVisualAncestorOf(hit); return !v.IsVisualAncestorOf(hit);
} }

284
src/Avalonia.Controls/ProgressBar.cs

@ -31,77 +31,157 @@ namespace Avalonia.Controls
private double _containerAnimationEndPosition; private double _containerAnimationEndPosition;
private double _container2AnimationStartPosition; private double _container2AnimationStartPosition;
private double _container2AnimationEndPosition; private double _container2AnimationEndPosition;
private double _indeterminateStartingOffset;
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerAnimationStartPositionProperty = private double _indeterminateEndingOffset;
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerAnimationStartPosition), /// <summary>
p => p.ContainerAnimationStartPosition, /// Defines the <see cref="ContainerAnimationStartPosition"/> property.
(p, o) => p.ContainerAnimationStartPosition = o, 0d); /// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double>
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerAnimationEndPositionProperty = ContainerAnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>( AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerAnimationEndPosition), nameof(ContainerAnimationStartPosition),
p => p.ContainerAnimationEndPosition, p => p.ContainerAnimationStartPosition,
(p, o) => p.ContainerAnimationEndPosition = o, 0d); (p, o) => p.ContainerAnimationStartPosition = o);
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2AnimationStartPositionProperty = /// <summary>
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>( /// Defines the <see cref="ContainerAnimationEndPosition"/> property.
nameof(Container2AnimationStartPosition), /// </summary>
p => p.Container2AnimationStartPosition, public static readonly DirectProperty<ProgressBarTemplateSettings, double>
(p, o) => p.Container2AnimationStartPosition = o, 0d); ContainerAnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2AnimationEndPositionProperty = nameof(ContainerAnimationEndPosition),
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>( p => p.ContainerAnimationEndPosition,
nameof(Container2AnimationEndPosition), (p, o) => p.ContainerAnimationEndPosition = o);
p => p.Container2AnimationEndPosition,
(p, o) => p.Container2AnimationEndPosition = o); /// <summary>
/// Defines the <see cref="Container2AnimationStartPosition"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double>
Container2AnimationStartPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2AnimationStartPosition),
p => p.Container2AnimationStartPosition,
(p, o) => p.Container2AnimationStartPosition = o);
/// <summary>
/// Defines the <see cref="Container2AnimationEndPosition"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double>
Container2AnimationEndPositionProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2AnimationEndPosition),
p => p.Container2AnimationEndPosition,
(p, o) => p.Container2AnimationEndPosition = o);
/// <summary>
/// Defines the <see cref="Container2Width"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2WidthProperty = public static readonly DirectProperty<ProgressBarTemplateSettings, double> Container2WidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>( AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(Container2Width), nameof(Container2Width),
p => p.Container2Width, p => p.Container2Width,
(p, o) => p.Container2Width = o); (p, o) => p.Container2Width = o);
/// <summary>
/// Defines the <see cref="ContainerWidth"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerWidthProperty = public static readonly DirectProperty<ProgressBarTemplateSettings, double> ContainerWidthProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>( AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(ContainerWidth), nameof(ContainerWidth),
p => p.ContainerWidth, p => p.ContainerWidth,
(p, o) => p.ContainerWidth = o); (p, o) => p.ContainerWidth = o);
/// <summary>
/// Defines the <see cref="IndeterminateStartingOffset"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o);
/// <summary>
/// Defines the <see cref="IndeterminateEndingOffset"/> property.
/// </summary>
public static readonly DirectProperty<ProgressBarTemplateSettings, double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect<ProgressBarTemplateSettings, double>(
nameof(IndeterminateEndingOffset),
p => p.IndeterminateEndingOffset,
(p, o) => p.IndeterminateEndingOffset = o);
/// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the first indeterminate indicator's width.
/// </summary>
public double ContainerWidth
{
get => _containerWidth;
set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value);
}
/// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the second indeterminate indicator's width.
/// </summary>
public double Container2Width
{
get => _container2Width;
set => SetAndRaise(Container2WidthProperty, ref _container2Width, value);
}
/// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the first indeterminate indicator's start position when animated.
/// </summary>
public double ContainerAnimationStartPosition public double ContainerAnimationStartPosition
{ {
get => _containerAnimationStartPosition; get => _containerAnimationStartPosition;
set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition, value); set => SetAndRaise(ContainerAnimationStartPositionProperty, ref _containerAnimationStartPosition,
value);
} }
/// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the first indeterminate indicator's end position when animated.
/// </summary>
public double ContainerAnimationEndPosition public double ContainerAnimationEndPosition
{ {
get => _containerAnimationEndPosition; get => _containerAnimationEndPosition;
set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value); set => SetAndRaise(ContainerAnimationEndPositionProperty, ref _containerAnimationEndPosition, value);
} }
/// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the second indeterminate indicator's start position when animated.
/// </summary>
public double Container2AnimationStartPosition public double Container2AnimationStartPosition
{ {
get => _container2AnimationStartPosition; get => _container2AnimationStartPosition;
set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition, value); set => SetAndRaise(Container2AnimationStartPositionProperty, ref _container2AnimationStartPosition,
value);
} }
public double Container2Width /// <summary>
/// Used by <see cref="Avalonia.Themes.Fluent"/> to define the second indeterminate indicator's end position when animated.
/// </summary>
public double Container2AnimationEndPosition
{ {
get => _container2Width; get => _container2AnimationEndPosition;
set => SetAndRaise(Container2WidthProperty, ref _container2Width, value); set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value);
} }
public double ContainerWidth /// <summary>
/// Used by <see cref="Avalonia.Themes.Simple"/> to define the starting point of its indeterminate animation.
/// </summary>
public double IndeterminateStartingOffset
{ {
get => _containerWidth; get => _indeterminateStartingOffset;
set => SetAndRaise(ContainerWidthProperty, ref _containerWidth, value); set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
} }
public double Container2AnimationEndPosition /// <summary>
/// Used by <see cref="Avalonia.Themes.Simple"/> to define the ending point of its indeterminate animation.
/// </summary>
public double IndeterminateEndingOffset
{ {
get => _container2AnimationEndPosition; get => _indeterminateEndingOffset;
set => SetAndRaise(Container2AnimationEndPositionProperty, ref _container2AnimationEndPosition, value); set => SetAndRaise(IndeterminateEndingOffsetProperty, ref _indeterminateEndingOffset, value);
} }
} }
@ -131,7 +211,7 @@ namespace Avalonia.Controls
/// Defines the <see cref="Orientation"/> property. /// Defines the <see cref="Orientation"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<Orientation> OrientationProperty = public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal); AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation));
/// <summary> /// <summary>
/// Defines the <see cref="Percentage"/> property. /// Defines the <see cref="Percentage"/> property.
@ -141,18 +221,6 @@ namespace Avalonia.Controls
nameof(Percentage), nameof(Percentage),
o => o.Percentage); o => o.Percentage);
/// <summary>
/// Defines the <see cref="IndeterminateStartingOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> IndeterminateStartingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateStartingOffset));
/// <summary>
/// Defines the <see cref="IndeterminateEndingOffset"/> property.
/// </summary>
public static readonly StyledProperty<double> IndeterminateEndingOffsetProperty =
AvaloniaProperty.Register<ProgressBar, double>(nameof(IndeterminateEndingOffset));
/// <summary> /// <summary>
/// Gets the overall percentage complete of the progress /// Gets the overall percentage complete of the progress
/// </summary> /// </summary>
@ -162,30 +230,13 @@ namespace Avalonia.Controls
/// </remarks> /// </remarks>
public double Percentage public double Percentage
{ {
get { return _percentage; } get => _percentage;
private set { SetAndRaise(PercentageProperty, ref _percentage, value); } 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() static ProgressBar()
{ {
ValueProperty.OverrideMetadata<ProgressBar>(new(defaultBindingMode: BindingMode.OneWay)); ValueProperty.OverrideMetadata<ProgressBar>(new(defaultBindingMode: BindingMode.OneWay));
ValueProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
OrientationProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
} }
/// <summary> /// <summary>
@ -251,6 +302,15 @@ namespace Avalonia.Controls
{ {
base.OnPropertyChanged(change); base.OnPropertyChanged(change);
if (change.Property == ValueProperty ||
change.Property == MinimumProperty ||
change.Property == MaximumProperty ||
change.Property == IsIndeterminateProperty ||
change.Property == OrientationProperty)
{
UpdateIndicator();
}
if (change.Property == IsIndeterminateProperty) if (change.Property == IsIndeterminateProperty)
{ {
UpdatePseudoClasses(change.GetNewValue<bool>(), null); UpdatePseudoClasses(change.GetNewValue<bool>(), null);
@ -286,64 +346,50 @@ namespace Avalonia.Controls
// Gets the size of the parent indicator container // Gets the size of the parent indicator container
var barSize = _indicator?.VisualParent?.Bounds.Size ?? Bounds.Size; 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 dim = Orientation == Orientation.Horizontal ? barSize.Width : barSize.Height;
var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar var barIndicatorWidth = dim * 0.4; // Indicator width at 40% of ProgressBar
var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar var barIndicatorWidth2 = dim * 0.6; // Indicator width at 60% of ProgressBar
TemplateSettings.ContainerWidth = barIndicatorWidth; TemplateSettings.ContainerWidth = barIndicatorWidth;
TemplateSettings.Container2Width = barIndicatorWidth2; TemplateSettings.Container2Width = barIndicatorWidth2;
TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180% TemplateSettings.ContainerAnimationStartPosition = barIndicatorWidth * -1.8; // Position at -180%
TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300% TemplateSettings.ContainerAnimationEndPosition = barIndicatorWidth * 3.0; // Position at 300%
TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150% TemplateSettings.Container2AnimationStartPosition = barIndicatorWidth2 * -1.5; // Position at -150%
TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166% TemplateSettings.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
// Remove these properties when we switch to fluent as default and removed the old one. TemplateSettings.IndeterminateStartingOffset = -dim;
SetCurrentValue(IndeterminateStartingOffsetProperty,-dim); TemplateSettings.IndeterminateEndingOffset = dim;
SetCurrentValue(IndeterminateEndingOffsetProperty,dim); }
else
{
var percent = Math.Abs(Maximum - Minimum) < double.Epsilon ?
1.0 :
(Value - Minimum) / (Maximum - Minimum);
var padding = Padding; // When the Orientation changed, the indicator's Width or Height should set to double.NaN.
var rectangle = new RectangleGeometry( // Indicator size calculation should consider the ProgressBar's Padding property setting
new Rect( if (Orientation == Orientation.Horizontal)
padding.Left, {
padding.Top, _indicator.Width = (barSize.Width - _indicator.Margin.Left - _indicator.Margin.Right) * percent;
barSize.Width - (padding.Right + padding.Left), _indicator.Height = double.NaN;
barSize.Height - (padding.Bottom + padding.Top)
));
} }
else else
{ {
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum); _indicator.Width = double.NaN;
_indicator.Height = (barSize.Height - _indicator.Margin.Top - _indicator.Margin.Bottom) *
// When the Orientation changed, the indicator's Width or Height should set to double.NaN. percent;
// 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;
} }
}
}
private void UpdateIndicatorWhenPropChanged(AvaloniaPropertyChangedEventArgs e)
{ Percentage = percent * 100;
UpdateIndicator(); }
} }
private void UpdatePseudoClasses( private void UpdatePseudoClasses(
@ -355,11 +401,9 @@ namespace Avalonia.Controls
PseudoClasses.Set(":indeterminate", isIndeterminate.Value); PseudoClasses.Set(":indeterminate", isIndeterminate.Value);
} }
if (o.HasValue) if (!o.HasValue) return;
{ PseudoClasses.Set(":vertical", o == Orientation.Vertical);
PseudoClasses.Set(":vertical", o == Orientation.Vertical); PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
PseudoClasses.Set(":horizontal", o == Orientation.Horizontal);
}
} }
} }
} }

38
src/Avalonia.Controls/TopLevel.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using Avalonia.Reactive; using Avalonia.Reactive;
@ -22,6 +23,7 @@ using Avalonia.Utilities;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Metadata;
using Avalonia.Rendering.Composition; using Avalonia.Rendering.Composition;
namespace Avalonia.Controls namespace Avalonia.Controls
@ -65,8 +67,8 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Defines the <see cref="TransparencyLevelHint"/> property. /// Defines the <see cref="TransparencyLevelHint"/> property.
/// </summary> /// </summary>
public static readonly StyledProperty<WindowTransparencyLevel> TransparencyLevelHintProperty = public static readonly StyledProperty<IReadOnlyList<WindowTransparencyLevel>> TransparencyLevelHintProperty =
AvaloniaProperty.Register<TopLevel, WindowTransparencyLevel>(nameof(TransparencyLevelHint), WindowTransparencyLevel.None); AvaloniaProperty.Register<TopLevel, IReadOnlyList<WindowTransparencyLevel>>(nameof(TransparencyLevelHint), Array.Empty<WindowTransparencyLevel>());
/// <summary> /// <summary>
/// Defines the <see cref="ActualTransparencyLevel"/> property. /// Defines the <see cref="ActualTransparencyLevel"/> property.
@ -173,7 +175,7 @@ namespace Avalonia.Controls
PlatformImpl = impl ?? throw new InvalidOperationException( PlatformImpl = impl ?? throw new InvalidOperationException(
"Could not create window implementation: maybe no windowing subsystem was initialized?"); "Could not create window implementation: maybe no windowing subsystem was initialized?");
_actualTransparencyLevel = PlatformImpl.TransparencyLevel; _actualTransparencyLevel = PlatformImpl.TransparencyLevel;
dependencyResolver ??= AvaloniaLocator.Current; dependencyResolver ??= AvaloniaLocator.Current;
@ -311,8 +313,11 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Gets or sets the <see cref="WindowTransparencyLevel"/> that the TopLevel should use when possible. /// Gets or sets the <see cref="WindowTransparencyLevel"/> 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".
/// </summary> /// </summary>
public WindowTransparencyLevel TransparencyLevelHint public IReadOnlyList<WindowTransparencyLevel> TransparencyLevelHint
{ {
get { return GetValue(TransparencyLevelHintProperty); } get { return GetValue(TransparencyLevelHintProperty); }
set { SetValue(TransparencyLevelHintProperty, value); } set { SetValue(TransparencyLevelHintProperty, value); }
@ -536,8 +541,8 @@ namespace Avalonia.Controls
{ {
if (PlatformImpl != null) if (PlatformImpl != null)
{ {
PlatformImpl.SetTransparencyLevelHint(change.GetNewValue<WindowTransparencyLevel>()); PlatformImpl.SetTransparencyLevelHint(
HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); change.GetNewValue<IReadOnlyList<WindowTransparencyLevel>>() ?? Array.Empty<WindowTransparencyLevel>());
} }
} }
else if (change.Property == ActualThemeVariantProperty) else if (change.Property == ActualThemeVariantProperty)
@ -632,27 +637,11 @@ namespace Avalonia.Controls
ScalingChanged?.Invoke(this, EventArgs.Empty); 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) private void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel)
{ {
if(_transparencyFallbackBorder != null) if (_transparencyFallbackBorder != null)
{ {
if(transparencyLevel == WindowTransparencyLevel.None || if (transparencyLevel == WindowTransparencyLevel.None)
TransparencyLevelHint == WindowTransparencyLevel.None ||
!TransparencyLevelsMatch(TransparencyLevelHint, transparencyLevel))
{ {
_transparencyFallbackBorder.Background = TransparencyBackgroundFallback; _transparencyFallbackBorder.Background = TransparencyBackgroundFallback;
} }
@ -682,7 +671,6 @@ namespace Avalonia.Controls
return; return;
_transparencyFallbackBorder = e.NameScope.Find<Border>("PART_TransparencyFallback"); _transparencyFallbackBorder = e.NameScope.Find<Border>("PART_TransparencyFallback");
HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel);
} }

78
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)
{ {
/// <summary> _value = value;
/// The window background is Black where nothing is drawn in the window. }
/// </summary>
None, /// <summary>
/// The window background is Black where nothing is drawn in the window.
/// <summary> /// </summary>
/// The window background is Transparent where nothing is drawn in the window. public static WindowTransparencyLevel None { get; } = new(nameof(None));
/// </summary>
Transparent, /// <summary>
/// The window background is Transparent where nothing is drawn in the window.
/// <summary> /// </summary>
/// The window background is a blur-behind where nothing is drawn in the window. public static WindowTransparencyLevel Transparent { get; } = new(nameof(Transparent));
/// </summary>
Blur, /// <summary>
/// The window background is a blur-behind where nothing is drawn in the window.
/// <summary> /// </summary>
/// The window background is a blur-behind with a high blur radius. This level may fallback to Blur. public static WindowTransparencyLevel Blur { get; } = new(nameof(Blur));
/// </summary>
AcrylicBlur, /// <summary>
/// The window background is a blur-behind with a high blur radius. This level may fallback to Blur.
/// <summary> /// </summary>
/// Force acrylic on some incompatible versions of Windows 10. public static WindowTransparencyLevel AcrylicBlur { get; } = new(nameof(AcrylicBlur));
/// </summary>
ForceAcrylicBlur, /// <summary>
/// The window background is based on desktop wallpaper tint with a blur. This will only work on Windows 11
/// <summary> /// </summary>
/// 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));
/// </summary>
Mica public override string ToString()
{
return _value;
} }
} }
public class WindowTransparencyLevelCollection : ReadOnlyCollection<WindowTransparencyLevel>
{
public WindowTransparencyLevelCollection(IList<WindowTransparencyLevel> list) : base(list)
{
}
}

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -177,13 +177,13 @@ namespace Avalonia.DesignerSupport.Remote
public Action GotInputWhenDisabled { get; set; } public Action GotInputWhenDisabled { get; set; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel) { }
public void SetWindowManagerAddShadowHint(bool enabled) public void SetWindowManagerAddShadowHint(bool enabled)
{ {
} }
public WindowTransparencyLevel TransparencyLevel { get; private set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public bool IsClientAreaExtendedToDecorations { get; } public bool IsClientAreaExtendedToDecorations { get; }

46
src/Avalonia.Native/WindowImplBase.cs

@ -65,6 +65,7 @@ namespace Avalonia.Native
private NativeControlHostImpl _nativeControlHost; private NativeControlHostImpl _nativeControlHost;
private IStorageProvider _storageProvider; private IStorageProvider _storageProvider;
private PlatformBehaviorInhibition _platformBehaviorInhibition; private PlatformBehaviorInhibition _platformBehaviorInhibition;
private WindowTransparencyLevel _transparencyLevel = WindowTransparencyLevel.None;
internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts, internal WindowBaseImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature) AvaloniaNativeGlPlatformGraphics glFeature)
@ -479,26 +480,47 @@ namespace Avalonia.Native
_native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle); _native?.BeginDragAndDropOperation(effects, point, clipboard, callback, sourceHandle);
} }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
{ {
if (TransparencyLevel != transparencyLevel) foreach (var level in transparencyLevels)
{ {
if (transparencyLevel > WindowTransparencyLevel.Transparent) AvnWindowTransparencyMode? mode = null;
transparencyLevel = WindowTransparencyLevel.AcrylicBlur;
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 if (mode.HasValue && level != TransparencyLevel)
? AvnWindowTransparencyMode.Opaque {
: transparencyLevel == WindowTransparencyLevel.Transparent _native?.SetTransparencyMode(mode.Value);
? AvnWindowTransparencyMode.Transparent TransparencyLevel = level;
: AvnWindowTransparencyMode.Blur); 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) public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{ {

8
src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml

@ -87,10 +87,10 @@
IterationCount="Infinite" IterationCount="Infinite"
Duration="0:0:3"> Duration="0:0:3">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X" Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.IndeterminateStartingOffset}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.X" Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.X" Value="{Binding $parent[ProgressBar].TemplateSettings.IndeterminateEndingOffset}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>
@ -102,10 +102,10 @@
IterationCount="Infinite" IterationCount="Infinite"
Duration="0:0:3"> Duration="0:0:3">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="TranslateTransform.Y" Value="{Binding IndeterminateStartingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.IndeterminateStartingOffset}" />
</KeyFrame> </KeyFrame>
<KeyFrame Cue="100%"> <KeyFrame Cue="100%">
<Setter Property="TranslateTransform.Y" Value="{Binding IndeterminateEndingOffset, RelativeSource={RelativeSource TemplatedParent}}" /> <Setter Property="TranslateTransform.Y" Value="{Binding $parent[ProgressBar].TemplateSettings.IndeterminateEndingOffset}" />
</KeyFrame> </KeyFrame>
</Animation> </Animation>
</Style.Animations> </Style.Animations>

81
src/Avalonia.X11/TransparencyHelper.cs

@ -1,6 +1,9 @@
using System; using System;
using System.Collections.Generic;
using Avalonia.Controls; using Avalonia.Controls;
#nullable enable
namespace Avalonia.X11 namespace Avalonia.X11
{ {
internal class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber internal class TransparencyHelper : IDisposable, X11Globals.IGlobalsSubscriber
@ -9,11 +12,23 @@ namespace Avalonia.X11
private readonly IntPtr _window; private readonly IntPtr _window;
private readonly X11Globals _globals; private readonly X11Globals _globals;
private WindowTransparencyLevel _currentLevel; private WindowTransparencyLevel _currentLevel;
private WindowTransparencyLevel _requestedLevel; private IReadOnlyList<WindowTransparencyLevel>? _requestedLevels;
private bool _blurAtomsAreSet; private bool _blurAtomsAreSet;
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; } public Action<WindowTransparencyLevel>? 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) public TransparencyHelper(X11Info x11, IntPtr window, X11Globals globals)
{ {
@ -23,25 +38,53 @@ namespace Avalonia.X11
_globals.AddSubscriber(this); _globals.AddSubscriber(this);
} }
public void SetTransparencyRequest(WindowTransparencyLevel level) public void SetTransparencyRequest(IReadOnlyList<WindowTransparencyLevel> 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; // None is suppported when composition is disabled.
UpdateTransparency(); 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() private void UpdateTransparency()
{ {
var newLevel = UpdateAtomsAndGetTransparency(); SetTransparencyRequest(_requestedLevels ?? Array.Empty<WindowTransparencyLevel>());
if (newLevel != _currentLevel)
{
_currentLevel = newLevel;
TransparencyLevelChanged?.Invoke(newLevel);
}
} }
private WindowTransparencyLevel UpdateAtomsAndGetTransparency() private void SetBlur(bool blur)
{ {
if (_requestedLevel >= WindowTransparencyLevel.Blur) if (blur)
{ {
if (!_blurAtomsAreSet) if (!_blurAtomsAreSet)
{ {
@ -59,15 +102,7 @@ namespace Avalonia.X11
_blurAtomsAreSet = false; _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() public void Dispose()
{ {

8
src/Avalonia.X11/X11Window.cs

@ -196,7 +196,7 @@ namespace Avalonia.X11
_rawEventGrouper = new RawEventGrouper(DispatchInput, platform.EventGrouperDispatchQueue); _rawEventGrouper = new RawEventGrouper(DispatchInput, platform.EventGrouperDispatchQueue);
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals); _transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None); _transparencyHelper.SetTransparencyRequest(Array.Empty<WindowTransparencyLevel>());
CreateIC(); CreateIC();
@ -1306,8 +1306,10 @@ namespace Avalonia.X11
public IPopupPositioner? PopupPositioner { get; } public IPopupPositioner? PopupPositioner { get; }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) => public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels)
_transparencyHelper?.SetTransparencyRequest(transparencyLevel); {
_transparencyHelper?.SetTransparencyRequest(transparencyLevels);
}
public void SetWindowManagerAddShadowHint(bool enabled) public void SetWindowManagerAddShadowHint(bool enabled)
{ {

10
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -39,7 +39,6 @@ namespace Avalonia.Browser
{ {
Surfaces = Enumerable.Empty<object>(); Surfaces = Enumerable.Empty<object>();
_avaloniaView = avaloniaView; _avaloniaView = avaloniaView;
TransparencyLevel = WindowTransparencyLevel.None;
AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1); AcrylicCompensationLevels = new AcrylicPlatformCompensationLevels(1, 1, 1);
_touchDevice = new TouchDevice(); _touchDevice = new TouchDevice();
_penDevice = new PenDevice(); _penDevice = new PenDevice();
@ -218,13 +217,8 @@ namespace Avalonia.Browser
return null; return null;
} }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
{ {
if (transparencyLevel == WindowTransparencyLevel.None
|| transparencyLevel == WindowTransparencyLevel.Transparent)
{
TransparencyLevel = transparencyLevel;
}
} }
public Size ClientSize => _clientSize; public Size ClientSize => _clientSize;
@ -244,7 +238,7 @@ namespace Avalonia.Browser
public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard; public IKeyboardDevice KeyboardDevice { get; } = BrowserWindowingPlatform.Keyboard;
public WindowTransparencyLevel TransparencyLevel { get; private set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) 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. // not in the standard, but we potentially can use "apple-mobile-web-app-status-bar-style" for iOS and "theme-color" for android.

2
src/Headless/Avalonia.Headless/HeadlessWindowImpl.cs

@ -336,7 +336,7 @@ namespace Avalonia.Headless
} }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
{ {
} }

4
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -72,9 +72,9 @@ using Avalonia.Rendering.Composition;
public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling); public Size ScaledSize => _outputBackend.PixelSize.ToSize(RenderScaling);
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel) { }
public WindowTransparencyLevel TransparencyLevel { get; private set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }

16
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<IXamlAstValueNode>());
return true;
}
}
}
if (type.Equals(types.Uri)) if (type.Equals(types.Uri))
{ {
var uriText = text.Trim(); var uriText = text.Trim();
@ -385,7 +398,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
result = new AvaloniaXamlIlArrayConstantAstNode(node, elementType.MakeArrayType(1), elementType, nodes); result = new AvaloniaXamlIlArrayConstantAstNode(node, elementType.MakeArrayType(1), elementType, nodes);
return true; 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); var listType = context.Configuration.WellKnownTypes.IListOfT.MakeGenericType(elementType);
result = new AvaloniaXamlIlArrayConstantAstNode(node, listType, elementType, nodes); result = new AvaloniaXamlIlArrayConstantAstNode(node, listType, elementType, nodes);

4
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 IXamlConstructor UriConstructor { get; }
public IXamlType Style { get; } public IXamlType Style { get; }
public IXamlType ControlTheme { get; } public IXamlType ControlTheme { get; }
public IXamlType WindowTransparencyLevel { get; }
public IXamlType IReadOnlyListOfT { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg) public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{ {
@ -199,6 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily"); FontFamily = cfg.TypeSystem.GetType("Avalonia.Media.FontFamily");
FontFamilyConstructorUriName = FontFamily.GetConstructor(new List<IXamlType> { Uri, XamlIlTypes.String }); FontFamilyConstructorUriName = FontFamily.GetConstructor(new List<IXamlType> { Uri, XamlIlTypes.String });
ThemeVariant = cfg.TypeSystem.GetType("Avalonia.Styling.ThemeVariant"); ThemeVariant = cfg.TypeSystem.GetType("Avalonia.Styling.ThemeVariant");
WindowTransparencyLevel = cfg.TypeSystem.GetType("Avalonia.Controls.WindowTransparencyLevel");
(IXamlType, IXamlConstructor) GetNumericTypeInfo(string name, IXamlType componentType, int componentCount) (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<IXamlType>() { cfg.WellKnownTypes.String, UriKind }); UriConstructor = Uri.GetConstructor(new List<IXamlType>() { cfg.WellKnownTypes.String, UriKind });
Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style");
ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");
} }
} }

4
src/Windows/Avalonia.Win32.Interop/Wpf/WpfTopLevelImpl.cs

@ -241,9 +241,9 @@ namespace Avalonia.Win32.Interop.Wpf
public IPopupImpl CreatePopup() => null; public IPopupImpl CreatePopup() => null;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) { } public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel) { }
public WindowTransparencyLevel TransparencyLevel { get; private set; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { }

7
src/Windows/Avalonia.Win32/PlatformConstants.cs

@ -2,12 +2,13 @@ using System;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
public static class PlatformConstants internal static class PlatformConstants
{ {
public const string WindowHandleType = "HWND"; public const string WindowHandleType = "HWND";
public const string CursorHandleType = "HCURSOR"; public const string CursorHandleType = "HCURSOR";
internal static readonly Version Windows8 = new Version(6, 2); public static readonly Version Windows10 = new Version(10, 0);
internal static readonly Version Windows7 = new Version(6, 1); public static readonly Version Windows8 = new Version(6, 2);
public static readonly Version Windows7 = new Version(6, 1);
} }
} }

2
src/Windows/Avalonia.Win32/TrayIconImpl.cs

@ -147,7 +147,7 @@ namespace Avalonia.Win32
SystemDecorations = SystemDecorations.None, SystemDecorations = SystemDecorations.None,
SizeToContent = SizeToContent.WidthAndHeight, SizeToContent = SizeToContent.WidthAndHeight,
Background = null, Background = null,
TransparencyLevelHint = WindowTransparencyLevel.Transparent, TransparencyLevelHint = new[] { WindowTransparencyLevel.Transparent },
Content = new TrayIconMenuFlyoutPresenter() Content = new TrayIconMenuFlyoutPresenter()
{ {
ItemsSource = menuItems ItemsSource = menuItems

1
src/Windows/Avalonia.Win32/WinRT/Composition/WinUiCompositionShared.cs

@ -12,6 +12,7 @@ internal class WinUiCompositionShared : IDisposable
public ICompositionBrush? MicaBrush { get; } public ICompositionBrush? MicaBrush { get; }
public object SyncRoot { get; } = new(); public object SyncRoot { get; } = new();
public static readonly Version MinAcrylicVersion = new(10, 0, 15063);
public static readonly Version MinHostBackdropVersion = new(10, 0, 22000); public static readonly Version MinHostBackdropVersion = new(10, 0, 22000);
public WinUiCompositionShared(ICompositor compositor) public WinUiCompositionShared(ICompositor compositor)

268
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
@ -25,6 +25,7 @@ using Avalonia.Win32.WinRT.Composition;
using Avalonia.Win32.WinRT; using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using System.Diagnostics;
namespace Avalonia.Win32 namespace Avalonia.Win32
{ {
@ -97,6 +98,7 @@ namespace Avalonia.Win32
private bool _hiddenWindowIsParent; private bool _hiddenWindowIsParent;
private uint _langid; private uint _langid;
private bool _ignoreWmChar; private bool _ignoreWmChar;
private WindowTransparencyLevel _transparencyLevel;
private const int MaxPointerHistorySize = 512; private const int MaxPointerHistorySize = 512;
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(); private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new();
@ -180,6 +182,7 @@ namespace Avalonia.Win32
_storageProvider = new Win32StorageProvider(this); _storageProvider = new Win32StorageProvider(this);
_nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition); _nativeControlHost = new Win32NativeControlHost(this, _isUsingComposition);
_transparencyLevel = _isUsingComposition ? WindowTransparencyLevel.Transparent : WindowTransparencyLevel.None;
s_instances.Add(this); 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; protected IntPtr Hwnd => _hwnd;
@ -344,82 +358,132 @@ namespace Avalonia.Win32
return null; return null;
} }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> 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 we get here, we didn't find a supported level. Use the defualt of Transparent or
{ // None, depending on whether composition is enabled.
if (Win32Platform.WindowsVersion.Major >= 6) if (_isUsingComposition)
{ {
if (DwmIsCompositionEnabled(out var compositionEnabled) != 0 || !compositionEnabled) SetTransparencyTransparent(windowsVersion);
{ TransparencyLevel = WindowTransparencyLevel.Transparent;
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 else
{ {
return WindowTransparencyLevel.None; TransparencyLevel = WindowTransparencyLevel.None;
} }
} }
private WindowTransparencyLevel Win7EnableBlur(WindowTransparencyLevel transparencyLevel) private bool IsSupported(WindowTransparencyLevel level, Version windowsVersion)
{ {
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) // Only None is suppported when composition is disabled.
{ if (!_isUsingComposition)
transparencyLevel = WindowTransparencyLevel.Blur; 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); DwmEnableBlurBehindWindow(_hwnd, ref blurInfo);
}
if (transparencyLevel == WindowTransparencyLevel.Transparent) private void SetTransparencyAcrylicBlur(Version windowsVersion)
{ {
return WindowTransparencyLevel.None; // Acrylic blur only supported with composition on Windows >= 10.0.15063.
} if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinAcrylicVersion)
else return;
{
return transparencyLevel; SetUseHostBackdropBrush(true);
} _blurHost?.SetBlur(BlurEffect.Acrylic);
} }
private WindowTransparencyLevel Win8xEnableBlur(WindowTransparencyLevel transparencyLevel) private void SetTransparencyMica(Version windowsVersion)
{ {
var accent = new AccentPolicy(); // Mica only supported with composition on Windows >= 10.0.22000.
var accentStructSize = Marshal.SizeOf<AccentPolicy>(); if (!_isUsingComposition || windowsVersion < WinUiCompositionShared.MinHostBackdropVersion)
return;
if (transparencyLevel == WindowTransparencyLevel.AcrylicBlur) SetUseHostBackdropBrush(false);
{ _blurHost?.SetBlur(BlurEffect.Mica);
transparencyLevel = WindowTransparencyLevel.Blur; }
}
if (transparencyLevel == WindowTransparencyLevel.Transparent) private void SetAccentState(AccentState state)
{ {
accent.AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND; var accent = new AccentPolicy();
} var accentStructSize = Marshal.SizeOf(accent);
else
{ //Some of the AccentState Enum's values have different meanings on Windows 8.x than on Windows 10
accent.AccentState = AccentState.ACCENT_DISABLED; accent.AccentState = state;
}
var accentPtr = Marshal.AllocHGlobal(accentStructSize); var accentPtr = Marshal.AllocHGlobal(accentStructSize);
Marshal.StructureToPtr(accent, accentPtr, false); Marshal.StructureToPtr(accent, accentPtr, false);
@ -430,98 +494,18 @@ namespace Avalonia.Win32
data.Data = accentPtr; data.Data = accentPtr;
SetWindowCompositionAttribute(_hwnd, ref data); SetWindowCompositionAttribute(_hwnd, ref data);
Marshal.FreeHGlobal(accentPtr); Marshal.FreeHGlobal(accentPtr);
if (transparencyLevel >= WindowTransparencyLevel.Blur)
{
Win7EnableBlur(transparencyLevel);
}
return transparencyLevel;
} }
private WindowTransparencyLevel Win10EnableBlur(WindowTransparencyLevel transparencyLevel) private void SetUseHostBackdropBrush(bool useHostBackdropBrush)
{ {
if (_isUsingComposition) if (Win32Platform.WindowsVersion < WinUiCompositionShared.MinHostBackdropVersion)
{ return;
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);
return transparencyLevel; unsafe
}
else
{ {
bool canUseAcrylic = Win32Platform.WindowsVersion.Major > 10 || Win32Platform.WindowsVersion.Build >= 19628; var pvUseBackdropBrush = useHostBackdropBrush ? 1 : 0;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
var accent = new AccentPolicy();
var accentStructSize = Marshal.SizeOf<AccentPolicy>();
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;
} }
} }

6
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -137,7 +137,7 @@ namespace Avalonia.iOS
return null; return null;
} }
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
{ {
// No-op // No-op
} }
@ -157,8 +157,8 @@ namespace Avalonia.iOS
// legacy no-op // legacy no-op
public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IMouseDevice MouseDevice { get; } = new MouseDevice();
public WindowTransparencyLevel TransparencyLevel { get; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{ {
// TODO adjust status bar depending on full screen mode. // TODO adjust status bar depending on full screen mode.

6
src/iOS/Avalonia.iOS/Platform.cs

@ -47,12 +47,8 @@ namespace Avalonia.iOS
.Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance) .Bind<IDispatcherImpl>().ToConstant(DispatcherImpl.Instance)
.Bind<IKeyboardDevice>().ToConstant(keyboard); .Bind<IKeyboardDevice>().ToConstant(keyboard);
Compositor = new Compositor( Compositor = new Compositor(AvaloniaLocator.Current.GetService<IPlatformGraphics>());
AvaloniaLocator.Current.GetRequiredService<IRenderLoop>(),
AvaloniaLocator.Current.GetService<IPlatformGraphics>());
} }
} }
} }

14
tests/Avalonia.Controls.UnitTests/ContextMenuTests.cs

@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
int openedCount = 0; int openedCount = 0;
sut.MenuOpened += (sender, args) => sut.Opened += (sender, args) =>
{ {
openedCount++; openedCount++;
}; };
@ -139,7 +139,7 @@ namespace Avalonia.Controls.UnitTests
int openedCount = 0; int openedCount = 0;
sut.MenuOpened += (sender, args) => sut.Opened += (sender, args) =>
{ {
openedCount++; openedCount++;
}; };
@ -168,7 +168,7 @@ namespace Avalonia.Controls.UnitTests
bool opened = false; bool opened = false;
sut.MenuOpened += (sender, args) => sut.Opened += (sender, args) =>
{ {
opened = true; opened = true;
}; };
@ -221,7 +221,7 @@ namespace Avalonia.Controls.UnitTests
int closedCount = 0; int closedCount = 0;
sut.MenuClosed += (sender, args) => sut.Closed += (sender, args) =>
{ {
closedCount++; closedCount++;
}; };
@ -259,7 +259,7 @@ namespace Avalonia.Controls.UnitTests
var tracker = 0; var tracker = 0;
var c = new ContextMenu(); var c = new ContextMenu();
c.ContextMenuClosing += (s, e) => c.Closing += (s, e) =>
{ {
tracker++; tracker++;
e.Cancel = true; e.Cancel = true;
@ -431,7 +431,7 @@ namespace Avalonia.Controls.UnitTests
}; };
new Window { Content = target }; 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); _mouse.Click(target, MouseButton.Right);
@ -575,7 +575,7 @@ namespace Avalonia.Controls.UnitTests
var window = PreparedWindow(target); var window = PreparedWindow(target);
var overlay = LightDismissOverlayLayer.GetLightDismissOverlayLayer(window); 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(); window.Show();

26
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 = @"<Window xmlns='https://github.com/avaloniaui' TransparencyLevelHint='Blur,Transparent,None'/>";
var target = AvaloniaRuntimeXamlLoader.Parse<Window>(xaml);
Assert.Equal(
new[]
{
WindowTransparencyLevel.Blur,
WindowTransparencyLevel.Transparent,
WindowTransparencyLevel.None,
}, target.TransparencyLevelHint);
}
}
}

4
tests/Avalonia.UnitTests/CompositorTestServices.cs

@ -186,11 +186,11 @@ public class CompositorTestServices : IDisposable
public IMouseDevice MouseDevice { get; } = new MouseDevice(); public IMouseDevice MouseDevice { get; } = new MouseDevice();
public IPopupImpl CreatePopup() => throw new NotImplementedException(); public IPopupImpl CreatePopup() => throw new NotImplementedException();
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel) public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevel)
{ {
} }
public WindowTransparencyLevel TransparencyLevel { get; } public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) public void SetFrameThemeVariant(PlatformThemeVariant themeVariant)
{ {

Loading…
Cancel
Save