Added ContextMenu/Flyout animations. Added platform services. Use bitmap cache for all views.

This commit is contained in:
2025-03-18 09:31:32 -04:00
parent 7c70eb3814
commit 9214e97100
17 changed files with 649 additions and 169 deletions

View File

@@ -0,0 +1,103 @@
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
using System;
using System.Threading.Tasks;
namespace Harmonia.UI.Controls;
public class AnimatedMenuFlyout : MenuFlyout
{
protected override Control CreatePresenter()
{
Control presenter = base.CreatePresenter();
presenter.AttachedToVisualTree += OnPresenterAttachedToVisualTree;
return presenter;
}
private async void OnPresenterAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{
if (sender is not MenuFlyoutPresenter menuFlyoutPresenter)
return;
await ApplyAnimationAsync(menuFlyoutPresenter);
}
private async Task ApplyAnimationAsync(MenuFlyoutPresenter presenter)
{
double translateYStart = ShouldSlideDown(presenter) ? - 100 : 100;
Animation animation = new()
{
Duration = TimeSpan.FromMilliseconds(200),
Children =
{
new KeyFrame
{
Cue = new Cue(0f),
Setters =
{
new Setter(Visual.OpacityProperty, 0.0),
new Setter(TranslateTransform.YProperty, translateYStart)
}
},
new KeyFrame
{
Cue = new Cue(1f),
Setters =
{
new Setter(Visual.OpacityProperty, 1.0),
new Setter(TranslateTransform.YProperty, 0.0)
}
}
}
};
await animation.RunAsync(presenter);
}
private static bool ShouldSlideDown(MenuFlyoutPresenter presenter)
{
if (presenter.Parent is not Control targetControl)
return true;
Rect? topLevelBounds = GetTopLevelBounds();
if (topLevelBounds == null)
return true;
Rect targetBounds = targetControl.Bounds;
double availableSpaceBelow = topLevelBounds.Value.Height - (targetBounds.Y + targetBounds.Height);
double availableSpaceAbove = targetBounds.Y;
return availableSpaceBelow >= availableSpaceAbove; // Slide down if more space below
}
private static Rect? GetTopLevelBounds()
{
//Desktop
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
return desktop.MainWindow?.Bounds;
}
//Android (and iOS?)
else if (Application.Current?.ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
IRenderRoot? visualRoot = singleViewPlatform.MainView?.GetVisualRoot();
if (visualRoot is TopLevel topLevel)
{
return topLevel.Bounds;
}
}
return null;
}
}