mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-12 21:38:38 +00:00
231 lines
8.6 KiB
C#
231 lines
8.6 KiB
C#
using System;
|
|
using System.Reactive;
|
|
using System.Reactive.Disposables;
|
|
using System.Reactive.Linq;
|
|
using System.Timers;
|
|
using Artemis.Core;
|
|
using Artemis.Core.Services;
|
|
using Artemis.UI.Shared;
|
|
using Artemis.UI.Shared.Services.ProfileEditor;
|
|
using Avalonia.Threading;
|
|
using PropertyChanged.SourceGenerator;
|
|
using ReactiveUI;
|
|
|
|
namespace Artemis.UI.Screens.ProfileEditor.Playback;
|
|
|
|
public partial class PlaybackViewModel : ActivatableViewModelBase
|
|
{
|
|
private readonly IProfileEditorService _profileEditorService;
|
|
private readonly ISettingsService _settingsService;
|
|
private ObservableAsPropertyHelper<TimeSpan>? _currentTime;
|
|
private ObservableAsPropertyHelper<string?>? _formattedCurrentTime;
|
|
private ObservableAsPropertyHelper<bool>? _keyBindingsEnabled;
|
|
private DateTime _lastUpdate;
|
|
private ObservableAsPropertyHelper<bool>? _playing;
|
|
private RenderProfileElement? _profileElement;
|
|
[Notify] private bool _repeating;
|
|
[Notify] private bool _repeatSegment;
|
|
[Notify] private bool _repeatTimeline;
|
|
|
|
public PlaybackViewModel(IProfileEditorService profileEditorService, ISettingsService settingsService)
|
|
{
|
|
_profileEditorService = profileEditorService;
|
|
_settingsService = settingsService;
|
|
|
|
if (_settingsService.GetSetting("ProfileEditor.RepeatTimeline", true).Value)
|
|
{
|
|
_repeating = true;
|
|
_repeatTimeline = true;
|
|
}
|
|
else if (_settingsService.GetSetting("ProfileEditor.RepeatSegment", false).Value)
|
|
{
|
|
_repeating = true;
|
|
_repeatSegment = true;
|
|
}
|
|
else
|
|
{
|
|
_repeating = false;
|
|
_repeatTimeline = true;
|
|
}
|
|
|
|
this.WhenActivated(d =>
|
|
{
|
|
_profileEditorService.ProfileElement.Subscribe(e => _profileElement = e).DisposeWith(d);
|
|
_currentTime = _profileEditorService.Time.ToProperty(this, vm => vm.CurrentTime).DisposeWith(d);
|
|
_formattedCurrentTime = _profileEditorService.Time.Select(t => $"{Math.Floor(t.TotalSeconds):00}.{t.Milliseconds:000}").ToProperty(this, vm => vm.FormattedCurrentTime).DisposeWith(d);
|
|
_playing = _profileEditorService.Playing.ToProperty(this, vm => vm.Playing).DisposeWith(d);
|
|
_keyBindingsEnabled = Shared.UI.CurrentKeyBindingsEnabled.ToProperty(this, vm => vm.KeyBindingsEnabled).DisposeWith(d);
|
|
|
|
// Update timer
|
|
Timer updateTimer = new(TimeSpan.FromMilliseconds(60.0 / 1000));
|
|
updateTimer.Elapsed += (_, _) => Update();
|
|
updateTimer.DisposeWith(d);
|
|
_profileEditorService.Playing.Subscribe(_ => _lastUpdate = DateTime.Now).DisposeWith(d);
|
|
_profileEditorService.Playing.Subscribe(p => updateTimer.Enabled = p).DisposeWith(d);
|
|
_lastUpdate = DateTime.MinValue;
|
|
|
|
Disposable.Create(() =>
|
|
{
|
|
_settingsService.GetSetting("ProfileEditor.RepeatTimeline", true).Value = _repeating && _repeatTimeline;
|
|
_settingsService.GetSetting("ProfileEditor.RepeatSegment", false).Value = _repeating && _repeatSegment;
|
|
}).DisposeWith(d);
|
|
});
|
|
|
|
PlayFromStart = ReactiveCommand.Create(ExecutePlayFromStart, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
|
|
TogglePlay = ReactiveCommand.Create(ExecuteTogglePlay, this.WhenAnyValue(vm => vm.KeyBindingsEnabled));
|
|
GoToStart = ReactiveCommand.Create(ExecuteGoToStart);
|
|
GoToEnd = ReactiveCommand.Create(ExecuteGoToEnd);
|
|
GoToPreviousFrame = ReactiveCommand.Create(ExecuteGoToPreviousFrame);
|
|
GoToNextFrame = ReactiveCommand.Create(ExecuteGoToNextFrame);
|
|
CycleRepeating = ReactiveCommand.Create(ExecuteCycleRepeating);
|
|
}
|
|
|
|
public TimeSpan CurrentTime => _currentTime?.Value ?? TimeSpan.Zero;
|
|
public string? FormattedCurrentTime => _formattedCurrentTime?.Value;
|
|
public bool Playing => _playing?.Value ?? false;
|
|
public bool KeyBindingsEnabled => _keyBindingsEnabled?.Value ?? false;
|
|
|
|
public ReactiveCommand<Unit, Unit> PlayFromStart { get; }
|
|
public ReactiveCommand<Unit, Unit> TogglePlay { get; }
|
|
public ReactiveCommand<Unit, Unit> GoToStart { get; }
|
|
public ReactiveCommand<Unit, Unit> GoToEnd { get; }
|
|
public ReactiveCommand<Unit, Unit> GoToPreviousFrame { get; }
|
|
public ReactiveCommand<Unit, Unit> GoToNextFrame { get; }
|
|
public ReactiveCommand<Unit, Unit> CycleRepeating { get; }
|
|
|
|
private void ExecutePlayFromStart()
|
|
{
|
|
ExecuteGoToStart();
|
|
if (!Playing)
|
|
_profileEditorService.Play();
|
|
}
|
|
|
|
private void ExecuteTogglePlay()
|
|
{
|
|
if (!Playing)
|
|
_profileEditorService.Play();
|
|
else
|
|
_profileEditorService.Pause();
|
|
}
|
|
|
|
private void ExecuteGoToStart()
|
|
{
|
|
_profileEditorService.ChangeTime(TimeSpan.Zero);
|
|
}
|
|
|
|
private void ExecuteGoToEnd()
|
|
{
|
|
if (_profileElement == null)
|
|
return;
|
|
|
|
_profileEditorService.ChangeTime(_profileElement.Timeline.EndSegmentEndPosition);
|
|
}
|
|
|
|
private void ExecuteGoToPreviousFrame()
|
|
{
|
|
if (_profileElement == null)
|
|
return;
|
|
|
|
double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
|
double newTime = Math.Max(0, Math.Round((CurrentTime.TotalMilliseconds - frameTime) / frameTime) * frameTime);
|
|
_profileEditorService.ChangeTime(TimeSpan.FromMilliseconds(newTime));
|
|
}
|
|
|
|
private void ExecuteGoToNextFrame()
|
|
{
|
|
if (_profileElement == null)
|
|
return;
|
|
|
|
double frameTime = 1000.0 / _settingsService.GetSetting("Core.TargetFrameRate", 30).Value;
|
|
double newTime = Math.Round((CurrentTime.TotalMilliseconds + frameTime) / frameTime) * frameTime;
|
|
newTime = Math.Min(newTime, _profileElement.Timeline.EndSegmentEndPosition.TotalMilliseconds);
|
|
_profileEditorService.ChangeTime(TimeSpan.FromMilliseconds(newTime));
|
|
}
|
|
|
|
private void ExecuteCycleRepeating()
|
|
{
|
|
if (!Repeating)
|
|
{
|
|
RepeatTimeline = true;
|
|
RepeatSegment = false;
|
|
Repeating = true;
|
|
}
|
|
else if (RepeatTimeline)
|
|
{
|
|
RepeatTimeline = false;
|
|
RepeatSegment = true;
|
|
this.RaisePropertyChanged(nameof(Repeating));
|
|
}
|
|
else if (RepeatSegment)
|
|
{
|
|
RepeatTimeline = true;
|
|
RepeatSegment = false;
|
|
Repeating = false;
|
|
}
|
|
}
|
|
|
|
private TimeSpan GetCurrentSegmentStart()
|
|
{
|
|
if (_profileElement == null)
|
|
return TimeSpan.Zero;
|
|
|
|
if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
|
|
return TimeSpan.Zero;
|
|
if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
|
|
return _profileElement.Timeline.MainSegmentStartPosition;
|
|
if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
|
|
return _profileElement.Timeline.EndSegmentStartPosition;
|
|
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
private TimeSpan GetCurrentSegmentEnd()
|
|
{
|
|
if (_profileElement == null)
|
|
return TimeSpan.Zero;
|
|
|
|
if (CurrentTime < _profileElement.Timeline.StartSegmentEndPosition)
|
|
return _profileElement.Timeline.StartSegmentEndPosition;
|
|
if (CurrentTime < _profileElement.Timeline.MainSegmentEndPosition)
|
|
return _profileElement.Timeline.MainSegmentEndPosition;
|
|
if (CurrentTime < _profileElement.Timeline.EndSegmentEndPosition)
|
|
return _profileElement.Timeline.EndSegmentEndPosition;
|
|
|
|
return TimeSpan.Zero;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
try
|
|
{
|
|
if (_lastUpdate == DateTime.MinValue)
|
|
_lastUpdate = DateTime.Now;
|
|
|
|
TimeSpan newTime = CurrentTime.Add(DateTime.Now - _lastUpdate);
|
|
if (_profileElement != null)
|
|
{
|
|
if (Repeating && RepeatTimeline)
|
|
{
|
|
if (newTime > _profileElement.Timeline.Length)
|
|
newTime = TimeSpan.Zero;
|
|
}
|
|
else if (Repeating && RepeatSegment)
|
|
{
|
|
if (newTime > GetCurrentSegmentEnd())
|
|
newTime = GetCurrentSegmentStart();
|
|
}
|
|
else if (newTime > _profileElement.Timeline.Length)
|
|
{
|
|
newTime = _profileElement.Timeline.Length;
|
|
_profileEditorService.Pause();
|
|
}
|
|
}
|
|
|
|
Dispatcher.UIThread.Invoke(() => _profileEditorService.ChangeTime(newTime));
|
|
}
|
|
finally
|
|
{
|
|
_lastUpdate = DateTime.Now;
|
|
}
|
|
}
|
|
} |