Browse Source

Merge pull request #202 from ionite34/animation-scale

Added animation scale setting
pull/109/head
JT 1 year ago committed by GitHub
parent
commit
0cfb59c1c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      StabilityMatrix.Avalonia/Animations/BaseTransitionInfo.cs
  2. 59
      StabilityMatrix.Avalonia/Animations/BetterDrillInNavigationTransition.cs
  3. 66
      StabilityMatrix.Avalonia/Animations/BetterEntranceNavigationTransition.cs
  4. 96
      StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs
  5. 37
      StabilityMatrix.Avalonia/Services/NavigationService.cs
  6. 19
      StabilityMatrix.Avalonia/ViewLocator.cs
  7. 3
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  8. 20
      StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs
  9. 3
      StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs
  10. 18
      StabilityMatrix.Avalonia/Views/SettingsPage.axaml
  11. 2
      StabilityMatrix.Core/Models/Settings/Settings.cs

12
StabilityMatrix.Avalonia/Animations/BaseTransitionInfo.cs

@ -0,0 +1,12 @@
using System;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public abstract class BaseTransitionInfo : NavigationTransitionInfo
{
/// <summary>
/// The duration of the animation at 1x animation scale
/// </summary>
public abstract TimeSpan Duration { get; set; }
}

59
StabilityMatrix.Avalonia/Animations/BetterDrillInNavigationTransition.cs

@ -0,0 +1,59 @@
using System;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterDrillInNavigationTransition : BaseTransitionInfo
{
/// <summary>
/// Gets or sets whether the animation should drill in (false) or drill out (true)
/// </summary>
public bool IsReversed { get; set; } = false; //Zoom out if true
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(400);
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 0.0),
new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.5 : 0.0),
new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.5 : 0.0)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1.0),
new Setter(ScaleTransform.ScaleXProperty, IsReversed ? 1.0 : 1.0),
new Setter(ScaleTransform.ScaleYProperty, IsReversed ? 1.0 : 1.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visualCtrl)
{
visualCtrl.Opacity = 1;
}
}
}

66
StabilityMatrix.Avalonia/Animations/BetterEntranceNavigationTransition.cs

@ -0,0 +1,66 @@
using System;
using System.Threading;
using AsyncAwaitBestPractices;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterEntranceNavigationTransition : BaseTransitionInfo
{
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(500);
/// <summary>
/// Gets or sets the Horizontal Offset used when animating
/// </summary>
public double FromHorizontalOffset { get; set; } = 0;
/// <summary>
/// Gets or sets the Vertical Offset used when animating
/// </summary>
public double FromVerticalOffset { get; set; } = 100;
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 0.0),
new Setter(TranslateTransform.XProperty,FromHorizontalOffset),
new Setter(TranslateTransform.YProperty, FromVerticalOffset)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1d),
new Setter(TranslateTransform.XProperty,0.0),
new Setter(TranslateTransform.YProperty, 0.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visualCtrl)
{
visualCtrl.Opacity = 1;
}
}
}

96
StabilityMatrix.Avalonia/Animations/BetterSlideNavigationTransition.cs

@ -0,0 +1,96 @@
using System;
using System.Threading;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
using FluentAvalonia.UI.Media.Animation;
namespace StabilityMatrix.Avalonia.Animations;
public class BetterSlideNavigationTransition : BaseTransitionInfo
{
public override TimeSpan Duration { get; set; } = TimeSpan.FromMilliseconds(167);
/// <summary>
/// Gets or sets the type of animation effect to play during the slide transition.
/// </summary>
public SlideNavigationTransitionEffect Effect { get; set; } = SlideNavigationTransitionEffect.FromRight;
/// <summary>
/// Gets or sets the HorizontalOffset used when animating from the Left or Right
/// </summary>
public double FromHorizontalOffset { get; set; } = 56;
/// <summary>
/// Gets or sets the VerticalOffset used when animating from the Top or Bottom
/// </summary>
public double FromVerticalOffset { get; set; } = 56;
public override async void RunAnimation(Animatable ctrl, CancellationToken cancellationToken)
{
double length = 0;
bool isVertical = false;
switch (Effect)
{
case SlideNavigationTransitionEffect.FromLeft:
length = -FromHorizontalOffset;
break;
case SlideNavigationTransitionEffect.FromRight:
length = FromHorizontalOffset;
break;
case SlideNavigationTransitionEffect.FromTop:
length = -FromVerticalOffset;
isVertical = true;
break;
case SlideNavigationTransitionEffect.FromBottom:
length = FromVerticalOffset;
isVertical = true;
break;
}
var animation = new Animation
{
Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0),
Children =
{
new KeyFrame
{
Setters =
{
new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, length),
new Setter(Visual.OpacityProperty, 0d)
},
Cue = new Cue(0d)
},
new KeyFrame
{
Setters=
{
new Setter(Visual.OpacityProperty, 1d)
},
Cue = new Cue(0.05d)
},
new KeyFrame
{
Setters =
{
new Setter(Visual.OpacityProperty, 1d),
new Setter(isVertical ? TranslateTransform.YProperty : TranslateTransform.XProperty, 0.0)
},
Cue = new Cue(1d)
}
},
Duration = Duration,
FillMode = FillMode.Forward
};
await animation.RunAsync(ctrl, cancellationToken);
if (ctrl is Visual visual)
{
visual.Opacity = 1;
}
}
}

37
StabilityMatrix.Avalonia/Services/NavigationService.cs

@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Navigation;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.ViewModels;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.Services;
@ -26,6 +30,23 @@ public class NavigationService : INavigationService
throw new InvalidOperationException("SetFrame was not called before NavigateTo.");
}
if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)
{
// Handle animation scale
switch (transitionInfo)
{
// If the transition info is null or animation scale is 0, suppress the transition
case null:
case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:
transitionInfo = new SuppressNavigationTransitionInfo();
break;
case BaseTransitionInfo baseTransitionInfo:
baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;
break;
}
}
_frame.NavigateToType(typeof(TViewModel),
null,
new FrameNavigationOptions
@ -52,6 +73,22 @@ public class NavigationService : INavigationService
throw new InvalidOperationException("SetFrame was not called before NavigateTo.");
}
if (App.Services.GetService(typeof(ISettingsManager)) is ISettingsManager settingsManager)
{
// Handle animation scale
switch (transitionInfo)
{
// If the transition info is null or animation scale is 0, suppress the transition
case null:
case BaseTransitionInfo when settingsManager.Settings.AnimationScale == 0f:
transitionInfo = new SuppressNavigationTransitionInfo();
break;
case BaseTransitionInfo baseTransitionInfo:
baseTransitionInfo.Duration *= settingsManager.Settings.AnimationScale;
break;
}
}
_frame.NavigateFromObject(viewModel,
new FrameNavigationOptions
{

19
StabilityMatrix.Avalonia/ViewLocator.cs

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using FluentAvalonia.UI.Controls;
@ -51,28 +52,30 @@ public class ViewLocator : IDataTemplate, INavigationPageFactory
/// <inheritdoc />
public Control? GetPage(Type srcType)
{
if (Attribute.GetCustomAttribute(srcType, typeof(ViewAttribute)) is ViewAttribute viewAttr)
if (Attribute.GetCustomAttribute(srcType, typeof(ViewAttribute)) is not ViewAttribute
viewAttr)
{
throw new InvalidOperationException("View not found for " + srcType.FullName);
}
var viewType = viewAttr.GetViewType();
var view = GetView(viewType);
view.DataContext ??= App.Services.GetService(srcType);
return view;
}
throw new InvalidOperationException("View not found for " + srcType.FullName);
}
/// <inheritdoc />
public Control GetPageFromObject(object target)
{
if (Attribute.GetCustomAttribute(target.GetType(), typeof(ViewAttribute)) is ViewAttribute viewAttr)
if (Attribute.GetCustomAttribute(target.GetType(), typeof(ViewAttribute)) is not
ViewAttribute viewAttr)
{
throw new InvalidOperationException("View not found for " + target.GetType().FullName);
}
var viewType = viewAttr.GetViewType();
var view = GetView(viewType);
view.DataContext ??= target;
return view;
}
throw new InvalidOperationException("View not found for " + target.GetType().FullName);
}
}

3
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

@ -10,6 +10,7 @@ using FluentAvalonia.UI.Controls;
using FluentAvalonia.UI.Media.Animation;
using NLog;
using Polly;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
@ -67,7 +68,7 @@ public partial class PackageCardViewModel : Base.ProgressViewModel
settingsManager.Transaction(s => s.ActiveInstalledPackageId = Package.Id);
navigationService.NavigateTo<LaunchPageViewModel>(new EntranceNavigationTransitionInfo());
navigationService.NavigateTo<LaunchPageViewModel>(new BetterDrillInNavigationTransition());
EventManager.Instance.OnPackageLaunchRequested(Package.Id);
}

20
StabilityMatrix.Avalonia/ViewModels/SettingsViewModel.cs

@ -65,6 +65,21 @@ public partial class SettingsViewModel : PageViewModelBase
"System",
};
public IReadOnlyList<float> AnimationScaleOptions { get; } = new[]
{
0f,
0.25f,
0.5f,
0.75f,
1f,
1.25f,
1.5f,
1.75f,
2f,
};
[ObservableProperty] private float selectedAnimationScale;
// Shared folder options
[ObservableProperty] private bool removeSymlinksOnShutdown;
@ -100,6 +115,7 @@ public partial class SettingsViewModel : PageViewModelBase
SelectedTheme = settingsManager.Settings.Theme ?? AvailableThemes[1];
RemoveSymlinksOnShutdown = settingsManager.Settings.RemoveFolderLinksOnShutdown;
SelectedAnimationScale = settingsManager.Settings.AnimationScale;
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedTheme,
@ -108,6 +124,10 @@ public partial class SettingsViewModel : PageViewModelBase
settingsManager.RelayPropertyFor(this,
vm => vm.IsDiscordRichPresenceEnabled,
settings => settings.IsDiscordRichPresenceEnabled);
settingsManager.RelayPropertyFor(this,
vm => vm.SelectedAnimationScale,
settings => settings.AnimationScale);
}
partial void OnSelectedThemeChanged(string? value)

3
StabilityMatrix.Avalonia/Views/MainWindow.axaml.cs

@ -20,6 +20,7 @@ using FluentAvalonia.UI.Media;
using FluentAvalonia.UI.Media.Animation;
using FluentAvalonia.UI.Windowing;
using Microsoft.Extensions.DependencyInjection;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels;
@ -141,7 +142,7 @@ public partial class MainWindow : AppWindowBase
{
throw new InvalidOperationException($"NavigationViewItem Tag must be of type ViewModelBase, not {nvi.Tag?.GetType()}");
}
navigationService.NavigateTo(vm, e.RecommendedNavigationTransitionInfo);
navigationService.NavigateTo(vm, new BetterEntranceNavigationTransition());
}
}

18
StabilityMatrix.Avalonia/Views/SettingsPage.axaml

@ -204,6 +204,24 @@
TextWrapping="WrapWithOverflow" />
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Animation Scale" IconSource="Clock"
Description="Lower values = faster animations. 0x means animations are instant."
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>
<ComboBox Margin="0, 8"
ItemsSource="{Binding AnimationScaleOptions}"
SelectedItem="{Binding SelectedAnimationScale}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding }"/><Run Text="x"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</ui:SettingsExpanderItem.Footer>
</ui:SettingsExpanderItem>
<ui:SettingsExpanderItem Content="Notification" IconSource="CommentAdd"
Margin="4,0,4,4">
<ui:SettingsExpanderItem.Footer>

2
StabilityMatrix.Core/Models/Settings/Settings.cs

@ -51,6 +51,8 @@ public class Settings
public HashSet<string>? InstalledModelHashes { get; set; } = new();
public float AnimationScale { get; set; } = 1.0f;
public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)
{
RemoveInstalledPackageAndUpdateActive(package.Id);

Loading…
Cancel
Save