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; } }