mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 11:56:30 +00:00
301 lines
13 KiB
C#
301 lines
13 KiB
C#
using System;
|
|
using System.ComponentModel;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using Artemis.Core.Models.Profile;
|
|
using Artemis.UI.Shared.Services.Interfaces;
|
|
using Artemis.UI.Shared.Utilities;
|
|
using Stylet;
|
|
|
|
namespace Artemis.UI.Screens.Module.ProfileEditor.LayerProperties.Timeline
|
|
{
|
|
public class TimelineSegmentViewModel : PropertyChangedBase, IDisposable
|
|
{
|
|
private bool _draggingSegment;
|
|
private bool _showSegmentName;
|
|
private bool _showRepeatButton;
|
|
private bool _showDisableButton;
|
|
|
|
public TimelineSegmentViewModel(IProfileEditorService profileEditorService, SegmentViewModelType segment)
|
|
{
|
|
ProfileEditorService = profileEditorService;
|
|
Segment = segment;
|
|
SelectedProfileElement = ProfileEditorService.SelectedProfileElement;
|
|
|
|
switch (Segment)
|
|
{
|
|
case SegmentViewModelType.Start:
|
|
ToolTip = "This segment is played when a layer starts displaying because it's conditions are met";
|
|
break;
|
|
case SegmentViewModelType.Main:
|
|
ToolTip = "This segment is played while a condition is met, either once or on a repeating loop";
|
|
break;
|
|
case SegmentViewModelType.End:
|
|
ToolTip = "This segment is played once a condition is no longer met";
|
|
break;
|
|
default:
|
|
throw new ArgumentOutOfRangeException(nameof(segment));
|
|
}
|
|
|
|
UpdateDisplay();
|
|
ProfileEditorService.PixelsPerSecondChanged += ProfileEditorServiceOnPixelsPerSecondChanged;
|
|
SelectedProfileElement.PropertyChanged += SelectedProfileElementOnPropertyChanged;
|
|
}
|
|
|
|
public RenderProfileElement SelectedProfileElement { get; }
|
|
|
|
public SegmentViewModelType Segment { get; }
|
|
public IProfileEditorService ProfileEditorService { get; }
|
|
public string ToolTip { get; }
|
|
|
|
public TimeSpan SegmentLength
|
|
{
|
|
get
|
|
{
|
|
return Segment switch
|
|
{
|
|
SegmentViewModelType.Start => SelectedProfileElement?.StartSegmentLength ?? TimeSpan.Zero,
|
|
SegmentViewModelType.Main => SelectedProfileElement?.MainSegmentLength ?? TimeSpan.Zero,
|
|
SegmentViewModelType.End => SelectedProfileElement?.EndSegmentLength ?? TimeSpan.Zero,
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
}
|
|
}
|
|
|
|
public double SegmentWidth => ProfileEditorService.PixelsPerSecond * SegmentLength.TotalSeconds;
|
|
|
|
public bool SegmentEnabled => SegmentLength != TimeSpan.Zero;
|
|
public bool IsMainSegment => Segment == SegmentViewModelType.Main;
|
|
|
|
// 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?.RepeatMainSegment ?? false;
|
|
}
|
|
set
|
|
{
|
|
if (Segment != SegmentViewModelType.Main)
|
|
return;
|
|
|
|
SelectedProfileElement.RepeatMainSegment = value;
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
NotifyOfPropertyChange(nameof(RepeatSegment));
|
|
}
|
|
}
|
|
|
|
public double SegmentStartPosition
|
|
{
|
|
get
|
|
{
|
|
return Segment switch
|
|
{
|
|
SegmentViewModelType.Start => 0,
|
|
SegmentViewModelType.Main => ProfileEditorService.PixelsPerSecond * SelectedProfileElement.StartSegmentLength.TotalSeconds,
|
|
SegmentViewModelType.End => ProfileEditorService.PixelsPerSecond * (SelectedProfileElement.StartSegmentLength.TotalSeconds + SelectedProfileElement.MainSegmentLength.TotalSeconds),
|
|
_ => throw new ArgumentOutOfRangeException()
|
|
};
|
|
}
|
|
}
|
|
|
|
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 void Dispose()
|
|
{
|
|
ProfileEditorService.PixelsPerSecondChanged -= ProfileEditorServiceOnPixelsPerSecondChanged;
|
|
SelectedProfileElement.PropertyChanged -= SelectedProfileElementOnPropertyChanged;
|
|
}
|
|
|
|
public void DisableSegment()
|
|
{
|
|
var keyframes = SelectedProfileElement.GetAllKeyframes();
|
|
var startSegmentEnd = SelectedProfileElement.StartSegmentLength;
|
|
var mainSegmentEnd = SelectedProfileElement.StartSegmentLength + SelectedProfileElement.MainSegmentLength;
|
|
|
|
var oldSegmentLength = SegmentLength;
|
|
|
|
if (Segment == SegmentViewModelType.Start)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position < startSegmentEnd))
|
|
baseLayerPropertyKeyframe.Remove();
|
|
SelectedProfileElement.StartSegmentLength = TimeSpan.Zero;
|
|
}
|
|
else if (Segment == SegmentViewModelType.Main)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > startSegmentEnd && k.Position < mainSegmentEnd))
|
|
baseLayerPropertyKeyframe.Remove();
|
|
SelectedProfileElement.MainSegmentLength = TimeSpan.Zero;
|
|
}
|
|
else if (Segment == SegmentViewModelType.End)
|
|
{
|
|
// Remove keyframes that fall in this segment
|
|
foreach (var baseLayerPropertyKeyframe in keyframes.Where(k => k.Position > mainSegmentEnd))
|
|
baseLayerPropertyKeyframe.Remove();
|
|
SelectedProfileElement.EndSegmentLength = TimeSpan.Zero;
|
|
}
|
|
|
|
NotifyOfPropertyChange(nameof(SegmentEnabled));
|
|
ShiftNextSegment(SegmentLength - oldSegmentLength);
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
}
|
|
|
|
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);
|
|
|
|
NotifyOfPropertyChange(nameof(SegmentEnabled));
|
|
ProfileEditorService.UpdateSelectedProfileElement();
|
|
UpdateDisplay();
|
|
}
|
|
|
|
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;
|
|
|
|
foreach (var baseLayerPropertyKeyframe in SelectedProfileElement.GetAllKeyframes().Where(k => k.Position >= segmentEnd))
|
|
baseLayerPropertyKeyframe.Position += amount;
|
|
}
|
|
|
|
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))
|
|
newTime = ProfileEditorService.SnapToTimeline(newTime, TimeSpan.FromMilliseconds(1000f / ProfileEditorService.PixelsPerSecond * 5), false, true, true);
|
|
// 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;
|
|
}
|
|
|
|
NotifyOfPropertyChange(nameof(SegmentLength));
|
|
NotifyOfPropertyChange(nameof(SegmentWidth));
|
|
|
|
ShiftNextSegment(SegmentLength - oldSegmentLength);
|
|
UpdateDisplay();
|
|
}
|
|
|
|
private void SelectedProfileElementOnPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
{
|
|
if (e.PropertyName == nameof(RenderProfileElement.StartSegmentLength) ||
|
|
e.PropertyName == nameof(RenderProfileElement.MainSegmentLength) ||
|
|
e.PropertyName == nameof(RenderProfileElement.EndSegmentLength))
|
|
{
|
|
NotifyOfPropertyChange(nameof(SegmentStartPosition));
|
|
NotifyOfPropertyChange(nameof(SegmentWidth));
|
|
}
|
|
}
|
|
|
|
|
|
private void ProfileEditorServiceOnPixelsPerSecondChanged(object? sender, EventArgs e)
|
|
{
|
|
NotifyOfPropertyChange(nameof(SegmentWidth));
|
|
NotifyOfPropertyChange(nameof(SegmentStartPosition));
|
|
|
|
UpdateDisplay();
|
|
}
|
|
|
|
private void UpdateDisplay()
|
|
{
|
|
if (!IsMainSegment)
|
|
ShowSegmentName = SegmentWidth > 60;
|
|
else
|
|
ShowSegmentName = SegmentWidth > 80;
|
|
|
|
ShowRepeatButton = SegmentWidth > 45 && IsMainSegment;
|
|
ShowDisableButton = SegmentWidth > 25;
|
|
}
|
|
}
|
|
|
|
public enum SegmentViewModelType
|
|
{
|
|
Start,
|
|
Main,
|
|
End
|
|
}
|
|
} |