mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-05-24 06:46:26 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
240 lines
5.5 KiB
C#
240 lines
5.5 KiB
C#
using Sandbox.MovieMaker;
|
|
using System.Collections.Immutable;
|
|
using System.Linq;
|
|
using Editor.MovieMaker.BlockDisplays;
|
|
|
|
namespace Editor.MovieMaker;
|
|
|
|
#nullable enable
|
|
|
|
public sealed class KeyframeHandle : GraphicsItem, IComparable<KeyframeHandle>, ITrackItem, IMovieDraggable, IMovieContextMenu, ISnapSource
|
|
{
|
|
private Keyframe _keyframe;
|
|
private bool _isDragging;
|
|
|
|
public new TimelineTrack Parent { get; }
|
|
public Session Session { get; }
|
|
public TrackView View { get; }
|
|
|
|
public KeyframeEditMode? EditMode => Session.EditMode as KeyframeEditMode;
|
|
|
|
public Keyframe Keyframe
|
|
{
|
|
get => _keyframe;
|
|
set
|
|
{
|
|
if ( _keyframe == value ) return;
|
|
|
|
_keyframe = value;
|
|
ToolTip = $"{View.Track.Name} = {Keyframe.Value?.ToString() ?? "null"}";
|
|
|
|
UpdatePosition();
|
|
}
|
|
}
|
|
|
|
public MovieTime Time
|
|
{
|
|
get => Keyframe.Time;
|
|
set => Keyframe = Keyframe with { Time = value };
|
|
}
|
|
|
|
bool IMovieItem.MultiSelectable => true;
|
|
TimelineTrack ITrackItem.Track => Parent;
|
|
|
|
public KeyframeHandle( TimelineTrack parent, Keyframe keyframe )
|
|
: base( parent )
|
|
{
|
|
Parent = parent;
|
|
Session = parent.Session;
|
|
View = parent.View;
|
|
|
|
HandlePosition = new Vector2( 0.5f, 0f );
|
|
ZIndex = 100;
|
|
|
|
HoverEvents = true;
|
|
|
|
Focusable = true;
|
|
Selectable = true;
|
|
|
|
Cursor = CursorShape.Finger;
|
|
Keyframe = keyframe;
|
|
}
|
|
|
|
public void UpdatePosition()
|
|
{
|
|
PrepareGeometryChange();
|
|
|
|
Position = new Vector2( Session.TimeToPixels( Time ), 0f );
|
|
Size = new Vector2( 16f, Parent.Height );
|
|
|
|
Update();
|
|
}
|
|
|
|
protected override void OnSelectionChanged()
|
|
{
|
|
base.OnSelectionChanged();
|
|
UpdatePosition();
|
|
|
|
ZIndex = Selected ? 101 : 100;
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
if ( View.IsLocked ) return;
|
|
|
|
Paint.ClearPen();
|
|
Paint.SetBrushRadial( LocalRect.Center, Width * 0.5f, Timeline.Colors.ChannelBackground, Color.Transparent );
|
|
Paint.DrawRect( LocalRect );
|
|
|
|
var c = PaintExtensions.PaintSelectColor( Parent.HandleColor.WithAlpha( 0.5f ), Parent.HandleColor, Timeline.Colors.HandleSelected );
|
|
|
|
Paint.SetBrushAndPen( c );
|
|
|
|
switch ( Keyframe.Interpolation )
|
|
{
|
|
case KeyframeInterpolation.Step:
|
|
Paint.DrawRect( new Rect( Size * 0.5f, 0f ).Grow( 4f ), 1f );
|
|
break;
|
|
|
|
case KeyframeInterpolation.Linear:
|
|
PaintExtensions.PaintTriangle( Size * 0.5f, 10 );
|
|
break;
|
|
|
|
case KeyframeInterpolation.Quadratic:
|
|
Paint.DrawCircle( Size * 0.5f, 8f );
|
|
break;
|
|
|
|
case KeyframeInterpolation.Cubic:
|
|
Paint.DrawCircle( Size * 0.5f, 6f );
|
|
Paint.ClearBrush();
|
|
|
|
Paint.SetPen( c );
|
|
Paint.DrawCircle( Size * 0.5f, 10f );
|
|
break;
|
|
}
|
|
|
|
Paint.SetPen( c.WithAlphaMultiplied( 0.3f ) );
|
|
Paint.DrawLine( new Vector2( Width * 0.5f, 0 ), new Vector2( Width * 0.5f, Height ) );
|
|
}
|
|
|
|
public void ShowContextMenu( EditMode.ContextMenuEvent ev )
|
|
{
|
|
if ( EditMode is not { } editMode ) return;
|
|
|
|
ev.Accepted = true;
|
|
|
|
editMode.Session.PlayheadTime = Keyframe.Time;
|
|
|
|
var selection = GraphicsView.SelectedItems
|
|
.OfType<KeyframeHandle>()
|
|
.ToImmutableArray();
|
|
|
|
ev.Menu.AddHeading( $"Selected Keyframe{(selection.Length > 1 ? "s" : "")}" );
|
|
|
|
CreateInterpolationMenu( selection, ev.Menu );
|
|
|
|
ev.Menu.AddHeading( "Clipboard" );
|
|
|
|
ev.Menu.AddOption( "Copy", "content_copy", () => editMode.Copy() );
|
|
ev.Menu.AddOption( "Cut", "content_cut", () => editMode.Cut() );
|
|
|
|
if ( GetOverlappingClipboard( selection ) is { } clipboard )
|
|
{
|
|
ev.Menu.AddOption( "Paste", "content_paste", () => editMode.Paste( clipboard, Time - clipboard.Time ) );
|
|
}
|
|
|
|
ev.Menu.AddOption( "Delete", "delete", () => editMode.Delete() );
|
|
}
|
|
|
|
private KeyframeEditMode.ClipboardData? GetOverlappingClipboard( IReadOnlyList<KeyframeHandle> selection )
|
|
{
|
|
if ( EditMode?.Clipboard is not { } clipboard ) return null;
|
|
|
|
if ( !selection.Any( x => clipboard.Keyframes.Any( y => y.Guid == x.View.Track.Id ) ) )
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return clipboard;
|
|
}
|
|
|
|
private void CreateInterpolationMenu( IReadOnlyList<KeyframeHandle> selection, Menu parent )
|
|
{
|
|
var menu = parent.AddMenu( "Interpolation Mode", "gradient" );
|
|
var currentMode = selection.All( x => x.Keyframe.Interpolation == selection[0].Keyframe.Interpolation )
|
|
? selection[0].Keyframe.Interpolation
|
|
: KeyframeInterpolation.Unknown;
|
|
|
|
foreach ( var value in Enum.GetValues<KeyframeInterpolation>() )
|
|
{
|
|
if ( value < 0 ) continue;
|
|
|
|
var option = menu.AddOption( value.ToString().ToTitleCase(), action: () =>
|
|
{
|
|
foreach ( var handle in selection )
|
|
{
|
|
handle.Keyframe = handle.Keyframe with { Interpolation = value };
|
|
}
|
|
|
|
EditMode?.UpdateTracksFromHandles( selection );
|
|
} );
|
|
|
|
option.Checkable = true;
|
|
option.Checked = value == currentMode;
|
|
}
|
|
}
|
|
|
|
public int CompareTo( KeyframeHandle? other )
|
|
{
|
|
if ( ReferenceEquals( this, other ) )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if ( other is null )
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
var timeCompare = Time.CompareTo( other.Time );
|
|
|
|
if ( timeCompare != 0 )
|
|
{
|
|
return timeCompare;
|
|
}
|
|
|
|
// When overlapping, put selected first
|
|
|
|
return -Selected.CompareTo( other.Selected );
|
|
}
|
|
|
|
MovieTimeRange IMovieItem.TimeRange => Keyframe.Time;
|
|
void IMovieDraggable.StartDrag() => _isDragging = true;
|
|
|
|
void IMovieDraggable.Drag( MovieTime delta )
|
|
{
|
|
Time += delta;
|
|
}
|
|
|
|
void IMovieDraggable.EndDrag() => _isDragging = false;
|
|
|
|
public IEnumerable<SnapTarget> GetSnapTargets( MovieTime sourceTime, bool isPrimary ) => _isDragging ? [] : [Time];
|
|
|
|
public bool SnapFilter( ISnapSource source )
|
|
{
|
|
if ( source == this ) return false;
|
|
if ( source is not BlockItem block ) return true;
|
|
|
|
var view = View;
|
|
|
|
while ( view is not null )
|
|
{
|
|
if ( view == block.Parent.View ) return false;
|
|
|
|
view = view.Parent;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|