mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
404 lines
16 KiB
C#
404 lines
16 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using Artemis.Core;
|
|
using Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline.Dialogs;
|
|
using Artemis.UI.Shared;
|
|
using Artemis.UI.Shared.Services;
|
|
using Stylet;
|
|
|
|
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
|
|
{
|
|
public sealed class TimelineSegmentViewModel : Screen
|
|
{
|
|
private readonly IDialogService _dialogService;
|
|
private bool _draggingSegment;
|
|
private bool _segmentEnabled;
|
|
private TimeSpan _segmentEnd;
|
|
private TimeSpan _segmentLength;
|
|
private TimeSpan _segmentStart;
|
|
private double _segmentStartPosition;
|
|
private double _segmentWidth;
|
|
private bool _showDisableButton;
|
|
private bool _showRepeatButton;
|
|
private bool _showSegmentName;
|
|
|
|
public TimelineSegmentViewModel(SegmentViewModelType segment, IObservableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
|
|
IProfileEditorService profileEditorService, IDialogService dialogService)
|
|
{
|
|
_dialogService = dialogService;
|
|
ProfileEditorService = profileEditorService;
|
|
Segment = segment;
|
|
LayerPropertyGroups = layerPropertyGroups;
|
|
|
|
if (Segment == SegmentViewModelType.Start)
|
|
ToolTip = "This segment is played when a layer starts displaying because it's conditions are met";
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
ToolTip = "This segment is played while a condition is met, either once or on a repeating loop";
|
|
else if (Segment == SegmentViewModelType.End)
|
|
ToolTip = "This segment is played once a condition is no longer met";
|
|
IsMainSegment = Segment == SegmentViewModelType.Main;
|
|
}
|
|
|
|
public RenderProfileElement SelectedProfileElement => ProfileEditorService.SelectedProfileElement;
|
|
public SegmentViewModelType Segment { get; }
|
|
public IObservableCollection<LayerPropertyGroupViewModel> LayerPropertyGroups { get; }
|
|
public IProfileEditorService ProfileEditorService { get; }
|
|
|
|
public string ToolTip { get; }
|
|
public bool IsMainSegment { get; }
|
|
|
|
public TimeSpan SegmentLength
|
|
{
|
|
get => _segmentLength;
|
|
set => SetAndNotify(ref _segmentLength, value);
|
|
}
|
|
|
|
public TimeSpan SegmentStart
|
|
{
|
|
get => _segmentStart;
|
|
set => SetAndNotify(ref _segmentStart, value);
|
|
}
|
|
|
|
public TimeSpan SegmentEnd
|
|
{
|
|
get => _segmentEnd;
|
|
set => SetAndNotify(ref _segmentEnd, value);
|
|
}
|
|
|
|
public double SegmentWidth
|
|
{
|
|
get => _segmentWidth;
|
|
set => SetAndNotify(ref _segmentWidth, value);
|
|
}
|
|
|
|
public double SegmentStartPosition
|
|
{
|
|
get => _segmentStartPosition;
|
|
set => SetAndNotify(ref _segmentStartPosition, value);
|
|
}
|
|
|
|
public bool SegmentEnabled
|
|
{
|
|
get => _segmentEnabled;
|
|
set => SetAndNotify(ref _segmentEnabled, value);
|
|
}
|
|
|
|
// Only the main segment supports this, for any other segment the getter always returns false and the setter does nothing
|
|
public bool RepeatSegment
|
|
{
|
|
get
|
|
{
|
|
if (Segment != SegmentViewModelType.Main)
|
|
return false;
|
|
return SelectedProfileElement?.Timeline.PlayMode == TimelinePlayMode.Repeat;
|
|
}
|
|
set
|
|
{
|
|
if (Segment != SegmentViewModelType.Main || SelectedProfileElement == null)
|
|
return;
|
|
SelectedProfileElement.Timeline.PlayMode = value ? TimelinePlayMode.Repeat : TimelinePlayMode.Once;
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
NotifyOfPropertyChange(nameof(RepeatSegment));
|
|
}
|
|
}
|
|
|
|
public bool ShowSegmentName
|
|
{
|
|
get => _showSegmentName;
|
|
set => SetAndNotify(ref _showSegmentName, value);
|
|
}
|
|
|
|
public bool ShowRepeatButton
|
|
{
|
|
get => _showRepeatButton;
|
|
set => SetAndNotify(ref _showRepeatButton, value);
|
|
}
|
|
|
|
public bool ShowDisableButton
|
|
{
|
|
get => _showDisableButton;
|
|
set => SetAndNotify(ref _showDisableButton, value);
|
|
}
|
|
|
|
public async Task OpenSettingsDialog()
|
|
{
|
|
await _dialogService.ShowDialog<TimelineSegmentDialogViewModel>(new Dictionary<string, object> {{"segment", this}});
|
|
}
|
|
|
|
public void ShiftNextSegment(TimeSpan amount)
|
|
{
|
|
if (Segment == SegmentViewModelType.Start)
|
|
ShiftKeyframes(SelectedProfileElement.Timeline.StartSegmentEndPosition, null, amount);
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
ShiftKeyframes(SelectedProfileElement.Timeline.MainSegmentEndPosition, null, amount);
|
|
else if (Segment == SegmentViewModelType.End)
|
|
ShiftKeyframes(SelectedProfileElement.Timeline.EndSegmentEndPosition, null, amount);
|
|
}
|
|
|
|
private void WipeKeyframes(TimeSpan? start, TimeSpan? end)
|
|
{
|
|
foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups)
|
|
layerPropertyGroupViewModel.WipeKeyframes(start, end);
|
|
}
|
|
|
|
private void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
|
|
{
|
|
foreach (LayerPropertyGroupViewModel layerPropertyGroupViewModel in LayerPropertyGroups)
|
|
layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount);
|
|
}
|
|
|
|
#region Overrides of Screen
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnInitialActivate()
|
|
{
|
|
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
|
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
|
|
if (ProfileEditorService.SelectedProfileElement != null)
|
|
ProfileEditorService.SelectedProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
|
|
|
|
Update();
|
|
base.OnInitialActivate();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override void OnClose()
|
|
{
|
|
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
|
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
|
|
if (SelectedProfileElement != null)
|
|
SelectedProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
|
|
base.OnClose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Updating
|
|
|
|
private void UpdateHeader()
|
|
{
|
|
if (!IsMainSegment)
|
|
ShowSegmentName = SegmentWidth > 60;
|
|
else
|
|
ShowSegmentName = SegmentWidth > 80;
|
|
|
|
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
|
|
ShowDisableButton = SegmentWidth > 25;
|
|
|
|
if (Segment == SegmentViewModelType.Main)
|
|
NotifyOfPropertyChange(nameof(RepeatSegment));
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (SelectedProfileElement == null)
|
|
{
|
|
SegmentStart = TimeSpan.Zero;
|
|
SegmentEnd = TimeSpan.Zero;
|
|
SegmentLength = TimeSpan.Zero;
|
|
SegmentStartPosition = 0;
|
|
SegmentWidth = 0;
|
|
SegmentEnabled = false;
|
|
return;
|
|
}
|
|
|
|
// It would be nice to do this differently if this patterns end up appearing in more places
|
|
if (Segment == SegmentViewModelType.Start)
|
|
{
|
|
SegmentStart = TimeSpan.Zero;
|
|
SegmentEnd = SelectedProfileElement.Timeline.StartSegmentEndPosition;
|
|
SegmentLength = SelectedProfileElement.Timeline.StartSegmentLength;
|
|
}
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
{
|
|
SegmentStart = SelectedProfileElement.Timeline.MainSegmentStartPosition;
|
|
SegmentEnd = SelectedProfileElement.Timeline.MainSegmentEndPosition;
|
|
SegmentLength = SelectedProfileElement.Timeline.MainSegmentLength;
|
|
}
|
|
else if (Segment == SegmentViewModelType.End)
|
|
{
|
|
SegmentStart = SelectedProfileElement.Timeline.EndSegmentStartPosition;
|
|
SegmentEnd = SelectedProfileElement.Timeline.EndSegmentEndPosition;
|
|
SegmentLength = SelectedProfileElement.Timeline.EndSegmentLength;
|
|
}
|
|
|
|
SegmentStartPosition = SegmentStart.TotalSeconds * ProfileEditorService.PixelsPerSecond;
|
|
SegmentWidth = SegmentLength.TotalSeconds * ProfileEditorService.PixelsPerSecond;
|
|
SegmentEnabled = SegmentLength != TimeSpan.Zero;
|
|
|
|
UpdateHeader();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Controls
|
|
|
|
public void DisableSegment()
|
|
{
|
|
TimeSpan oldSegmentLength = SegmentLength;
|
|
|
|
if (Segment == SegmentViewModelType.Start)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
WipeKeyframes(null, SelectedProfileElement.Timeline.StartSegmentEndPosition);
|
|
SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.Zero;
|
|
}
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
WipeKeyframes(SelectedProfileElement.Timeline.MainSegmentStartPosition, SelectedProfileElement.Timeline.MainSegmentEndPosition);
|
|
SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.Zero;
|
|
}
|
|
else if (Segment == SegmentViewModelType.End)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
WipeKeyframes(SelectedProfileElement.Timeline.EndSegmentStartPosition, SelectedProfileElement.Timeline.EndSegmentEndPosition);
|
|
SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.Zero;
|
|
}
|
|
|
|
ShiftNextSegment(SegmentLength - oldSegmentLength);
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
Update();
|
|
}
|
|
|
|
public void EnableSegment()
|
|
{
|
|
ShiftNextSegment(TimeSpan.FromSeconds(1));
|
|
if (Segment == SegmentViewModelType.Start)
|
|
SelectedProfileElement.Timeline.StartSegmentLength = TimeSpan.FromSeconds(1);
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
SelectedProfileElement.Timeline.MainSegmentLength = TimeSpan.FromSeconds(1);
|
|
else if (Segment == SegmentViewModelType.End)
|
|
SelectedProfileElement.Timeline.EndSegmentLength = TimeSpan.FromSeconds(1);
|
|
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
Update();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Mouse events
|
|
|
|
public void SegmentMouseDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
((IInputElement) sender).CaptureMouse();
|
|
_draggingSegment = true;
|
|
}
|
|
|
|
public void SegmentMouseUp(object sender, MouseButtonEventArgs e)
|
|
{
|
|
((IInputElement) sender).ReleaseMouseCapture();
|
|
_draggingSegment = false;
|
|
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
}
|
|
|
|
public void SegmentMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (e.LeftButton != MouseButtonState.Pressed || !_draggingSegment)
|
|
return;
|
|
|
|
// Get the parent scroll viewer, need that for our position
|
|
ScrollViewer parent = VisualTreeUtilities.FindParent<ScrollViewer>((DependencyObject) sender, "TimelineHeaderScrollViewer");
|
|
|
|
double x = Math.Max(0, e.GetPosition(parent).X);
|
|
TimeSpan newTime = TimeSpan.FromSeconds(x / ProfileEditorService.PixelsPerSecond);
|
|
|
|
// Round the time to something that fits the current zoom level
|
|
if (ProfileEditorService.PixelsPerSecond < 200)
|
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 5.0) * 5.0);
|
|
else if (ProfileEditorService.PixelsPerSecond < 500)
|
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 2.0) * 2.0);
|
|
else
|
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds));
|
|
|
|
// If holding down shift, snap to the closest element on the timeline
|
|
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift))
|
|
{
|
|
List<TimeSpan> keyframeTimes = LayerPropertyGroups.SelectMany(g => g.GetAllKeyframeViewModels(true)).Select(k => k.Position).ToList();
|
|
newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, keyframeTimes);
|
|
}
|
|
// If holding down control, round to the closest 50ms
|
|
else if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
|
|
{
|
|
newTime = TimeSpan.FromMilliseconds(Math.Round(newTime.TotalMilliseconds / 50.0) * 50.0);
|
|
}
|
|
|
|
UpdateEndPosition(newTime);
|
|
}
|
|
|
|
public void UpdateEndPosition(TimeSpan end)
|
|
{
|
|
if (end < TimeSpan.FromMilliseconds(100))
|
|
end = TimeSpan.FromMilliseconds(100);
|
|
|
|
if (Segment == SegmentViewModelType.Start)
|
|
SelectedProfileElement.Timeline.StartSegmentEndPosition = end;
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
SelectedProfileElement.Timeline.MainSegmentEndPosition = end;
|
|
else if (Segment == SegmentViewModelType.End)
|
|
SelectedProfileElement.Timeline.EndSegmentEndPosition = end;
|
|
|
|
Update();
|
|
}
|
|
|
|
public void UpdateLength(TimeSpan length)
|
|
{
|
|
if (length < TimeSpan.FromMilliseconds(100))
|
|
length = TimeSpan.FromMilliseconds(100);
|
|
|
|
if (Segment == SegmentViewModelType.Start)
|
|
SelectedProfileElement.Timeline.StartSegmentLength = length;
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
SelectedProfileElement.Timeline.MainSegmentLength = length;
|
|
else if (Segment == SegmentViewModelType.End)
|
|
SelectedProfileElement.Timeline.EndSegmentLength = length;
|
|
|
|
Update();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event handlers
|
|
|
|
private void ProfileEditorServiceOnProfileElementSelected(object sender, RenderProfileElementEventArgs e)
|
|
{
|
|
if (e.PreviousRenderProfileElement != null)
|
|
e.PreviousRenderProfileElement.Timeline.PropertyChanged -= TimelineOnPropertyChanged;
|
|
if (e.RenderProfileElement != null)
|
|
e.RenderProfileElement.Timeline.PropertyChanged += TimelineOnPropertyChanged;
|
|
|
|
Update();
|
|
}
|
|
|
|
private void TimelineOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(Core.Timeline.StartSegmentLength) ||
|
|
e.PropertyName == nameof(Core.Timeline.MainSegmentLength) ||
|
|
e.PropertyName == nameof(Core.Timeline.EndSegmentLength))
|
|
Update();
|
|
else if (e.PropertyName == nameof(Core.Timeline.PlayMode))
|
|
NotifyOfPropertyChange(nameof(RepeatSegment));
|
|
}
|
|
|
|
private void ProfileEditorServiceOnPixelsPerSecondChanged(object sender, EventArgs e)
|
|
{
|
|
Update();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public enum SegmentViewModelType
|
|
{
|
|
Start,
|
|
Main,
|
|
End
|
|
}
|
|
} |