diff --git a/src/Android/Avalonia.Android/AvaloniaActivity.cs b/src/Android/Avalonia.Android/AvaloniaActivity.cs
new file mode 100644
index 0000000000..187349fbb6
--- /dev/null
+++ b/src/Android/Avalonia.Android/AvaloniaActivity.cs
@@ -0,0 +1,170 @@
+#nullable enable
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.Versioning;
+using Android.App;
+using Android.Content;
+using Android.Content.PM;
+using Android.OS;
+using Android.Runtime;
+using Android.Views;
+using AndroidX.AppCompat.App;
+using Avalonia.Controls.ApplicationLifetimes;
+
+namespace Avalonia.Android;
+
+///
+/// Common implementation of android activity that is integrated with Avalonia views.
+/// If you need a base class for main activity of Avalonia app, see or .
+///
+public class AvaloniaActivity : AppCompatActivity, IAvaloniaActivity
+{
+ private EventHandler? _onActivated, _onDeactivated;
+ private GlobalLayoutListener? _listener;
+ private object? _content;
+ internal AvaloniaView? _view;
+
+ public Action? ActivityResult { get; set; }
+ public Action? RequestPermissionsResult { get; set; }
+
+ public event EventHandler? BackRequested;
+
+ public object? Content
+ {
+ get => _content;
+ set
+ {
+ if (_content != value)
+ {
+ _content = value;
+ if (_view is not null)
+ {
+ _view.Content = _content;
+ }
+ }
+ }
+ }
+
+ event EventHandler? IAvaloniaActivity.Activated
+ {
+ add { _onActivated += value; }
+ remove { _onActivated -= value; }
+ }
+
+ event EventHandler? IAvaloniaActivity.Deactivated
+ {
+ add { _onDeactivated += value; }
+ remove { _onDeactivated -= value; }
+ }
+
+ [ObsoletedOSPlatform("android33.0")]
+ public override void OnBackPressed()
+ {
+ var eventArgs = new AndroidBackRequestedEventArgs();
+
+ BackRequested?.Invoke(this, eventArgs);
+
+ if (!eventArgs.Handled)
+ {
+ base.OnBackPressed();
+ }
+ }
+
+ protected override void OnCreate(Bundle? savedInstanceState)
+ {
+ InitializeAvaloniaView(_content);
+
+ base.OnCreate(savedInstanceState);
+
+ SetContentView(_view);
+
+ _listener = new GlobalLayoutListener(_view);
+
+ _view.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
+
+ if (Intent?.Data is {} androidUri
+ && androidUri.IsAbsolute
+ && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var protocolUri))
+ {
+ _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, protocolUri));
+ }
+ }
+
+ protected override void OnStop()
+ {
+ _onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
+ base.OnStop();
+ }
+
+ protected override void OnStart()
+ {
+ _onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
+ base.OnStart();
+ }
+
+ protected override void OnResume()
+ {
+ base.OnResume();
+
+ // Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
+ if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
+ {
+ attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
+ }
+ }
+
+ protected override void OnDestroy()
+ {
+ if (_view is not null)
+ {
+ _view.Content = null;
+ _view.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
+ _view.Dispose();
+ _view = null;
+ }
+
+ base.OnDestroy();
+ }
+
+ protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
+ {
+ base.OnActivityResult(requestCode, resultCode, data);
+
+ ActivityResult?.Invoke(requestCode, resultCode, data);
+ }
+
+ [SupportedOSPlatform("android23.0")]
+ public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
+ {
+ base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
+
+ RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
+ }
+
+ [MemberNotNull(nameof(_view))]
+ private protected virtual void InitializeAvaloniaView(object? initialContent)
+ {
+ if (Avalonia.Application.Current is null)
+ {
+ throw new InvalidOperationException(
+ "Avalonia Application was not initialized. Make sure you have created AvaloniaMainActivity.");
+ }
+
+ _view = new AvaloniaView(this) { Content = initialContent };
+ }
+
+ private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
+ {
+ private readonly AvaloniaView _view;
+
+ public GlobalLayoutListener(AvaloniaView view)
+ {
+ _view = view;
+ }
+
+ public void OnGlobalLayout()
+ {
+ _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
index f203b6c544..baed45ab8e 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.App.cs
@@ -1,66 +1,15 @@
#nullable enable
+using Android.OS;
+using Android.Views;
using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Platform;
-namespace Avalonia.Android
-{
- partial class AvaloniaMainActivity where TApp : Application, new()
- {
- protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder.UseAndroid();
-
- private static AppBuilder? s_appBuilder;
- internal static object? ViewContent;
-
- public object? Content
- {
- get
- {
- return ViewContent;
- }
- set
- {
- ViewContent = value;
- if (View != null)
- View.Content = value;
- }
- }
-
- protected AppBuilder CreateAppBuilder()
- {
- var builder = AppBuilder.Configure();
-
- return CustomizeAppBuilder(builder);
- }
-
- private void InitializeApp()
- {
- if (s_appBuilder == null)
- {
- var builder = CreateAppBuilder();
+namespace Avalonia.Android;
- builder.SetupWithLifetime(new SingleViewLifetime());
-
- s_appBuilder = builder;
- }
-
- if (Avalonia.Application.Current?.TryGetFeature()
- is AndroidActivatableLifetime activatableLifetime)
- {
- activatableLifetime.Activity = this;
- }
-
- View = new AvaloniaView(this);
- if (ViewContent != null)
- {
- View.Content = ViewContent;
- }
-
- if (Avalonia.Application.Current?.ApplicationLifetime is SingleViewLifetime lifetime)
- {
- lifetime.View = View;
- }
- }
- }
+public class AvaloniaMainActivity : AvaloniaMainActivity
+ where TApp : Application, new()
+{
+ protected override AppBuilder CreateAppBuilder() => AppBuilder.Configure();
}
diff --git a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
index eed6a68eb5..48411d792f 100644
--- a/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
+++ b/src/Android/Avalonia.Android/AvaloniaMainActivity.cs
@@ -1,141 +1,63 @@
+#nullable enable
+
using System;
-using System.Runtime.Versioning;
-using Android.App;
-using Android.Content;
-using Android.Content.PM;
+using System.Diagnostics.CodeAnalysis;
using Android.OS;
-using Android.Runtime;
-using Android.Views;
-using AndroidX.AppCompat.App;
+using Avalonia.Android.Platform;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Platform;
-namespace Avalonia.Android
-{
- public class AvaloniaMainActivity : AppCompatActivity, IAvaloniaActivity
- {
- private EventHandler _onActivated, _onDeactivated;
-
- public Action ActivityResult { get; set; }
- public Action RequestPermissionsResult { get; set; }
+namespace Avalonia.Android;
- public event EventHandler BackRequested;
- event EventHandler IAvaloniaActivity.Activated
- {
- add { _onActivated += value; }
- remove { _onActivated -= value; }
- }
-
- event EventHandler IAvaloniaActivity.Deactivated
- {
- add { _onDeactivated += value; }
- remove { _onDeactivated -= value; }
- }
-
- [ObsoletedOSPlatform("android33.0")]
- public override void OnBackPressed()
- {
- var eventArgs = new AndroidBackRequestedEventArgs();
-
- BackRequested?.Invoke(this, eventArgs);
-
- if (!eventArgs.Handled)
- {
- base.OnBackPressed();
- }
- }
-
- protected override void OnCreate(Bundle savedInstanceState)
- {
- base.OnCreate(savedInstanceState);
-
- if (Intent?.Data is {} androidUri
- && androidUri.IsAbsolute
- && Uri.TryCreate(androidUri.ToString(), UriKind.Absolute, out var protocolUri))
- {
- _onActivated?.Invoke(this, new ProtocolActivatedEventArgs(ActivationKind.OpenUri, protocolUri));
- }
- }
-
- protected override void OnStop()
- {
- _onDeactivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
- base.OnStop();
- }
-
- protected override void OnStart()
- {
- _onActivated?.Invoke(this, new ActivatedEventArgs(ActivationKind.Background));
- base.OnStart();
- }
+public class AvaloniaMainActivity : AvaloniaActivity
+{
+ private protected static SingleViewLifetime? Lifetime;
- protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
+ public override void OnCreate(Bundle? savedInstanceState, PersistableBundle? persistentState)
+ {
+ // Global IActivatableLifetime expects a main activity, so we need to replace it on each OnCreate.
+ if (Avalonia.Application.Current?.TryGetFeature()
+ is AndroidActivatableLifetime activatableLifetime)
{
- base.OnActivityResult(requestCode, resultCode, data);
-
- ActivityResult?.Invoke(requestCode, resultCode, data);
+ activatableLifetime.Activity = this;
}
- [SupportedOSPlatform("android23.0")]
- public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
- {
- base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
-
- RequestPermissionsResult?.Invoke(requestCode, permissions, grantResults);
- }
+ base.OnCreate(savedInstanceState, persistentState);
}
- public abstract partial class AvaloniaMainActivity : AvaloniaMainActivity where TApp : Application, new()
+ private protected override void InitializeAvaloniaView(object? initialContent)
{
- internal AvaloniaView View { get; set; }
-
- private GlobalLayoutListener _listener;
-
- protected override void OnCreate(Bundle savedInstanceState)
- {
- InitializeApp();
-
- base.OnCreate(savedInstanceState);
-
- SetContentView(View);
-
- _listener = new GlobalLayoutListener(View);
-
- View.ViewTreeObserver?.AddOnGlobalLayoutListener(_listener);
- }
-
- protected override void OnResume()
- {
- base.OnResume();
-
- // Android only respects LayoutInDisplayCutoutMode value if it has been set once before window becomes visible.
- if (OperatingSystem.IsAndroidVersionAtLeast(28) && Window is { Attributes: { } attributes })
- {
- attributes.LayoutInDisplayCutoutMode = LayoutInDisplayCutoutMode.ShortEdges;
- }
- }
-
- protected override void OnDestroy()
- {
- View.Content = null;
-
- View.ViewTreeObserver?.RemoveOnGlobalLayoutListener(_listener);
-
- base.OnDestroy();
- }
-
- private class GlobalLayoutListener : Java.Lang.Object, ViewTreeObserver.IOnGlobalLayoutListener
- {
- private readonly AvaloniaView _view;
-
- public GlobalLayoutListener(AvaloniaView view)
- {
- _view = view;
- }
-
- public void OnGlobalLayout()
- {
- _view.TopLevelImpl?.Resize(_view.TopLevelImpl.ClientSize);
- }
+ // Android can run OnCreate + InitializeAvaloniaView multiple times per process lifetime.
+ // On each call we need to create new AvaloniaView, but we can't recreate Avalonia nor Avalonia controls.
+ // So, if lifetime was already created previously - recreate AvaloniaView.
+ // If not, initialize Avalonia, and create AvaloniaView inside of AfterSetup callback.
+ // We need this AfterSetup callback to match iOS/Browser behavior and ensure that view/toplevel is available in custom AfterSetup calls.
+ if (Lifetime is not null)
+ {
+ Lifetime.Activity = this;
+ _view = new AvaloniaView(this) { Content = initialContent };
+ }
+ else
+ {
+ var builder = CreateAppBuilder();
+ builder = CustomizeAppBuilder(builder);
+
+ Lifetime = new SingleViewLifetime();
+ Lifetime.Activity = this;
+
+ builder
+ .AfterApplicationSetup(_ =>
+ {
+ _view = new AvaloniaView(this) { Content = initialContent };
+ })
+ .SetupWithLifetime(Lifetime);
+
+ // AfterPlatformServicesSetup should always be called. If it wasn't, we have an unusual problem.
+ if (_view is null)
+ throw new InvalidOperationException("Unknown error: AvaloniaView initialization has failed.");
}
}
+
+ protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure().UseAndroid();
+ protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
}
diff --git a/src/Android/Avalonia.Android/AvaloniaView.cs b/src/Android/Avalonia.Android/AvaloniaView.cs
index c120f9ee77..e9ea41ab61 100644
--- a/src/Android/Avalonia.Android/AvaloniaView.cs
+++ b/src/Android/Avalonia.Android/AvaloniaView.cs
@@ -35,6 +35,7 @@ namespace Avalonia.Android
}
internal TopLevelImpl TopLevelImpl => _view;
+ internal TopLevel TopLevel => _root;
public object Content
{
@@ -121,9 +122,6 @@ namespace Avalonia.Android
MaxClientSize = size;
base.OnResized(size);
}
-
- public WindowState WindowState { get; set; }
- public IDisposable ShowDialog() => null;
}
}
}
diff --git a/src/Android/Avalonia.Android/IAvaloniaActivity.cs b/src/Android/Avalonia.Android/IAvaloniaActivity.cs
index 005096a0bd..d6239400e8 100644
--- a/src/Android/Avalonia.Android/IAvaloniaActivity.cs
+++ b/src/Android/Avalonia.Android/IAvaloniaActivity.cs
@@ -1,10 +1,12 @@
-using System;
+#nullable enable
+using System;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Android;
public interface IAvaloniaActivity : IActivityResultHandler, IActivityNavigationService
{
- event EventHandler Activated;
- event EventHandler Deactivated;
+ object? Content { get; set; }
+ event EventHandler? Activated;
+ event EventHandler? Deactivated;
}
diff --git a/src/Android/Avalonia.Android/SingleViewLifetime.cs b/src/Android/Avalonia.Android/SingleViewLifetime.cs
index c912d2f890..f525a2773a 100644
--- a/src/Android/Avalonia.Android/SingleViewLifetime.cs
+++ b/src/Android/Avalonia.Android/SingleViewLifetime.cs
@@ -1,26 +1,47 @@
-using Avalonia.Controls;
+#nullable enable
+using System.Diagnostics.CodeAnalysis;
+using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
-namespace Avalonia.Android
+namespace Avalonia.Android;
+
+internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
- internal class SingleViewLifetime : ISingleViewApplicationLifetime
- {
- private AvaloniaView _view;
+ private Control? _mainView;
+ private AvaloniaMainActivity? _activity;
- public AvaloniaView View
+ ///
+ /// Since Main Activity can be swapped, we should adjust litetime as well.
+ ///
+ public AvaloniaMainActivity Activity
+ {
+ [return: MaybeNull] get => _activity!;
+ internal set
+ {
+ if (_activity != null)
+ {
+ _activity.Content = null;
+ }
+ _activity = value;
+ _activity.Content = _mainView;
+ }
+ }
+
+ public Control? MainView
+ {
+ get => _mainView;
+ set
{
- get => _view; internal set
+ if (_mainView != value)
{
- if (_view != null)
+ _mainView = value;
+ if (_activity != null)
{
- _view.Content = null;
- _view.Dispose();
+ _activity.Content = _mainView;
}
- _view = value;
- _view.Content = MainView;
}
}
-
- public Control MainView { get; set; }
}
+
+ public TopLevel? TopLevel => _activity?._view?.TopLevel;
}
diff --git a/src/Avalonia.Controls/AppBuilder.cs b/src/Avalonia.Controls/AppBuilder.cs
index b77ce7ebb2..6ee987a0c8 100644
--- a/src/Avalonia.Controls/AppBuilder.cs
+++ b/src/Avalonia.Controls/AppBuilder.cs
@@ -69,10 +69,14 @@ namespace Avalonia
public string? RenderingSubsystemName { get; private set; }
///
- /// Gets or sets a method to call after the is setup.
+ /// Gets a method to call after the is setup.
///
public Action AfterSetupCallback { get; private set; } = builder => { };
+ ///
+ /// Callbacks that are commonly used by backends to initialize avalonia views.
+ ///
+ private Action AfterApplicationSetupCallback { get; set; } = builder => { };
public Action AfterPlatformServicesSetupCallback { get; private set; } = builder => { };
@@ -159,8 +163,14 @@ namespace Avalonia
AfterSetupCallback = (Action)Delegate.Combine(AfterSetupCallback, callback);
return Self;
}
-
-
+
+ [PrivateApi]
+ public AppBuilder AfterApplicationSetup(Action callback)
+ {
+ AfterApplicationSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
+ return Self;
+ }
+
public AppBuilder AfterPlatformServicesSetup(Action callback)
{
AfterPlatformServicesSetupCallback = (Action)Delegate.Combine(AfterPlatformServicesSetupCallback, callback);
@@ -328,6 +338,7 @@ namespace Avalonia
AvaloniaLocator.CurrentMutable.BindToSelf(Instance);
Instance.RegisterServices();
Instance.Initialize();
+ AfterApplicationSetupCallback?.Invoke(Self);
AfterSetupCallback?.Invoke(Self);
Instance.OnFrameworkInitializationCompleted();
}
diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ISingleTopLevelApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ISingleTopLevelApplicationLifetime.cs
new file mode 100644
index 0000000000..fe9078a133
--- /dev/null
+++ b/src/Avalonia.Controls/ApplicationLifetimes/ISingleTopLevelApplicationLifetime.cs
@@ -0,0 +1,15 @@
+using System.ComponentModel;
+using Avalonia.Metadata;
+
+namespace Avalonia.Controls.ApplicationLifetimes;
+
+///
+/// Used in our internal projects. Until we figure out way to add this information to the public API.
+///
+[NotClientImplementable]
+[PrivateApi]
+[EditorBrowsable(EditorBrowsableState.Never)]
+public interface ISingleTopLevelApplicationLifetime : IApplicationLifetime
+{
+ TopLevel? TopLevel { get; }
+}
diff --git a/src/Browser/Avalonia.Browser/AvaloniaView.cs b/src/Browser/Avalonia.Browser/AvaloniaView.cs
index 5129f3095c..e2a6212e13 100644
--- a/src/Browser/Avalonia.Browser/AvaloniaView.cs
+++ b/src/Browser/Avalonia.Browser/AvaloniaView.cs
@@ -140,6 +140,16 @@ namespace Avalonia.Browser
InputHelper.FocusElement(_containerElement);
}
+ public Control? Content
+ {
+ get => (Control)_topLevel.Content!;
+ set => _topLevel.Content = value;
+ }
+
+ public bool IsComposing { get; private set; }
+
+ internal TopLevel TopLevel => _topLevel;
+
private static RawPointerPoint ExtractRawPointerFromJSArgs(JSObject args)
{
var point = new RawPointerPoint
@@ -429,15 +439,7 @@ namespace Avalonia.Browser
Dispatcher.UIThread.RunJobs(DispatcherPriority.UiThreadRender);
ManualTriggerRenderTimer.Instance.RaiseTick();
}
-
- public Control? Content
- {
- get => (Control)_topLevel.Content!;
- set => _topLevel.Content = value;
- }
-
- public bool IsComposing { get; private set; }
-
+
internal INativeControlHostImpl GetNativeControlHostImpl()
{
return new BrowserNativeControlHost(_nativeControlsContainer);
diff --git a/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs b/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
index eaa72c1931..9c41fff43f 100644
--- a/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
+++ b/src/Browser/Avalonia.Browser/BrowserAppBuilder.cs
@@ -55,7 +55,7 @@ public static class BrowserAppBuilder
var lifetime = new BrowserSingleViewLifetime();
builder
- .AfterSetup(_ =>
+ .AfterApplicationSetup(_ =>
{
lifetime.View = new AvaloniaView(mainDivId);
})
diff --git a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
index ba404c2600..a28bb469f3 100644
--- a/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
+++ b/src/Browser/Avalonia.Browser/BrowserSingleViewLifetime.cs
@@ -6,7 +6,7 @@ using Avalonia.Browser;
namespace Avalonia;
-internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
+internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
public AvaloniaView? View;
@@ -32,4 +32,6 @@ internal class BrowserSingleViewLifetime : ISingleViewApplicationLifetime
throw new InvalidOperationException("Browser lifetime was not initialized. Make sure AppBuilder.StartBrowserApp was called.");
}
}
+
+ public TopLevel? TopLevel => View?.TopLevel;
}
diff --git a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
index 29ca911a82..9c01d356dd 100644
--- a/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
+++ b/src/Headless/Avalonia.Headless.Vnc/HeadlessVncPlatformExtensions.cs
@@ -26,7 +26,7 @@ namespace Avalonia
UseHeadlessDrawing = false,
FrameBufferFormat = PixelFormat.Bgra8888
})
- .AfterSetup(_ =>
+ .AfterApplicationSetup(_ =>
{
var lt = ((IClassicDesktopStyleApplicationLifetime)builder.Instance!.ApplicationLifetime!);
lt.Startup += async delegate
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index 7eb3088cf7..d226a89e7d 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using Avalonia;
@@ -88,7 +89,7 @@ namespace Avalonia.LinuxFramebuffer
}
}
- class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime
+ class LinuxFramebufferLifetime : IControlledApplicationLifetime, ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
private readonly IOutputBackend _fb;
private readonly IInputBackend? _inputBackend;
@@ -114,25 +115,7 @@ namespace Avalonia.LinuxFramebuffer
{
if (_topLevel == null)
{
- var inputBackend = _inputBackend;
- if (inputBackend == null)
- {
- if (Environment.GetEnvironmentVariable("AVALONIA_USE_EVDEV") == "1")
- inputBackend = EvDevBackend.CreateFromEnvironment();
- else
- inputBackend = new LibInputBackend();
- }
-
- var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, inputBackend));
- tl.Prepare();
- tl.StartRendering();
- _topLevel = tl;
-
-
- if (_topLevel is IFocusScope scope && _topLevel.FocusManager is FocusManager focusManager)
- {
- focusManager.SetFocusScope(scope);
- }
+ EnsureTopLevel();
}
_topLevel.Content = value;
@@ -156,6 +139,38 @@ namespace Avalonia.LinuxFramebuffer
ExitCode = e.ApplicationExitCode;
_cts.Cancel();
}
+
+ public TopLevel? TopLevel
+ {
+ get
+ {
+ EnsureTopLevel();
+ return _topLevel;
+ }
+ }
+
+ [MemberNotNull(nameof(_topLevel))]
+ private void EnsureTopLevel()
+ {
+ var inputBackend = _inputBackend;
+ if (inputBackend == null)
+ {
+ if (Environment.GetEnvironmentVariable("AVALONIA_USE_EVDEV") == "1")
+ inputBackend = EvDevBackend.CreateFromEnvironment();
+ else
+ inputBackend = new LibInputBackend();
+ }
+
+ var tl = new EmbeddableControlRoot(new FramebufferToplevelImpl(_fb, inputBackend));
+ tl.Prepare();
+ tl.StartRendering();
+ _topLevel = tl;
+
+ if (_topLevel is IFocusScope scope && _topLevel.FocusManager is FocusManager focusManager)
+ {
+ focusManager.SetFocusScope(scope);
+ }
+ }
}
}
diff --git a/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs b/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
index 3e8abf2e54..8118ae2707 100644
--- a/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiAvaloniaView.cs
@@ -36,7 +36,7 @@ public class NuiAvaloniaView : GLView, ITizenView, ITextInputMethodImpl
set => _inputRoot = value;
}
- private TopLevel TopLevel
+ internal TopLevel TopLevel
=> _topLevel ?? throw new InvalidOperationException($"{nameof(NuiAvaloniaView)} hasn't been initialized");
internal TopLevelImpl TopLevelImpl
diff --git a/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs b/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
index 105bce26bd..d0745ab038 100644
--- a/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
+++ b/src/Tizen/Avalonia.Tizen/NuiTizenApplication.cs
@@ -13,7 +13,7 @@ public class NuiTizenApplication : NUIApplication
private SingleViewLifetime? _lifetime;
- private class SingleViewLifetime : ISingleViewApplicationLifetime
+ private class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
{
public NuiAvaloniaView View { get; }
@@ -27,8 +27,11 @@ public class NuiTizenApplication : NUIApplication
get => View.Content;
set => View.Content = value;
}
+
+ public TopLevel? TopLevel => View.TopLevel;
}
+ protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure().UseTizen();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
protected override void OnCreate()
@@ -55,13 +58,12 @@ public class NuiTizenApplication : NUIApplication
SynchronizationContext.SetSynchronizationContext(new SynchronizationContext());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "App builder");
- var builder = AppBuilder.Configure().UseTizen();
+ var builder = CreateAppBuilder();
TizenThreadingInterface.MainloopContext.Post(_ =>
{
- CustomizeAppBuilder(builder);
-
- builder.AfterSetup(_ => _lifetime!.View.Initialise());
+ builder = CustomizeAppBuilder(builder);
+ builder.AfterApplicationSetup(_ => _lifetime!.View.Initialise());
Logger.TryGet(LogEventLevel.Debug, LogKey)?.Log(null, "Setup lifetime");
builder.SetupWithLifetime(_lifetime!);
diff --git a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
index 7a07fab2fe..4aeb9fa8f5 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaAppDelegate.cs
@@ -33,20 +33,22 @@ namespace Avalonia.iOS
add { _onDeactivated += value; }
remove { _onDeactivated -= value; }
}
-
+
+ protected virtual AppBuilder CreateAppBuilder() => AppBuilder.Configure().UseiOS();
protected virtual AppBuilder CustomizeAppBuilder(AppBuilder builder) => builder;
-
+
[Export("window")]
public UIWindow? Window { get; set; }
[Export("application:didFinishLaunchingWithOptions:")]
public bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
{
- var builder = AppBuilder.Configure().UseiOS(this);
+ var builder = CreateAppBuilder();
+ builder = CustomizeAppBuilder(builder);
var lifetime = new SingleViewLifetime();
- builder.AfterSetup(_ =>
+ builder.AfterApplicationSetup(_ =>
{
Window = new UIWindow();
@@ -60,8 +62,6 @@ namespace Avalonia.iOS
view.InitWithController(controller);
});
- CustomizeAppBuilder(builder);
-
builder.SetupWithLifetime(lifetime);
Window!.MakeKeyAndVisible();
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index ed04a989d3..43317286c9 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -29,6 +29,7 @@ namespace Avalonia.iOS
{
internal IInputRoot InputRoot
=> _inputRoot ?? throw new InvalidOperationException($"{nameof(IWindowImpl.SetInputRoot)} must have been called");
+ internal TopLevel TopLevel => _topLevel;
private readonly TopLevelImpl _topLevelImpl;
private readonly EmbeddableControlRoot _topLevel;
diff --git a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs
index 49b8e97769..7c56904fdc 100644
--- a/src/iOS/Avalonia.iOS/SingleViewLifetime.cs
+++ b/src/iOS/Avalonia.iOS/SingleViewLifetime.cs
@@ -1,16 +1,45 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.iOS;
-internal class SingleViewLifetime : ISingleViewApplicationLifetime
-{
- public AvaloniaView? View;
+internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime
+{
+ private Control? _mainView;
+ private AvaloniaView? _view;
+
+ public AvaloniaView View
+ {
+ [return: MaybeNull] get => _view!;
+ internal set
+ {
+ if (_view != null)
+ {
+ _view.Content = null;
+ _view.Dispose();
+ }
+ _view = value;
+ _view.Content = _mainView;
+ }
+ }
public Control? MainView
{
- get => View!.Content;
- set => View!.Content = value;
+ get => _mainView;
+ set
+ {
+ if (_mainView != value)
+ {
+ _mainView = value;
+ if (_view != null)
+ {
+ _view.Content = _mainView;
+ }
+ }
+ }
}
+
+ public TopLevel? TopLevel => View?.TopLevel;
}