Editor/MovieMaker/Modes/Keyframe/Clipboard.cs
using System.Collections;
using System.Collections.Immutable;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using Sandbox.MovieMaker;
namespace Editor.MovieMaker;
#nullable enable
partial class KeyframeEditMode
{
public record ClipboardData( MovieTime Time, IReadOnlyDictionary<Guid, JsonArray> Keyframes );
private ClipboardData? _clipboardData;
private int _clipboardHash;
public ClipboardData? Clipboard
{
get
{
var text = EditorUtility.Clipboard.Paste();
var hash = text?.GetHashCode() ?? 0;
if ( hash == _clipboardHash ) return _clipboardData;
_clipboardHash = hash;
try
{
var data = JsonSerializer.Deserialize<ClipboardData>( text ?? "null", EditorJsonOptions );
if ( data?.Keyframes.Count is not > 0 )
{
return _clipboardData = null;
}
return _clipboardData = data;
}
catch
{
return _clipboardData = null;
}
}
set
{
if ( value?.Keyframes.Count is not > 0 )
{
_clipboardData = null;
_clipboardHash = 0;
EditorUtility.Clipboard.Copy( "" );
return;
}
var text = JsonSerializer.Serialize( value, EditorJsonOptions );
_clipboardData = value;
_clipboardHash = text.GetHashCode();
EditorUtility.Clipboard.Copy( text );
}
}
protected override void OnCut()
{
Copy();
Delete();
}
protected override void OnCopy()
{
var groupedByTrack = SelectedKeyframes
.GroupBy( x => x.View.Track );
var time = SelectedKeyframes.Select( x => x.Time )
.DefaultIfEmpty( MovieTime.Zero )
.Min();
var data = new ClipboardData( time,
groupedByTrack.ToImmutableDictionary(
x => x.Key.Id,
x => JsonSerializer.SerializeToNode(
x.Select( x => x.Keyframe ).ToImmutableArray(),
EditorJsonOptions )!.AsArray() ) );
if ( data.Keyframes.Count == 0 ) return;
Clipboard = data;
}
protected override void OnPaste()
{
if ( Clipboard is { } data )
{
Paste( data, MovieTime.Zero );
}
}
public void Paste( ClipboardData data, MovieTime offset )
{
Timeline.DeselectAll();
foreach ( var (trackId, array) in data.Keyframes )
{
var view = Session.TrackList.EditableTracks
.FirstOrDefault( x => x.Track.Id == trackId );
if ( view?.Track is not IProjectPropertyTrack { TargetType: { } propertyType } ) continue;
if ( GetTimelineTrack( view ) is not { } timelineTrack ) continue;
if ( GetHandles( timelineTrack ) is not { } handles ) continue;
var keyframeType = typeof( Keyframe<> ).MakeGenericType( propertyType );
var arrayType = typeof( ImmutableArray<> ).MakeGenericType( keyframeType );
var keyframes = (IEnumerable)array.Deserialize( arrayType, EditorJsonOptions )!;
handles.AddRange( keyframes.Cast<IKeyframe>(), offset );
view.MarkValueChanged();
}
}
}