Browse Source

Merge branch 'master' into features/DevTools/PropertyType

pull/6280/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
28255bd646
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      native/Avalonia.Native/src/OSX/app.mm
  2. 13
      native/Avalonia.Native/src/OSX/window.mm
  3. 149
      samples/RenderDemo/Pages/AnimationsPage.xaml
  4. 84
      samples/RenderDemo/Pages/TransitionsPage.xaml
  5. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  6. 29
      src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs
  7. 12
      src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs
  8. 16
      src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs
  9. 13
      src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs
  10. 3
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  11. 1
      src/Avalonia.Native/avn.idl
  12. 2
      src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
  13. 138
      src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs
  14. 123
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  15. 20
      src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs
  16. 20
      src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs
  17. 44
      src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs
  18. 11
      src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs
  19. 2
      src/Avalonia.Visuals/Media/GradientBrush.cs
  20. 1
      src/Avalonia.Visuals/Media/SolidColorBrush.cs
  21. 7
      src/Avalonia.Visuals/RelativePoint.cs
  22. 30
      tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

6
native/Avalonia.Native/src/OSX/app.mm

@ -50,6 +50,12 @@ ComPtr<IAvnApplicationEvents> _events;
_events->FilesOpened(array); _events->FilesOpened(array);
} }
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
}
@end @end
@interface AvnApplication : NSApplication @interface AvnApplication : NSApplication

13
native/Avalonia.Native/src/OSX/window.mm

@ -2031,18 +2031,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
+(void)closeAll +(void)closeAll
{ {
NSArray<NSWindow*>* windows = [NSArray arrayWithArray:[NSApp windows]]; [[NSApplication sharedApplication] terminate:self];
auto numWindows = [windows count];
for(int i = 0; i < numWindows; i++)
{
auto window = (AvnWindow*)[windows objectAtIndex:i];
if([window parentWindow] == nullptr) // Avalonia will handle the child windows.
{
[window performClose:nil];
}
}
} }
- (void)performClose:(id)sender - (void)performClose:(id)sender

149
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -161,6 +161,151 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="Border.Rect7">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background" Value="Red" />
</KeyFrame>
<KeyFrame Cue="30%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="60%">
<Setter Property="Background" Value="Blue" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect8">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0.25" Color="Blue"/>
<GradientStop Offset="0.5" Color="Blue"/>
<GradientStop Offset="0.75" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect9">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<ConicGradientBrush Center="50%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="0.25" Color="Yellow"/>
<GradientStop Offset="0.5" Color="Red"/>
<GradientStop Offset="0.75" Color="Blue"/>
<GradientStop Offset="1" Color="Green"/>
</ConicGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Rect10">
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Normal">
<KeyFrame Cue="0%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="25%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,0%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="50%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,0%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="75%">
<Setter Property="Background">
<RadialGradientBrush Center="100%,100%" Radius="1">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Background">
<RadialGradientBrush Center="0%,100%" Radius="0.8">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
<Grid> <Grid>
@ -181,6 +326,10 @@
<Border Classes="Test Rect6" Background="Red"/> <Border Classes="Test Rect6" Background="Red"/>
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" /> <Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" /> <Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
<Border Classes="Test Rect7" Child="{x:Null}" />
<Border Classes="Test Rect8" Child="{x:Null}" />
<Border Classes="Test Rect9" Child="{x:Null}" />
<Border Classes="Test Rect10" Child="{x:Null}" />
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

84
samples/RenderDemo/Pages/TransitionsPage.xaml

@ -167,13 +167,80 @@
<Style Selector="Border.Rect11:pointerover"> <Style Selector="Border.Rect11:pointerover">
<Setter Property="Background" > <Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%"> <LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<LinearGradientBrush.GradientStops> <GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="0" Color="Red"/> <GradientStop Offset="1" Color="Blue"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush> </LinearGradientBrush>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Border.Rect12">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect12:pointerover">
<Setter Property="Background" >
<LinearGradientBrush StartPoint="100%,0%" EndPoint="0%,100%">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<ConicGradientBrush Center="50%,50%" Angle="0">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect13:pointerover">
<Setter Property="Background" >
<ConicGradientBrush Center="70%,70%" Angle="90">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</ConicGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14">
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Background" Duration="0:0:0.5" />
</Transitions>
</Setter>
<Setter Property="Background" >
<RadialGradientBrush Center="50%,50%" Radius="0.5">
<GradientStop Offset="0" Color="Red"/>
<GradientStop Offset="1" Color="Blue"/>
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.Rect14:pointerover">
<Setter Property="Background" >
<RadialGradientBrush Center="30%,30%" Radius="0.2">
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1" Color="Yellow"/>
</RadialGradientBrush>
</Setter>
</Style>
</Styles> </Styles>
</UserControl.Styles> </UserControl.Styles>
@ -202,6 +269,15 @@
<Border Classes="Test Rect10" /> <Border Classes="Test Rect10" />
<Border Classes="Test Rect11" /> <Border Classes="Test Rect11" />
<Border Classes="Test Rect12" Child="{x:Null}"/>
<Border Classes="Test Rect13" Child="{x:Null}"/>
<Border Classes="Test Rect14" Child="{x:Null}"/>
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
<Border Classes="Test Rect14" />
</WrapPanel> </WrapPanel>
</StackPanel> </StackPanel>
</Grid> </Grid>

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -30,6 +30,9 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.EventHandler<System.ComponentModel.CancelEventArgs> Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.ShutdownRequested' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.add_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime.remove_ShutdownRequested(System.EventHandler<System.ComponentModel.CancelEventArgs>)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
@ -38,4 +41,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalo
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
Total Issues: 39 Total Issues: 42

29
src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Avalonia.Controls; using Avalonia.Controls;
@ -42,9 +43,13 @@ namespace Avalonia.Controls.ApplicationLifetimes
"Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed");
_activeLifetime = this; _activeLifetime = this;
} }
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup; public event EventHandler<ControlledApplicationLifetimeStartupEventArgs> Startup;
/// <inheritdoc/>
public event EventHandler<CancelEventArgs> ShutdownRequested;
/// <inheritdoc/> /// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit; public event EventHandler<ControlledApplicationLifetimeExitEventArgs> Exit;
@ -111,6 +116,11 @@ namespace Avalonia.Controls.ApplicationLifetimes
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args); ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(args);
} }
var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
if (lifetimeEvents != null)
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_cts = new CancellationTokenSource(); _cts = new CancellationTokenSource();
MainWindow?.Show(); MainWindow?.Show();
Dispatcher.UIThread.MainLoop(_cts.Token); Dispatcher.UIThread.MainLoop(_cts.Token);
@ -123,6 +133,23 @@ namespace Avalonia.Controls.ApplicationLifetimes
if (_activeLifetime == this) if (_activeLifetime == this)
_activeLifetime = null; _activeLifetime = null;
} }
private void OnShutdownRequested(object sender, CancelEventArgs e)
{
ShutdownRequested?.Invoke(this, e);
if (e.Cancel)
return;
// When an OS shutdown request is received, try to close all non-owned windows. Windows can cancel
// shutdown by setting e.Cancel = true in the Closing event. Owned windows will be shutdown by their
// owners.
foreach (var w in Windows)
if (w.Owner is null)
w.Close();
if (Windows.Count > 0)
e.Cancel = true;
}
} }
public class ClassicDesktopStyleApplicationLifetimeOptions public class ClassicDesktopStyleApplicationLifetimeOptions

12
src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
namespace Avalonia.Controls.ApplicationLifetimes namespace Avalonia.Controls.ApplicationLifetimes
{ {
@ -34,5 +35,16 @@ namespace Avalonia.Controls.ApplicationLifetimes
Window MainWindow { get; set; } Window MainWindow { get; set; }
IReadOnlyList<Window> Windows { get; } IReadOnlyList<Window> Windows { get; }
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit. This event
/// provides a first-chance to cancel application shutdown; if shutdown is not canceled at this point the application
/// will try to close each non-owned open window, invoking the <see cref="Window.Closing"/> event on each and allowing
/// each window to cancel the shutdown.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
} }
} }

16
src/Avalonia.Controls/Platform/IPlatformLifetimeEventsImpl.cs

@ -0,0 +1,16 @@
using System;
using System.ComponentModel;
namespace Avalonia.Platform
{
public interface IPlatformLifetimeEventsImpl
{
/// <summary>
/// Raised by the platform when a shutdown is requested.
/// </summary>
/// <remarks>
/// Raised on on OSX via the Quit menu or right-clicking on the application icon and selecting Quit.
/// </remarks>
event EventHandler<CancelEventArgs> ShutdownRequested;
}
}

13
src/Avalonia.Native/AvaloniaNativeApplicationPlatform.cs

@ -1,14 +1,25 @@
using System; using System;
using System.ComponentModel;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.Native namespace Avalonia.Native
{ {
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents, IPlatformLifetimeEventsImpl
{ {
public event EventHandler<CancelEventArgs> ShutdownRequested;
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls)
{ {
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); ((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray());
} }
public int TryShutdown()
{
if (ShutdownRequested is null) return 1;
var e = new CancelEventArgs();
ShutdownRequested(this, e);
return (!e.Cancel).AsComBool();
}
} }
} }

3
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -111,7 +111,8 @@ namespace Avalonia.Native
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta)) .Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider()) .Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory)); .Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
.Bind<IPlatformLifetimeEventsImpl>().ToConstant(applicationPlatform);
if (_options.UseGpu) if (_options.UseGpu)
{ {

1
src/Avalonia.Native/avn.idl

@ -733,4 +733,5 @@ interface IAvnNativeControlHostTopLevelAttachment : IUnknown
interface IAvnApplicationEvents : IUnknown interface IAvnApplicationEvents : IUnknown
{ {
void FilesOpened (IAvnStringArray* urls); void FilesOpened (IAvnStringArray* urls);
bool TryShutdown();
} }

2
src/Avalonia.Themes.Fluent/Controls/TextBox.xaml

@ -76,9 +76,7 @@
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}" IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- TODO eliminate this margin... text layout issue? -->
<TextPresenter Name="PART_TextPresenter" <TextPresenter Name="PART_TextPresenter"
Margin="0 1 0 0"
Text="{TemplateBinding Text, Mode=TwoWay}" Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}" CaretIndex="{TemplateBinding CaretIndex}"
SelectionStart="{TemplateBinding SelectionStart}" SelectionStart="{TemplateBinding SelectionStart}"

138
src/Avalonia.Visuals/Animation/Animators/BaseBrushAnimator.cs

@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reactive.Disposables; using System.Diagnostics.CodeAnalysis;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Media; using Avalonia.Media;
#nullable enable
namespace Avalonia.Animation.Animators namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
@ -12,10 +15,8 @@ namespace Avalonia.Animation.Animators
/// redirect them to the properly registered /// redirect them to the properly registered
/// animators in this class. /// animators in this class.
/// </summary> /// </summary>
public class BaseBrushAnimator : Animator<IBrush> public class BaseBrushAnimator : Animator<IBrush?>
{ {
private IAnimator _targetAnimator;
private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators = private static readonly List<(Func<Type, bool> Match, Type AnimatorType)> _brushAnimators =
new List<(Func<Type, bool> Match, Type AnimatorType)>(); new List<(Func<Type, bool> Match, Type AnimatorType)>();
@ -31,7 +32,7 @@ namespace Avalonia.Animation.Animators
/// The type of the animator to instantiate. /// The type of the animator to instantiate.
/// </typeparam> /// </typeparam>
public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition) public static void RegisterBrushAnimator<TAnimator>(Func<Type, bool> condition)
where TAnimator : IAnimator where TAnimator : IAnimator, new()
{ {
_brushAnimators.Insert(0, (condition, typeof(TAnimator))); _brushAnimators.Insert(0, (condition, typeof(TAnimator)));
} }
@ -40,30 +41,127 @@ namespace Avalonia.Animation.Animators
public override IDisposable Apply(Animation animation, Animatable control, IClock clock, public override IDisposable Apply(Animation animation, Animatable control, IClock clock,
IObservable<bool> match, Action onComplete) IObservable<bool> match, Action onComplete)
{ {
foreach (var valueType in _brushAnimators) if (TryCreateCustomRegisteredAnimator(out var animator)
|| TryCreateGradientAnimator(out animator)
|| TryCreateSolidColorBrushAnimator(out animator))
{ {
if (!valueType.Match(this[0].Value.GetType())) continue; return animator.Apply(animation, control, clock, match, onComplete);
}
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log(
this,
"The animation's keyframe value types set is not supported.");
return base.Apply(animation, control, clock, match, onComplete);
}
_targetAnimator = (IAnimator)Activator.CreateInstance(valueType.AnimatorType); /// <summary>
/// Fallback implementation of <see cref="IBrush"/> animation.
/// </summary>
public override IBrush? Interpolate(double progress, IBrush? oldValue, IBrush? newValue) => progress >= 0.5 ? newValue : oldValue;
foreach (var keyframe in this) private bool TryCreateGradientAnimator([NotNullWhen(true)] out IAnimator? animator)
{
IGradientBrush? firstGradient = null;
foreach (var keyframe in this)
{
if (keyframe.Value is IGradientBrush gradientBrush)
{ {
_targetAnimator.Add(keyframe); firstGradient = gradientBrush;
break;
} }
}
_targetAnimator.Property = this.Property; if (firstGradient is null)
{
return _targetAnimator.Apply(animation, control, clock, match, onComplete); animator = null;
return false;
} }
Logger.TryGet(LogEventLevel.Error, LogArea.Animations)?.Log( var gradientAnimator = new GradientBrushAnimator();
this, gradientAnimator.Property = Property;
"The animation's keyframe values didn't match any brush animators registered in BaseBrushAnimator.");
foreach (var keyframe in this)
return Disposable.Empty; {
if (keyframe.Value is ISolidColorBrush solidColorBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
});
}
else if (keyframe.Value is IGradientBrush)
{
gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = gradientAnimator;
return true;
} }
/// <inheritdoc/> private bool TryCreateSolidColorBrushAnimator([NotNullWhen(true)] out IAnimator? animator)
public override IBrush Interpolate(double progress, IBrush oldValue, IBrush newValue) => null; {
var solidColorBrushAnimator = new ISolidColorBrushAnimator();
solidColorBrushAnimator.Property = Property;
foreach (var keyframe in this)
{
if (keyframe.Value is ISolidColorBrush)
{
solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
else
{
animator = null;
return false;
}
}
animator = solidColorBrushAnimator;
return true;
}
private bool TryCreateCustomRegisteredAnimator([NotNullWhen(true)] out IAnimator? animator)
{
if (_brushAnimators.Count > 0)
{
var firstKeyType = this[0].Value.GetType();
foreach (var (match, animatorType) in _brushAnimators)
{
if (!match(firstKeyType))
continue;
animator = (IAnimator)Activator.CreateInstance(animatorType);
if (animator != null)
{
animator.Property = Property;
foreach (var keyframe in this)
{
animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
}
return true;
}
}
}
animator = null;
return false;
}
} }
} }

123
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary>
public class GradientBrushAnimator : Animator<IGradientBrush?>
{
private static readonly RelativePointAnimator s_relativePointAnimator = new RelativePointAnimator();
private static readonly DoubleAnimator s_doubleAnimator = new DoubleAnimator();
public override IGradientBrush? Interpolate(double progress, IGradientBrush? oldValue, IGradientBrush? newValue)
{
if (oldValue is null || newValue is null)
{
return progress >= 0.5 ? newValue : oldValue;
}
switch (oldValue)
{
case IRadialGradientBrush oldRadial when newValue is IRadialGradientBrush newRadial:
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
s_doubleAnimator.Interpolate(progress, oldRadial.Radius, newRadial.Radius));
case IConicGradientBrush oldConic when newValue is IConicGradientBrush newConic:
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
case ILinearGradientBrush oldLinear when newValue is ILinearGradientBrush newLinear:
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
default:
return progress >= 0.5 ? newValue : oldValue;
}
}
public override IDisposable BindAnimation(Animatable control, IObservable<IGradientBrush?> instance)
{
return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
}
private IReadOnlyList<ImmutableGradientStop> InterpolateStops(double progress, IReadOnlyList<IGradientStop> oldValue, IReadOnlyList<IGradientStop> newValue)
{
var resultCount = Math.Max(oldValue.Count, newValue.Count);
var stops = new ImmutableGradientStop[resultCount];
for (int index = 0, oldIndex = 0, newIndex = 0; index < resultCount; index++)
{
stops[index] = new ImmutableGradientStop(
s_doubleAnimator.Interpolate(progress, oldValue[oldIndex].Offset, newValue[newIndex].Offset),
ColorAnimator.InterpolateCore(progress, oldValue[oldIndex].Color, newValue[newIndex].Color));
if (oldIndex < oldValue.Count - 1)
{
oldIndex++;
}
if (newIndex < newValue.Count - 1)
{
newIndex++;
}
}
return stops;
}
internal static IGradientBrush ConvertSolidColorBrushToGradient(IGradientBrush gradientBrush, ISolidColorBrush solidColorBrush)
{
switch (gradientBrush)
{
case IRadialGradientBrush oldRadial:
return new ImmutableRadialGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity,
oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
case IConicGradientBrush oldConic:
return new ImmutableConicGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity,
oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
case ILinearGradientBrush oldLinear:
return new ImmutableLinearGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity,
oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
default:
throw new NotSupportedException($"Gradient of type {gradientBrush?.GetType()} is not supported");
}
static IReadOnlyList<ImmutableGradientStop> CreateStopsFromSolidColorBrush(ISolidColorBrush solidColorBrush, IReadOnlyList<IGradientStop> baseStops)
{
var stops = new ImmutableGradientStop[baseStops.Count];
for (int index = 0; index < baseStops.Count; index++)
{
stops[index] = new ImmutableGradientStop(baseStops[index].Offset, solidColorBrush.Color);
}
return stops;
}
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/RelativePointAnimator.cs

@ -0,0 +1,20 @@
namespace Avalonia.Animation.Animators
{
/// <summary>
/// Animator that handles <see cref="RelativePoint"/> properties.
/// </summary>
public class RelativePointAnimator : Animator<RelativePoint>
{
private static readonly PointAnimator s_pointAnimator = new PointAnimator();
public override RelativePoint Interpolate(double progress, RelativePoint oldValue, RelativePoint newValue)
{
if (oldValue.Unit != newValue.Unit)
{
return progress >= 0.5 ? newValue : oldValue;
}
return new RelativePoint(s_pointAnimator.Interpolate(progress, oldValue.Point, newValue.Point), oldValue.Unit);
}
}
}

20
src/Avalonia.Visuals/Animation/Animators/SolidColorBrushAnimator.cs

@ -3,37 +3,39 @@ using Avalonia.Data;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable; using Avalonia.Media.Immutable;
#nullable enable
namespace Avalonia.Animation.Animators namespace Avalonia.Animation.Animators
{ {
/// <summary> /// <summary>
/// Animator that handles <see cref="SolidColorBrush"/> values. /// Animator that handles <see cref="SolidColorBrush"/> values.
/// </summary> /// </summary>
public class ISolidColorBrushAnimator : Animator<ISolidColorBrush> public class ISolidColorBrushAnimator : Animator<ISolidColorBrush?>
{ {
public override ISolidColorBrush Interpolate(double progress, ISolidColorBrush oldValue, ISolidColorBrush newValue) public override ISolidColorBrush? Interpolate(double progress, ISolidColorBrush? oldValue, ISolidColorBrush? newValue)
{ {
if (oldValue is null || newValue is null) if (oldValue is null || newValue is null)
{ {
return oldValue; return progress >= 0.5 ? newValue : oldValue;
} }
return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); return new ImmutableSolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
} }
public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush> instance) public override IDisposable BindAnimation(Animatable control, IObservable<ISolidColorBrush?> instance)
{ {
return control.Bind((AvaloniaProperty<IBrush>)Property, instance, BindingPriority.Animation); return control.Bind((AvaloniaProperty<IBrush?>)Property, instance, BindingPriority.Animation);
} }
} }
[Obsolete] [Obsolete("Use ISolidColorBrushAnimator instead")]
public class SolidColorBrushAnimator : Animator<SolidColorBrush> public class SolidColorBrushAnimator : Animator<SolidColorBrush?>
{ {
public override SolidColorBrush Interpolate(double progress, SolidColorBrush oldValue, SolidColorBrush newValue) public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
{ {
if (oldValue is null || newValue is null) if (oldValue is null || newValue is null)
{ {
return oldValue; return progress >= 0.5 ? newValue : oldValue;
} }
return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color)); return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));

44
src/Avalonia.Visuals/Animation/Transitions/BrushTransition.cs

@ -1,4 +1,5 @@
using System; using System;
using Avalonia.Animation.Animators; using Avalonia.Animation.Animators;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
@ -9,37 +10,46 @@ namespace Avalonia.Animation
{ {
/// <summary> /// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type. /// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="IBrush"/> type.
/// Only values of <see cref="ISolidColorBrush"/> will transition correctly at the moment.
/// </summary> /// </summary>
public class BrushTransition : Transition<IBrush?> public class BrushTransition : Transition<IBrush?>
{ {
private static readonly ISolidColorBrushAnimator s_animator = new ISolidColorBrushAnimator(); private static readonly GradientBrushAnimator s_gradientAnimator = new GradientBrushAnimator();
private static readonly ISolidColorBrushAnimator s_solidColorBrushAnimator = new ISolidColorBrushAnimator();
public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue) public override IObservable<IBrush?> DoTransition(IObservable<double> progress, IBrush? oldValue, IBrush? newValue)
{ {
var oldSolidColorBrush = TryGetSolidColorBrush(oldValue); if (oldValue is null || newValue is null)
var newSolidColorBrush = TryGetSolidColorBrush(newValue);
if (oldSolidColorBrush != null && newSolidColorBrush != null)
{ {
return new AnimatorTransitionObservable<ISolidColorBrush, ISolidColorBrushAnimator>( return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
s_animator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
} }
return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue); if (oldValue is IGradientBrush oldGradient)
} {
if (newValue is IGradientBrush newGradient)
{
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, newGradient);
}
else if (newValue is ISolidColorBrush newSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(oldGradient, newSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, oldGradient, convertedSolidColorBrush);
}
}
else if (newValue is IGradientBrush newGradient && oldValue is ISolidColorBrush oldSolidColorBrushToConvert)
{
var convertedSolidColorBrush = GradientBrushAnimator.ConvertSolidColorBrushToGradient(newGradient, oldSolidColorBrushToConvert);
return new AnimatorTransitionObservable<IGradientBrush?, GradientBrushAnimator>(s_gradientAnimator, progress, Easing, convertedSolidColorBrush, newGradient);
}
private static ISolidColorBrush? TryGetSolidColorBrush(IBrush? brush) if (oldValue is ISolidColorBrush oldSolidColorBrush && newValue is ISolidColorBrush newSolidColorBrush)
{
if (brush is null)
{ {
return Brushes.Transparent; return new AnimatorTransitionObservable<ISolidColorBrush?, ISolidColorBrushAnimator>(s_solidColorBrushAnimator, progress, Easing, oldSolidColorBrush, newSolidColorBrush);
} }
return brush as ISolidColorBrush; return new IncompatibleTransitionObservable(progress, Easing, oldValue, newValue);
} }
private class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?> private sealed class IncompatibleTransitionObservable : TransitionObservableBase<IBrush?>
{ {
private readonly IBrush? _from; private readonly IBrush? _from;
private readonly IBrush? _to; private readonly IBrush? _to;
@ -52,7 +62,7 @@ namespace Avalonia.Animation
protected override IBrush? ProduceValue(double progress) protected override IBrush? ProduceValue(double progress)
{ {
return progress < 0.5 ? _from : _to; return progress >= 0.5 ? _to : _from;
} }
} }
} }

11
src/Avalonia.Visuals/Animation/Transitions/RelativePointTransition.cs

@ -0,0 +1,11 @@
using Avalonia.Animation.Animators;
namespace Avalonia.Animation
{
/// <summary>
/// Transition class that handles <see cref="AvaloniaProperty"/> with <see cref="RelativePoint"/> type.
/// </summary>
public class RelativePointTransition : AnimatorDrivenTransition<RelativePoint, RelativePointAnimator>
{
}
}

2
src/Avalonia.Visuals/Media/GradientBrush.cs

@ -2,6 +2,8 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using Avalonia.Animation.Animators;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Metadata; using Avalonia.Metadata;

1
src/Avalonia.Visuals/Media/SolidColorBrush.cs

@ -16,7 +16,6 @@ namespace Avalonia.Media
static SolidColorBrush() static SolidColorBrush()
{ {
BaseBrushAnimator.RegisterBrushAnimator<ISolidColorBrushAnimator>(match => typeof(ISolidColorBrush).IsAssignableFrom(match));
AffectsRender<SolidColorBrush>(ColorProperty); AffectsRender<SolidColorBrush>(ColorProperty);
} }

7
src/Avalonia.Visuals/RelativePoint.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Avalonia.Animation.Animators;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia namespace Avalonia
@ -45,6 +47,11 @@ namespace Avalonia
private readonly RelativeUnit _unit; private readonly RelativeUnit _unit;
static RelativePoint()
{
Animation.Animation.RegisterAnimator<RelativePointAnimator>(prop => typeof(RelativePoint).IsAssignableFrom(prop.PropertyType));
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="RelativePoint"/> struct. /// Initializes a new instance of the <see cref="RelativePoint"/> struct.
/// </summary> /// </summary>

30
tests/Avalonia.Controls.UnitTests/DesktopStyleApplicationLifetimeTests.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
@ -209,6 +210,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Empty(lifetime.Windows); Assert.Empty(lifetime.Windows);
} }
} }
[Fact]
public void Should_Allow_Canceling_Shutdown_Via_ShutdownRequested_Event()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
using (var lifetime = new ClassicDesktopStyleApplicationLifetime())
{
var lifetimeEvents = new Mock<IPlatformLifetimeEventsImpl>();
AvaloniaLocator.CurrentMutable.Bind<IPlatformLifetimeEventsImpl>().ToConstant(lifetimeEvents.Object);
lifetime.Start(Array.Empty<string>());
var window = new Window();
var raised = 0;
window.Show();
lifetime.ShutdownRequested += (s, e) =>
{
e.Cancel = true;
++raised;
};
lifetimeEvents.Raise(x => x.ShutdownRequested += null, new CancelEventArgs());
Assert.Equal(1, raised);
Assert.Equal(new[] { window }, lifetime.Windows);
}
}
} }
} }

Loading…
Cancel
Save