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; }
|
||||
|
||||
/// <summary>
|
||||
/// The position of this keyframe in the timeline
|
||||
/// Gets or sets the position of this keyframe in the timeline
|
||||
/// </summary>
|
||||
TimeSpan Position { get; set; }
|
||||
|
||||
/// <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>
|
||||
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
|
||||
|
||||
void PopulateEasingViewModels();
|
||||
void ClearEasingViewModels();
|
||||
void Delete(bool save = true);
|
||||
|
||||
#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.UI.Shared;
|
||||
using Avalonia;
|
||||
@ -9,11 +8,11 @@ namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
|
||||
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;
|
||||
Description = easingFunction.Humanize();
|
||||
@ -30,21 +29,5 @@ public class TimelineEasingViewModel : ViewModelBase
|
||||
public Easings.Functions EasingFunction { get; }
|
||||
public List<Point> EasingPoints { get; }
|
||||
public string Description { get; }
|
||||
|
||||
public bool IsEasingModeSelected
|
||||
{
|
||||
get => _isEasingModeSelected;
|
||||
set
|
||||
{
|
||||
_isEasingModeSelected = value;
|
||||
if (value) OnEasingModeSelected();
|
||||
}
|
||||
}
|
||||
|
||||
public event EventHandler EasingModeSelected;
|
||||
|
||||
protected virtual void OnEasingModeSelected()
|
||||
{
|
||||
EasingModeSelected?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
public bool IsEasingModeSelected => _keyframe.EasingFunction == EasingFunction;
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
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"
|
||||
x:Class="Artemis.UI.Screens.ProfileEditor.Properties.Timeline.TimelineKeyframeView"
|
||||
ClipToBounds="False"
|
||||
@ -31,25 +32,24 @@
|
||||
</Transitions>
|
||||
</Ellipse.Transitions>
|
||||
<Ellipse.ContextFlyout>
|
||||
<MenuFlyout>
|
||||
<MenuFlyout Opening="FlyoutBase_OnOpening">
|
||||
<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>
|
||||
<avalonia:MaterialIcon Kind="Creation" />
|
||||
</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>
|
||||
<Separator />
|
||||
<MenuItem Header="Duplicate" Command="{Binding DuplicateKeyframes}" CommandParameter="{Binding}" InputGesture="Ctrl+D">
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
@ -43,4 +44,9 @@ public class TimelineKeyframeView : ReactiveUserControl<ITimelineKeyframeViewMod
|
||||
if (!_moved)
|
||||
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.Services.ProfileEditor;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.Input;
|
||||
using DynamicData;
|
||||
using Humanizer;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.UI.Screens.ProfileEditor.Properties.Timeline;
|
||||
@ -32,11 +34,6 @@ public class TimelineKeyframeViewModel<T> : ActivatableViewModelBase, ITimelineK
|
||||
{
|
||||
_pixelsPerSecond = p;
|
||||
profileEditorService.PixelsPerSecond.Subscribe(_ => Update()).DisposeWith(d);
|
||||
System.Reactive.Disposables.Disposable.Create(() =>
|
||||
{
|
||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||
timelineEasingViewModel.EasingModeSelected -= TimelineEasingViewModelOnEasingModeSelected;
|
||||
}).DisposeWith(d);
|
||||
}).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))
|
||||
.Cast<Easings.Functions>()
|
||||
.Select(e => new TimelineEasingViewModel(e, e == LayerPropertyKeyframe.EasingFunction)));
|
||||
|
||||
foreach (TimelineEasingViewModel timelineEasingViewModel in EasingViewModels)
|
||||
timelineEasingViewModel.EasingModeSelected += TimelineEasingViewModelOnEasingModeSelected;
|
||||
.Select(e => new TimelineEasingViewModel(e, Keyframe)));
|
||||
}
|
||||
|
||||
public void ClearEasingViewModels()
|
||||
public void SelectEasingFunction(Easings.Functions easingFunction)
|
||||
{
|
||||
EasingViewModels.Clear();
|
||||
}
|
||||
|
||||
private void TimelineEasingViewModelOnEasingModeSelected(object? sender, EventArgs e)
|
||||
{
|
||||
if (sender is TimelineEasingViewModel timelineEasingViewModel)
|
||||
SelectEasingMode(timelineEasingViewModel);
|
||||
}
|
||||
|
||||
public void SelectEasingMode(TimelineEasingViewModel easingViewModel)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
|
||||
LayerPropertyKeyframe.EasingFunction = easingViewModel.EasingFunction;
|
||||
// Set every selection to false except on the VM that made the change
|
||||
foreach (TimelineEasingViewModel propertyTrackEasingViewModel in EasingViewModels.Where(vm => vm != easingViewModel))
|
||||
propertyTrackEasingViewModel.IsEasingModeSelected = false;
|
||||
_profileEditorService.ExecuteCommand(new ChangeKeyframeEasing(Keyframe, easingFunction));
|
||||
}
|
||||
|
||||
#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)
|
||||
{
|
||||
_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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user