Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Avalonia.Controls/AppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,12 +353,21 @@ private void Setup()
SetupUnsafe();
}

/// <summary>
/// Allows <see cref="Setup"/> to be called again after it has already been called once.
/// </summary>
internal static void ResetSetupForUnitTests()
=> s_setupWasAlreadyCalled = false;

/// <summary>
/// Setup method that doesn't check for input initalizers being set.
/// Nor
/// </summary>
internal void SetupUnsafe()
{
var setupLifetime = _lifetime as ISetupApplicationLifetime;
setupLifetime?.BeforeAppInit();

_optionsInitializers?.Invoke();
RuntimePlatformServicesInitializer?.Invoke();
TextShapingSubsystemInitializer?.Invoke();
Expand All @@ -373,6 +382,8 @@ internal void SetupUnsafe()
AfterApplicationSetupCallback?.Invoke(Self);
AfterSetupCallback?.Invoke(Self);
Instance.OnFrameworkInitializationCompleted();

setupLifetime?.AfterAppInit();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
using Avalonia.Collections;
Expand All @@ -12,13 +13,19 @@

namespace Avalonia.Controls.ApplicationLifetimes
{
public class ClassicDesktopStyleApplicationLifetime : IClassicDesktopStyleApplicationLifetime, IDisposable
public class ClassicDesktopStyleApplicationLifetime :
IClassicDesktopStyleApplicationLifetime,
ISetupApplicationLifetime,
IDisposable
{
private int _exitCode;
private CancellationTokenSource? _cts;
private bool _isShuttingDown;
private readonly AvaloniaList<Window> _windows = new();
private CompositeDisposable? _compositeDisposable;
private CompositeDisposable? _globalEventsSubscriptions;
private bool _beforeInitCalled;
private bool _afterInitCalled;
private IPlatformLifetimeEventsImpl? _platformLifetimeEventsImpl;

/// <inheritdoc/>
public event EventHandler<ControlledApplicationLifetimeStartupEventArgs>? Startup;
Expand Down Expand Up @@ -67,16 +74,11 @@ public bool TryShutdown(int exitCode = 0)
return DoShutdown(new ShutdownRequestedEventArgs(), true, false, exitCode);
}

internal void SubscribeGlobalEvents()
private void SubscribeGlobalEvents()
{
if (_compositeDisposable is not null)
{
// There could be a case, when lifetime was setup without starting.
// Until developer started it manually later. To avoid API breaking changes, it will execute Setup method twice.
return;
}
Debug.Assert(_globalEventsSubscriptions is null);

_compositeDisposable = new CompositeDisposable(
_globalEventsSubscriptions = new CompositeDisposable(
Window.WindowOpenedEvent.AddClassHandler(typeof(Window), (sender, _) =>
{
var window = (Window)sender!;
Expand All @@ -93,16 +95,32 @@ internal void SubscribeGlobalEvents()
}));
}

internal void SetupCore(string[] args)
private void BeforeInit()
{
if (_beforeInitCalled)
return;

_beforeInitCalled = true;
SubscribeGlobalEvents();
}

Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));
void ISetupApplicationLifetime.BeforeAppInit()
=> BeforeInit();

var lifetimeEvents = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
private void AfterInit()
{
if (_afterInitCalled)
return;

if (lifetimeEvents != null)
lifetimeEvents.ShutdownRequested += OnShutdownRequested;
_afterInitCalled = true;

_platformLifetimeEventsImpl = AvaloniaLocator.Current.GetService<IPlatformLifetimeEventsImpl>();
_platformLifetimeEventsImpl?.ShutdownRequested += OnShutdownRequested;
}

void ISetupApplicationLifetime.AfterAppInit()
{
AfterInit();
}

public int Start(string[] args)
Expand All @@ -121,7 +139,11 @@ public int Start()

internal int StartCore(string[] args)
{
SetupCore(args);
// Before/AfterInit should have been called from ISetupApplicationLifetime.
// If somehow they weren't (e.g., for a manually started lifetime), do it now.
BeforeInit();
AfterInit();
Startup?.Invoke(this, new ControlledApplicationLifetimeStartupEventArgs(args));

_cts = new CancellationTokenSource();

Expand All @@ -143,8 +165,11 @@ private void ShowMainWindow()

public void Dispose()
{
_compositeDisposable?.Dispose();
_compositeDisposable = null;
_globalEventsSubscriptions?.Dispose();
_globalEventsSubscriptions = null;

_platformLifetimeEventsImpl?.ShutdownRequested -= OnShutdownRequested;
_platformLifetimeEventsImpl = null;
}

private bool DoShutdown(
Expand Down Expand Up @@ -222,15 +247,12 @@ namespace Avalonia
/// </summary>
public static class ClassicDesktopStyleApplicationLifetimeExtensions
{
private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder builder, string[] args,
private static ClassicDesktopStyleApplicationLifetime CreateLifetime(
string[] args,
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder)
{
var lifetime = new ClassicDesktopStyleApplicationLifetime();
lifetime.SubscribeGlobalEvents();

lifetime.Args = args;
var lifetime = new ClassicDesktopStyleApplicationLifetime { Args = args };
lifetimeBuilder?.Invoke(lifetime);

return lifetime;
}

Expand All @@ -244,8 +266,7 @@ private static ClassicDesktopStyleApplicationLifetime PrepareLifetime(AppBuilder
public static AppBuilder SetupWithClassicDesktopLifetime(this AppBuilder builder, string[] args,
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder = null)
{
var lifetime = PrepareLifetime(builder, args, lifetimeBuilder);
lifetime.SetupCore(args);
var lifetime = CreateLifetime(args, lifetimeBuilder);
return builder.SetupWithLifetime(lifetime);
}

Expand All @@ -260,7 +281,7 @@ public static int StartWithClassicDesktopLifetime(
this AppBuilder builder, string[] args,
Action<IClassicDesktopStyleApplicationLifetime>? lifetimeBuilder = null)
{
var lifetime = PrepareLifetime(builder, args, lifetimeBuilder);
var lifetime = CreateLifetime(args, lifetimeBuilder);
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}
Expand All @@ -275,7 +296,7 @@ public static int StartWithClassicDesktopLifetime(
public static int StartWithClassicDesktopLifetime(
this AppBuilder builder, string[] args, ShutdownMode shutdownMode)
{
var lifetime = PrepareLifetime(builder, args, l => l.ShutdownMode = shutdownMode);
var lifetime = CreateLifetime(args, l => l.ShutdownMode = shutdownMode);
builder.SetupWithLifetime(lifetime);
return lifetime.Start(args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Avalonia.Controls.ApplicationLifetimes;

/// <summary>
/// An interface for lifetimes that need to execute extra code before and after initialization.
/// </summary>
internal interface ISetupApplicationLifetime
{
/// <summary>
/// Called before anything is initialized: platforms, rendering, app, etc. aren't available yet.
/// </summary>
void BeforeAppInit();

/// <summary>
/// Called after the app has been initialized.
/// </summary>
void AfterAppInit();
}
Loading