Files
maui-linux/AnimationManager.cs
Dave Friedel f6eadaad57 Verify Views files against decompiled, extract embedded types
Fixed files:
- SkiaImageButton.cs: Added SVG support with multi-path search
- SkiaNavigationPage.cs: Added LinuxApplication.IsGtkMode check
- SkiaRefreshView.cs: Added ICommand support (Command, CommandParameter)
- SkiaTemplatedView.cs: Added missing using statements

Extracted embedded types to separate files (matching decompiled pattern):
- From SkiaMenuBar.cs: MenuBarItem, MenuItem, SkiaMenuFlyout, MenuItemClickedEventArgs
- From SkiaNavigationPage.cs: NavigationEventArgs
- From SkiaTabbedPage.cs: TabItem
- From SkiaVisualStateManager.cs: SkiaVisualStateGroupList, SkiaVisualStateGroup, SkiaVisualState, SkiaVisualStateSetter
- From SkiaSwipeView.cs: SwipeItem, SwipeStartedEventArgs, SwipeEndedEventArgs
- From SkiaFlyoutPage.cs: FlyoutLayoutBehavior (already separate)
- From SkiaIndicatorView.cs: IndicatorShape (already separate)
- From SkiaBorder.cs: SkiaFrame
- From SkiaCarouselView.cs: PositionChangedEventArgs
- From SkiaCollectionView.cs: SkiaSelectionMode, ItemsLayoutOrientation
- From SkiaContentPresenter.cs: LayoutAlignment

Verified matching decompiled:
- SkiaContextMenu.cs, SkiaFlexLayout.cs, SkiaGraphicsView.cs

Build: 0 errors

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 17:02:39 -05:00

183 lines
5.8 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace Microsoft.Maui.Platform.Linux;
public static class AnimationManager
{
private class RunningAnimation
{
public required SkiaView View { get; set; }
public string PropertyName { get; set; } = "";
public double StartValue { get; set; }
public double EndValue { get; set; }
public DateTime StartTime { get; set; }
public uint Duration { get; set; }
public Easing Easing { get; set; } = Easing.Linear;
public required TaskCompletionSource<bool> Completion { get; set; }
public CancellationToken Token { get; set; }
}
private static readonly List<RunningAnimation> _animations = new();
private static bool _isRunning;
private static CancellationTokenSource? _cts;
private static void EnsureRunning()
{
if (!_isRunning)
{
_isRunning = true;
_cts = new CancellationTokenSource();
_ = RunAnimationLoop(_cts.Token);
}
}
private static async Task RunAnimationLoop(CancellationToken token)
{
while (!token.IsCancellationRequested && _animations.Count > 0)
{
var now = DateTime.UtcNow;
var completed = new List<RunningAnimation>();
foreach (var animation in _animations.ToList())
{
if (animation.Token.IsCancellationRequested)
{
completed.Add(animation);
animation.Completion.TrySetResult(false);
continue;
}
var progress = Math.Clamp(
(now - animation.StartTime).TotalMilliseconds / animation.Duration,
0.0, 1.0);
var easedProgress = animation.Easing.Ease(progress);
var value = animation.StartValue + (animation.EndValue - animation.StartValue) * easedProgress;
SetProperty(animation.View, animation.PropertyName, value);
if (progress >= 1.0)
{
completed.Add(animation);
animation.Completion.TrySetResult(true);
}
}
foreach (var animation in completed)
{
_animations.Remove(animation);
}
if (_animations.Count == 0)
{
_isRunning = false;
return;
}
await Task.Delay(16, token);
}
_isRunning = false;
}
private static void SetProperty(SkiaView view, string propertyName, double value)
{
switch (propertyName)
{
case nameof(SkiaView.Opacity):
view.Opacity = (float)value;
break;
case nameof(SkiaView.Scale):
view.Scale = value;
break;
case nameof(SkiaView.ScaleX):
view.ScaleX = value;
break;
case nameof(SkiaView.ScaleY):
view.ScaleY = value;
break;
case nameof(SkiaView.Rotation):
view.Rotation = value;
break;
case nameof(SkiaView.RotationX):
view.RotationX = value;
break;
case nameof(SkiaView.RotationY):
view.RotationY = value;
break;
case nameof(SkiaView.TranslationX):
view.TranslationX = value;
break;
case nameof(SkiaView.TranslationY):
view.TranslationY = value;
break;
}
}
private static double GetProperty(SkiaView view, string propertyName)
{
return propertyName switch
{
nameof(SkiaView.Opacity) => view.Opacity,
nameof(SkiaView.Scale) => view.Scale,
nameof(SkiaView.ScaleX) => view.ScaleX,
nameof(SkiaView.ScaleY) => view.ScaleY,
nameof(SkiaView.Rotation) => view.Rotation,
nameof(SkiaView.RotationX) => view.RotationX,
nameof(SkiaView.RotationY) => view.RotationY,
nameof(SkiaView.TranslationX) => view.TranslationX,
nameof(SkiaView.TranslationY) => view.TranslationY,
_ => 0.0
};
}
public static Task<bool> AnimateAsync(
SkiaView view,
string propertyName,
double targetValue,
uint length = 250,
Easing? easing = null,
CancellationToken cancellationToken = default)
{
CancelAnimation(view, propertyName);
var animation = new RunningAnimation
{
View = view,
PropertyName = propertyName,
StartValue = GetProperty(view, propertyName),
EndValue = targetValue,
StartTime = DateTime.UtcNow,
Duration = length,
Easing = easing ?? Easing.Linear,
Completion = new TaskCompletionSource<bool>(),
Token = cancellationToken
};
_animations.Add(animation);
EnsureRunning();
return animation.Completion.Task;
}
public static void CancelAnimation(SkiaView view, string propertyName)
{
var animation = _animations.FirstOrDefault(a => a.View == view && a.PropertyName == propertyName);
if (animation != null)
{
_animations.Remove(animation);
animation.Completion.TrySetResult(false);
}
}
public static void CancelAnimations(SkiaView view)
{
foreach (var animation in _animations.Where(a => a.View == view).ToList())
{
_animations.Remove(animation);
animation.Completion.TrySetResult(false);
}
}
}