1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

361 lines
14 KiB
C#

using System;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using Artemis.Core;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Stylet;
namespace Artemis.UI.Screens.ProfileEditor.LayerProperties.Timeline
{
public class TimelineSegmentViewModel : Screen, IDisposable
{
private bool _draggingSegment;
private bool _showDisableButton;
private bool _showRepeatButton;
private bool _showSegmentName;
private TimeSpan _segmentLength;
private double _segmentWidth;
private bool _segmentEnabled;
private double _segmentStartPosition;
public TimelineSegmentViewModel(SegmentViewModelType segment, BindableCollection<LayerPropertyGroupViewModel> layerPropertyGroups,
IProfileEditorService profileEditorService)
{
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;
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected += ProfileEditorServiceOnProfileElementSelected;
if (ProfileEditorService.SelectedProfileElement != null)
ProfileEditorService.SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
Update();
}
public RenderProfileElement SelectedProfileElement => ProfileEditorService.SelectedProfileElement;
public SegmentViewModelType Segment { get; }
public BindableCollection<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 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?.DisplayContinuously ?? false;
}
set
{
if (Segment != SegmentViewModelType.Main)
return;
SelectedProfileElement.DisplayContinuously = value;
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);
}
#region Updating
private void UpdateHeader()
{
if (!IsMainSegment)
ShowSegmentName = SegmentWidth > 60;
else
ShowSegmentName = SegmentWidth > 80;
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
ShowDisableButton = SegmentWidth > 25;
}
private void Update()
{
if (SelectedProfileElement == null)
{
SegmentLength = TimeSpan.Zero;
SegmentStartPosition = 0;
SegmentWidth = 0;
SegmentEnabled = false;
return;
}
if (Segment == SegmentViewModelType.Start)
{
SegmentLength = SelectedProfileElement.StartSegmentLength;
SegmentStartPosition = 0;
}
else if (Segment == SegmentViewModelType.Main)
{
SegmentLength = SelectedProfileElement.MainSegmentLength;
SegmentStartPosition = ProfileEditorService.PixelsPerSecond * SelectedProfileElement.StartSegmentLength.TotalSeconds;
}
else if (Segment == SegmentViewModelType.End)
{
SegmentLength = SelectedProfileElement.EndSegmentLength;
SegmentStartPosition = ProfileEditorService.PixelsPerSecond * (SelectedProfileElement.StartSegmentLength.TotalSeconds +
SelectedProfileElement.MainSegmentLength.TotalSeconds);
}
SegmentWidth = ProfileEditorService.PixelsPerSecond * SegmentLength.TotalSeconds;
SegmentEnabled = SegmentLength != TimeSpan.Zero;
UpdateHeader();
}
#endregion
#region Controls
public void DisableSegment()
{
var startSegmentEnd = SelectedProfileElement.StartSegmentLength;
var mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
var oldSegmentLength = SegmentLength;
if (Segment == SegmentViewModelType.Start)
{
// Remove keyframes that fall in this segment
WipeKeyframes(null, startSegmentEnd);
SelectedProfileElement.StartSegmentLength = TimeSpan.Zero;
}
else if (Segment == SegmentViewModelType.Main)
{
// Remove keyframes that fall in this segment
WipeKeyframes(startSegmentEnd, startSegmentEnd);
SelectedProfileElement.MainSegmentLength = TimeSpan.Zero;
}
else if (Segment == SegmentViewModelType.End)
{
// Remove keyframes that fall in this segment
WipeKeyframes(mainSegmentEnd, null);
SelectedProfileElement.EndSegmentLength = TimeSpan.Zero;
}
ShiftNextSegment(SegmentLength - oldSegmentLength);
ProfileEditorService.UpdateSelectedProfileElement();
Update();
}
public void EnableSegment()
{
ShiftNextSegment(TimeSpan.FromSeconds(1));
if (Segment == SegmentViewModelType.Start)
SelectedProfileElement.StartSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.Main)
SelectedProfileElement.MainSegmentLength = TimeSpan.FromSeconds(1);
else if (Segment == SegmentViewModelType.End)
SelectedProfileElement.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
var parent = VisualTreeUtilities.FindParent<ScrollViewer>((DependencyObject) sender, "TimelineHeaderScrollViewer");
var x = Math.Max(0, e.GetPosition(parent).X);
var 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))
{
var 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);
var oldSegmentLength = SegmentLength;
if (Segment == SegmentViewModelType.Start)
{
if (newTime < TimeSpan.FromMilliseconds(100))
newTime = TimeSpan.FromMilliseconds(100);
SelectedProfileElement.StartSegmentLength = newTime;
}
else if (Segment == SegmentViewModelType.Main)
{
if (newTime < SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100))
newTime = SelectedProfileElement.StartSegmentLength + TimeSpan.FromMilliseconds(100);
SelectedProfileElement.MainSegmentLength = newTime - SelectedProfileElement.StartSegmentLength;
}
else if (Segment == SegmentViewModelType.End)
{
if (newTime < SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100))
newTime = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength + TimeSpan.FromMilliseconds(100);
SelectedProfileElement.EndSegmentLength = newTime - SelectedProfileElement.StartSegmentLength - SelectedProfileElement.MainSegmentLength;
}
ShiftNextSegment(SegmentLength - oldSegmentLength);
Update();
}
#endregion
#region IDIsposable
public void Dispose()
{
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
ProfileEditorService.ProfileElementSelected -= ProfileEditorServiceOnProfileElementSelected;
if (SelectedProfileElement != null)
SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
}
#endregion
#region Event handlers
private void ProfileEditorServiceOnProfileElementSelected(object? sender, RenderProfileElementEventArgs e)
{
if (e.PreviousRenderProfileElement != null)
e.PreviousRenderProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
if (e.RenderProfileElement != null)
e.RenderProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
Update();
}
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) ||
e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) ||
e.PropertyName == nameof(RenderProfileElement.EndSegmentLength))
Update();
else if (e.PropertyName == nameof(RenderProfileElement.DisplayContinuously))
NotifyOfPropertyChange(nameof(RepeatSegment));
}
private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e)
{
Update();
}
#endregion
public void ShiftNextSegment(TimeSpan amount)
{
var segmentEnd = TimeSpan.Zero;
if (Segment == SegmentViewModelType.Start)
segmentEnd = SelectedProfileElement.StartSegmentLength;
else if (Segment == SegmentViewModelType.Main)
segmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
else if (Segment == SegmentViewModelType.End)
segmentEnd = SelectedProfileElement.TimelineLength;
ShiftKeyframes(segmentEnd, null, amount);
}
private void WipeKeyframes(TimeSpan? start, TimeSpan? end)
{
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
layerPropertyGroupViewModel.WipeKeyframes(start, end);
}
private void ShiftKeyframes(TimeSpan? start, TimeSpan? end, TimeSpan amount)
{
foreach (var layerPropertyGroupViewModel in LayerPropertyGroups)
layerPropertyGroupViewModel.ShiftKeyframes(start, end, amount);
}
}
public enum SegmentViewModelType
{
Start,
Main,
End
}
}