diff --git a/README.md b/README.md
index 9d0e73c..1ca7b90 100644
--- a/README.md
+++ b/README.md
@@ -7,21 +7,21 @@ Avalonia Inside Shell reduces the complexity of mobile/desktop application devel
A single place to describe the visual hierarchy of an application.
A common navigation user experience.
A URI-based navigation scheme that permits navigation to any page in the application
-
+
We welcome feedback, suggestions, and contributions from anyone who is interested in this project. We appreciate your support and patience as we work towards releasing a stable version of this project.
## Screenshots
-
+
## Installation
To use AvaloniaInside.Shell in your Avalonia project, you can install the package via NuGet using the following command in the Package Manager Console:
```bash
-dotnet add package AvaloniaInside.Shell --version 1.1.3
+dotnet add package AvaloniaInside.Shell --version 1.2.0
```
Alternatively, you can also install the package through Visual Studio's NuGet Package Manager.
@@ -80,7 +80,7 @@ And to navigate to a specific page, we can use the Navigator property of the She
await MyShellView.Navigator.NavigateAsync("/main/home/confirmation", cancellationToken);
```
-### NavigationBar
+### NavigationBar

Each page that is currently on top of the navigation stack has access to the navigation bar's title and navigation item. In hierarchical hosts, the currently selected item in the host will be the one that has access to the navigation bar. For example, in the case of /home/pets/cat, the page associated with the cat would be able to modify the navigation bar. This can be done by setting the NavigationBar.Header and NavigationBar.Item properties, as shown in the code snippet below:
@@ -108,7 +108,7 @@ Each page that is currently on top of the navigation stack has access to the nav
#### Host
`Host` can be used to group pages under a common root, such as a `TabControl`.
-It has a `Page` property that specifies the view associated with the host, as well as an optional `Default` property that specifies the default child route. as well as a `Children` property that specifies any child `Route` objects.
+It has a `Page` property that specifies the view associated with the host, as well as an optional `Default` property that specifies the default child route. as well as a `Children` property that specifies any child `Route` objects.
#### SideMenuItem
@@ -128,7 +128,7 @@ It has a `Page` property that specifies the view associated with the host, as we
...
-
+
@@ -141,13 +141,13 @@ It has a `Page` property that specifies the view associated with the host, as we
-
+
-
+
diff --git a/src/AvaloniaInside.Shell/AvaloniaInside.Shell.csproj b/src/AvaloniaInside.Shell/AvaloniaInside.Shell.csproj
index c77b5b8..a5135a9 100644
--- a/src/AvaloniaInside.Shell/AvaloniaInside.Shell.csproj
+++ b/src/AvaloniaInside.Shell/AvaloniaInside.Shell.csproj
@@ -1,9 +1,9 @@
- net6.0;net7.0;net8.0
+ net6.0;net8.0
enable
latest
- 1.1.3
+ 1.2.0
Shell view for Avalonia
Shell reduces the complexity of mobile/desktop application development by providing the fundamental features that most applications require
AvaloniaInside
diff --git a/src/AvaloniaInside.Shell/Default.axaml b/src/AvaloniaInside.Shell/Default.axaml
index 9f8b677..87f9ab4 100644
--- a/src/AvaloniaInside.Shell/Default.axaml
+++ b/src/AvaloniaInside.Shell/Default.axaml
@@ -1,41 +1,20 @@
-
-
+
+
+
+
+
+ White
+
+
+ Black
+ Black
+
+
+
+
+
+
+
+
diff --git a/src/AvaloniaInside.Shell/Page.cs b/src/AvaloniaInside.Shell/Page.cs
index b43807c..d0408e8 100644
--- a/src/AvaloniaInside.Shell/Page.cs
+++ b/src/AvaloniaInside.Shell/Page.cs
@@ -1,4 +1,5 @@
-using Avalonia.Controls;
+using System;
+using Avalonia.Controls;
using System.Threading;
using System.Threading.Tasks;
@@ -8,6 +9,8 @@ public class Page : UserControl, INavigationLifecycle, INavigatorLifecycle
public ShellView? Shell { get; internal set; }
public INavigator? Navigator => Shell?.Navigator;
+ protected override Type StyleKeyOverride => typeof(Page);
+
public virtual Task AppearAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public virtual Task ArgumentAsync(object args, CancellationToken cancellationToken) => Task.CompletedTask;
public virtual Task DisappearAsync(CancellationToken cancellationToken) => Task.CompletedTask;
diff --git a/src/AvaloniaInside.Shell/Platform/Android/AndroidDefaultPageSlide.cs b/src/AvaloniaInside.Shell/Platform/Android/AndroidDefaultPageSlide.cs
index bbbda92..0aa805f 100644
--- a/src/AvaloniaInside.Shell/Platform/Android/AndroidDefaultPageSlide.cs
+++ b/src/AvaloniaInside.Shell/Platform/Android/AndroidDefaultPageSlide.cs
@@ -35,7 +35,7 @@ public class AndroidDefaultPageSlide : PlatformBasePageTransition
///
public override Easing Easing { get; set; } = new FastOutExtraSlowInEasing();
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
diff --git a/src/AvaloniaInside.Shell/Platform/Android/MaterialListPageSlide.cs b/src/AvaloniaInside.Shell/Platform/Android/MaterialListPageSlide.cs
index 6618499..3f048d8 100644
--- a/src/AvaloniaInside.Shell/Platform/Android/MaterialListPageSlide.cs
+++ b/src/AvaloniaInside.Shell/Platform/Android/MaterialListPageSlide.cs
@@ -23,7 +23,7 @@ public class MaterialListPageSlide : PlatformBasePageTransition
///
public override Easing Easing { get; set; } = new FastOutExtraSlowInEasing();
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
diff --git a/src/AvaloniaInside.Shell/Platform/Ios/AlertTransition.cs b/src/AvaloniaInside.Shell/Platform/Ios/AlertTransition.cs
new file mode 100644
index 0000000..e1235fe
--- /dev/null
+++ b/src/AvaloniaInside.Shell/Platform/Ios/AlertTransition.cs
@@ -0,0 +1,143 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Animation.Easings;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Animations;
+
+namespace AvaloniaInside.Shell.Platform.Ios;
+
+public class AlertTransition : PlatformBasePageTransition
+{
+ public static readonly AlertTransition Instance = new();
+
+ private const float EndingCue = 0.9f;
+ private const float StartingCue = 0.1f;
+
+ ///
+ /// Gets the duration of the animation.
+ ///
+ public override TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(.25);
+
+ ///
+ /// Scale factor to animate in
+ ///
+ public float ZoomInFactor { get; set; } = 1f;
+
+ ///
+ /// Scale factor to animate out
+ ///
+ public float ZoomOutFactor { get; set; } = 1.1f;
+
+ ///
+ /// Gets or sets element entrance easing.
+ ///
+ public override Easing Easing { get; set; } = Easing.Parse("1.0, 0.0, 0.0, 0.85");
+
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(StartingCue, new Vector3D(ZoomOutFactor, ZoomOutFactor, 1), Easing);
+ scaleAnimation.InsertKeyFrame(1.0f, new Vector3D(1, 1, 1), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(StartingCue, 0f, Easing);
+ fadeAnimation.InsertKeyFrame(1.0f, 1f, Easing);
+
+ var enteranceAnimation = compositor.CreateAnimationGroup();
+ enteranceAnimation.Add(scaleAnimation);
+ enteranceAnimation.Add(fadeAnimation);
+ return enteranceAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateExitAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(0f, new Vector3D(1, 1, 1), Easing);
+ scaleAnimation.InsertKeyFrame(EndingCue, new Vector3D(ZoomOutFactor, ZoomOutFactor, 1), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(0f, 1f, Easing);
+ fadeAnimation.InsertKeyFrame(EndingCue, 0f, Easing);
+
+ var exitAnimation = compositor.CreateAnimationGroup();
+ exitAnimation.Add(scaleAnimation);
+ exitAnimation.Add(fadeAnimation);
+ return exitAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateSendBackAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(0f, new Vector3D(1, 1, 1), Easing);
+ scaleAnimation.InsertKeyFrame(EndingCue, new Vector3D(ZoomInFactor, ZoomInFactor, 1), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(0f, 1f, Easing);
+ fadeAnimation.InsertKeyFrame(EndingCue, 0f, Easing);
+
+ var sendBackAnimation = compositor.CreateAnimationGroup();
+ sendBackAnimation.Add(scaleAnimation);
+ sendBackAnimation.Add(fadeAnimation);
+ return sendBackAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateBringBackAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(StartingCue, new Vector3D(ZoomInFactor, ZoomInFactor, 1), Easing);
+ scaleAnimation.InsertKeyFrame(1f, new Vector3D(1, 1, 1), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(StartingCue, 0f, Easing);
+ fadeAnimation.InsertKeyFrame(1f, 1f, Easing);
+
+ var bringBackAnimation = compositor.CreateAnimationGroup();
+ bringBackAnimation.Add(scaleAnimation);
+ bringBackAnimation.Add(fadeAnimation);
+ return bringBackAnimation;
+ }
+
+ protected override Task RunAnimationAsync(
+ CompositionVisual parentComposition,
+ CompositionVisual? fromElement,
+ CompositionVisual? toElement,
+ bool forward,
+ double distance,
+ double heightDistance,
+ CancellationToken cancellationToken)
+ {
+ if (toElement != null)
+ toElement.CenterPoint = new Vector3D(parentComposition.Size.X * 0.5, parentComposition.Size.Y * 0.5, 0);
+
+ if (fromElement != null)
+ fromElement.CenterPoint = new Vector3D(parentComposition.Size.X * 0.5, parentComposition.Size.Y * 0.5, 0);
+
+ return base.RunAnimationAsync(parentComposition, fromElement, toElement, forward, distance, heightDistance, cancellationToken);
+ }
+}
diff --git a/src/AvaloniaInside.Shell/Platform/Ios/IosModalSlide.cs b/src/AvaloniaInside.Shell/Platform/Ios/IosModalSlide.cs
new file mode 100644
index 0000000..2103524
--- /dev/null
+++ b/src/AvaloniaInside.Shell/Platform/Ios/IosModalSlide.cs
@@ -0,0 +1,136 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Animations;
+
+namespace AvaloniaInside.Shell.Platform.Ios;
+
+public class IosModalSlide : PlatformBasePageTransition
+{
+ public static readonly IosModalSlide Instance = new();
+
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ offsetAnimation.Duration = Duration;
+ offsetAnimation.Target = nameof(element.Offset);
+ offsetAnimation.InsertKeyFrame(0f, new Vector3D(0, heightDistance, 0), Easing);
+ offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(0, 40, 0), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(0f, 0.9f);
+ fadeAnimation.InsertKeyFrame(0.5f, 1f);
+
+ var entranceAnimation = compositor.CreateAnimationGroup();
+ entranceAnimation.Add(offsetAnimation);
+ entranceAnimation.Add(fadeAnimation);
+ return entranceAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateExitAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ ShouldHideAfterExit = false;
+
+ var compositor = element.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ offsetAnimation.Duration = Duration;
+ offsetAnimation.Target = nameof(element.Offset);
+ offsetAnimation.InsertKeyFrame(0f, new Vector3D(0, 40, 0), Easing);
+ offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(0, heightDistance, 0), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(.5f, 1f);
+ fadeAnimation.InsertKeyFrame(1f, 0.9f);
+
+ var exitAnimation = compositor.CreateAnimationGroup();
+ exitAnimation.Add(offsetAnimation);
+ exitAnimation.Add(fadeAnimation);
+ return exitAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateSendBackAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ ShouldHideAfterExit = false;
+
+ var compositor = element.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ offsetAnimation.Duration = Duration;
+ offsetAnimation.Target = nameof(element.Offset);
+ offsetAnimation.InsertKeyFrame(0f, new Vector3D(0, 0, 0), Easing);
+ offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(0, 10, 0), Easing);
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(0f, new Vector3D(1f, 1f, 0), Easing);
+ scaleAnimation.InsertKeyFrame(1f, new Vector3D(.9f, .97f, 0), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(.5f, 1f);
+ fadeAnimation.InsertKeyFrame(1f, 0.8f);
+
+ var sendBackAnimation = compositor.CreateAnimationGroup();
+ sendBackAnimation.Add(offsetAnimation);
+ sendBackAnimation.Add(scaleAnimation);
+ sendBackAnimation.Add(fadeAnimation);
+ return sendBackAnimation;
+ }
+
+ protected override CompositionAnimationGroup GetOrCreateBringBackAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ {
+ var compositor = element.Compositor;
+
+ var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ offsetAnimation.Duration = Duration;
+ offsetAnimation.Target = nameof(element.Offset);
+ offsetAnimation.InsertKeyFrame(0f, new Vector3D(0, 10, 0), Easing);
+ offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(0, 0, 0), Easing);
+
+ var scaleAnimation = compositor.CreateVector3DKeyFrameAnimation();
+ scaleAnimation.Duration = Duration;
+ scaleAnimation.Target = nameof(element.Scale);
+ scaleAnimation.InsertKeyFrame(0f, new Vector3D(.9f, .97f, 0), Easing);
+ scaleAnimation.InsertKeyFrame(1.0f, new Vector3D(1f, 1f, 0), Easing);
+
+ var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
+ fadeAnimation.Duration = Duration;
+ fadeAnimation.Target = nameof(element.Opacity);
+ fadeAnimation.InsertKeyFrame(0f, 0.9f);
+ fadeAnimation.InsertKeyFrame(0.5f, 1f);
+
+ var bringBackAnimation = compositor.CreateAnimationGroup();
+ bringBackAnimation.Add(offsetAnimation);
+ bringBackAnimation.Add(scaleAnimation);
+ bringBackAnimation.Add(fadeAnimation);
+ return bringBackAnimation;
+ }
+
+ protected override Task RunAnimationAsync(
+ CompositionVisual parentComposition,
+ CompositionVisual? fromElement,
+ CompositionVisual? toElement,
+ bool forward,
+ double distance,
+ double heightDistance,
+ CancellationToken cancellationToken)
+ {
+ if (toElement != null)
+ toElement.CenterPoint = new Vector3D(parentComposition.Size.X * 0.5, parentComposition.Size.Y * 0.5, 0);
+
+ if (fromElement != null)
+ fromElement.CenterPoint = new Vector3D(parentComposition.Size.X * 0.5, parentComposition.Size.Y * 0.5, 0);
+
+ return base.RunAnimationAsync(parentComposition, fromElement, toElement, forward, distance, heightDistance, cancellationToken);
+ }
+}
diff --git a/src/AvaloniaInside.Shell/Platform/Ios/IosPageSlide.cs b/src/AvaloniaInside.Shell/Platform/Ios/IosPageSlide.cs
index 2a22fe6..1c5502f 100644
--- a/src/AvaloniaInside.Shell/Platform/Ios/IosPageSlide.cs
+++ b/src/AvaloniaInside.Shell/Platform/Ios/IosPageSlide.cs
@@ -1,4 +1,5 @@
-using Avalonia;
+using System;
+using Avalonia;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
@@ -6,9 +7,12 @@ namespace AvaloniaInside.Shell.Platform.Ios;
public class DefaultIosPageSlide : PlatformBasePageTransition
{
+ private const float ScaleOutDuration = .8f;
+
public static readonly DefaultIosPageSlide Instance = new();
+ public override TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(.25);
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
@@ -35,13 +39,13 @@ protected override CompositionAnimationGroup GetOrCreateExitAnimation(Compositio
var compositor = element.Compositor;
var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
- offsetAnimation.Duration = Duration;
+ offsetAnimation.Duration = Duration * ScaleOutDuration;
offsetAnimation.Target = nameof(element.Offset);
offsetAnimation.InsertKeyFrame(0f, new Vector3D(0, 0, 0), Easing);
offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(widthDistance, 0, 0), Easing);
var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
- fadeAnimation.Duration = Duration;
+ fadeAnimation.Duration = Duration * ScaleOutDuration;
fadeAnimation.Target = nameof(element.Opacity);
fadeAnimation.InsertKeyFrame(.5f, 1f);
fadeAnimation.InsertKeyFrame(1f, 0.9f);
@@ -79,13 +83,13 @@ protected override CompositionAnimationGroup GetOrCreateBringBackAnimation(Compo
var compositor = element.Compositor;
var offsetAnimation = compositor.CreateVector3DKeyFrameAnimation();
- offsetAnimation.Duration = Duration;
+ offsetAnimation.Duration = Duration * ScaleOutDuration;
offsetAnimation.Target = nameof(element.Offset);
offsetAnimation.InsertKeyFrame(0f, new Vector3D(widthDistance / -4d, 0, 0), Easing);
offsetAnimation.InsertKeyFrame(1.0f, new Vector3D(0, 0, 0), Easing);
var fadeAnimation = compositor.CreateScalarKeyFrameAnimation();
- fadeAnimation.Duration = Duration;
+ fadeAnimation.Duration = Duration * ScaleOutDuration;
fadeAnimation.Target = nameof(element.Opacity);
fadeAnimation.InsertKeyFrame(0f, .9f);
fadeAnimation.InsertKeyFrame(1f, 1f);
diff --git a/src/AvaloniaInside.Shell/Platform/PlatformBasePageTransition.cs b/src/AvaloniaInside.Shell/Platform/PlatformBasePageTransition.cs
index 959093b..379f682 100644
--- a/src/AvaloniaInside.Shell/Platform/PlatformBasePageTransition.cs
+++ b/src/AvaloniaInside.Shell/Platform/PlatformBasePageTransition.cs
@@ -11,12 +11,12 @@
namespace AvaloniaInside.Shell.Platform;
public abstract class PlatformBasePageTransition : IPageTransition
{
- private CompositionAnimationGroup? _enteranceAnimation;
+ private CompositionAnimationGroup? _entranceAnimation;
private CompositionAnimationGroup? _exitAnimation;
private CompositionAnimationGroup? _sendBackAnimation;
private CompositionAnimationGroup? _bringBackAnimation;
- private double _lastDistance = 0;
+ private readonly double _lastDistance = 0;
///
/// Gets the duration of the animation.
@@ -28,7 +28,9 @@ public abstract class PlatformBasePageTransition : IPageTransition
///
public virtual Easing Easing { get; set; } = Easing.Parse("0.42, 0.0, 0.58, 1.0");
- protected abstract CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double distance, double heightDistance);
+ protected virtual bool ShouldHideAfterExit { get; set; } = true;
+
+ protected abstract CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double distance, double heightDistance);
protected abstract CompositionAnimationGroup GetOrCreateExitAnimation(CompositionVisual element, double distance, double heightDistance);
@@ -52,7 +54,7 @@ public async Task Start(Visual? from, Visual? to, bool forward, CancellationToke
if (distance != _lastDistance)
{
- _enteranceAnimation = null;
+ _entranceAnimation = null;
_exitAnimation = null;
_sendBackAnimation = null;
_bringBackAnimation = null;
@@ -60,7 +62,7 @@ public async Task Start(Visual? from, Visual? to, bool forward, CancellationToke
if (to != null) to.IsVisible = true;
await RunAnimationAsync(parentComposition, fromElement, toElement, forward, parent.Bounds.Width, parent.Bounds.Height, cancellationToken);
- if (from != null) from.IsVisible = false;
+ if (from != null && ShouldHideAfterExit) from.IsVisible = false;
}
protected virtual Task RunAnimationAsync(
@@ -75,7 +77,7 @@ protected virtual Task RunAnimationAsync(
if (toElement != null)
{
var animation = forward
- ? _enteranceAnimation ??= GetOrCreateEnteranceAnimation(parentComposition, widthDistance, heightDistance)
+ ? _entranceAnimation ??= GetOrCreateEntranceAnimation(parentComposition, widthDistance, heightDistance)
: _bringBackAnimation ??= GetOrCreateBringBackAnimation(parentComposition, widthDistance, heightDistance);
toElement.StartAnimationGroup(animation);
diff --git a/src/AvaloniaInside.Shell/Platform/PlatformSetup.cs b/src/AvaloniaInside.Shell/Platform/PlatformSetup.cs
index e328dc7..5b06317 100644
--- a/src/AvaloniaInside.Shell/Platform/PlatformSetup.cs
+++ b/src/AvaloniaInside.Shell/Platform/PlatformSetup.cs
@@ -49,4 +49,20 @@ public static IPageTransition? TransitionForTab
return MaterialListPageSlide.Instance;
}
}
+
+ public static IPageTransition TransitionForModal
+ {
+ get
+ {
+ if (OperatingSystem.IsAndroid())
+ return AndroidDefaultPageSlide.Instance;
+ if (OperatingSystem.IsIOS())
+ return AlertTransition.Instance;
+ if (OperatingSystem.IsWindows())
+ return DrillInNavigationTransition.Instance;;
+
+ //Default for the moment
+ return DrillInNavigationTransition.Instance;
+ }
+ }
}
diff --git a/src/AvaloniaInside.Shell/Platform/Windows/DrillInNavigationTransition.cs b/src/AvaloniaInside.Shell/Platform/Windows/DrillInNavigationTransition.cs
index 9694455..fb3122e 100644
--- a/src/AvaloniaInside.Shell/Platform/Windows/DrillInNavigationTransition.cs
+++ b/src/AvaloniaInside.Shell/Platform/Windows/DrillInNavigationTransition.cs
@@ -35,7 +35,7 @@ public class DrillInNavigationTransition : PlatformBasePageTransition
///
public override Easing Easing { get; set; } = Easing.Parse("0.85, 0.0, 0.0, 1.0");
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
diff --git a/src/AvaloniaInside.Shell/Platform/Windows/EntranceNavigationTransition.cs b/src/AvaloniaInside.Shell/Platform/Windows/EntranceNavigationTransition.cs
index 17423d4..425794d 100644
--- a/src/AvaloniaInside.Shell/Platform/Windows/EntranceNavigationTransition.cs
+++ b/src/AvaloniaInside.Shell/Platform/Windows/EntranceNavigationTransition.cs
@@ -31,7 +31,7 @@ public class EntranceNavigationTransition : PlatformBasePageTransition
public Easing Easing2 { get; set; } = Easing.Parse("0.85, 0.0, 0.75, 1.0");
public Easing Easing3 { get; set; } = Easing.Parse("0.85, 0.0, 0.0, 1.0");
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
diff --git a/src/AvaloniaInside.Shell/Platform/Windows/ListSlideNavigationTransition.cs b/src/AvaloniaInside.Shell/Platform/Windows/ListSlideNavigationTransition.cs
index 73a9bb3..d93a466 100644
--- a/src/AvaloniaInside.Shell/Platform/Windows/ListSlideNavigationTransition.cs
+++ b/src/AvaloniaInside.Shell/Platform/Windows/ListSlideNavigationTransition.cs
@@ -18,7 +18,7 @@ public class ListSlideNavigationTransition : PlatformBasePageTransition
public float FadeFactor { get; set; } = 0.7f;
- protected override CompositionAnimationGroup GetOrCreateEnteranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
+ protected override CompositionAnimationGroup GetOrCreateEntranceAnimation(CompositionVisual element, double widthDistance, double heightDistance)
{
var compositor = element.Compositor;
diff --git a/src/AvaloniaInside.Shell/ShellView.cs b/src/AvaloniaInside.Shell/ShellView.cs
index 750124e..90e0ce0 100644
--- a/src/AvaloniaInside.Shell/ShellView.cs
+++ b/src/AvaloniaInside.Shell/ShellView.cs
@@ -8,6 +8,7 @@
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Threading;
+using AvaloniaInside.Shell.Platform;
using AvaloniaInside.Shell.Platform.Windows;
using ReactiveUI;
using Splat;
@@ -180,7 +181,7 @@ public bool ApplyBottomSafePadding
#endregion
- #region ApplyBottomSafePadding
+ #region DefaultPageTransition
///
/// Defines the property.
@@ -188,7 +189,7 @@ public bool ApplyBottomSafePadding
public static readonly StyledProperty DefaultPageTransitionProperty =
AvaloniaProperty.Register(
nameof(DefaultPageTransition),
- defaultValue: new DrillInNavigationTransition());
+ defaultValue: PlatformSetup.TransitionForPage);
///
/// Gets or sets the animation played when content appears and disappears.
@@ -201,6 +202,27 @@ public IPageTransition? DefaultPageTransition
#endregion
+ #region ModalPageTransition
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty ModalPageTransitionProperty =
+ AvaloniaProperty.Register(
+ nameof(ModalPageTransition),
+ defaultValue: PlatformSetup.TransitionForModal);
+
+ ///
+ /// Gets or sets the animation played when content appears and disappears.
+ ///
+ public IPageTransition? ModalPageTransition
+ {
+ get => GetValue(ModalPageTransitionProperty);
+ set => SetValue(ModalPageTransitionProperty, value);
+ }
+
+ #endregion
+
#endregion
#region Attached properties
diff --git a/src/AvaloniaInside.Shell/StackContentView.cs b/src/AvaloniaInside.Shell/StackContentView.cs
index a4e0601..192fb50 100644
--- a/src/AvaloniaInside.Shell/StackContentView.cs
+++ b/src/AvaloniaInside.Shell/StackContentView.cs
@@ -1,4 +1,3 @@
-using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -6,199 +5,117 @@
using Avalonia.Animation;
using Avalonia.Controls;
using AvaloniaInside.Shell.Platform;
-using AvaloniaInside.Shell.Platform.Windows;
namespace AvaloniaInside.Shell;
-public class StackContentViewPanel : Panel
+public class StackContentView : Panel
{
- public Task? AnimationToBeDone { get; set; }
-
- protected override Size ArrangeOverride(Size finalSize)
- {
- var rcChild = new Rect(finalSize);
- var zindex = 0;
- foreach (var control in Children)
- {
- control.ZIndex = zindex++;
- control.Arrange(rcChild);
- }
-
- return finalSize;
- }
-
- protected override async void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
- {
- if (e.Action == NotifyCollectionChangedAction.Remove && AnimationToBeDone != null)
- {
- await AnimationToBeDone;
- AnimationToBeDone = null;
- }
- base.ChildrenChanged(sender, e);
- }
-}
-
-public class StackContentViewItem : ContentControl
-{
-
-}
-
-public class StackContentView : ItemsControl
-{
- private readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
- private NavigateType? _pendingNavigateType;
- private Control? _lastContainer;
- public static readonly StyledProperty HasContentProperty =
- AvaloniaProperty.Register(nameof(HasContent));
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty PageTransitionProperty =
- AvaloniaProperty.Register(
- nameof(PageTransition),
- defaultValue: PlatformSetup.TransitionForPage);
-
- ///
- /// Gets or sets the animation played when content appears and disappears.
- ///
- public IPageTransition? PageTransition
- {
- get => GetValue(PageTransitionProperty);
- set => SetValue(PageTransitionProperty, value);
- }
-
- public StackContentView()
- {
- //ItemsPanel = new ItemsPanelTemplate() { Content = new StackContentViewPanel() };
- }
-
- protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) =>
- new StackContentViewItem();
-
- protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) =>
- NeedsContainer(item, out recycleKey);
-
- protected override void PrepareContainerForItemOverride(Control element, object? item, int index)
- {
- base.PrepareContainerForItemOverride(element, item, index);
- }
-
- protected override void ContainerForItemPreparedOverride(Control container, object? item, int index)
- {
- base.ContainerForItemPreparedOverride(container, item, index);
-
- if (index == Items.Count - 1)
- {
- _lastContainer = container;
- _ = PageTransition?.Start(
- Items.Count > 1 ? ContainerFromIndex(index - 1) : null,
- container,
- true,
- CancellationToken.None);
- }
- }
-
- protected override async void ClearContainerForItemOverride(Control container)
- {
- var currentContainer = ContainerFromIndex(Items.Count - 1);
-
- if (_lastContainer != container)
- {
- base.ClearContainerForItemOverride(container);
- return;
- }
-
- var task = (PageTransition?.Start(
- container,
- currentContainer,
- false,
- CancellationToken.None) ?? Task.CompletedTask);
-
- if (ItemsPanelRoot is StackContentViewPanel panel)
- panel.AnimationToBeDone = task;
-
- await task;
-
- base.ClearContainerForItemOverride(container);
- _lastContainer = currentContainer;
- }
-
- public bool HasContent
- {
- get => GetValue(HasContentProperty);
- private set => SetValue(HasContentProperty, value);
- }
-
- public object? CurrentView => Items.LastOrDefault();
-
- public async Task PushViewAsync(object view,
- NavigateType navigateType,
- CancellationToken cancellationToken = default)
- {
- await _semaphoreSlim.WaitAsync(cancellationToken);
- try
- {
- var current = CurrentView;
-
- if (current != null && current == view) return;
- if (view is not Control control) return;
-
- // Bring to front if exists in collection
- if (Items.Contains(control))
- Items.Remove(control);
- Items.Add(control);
-
- UpdateCurrentView(current, control, navigateType, false);
-
- await OnContentUpdateAsync(control, cancellationToken);
- }
- finally
- {
- _semaphoreSlim.Release();
- }
- }
-
- protected virtual void UpdateCurrentView(object? from, object? to, NavigateType navigateType, bool removed)
- {
- //TODO: Apply specific animation type
- }
-
- public async Task RemoveViewAsync(object view, NavigateType navigateType, CancellationToken cancellationToken)
- {
- await _semaphoreSlim.WaitAsync(cancellationToken);
- try
- {
- if (!Items.Contains(view)) return false;
- var from = CurrentView;
-
- Items.Remove(view as Control);
-
- var to = CurrentView;
- if (from != to)
- {
- UpdateCurrentView(from, to, navigateType, true);
- await OnContentUpdateAsync(CurrentView, cancellationToken);
- }
- return true;
- }
- finally
- {
- _semaphoreSlim.Release();
- }
- }
-
- protected virtual Task OnContentUpdateAsync(object? view, CancellationToken cancellationToken)
- {
- HasContent = Items.Count > 0;
- return Task.CompletedTask;
- }
-
- public Task ClearStackAsync(CancellationToken cancellationToken)
- {
- while (Items.Count > 1)
- Items.RemoveAt(0);
-
- return Task.CompletedTask;
- }
+ private readonly SemaphoreSlim _semaphoreSlim = new(1, 1);
+ private NavigateType? _pendingNavigateType;
+
+ public static readonly StyledProperty HasContentProperty =
+ AvaloniaProperty.Register(nameof(HasContent));
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty PageTransitionProperty =
+ AvaloniaProperty.Register(
+ nameof(PageTransition),
+ defaultValue: PlatformSetup.TransitionForPage);
+
+ ///
+ /// Gets or sets the animation played when content appears and disappears.
+ ///
+ public IPageTransition? PageTransition
+ {
+ get => GetValue(PageTransitionProperty);
+ set => SetValue(PageTransitionProperty, value);
+ }
+
+ public bool HasContent
+ {
+ get => GetValue(HasContentProperty);
+ private set => SetValue(HasContentProperty, value);
+ }
+
+ public object? CurrentView => Children.LastOrDefault();
+
+ public async Task PushViewAsync(object view,
+ NavigateType navigateType,
+ CancellationToken cancellationToken = default)
+ {
+ await _semaphoreSlim.WaitAsync(cancellationToken);
+ try
+ {
+ var current = CurrentView;
+
+ if (current != null && current == view) return;
+ if (view is not Control control) return;
+
+ // Bring to front if exists in collection
+ if (Children.Contains(control))
+ Children.Remove(control);
+ Children.Add(control);
+
+ await OnContentUpdateAsync(control, cancellationToken);
+ await UpdateCurrentViewAsync(current, control, navigateType, false, cancellationToken);
+ }
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
+ }
+
+ protected virtual Task UpdateCurrentViewAsync(
+ object? from,
+ object? to,
+ NavigateType navigateType,
+ bool removed,
+ CancellationToken cancellationToken)
+ {
+ return PageTransition?.Start(
+ from as Visual,
+ to as Visual,
+ !removed,
+ cancellationToken) ?? Task.CompletedTask;
+ }
+
+ public async Task RemoveViewAsync(object view, NavigateType navigateType, CancellationToken cancellationToken)
+ {
+ await _semaphoreSlim.WaitAsync(cancellationToken);
+ try
+ {
+ if (!Children.Contains(view)) return false;
+
+ if (CurrentView == view)
+ {
+ var to = Children.Count > 1 ? Children[^2] : null;
+ await UpdateCurrentViewAsync(view, to, navigateType, true, cancellationToken);
+ }
+
+ Children.Remove(view as Control);
+ await OnContentUpdateAsync(CurrentView, cancellationToken);
+
+ return true;
+ }
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
+ }
+
+ protected virtual Task OnContentUpdateAsync(object? view, CancellationToken cancellationToken)
+ {
+ HasContent = Children.Count > 0;
+ return Task.CompletedTask;
+ }
+
+ public Task ClearStackAsync(CancellationToken cancellationToken)
+ {
+ while (Children.Count > 1)
+ Children.RemoveAt(0);
+
+ return Task.CompletedTask;
+ }
}
diff --git a/src/AvaloniaInside.Shell/Theme/StackContentView.axaml b/src/AvaloniaInside.Shell/Theme/StackContentView.axaml
index 665cd26..b6eb5ba 100644
--- a/src/AvaloniaInside.Shell/Theme/StackContentView.axaml
+++ b/src/AvaloniaInside.Shell/Theme/StackContentView.axaml
@@ -1,21 +1,6 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 5f86bb8..78dcb90 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,5 +1,5 @@
- 11.0.10
+ 11.1.3
diff --git a/src/Example/ShellBottomCustomNavigator/Directory.Build.props b/src/Example/ShellBottomCustomNavigator/Directory.Build.props
deleted file mode 100644
index 74d63f7..0000000
--- a/src/Example/ShellBottomCustomNavigator/Directory.Build.props
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- enable
- 11.0.7
-
-
diff --git a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Android/MainActivity.cs b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Android/MainActivity.cs
index d235bbd..4f9e311 100644
--- a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Android/MainActivity.cs
+++ b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Android/MainActivity.cs
@@ -3,6 +3,7 @@
using Avalonia;
using Avalonia.Android;
using Avalonia.ReactiveUI;
+using AvaloniaInside.Shell;
namespace ShellBottomCustomNavigator.Android;
@@ -18,6 +19,7 @@ protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.WithInterFont()
+ .UseShell()
.UseReactiveUI();
}
-}
\ No newline at end of file
+}
diff --git a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Browser/Program.cs b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Browser/Program.cs
index 83b77a7..dd4bcb1 100644
--- a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Browser/Program.cs
+++ b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.Browser/Program.cs
@@ -3,6 +3,7 @@
using Avalonia;
using Avalonia.Browser;
using Avalonia.ReactiveUI;
+using AvaloniaInside.Shell;
using ShellBottomCustomNavigator;
[assembly: SupportedOSPlatform("browser")]
@@ -12,8 +13,9 @@ internal sealed partial class Program
private static Task Main(string[] args) => BuildAvaloniaApp()
.WithInterFont()
.UseReactiveUI()
+ .UseShell()
.StartBrowserAppAsync("out");
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure();
-}
\ No newline at end of file
+}
diff --git a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.iOS/AppDelegate.cs b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.iOS/AppDelegate.cs
index 759995f..c917df7 100644
--- a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.iOS/AppDelegate.cs
+++ b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator.iOS/AppDelegate.cs
@@ -5,11 +5,12 @@
using Avalonia.iOS;
using Avalonia.Media;
using Avalonia.ReactiveUI;
+using AvaloniaInside.Shell;
namespace ShellBottomCustomNavigator.iOS;
-// The UIApplicationDelegate for the application. This class is responsible for launching the
-// User Interface of the application, as well as listening (and optionally responding) to
+// The UIApplicationDelegate for the application. This class is responsible for launching the
+// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
#pragma warning disable CA1711 // Identifiers should not have incorrect suffix
@@ -20,6 +21,7 @@ protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
{
return base.CustomizeAppBuilder(builder)
.WithInterFont()
+ .UseShell()
.UseReactiveUI();
}
-}
\ No newline at end of file
+}
diff --git a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/ShellBottomCustomNavigator.csproj b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/ShellBottomCustomNavigator.csproj
index 765b923..680219e 100644
--- a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/ShellBottomCustomNavigator.csproj
+++ b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/ShellBottomCustomNavigator.csproj
@@ -12,12 +12,12 @@
-
-
-
-
+
+
+
+
-
+
diff --git a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/Styles.axaml b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/Styles.axaml
index 526dd69..e723cff 100644
--- a/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/Styles.axaml
+++ b/src/Example/ShellBottomCustomNavigator/ShellBottomCustomNavigator/Styles.axaml
@@ -36,7 +36,7 @@
-
+
@@ -75,7 +75,7 @@
-
-
-
+
-->