1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
Robert 4b24834fd6 Plugins - Added profiling API
UI - Added profiling tab to debugger
2021-05-16 20:22:13 +02:00

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
}
}