mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Profile editor - Implemented keyframe easing selection
This commit is contained in:
parent
6f269af8d4
commit
a1f7f6dff8
@ -14,12 +14,12 @@ namespace Artemis.Core
|
|||||||
ILayerProperty UntypedLayerProperty { get; }
|
ILayerProperty UntypedLayerProperty { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The position of this keyframe in the timeline
|
/// Gets or sets the position of this keyframe in the timeline
|
||||||
/// </summary>
|
/// </summary>
|
||||||
TimeSpan Position { get; set; }
|
TimeSpan Position { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The easing function applied on the value of the keyframe
|
/// Gets or sets the easing function applied on the value of the keyframe
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Easings.Functions EasingFunction { get; set; }
|
Easings.Functions EasingFunction { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,43 @@
|
|||||||
|
using Artemis.Core;
|
||||||
|
using Humanizer;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profile editor command that can be used to change the easing function of a keyframe.
|
||||||
|
/// </summary>
|
||||||
|
public class ChangeKeyframeEasing : IProfileEditorCommand
|
||||||
|
{
|
||||||
|
private readonly ILayerPropertyKeyframe _keyframe;
|
||||||
|
private readonly Easings.Functions _easingFunction;
|
||||||
|
private readonly Easings.Functions _originalEasingFunction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="ChangeKeyframeEasing"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public ChangeKeyframeEasing(ILayerPropertyKeyframe keyframe, Easings.Functions easingFunction)
|
||||||
|
{
|
||||||
|
_keyframe = keyframe;
|
||||||
|
_easingFunction = easingFunction;
|
||||||
|
_originalEasingFunction = keyframe.EasingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IProfileEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Change easing to " + _easingFunction.Humanize(LetterCasing.LowerCase);
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_keyframe.EasingFunction = _easingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_keyframe.EasingFunction = _originalEasingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using Artemis.Core;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profile editor command that can be used to change the position of a keyframe.
|
||||||
|
/// </summary>
|
||||||
|
public class MoveKeyframe : IProfileEditorCommand
|
||||||
|
{
|
||||||
|
private readonly ILayerPropertyKeyframe _keyframe;
|
||||||
|
private readonly TimeSpan _originalPosition;
|
||||||
|
private readonly TimeSpan _position;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="MoveKeyframe" /> class.
|
||||||
|
/// </summary>
|
||||||
|
public MoveKeyframe(ILayerPropertyKeyframe keyframe, TimeSpan position)
|
||||||
|
{
|
||||||
|
_keyframe = keyframe;
|
||||||
|
_position = position;
|
||||||
|
_originalPosition = keyframe.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IProfileEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName => "Move keyframe";
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_keyframe.Position = _position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_keyframe.Position = _originalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a profile editor command that can be used to combine multiple commands into one.
|
||||||
|
/// </summary>
|
||||||
|
public class CompositeProfileEditorCommand : IProfileEditorCommand, IDisposable
|
||||||
|
{
|
||||||
|
private readonly List<IProfileEditorCommand> _commands;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new instance of the <see cref="CompositeProfileEditorCommand" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commands">The commands to execute.</param>
|
||||||
|
/// <param name="displayName">The display name of the composite command.</param>
|
||||||
|
public CompositeProfileEditorCommand(IEnumerable<IProfileEditorCommand> commands, string displayName)
|
||||||
|
{
|
||||||
|
if (commands == null)
|
||||||
|
throw new ArgumentNullException(nameof(commands));
|
||||||
|
_commands = commands.ToList();
|
||||||
|
DisplayName = displayName ?? throw new ArgumentNullException(nameof(displayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (IProfileEditorCommand profileEditorCommand in _commands)
|
||||||
|
if (profileEditorCommand is IDisposable disposable)
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Implementation of IProfileEditorCommand
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public string DisplayName { get; }
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
foreach (IProfileEditorCommand profileEditorCommand in _commands)
|
||||||
|
profileEditorCommand.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
// Undo in reverse by iterating from the back
|
||||||
|
for (int index = _commands.Count; index >= 0; index--)
|
||||||
|
_commands[index].Undo();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -25,7 +25,6 @@ public interface ITimelineKeyframeViewModel
|
|||||||
#region Context menu actions
|
#region Context menu actions
|
||||||
|
|
||||||
void PopulateEasingViewModels();
|
void PopulateEasingViewModels();
|
||||||
void ClearEasingViewModels();
|
|
||||||
void Delete(bool save = true);
|
void Delete(bool save = true);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineEasingView">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Polyline Stroke="{DynamicResource TextFillColorPrimaryBrush}"
|
||||||
|
StrokeThickness="1"
|
||||||
|
Points="{Binding EasingPoints}"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
Margin="0 0 10 0" />
|
||||||
|
<TextBlock Text="{Binding Description}" />
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline
|
||||||
|
{
|
||||||
|
public partial class TimelineEasingView : UserControl
|
||||||
|
{
|
||||||
|
public TimelineEasingView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Artemis.Core;
|
using Artemis.Core;
|
||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
@ -9,11 +8,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
|||||||
|
|
||||||
public class TimelineEasingViewModel : ViewModelBase
|
public class TimelineEasingViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
private bool _isEasingModeSelected;
|
private readonly ILayerPropertyKeyframe _keyframe;
|
||||||
|
|
||||||
public TimelineEasingViewModel(Easings.Functions easingFunction, bool isSelected)
|
public TimelineEasingViewModel(Easings.Functions easingFunction, ILayerPropertyKeyframe keyframe)
|
||||||
{
|
{
|
||||||
_isEasingModeSelected = isSelected;
|
_keyframe = keyframe;
|
||||||
|
|
||||||
EasingFunction = easingFunction;
|
EasingFunction = easingFunction;
|
||||||
Description = easingFunction.Humanize();
|
Description = easingFunction.Humanize();
|
||||||
@ -30,21 +29,5 @@ public class TimelineEasingViewModel : ViewModelBase
|
|||||||
public Easings.Functions EasingFunction { get; }
|
public Easings.Functions EasingFunction { get; }
|
||||||
public List<Point> EasingPoints { get; }
|
public List<Point> EasingPoints { get; }
|
||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
|
public bool IsEasingModeSelected => _keyframe.EasingFunction == EasingFunction;
|
||||||
public bool IsEasingModeSelected
|
|
||||||
{
|
|
||||||
get => _isEasingModeSelected;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_isEasingModeSelected = value;
|
|
||||||
if (value) OnEasingModeSelected();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler EasingModeSelected;
|
|
||||||
|
|
||||||
protected virtual void OnEasingModeSelected()
|
|
||||||
{
|
|
||||||
EasingModeSelected?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
|
||||||
|
xmlns:timeline="clr-namespace:Artemis.UI.Screens.ProfileEditor.Properties.Timeline"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
|
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
|
||||||
ClipToBounds="False"
|
ClipToBounds="False"
|
||||||
@ -31,25 +32,24 @@
|
|||||||
</Transitions>
|
</Transitions>
|
||||||
</Ellipse.Transitions>
|
</Ellipse.Transitions>
|
||||||
<Ellipse.ContextFlyout>
|
<Ellipse.ContextFlyout>
|
||||||
<MenuFlyout>
|
<MenuFlyout Opening="FlyoutBase_OnOpening">
|
||||||
<MenuItem Header="Easing" Items="{Binding EasingViewModels}">
|
<MenuItem Header="Easing" Items="{Binding EasingViewModels}">
|
||||||
|
<MenuItem.Styles>
|
||||||
|
<Style Selector="MenuItem > MenuItem">
|
||||||
|
<Setter Property="Icon">
|
||||||
|
<Setter.Value>
|
||||||
|
<Template>
|
||||||
|
<CheckBox IsHitTestVisible="False" IsChecked="{Binding IsEasingModeSelected}" />
|
||||||
|
</Template>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Command" Value="{Binding $parent[UserControl].DataContext.SelectEasingFunction}"></Setter>
|
||||||
|
<Setter Property="CommandParameter" Value="{Binding EasingFunction}"></Setter>
|
||||||
|
</Style>
|
||||||
|
</MenuItem.Styles>
|
||||||
<MenuItem.Icon>
|
<MenuItem.Icon>
|
||||||
<avalonia:MaterialIcon Kind="Creation" />
|
<avalonia:MaterialIcon Kind="Creation" />
|
||||||
</MenuItem.Icon>
|
</MenuItem.Icon>
|
||||||
<MenuItem.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<Polyline Stroke="{DynamicResource MaterialDesignBody}"
|
|
||||||
StrokeThickness="1"
|
|
||||||
Points="{Binding EasingPoints}"
|
|
||||||
Stretch="Uniform"
|
|
||||||
Width="20"
|
|
||||||
Height="20"
|
|
||||||
Margin="0 0 10 0" />
|
|
||||||
<TextBlock Text="{Binding Description}" />
|
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</MenuItem.ItemTemplate>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Header="Duplicate" Command="{Binding DuplicateKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+D">
|
<MenuItem Header="Duplicate" Command="{Binding DuplicateKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+D">
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Avalonia.ReactiveUI;
|
using Avalonia.ReactiveUI;
|
||||||
@ -43,4 +44,9 @@ public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewMod
|
|||||||
if (!_moved)
|
if (!_moved)
|
||||||
ViewModel?.Select(e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
|
ViewModel?.Select(e.KeyModifiers.HasFlag(KeyModifiers.Shift), e.KeyModifiers.HasFlag(KeyModifiers.Control));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FlyoutBase_OnOpening(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel?.PopulateEasingViewModels();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -6,7 +6,9 @@ using Artemis.Core;
|
|||||||
using Artemis.UI.Shared;
|
using Artemis.UI.Shared;
|
||||||
using Artemis.UI.Shared.Services.ProfileEditor;
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Input;
|
||||||
using DynamicData;
|
using DynamicData;
|
||||||
|
using Humanizer;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
|
|
||||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||||
@ -32,11 +34,6 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
|
|||||||
{
|
{
|
||||||
_pixelsPerSecond = p;
|
_pixelsPerSecond = p;
|
||||||
profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
||||||
System.Reactive.Disposables.Disposable.Create(() =>
|
|
||||||
{
|
|
||||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
|
||||||
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
|
||||||
}).DisposeWith(d);
|
|
||||||
}).DisposeWith(d);
|
}).DisposeWith(d);
|
||||||
|
|
||||||
_isSelected = profileEditorService.ConnectToKeyframes().ToCollection().Select(keyframes => keyframes.Contains(LayerPropertyKeyframe)).ToProperty(this, vm => vm.IsSelected).DisposeWith(d);
|
_isSelected = profileEditorService.ConnectToKeyframes().ToCollection().Select(keyframes => keyframes.Contains(LayerPropertyKeyframe)).ToProperty(this, vm => vm.IsSelected).DisposeWith(d);
|
||||||
@ -142,31 +139,45 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
|
|||||||
|
|
||||||
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
|
EasingViewModels.AddRange(Enum.GetValues(typeof(Easings.Functions))
|
||||||
.Cast<Easings.Functions>()
|
.Cast<Easings.Functions>()
|
||||||
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
|
.Select(e => new TimelineEasingViewModel(e, Keyframe)));
|
||||||
|
|
||||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
|
||||||
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearEasingViewModels()
|
public void SelectEasingFunction(Easings.Functions easingFunction)
|
||||||
{
|
{
|
||||||
EasingViewModels.Clear();
|
_profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimelineEasingViewModelOnEasingModeSelected(object? sender, EventArgs e)
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ChangeKeyframeEasing : IProfileEditorCommand
|
||||||
|
{
|
||||||
|
private readonly ILayerPropertyKeyframe _keyframe;
|
||||||
|
private readonly Easings.Functions _easingFunction;
|
||||||
|
private readonly Easings.Functions _originalEasingFunction;
|
||||||
|
|
||||||
|
public ChangeKeyframeEasing(ILayerPropertyKeyframe keyframe, Easings.Functions easingFunction)
|
||||||
{
|
{
|
||||||
if (sender is TimelineEasingViewModel timelineEasingViewModel)
|
_keyframe = keyframe;
|
||||||
SelectEasingMode(timelineEasingViewModel);
|
_easingFunction = easingFunction;
|
||||||
|
_originalEasingFunction = keyframe.EasingFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
|
#region Implementation of IProfileEditorCommand
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
|
|
||||||
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
|
/// <inheritdoc />
|
||||||
// Set every selection to false except on the VM that made the change
|
public string DisplayName => "Change easing to " + _easingFunction.Humanize(LetterCasing.LowerCase);
|
||||||
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
|
||||||
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
/// <inheritdoc />
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
_keyframe.EasingFunction = _easingFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Undo()
|
||||||
|
{
|
||||||
|
_keyframe.EasingFunction = _originalEasingFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user