mirror of
https://github.com/Facepunch/sbox-public.git
synced 2026-06-04 20:27:14 -04:00
This commit imports the C# engine code and game files, excluding C++ source code. [Source-Commit: ceb3d758046e50faa6258bc3b658a30c97743268]
522 lines
14 KiB
C#
522 lines
14 KiB
C#
using Sandbox;
|
|
using Sandbox.MovieMaker;
|
|
using System.Linq;
|
|
|
|
namespace Editor.MovieMaker;
|
|
|
|
#nullable enable
|
|
|
|
partial class MotionEditMode
|
|
{
|
|
private abstract class TimeSelectionItem : GraphicsItem, IMovieDraggable, IMovieContextMenu
|
|
{
|
|
/// <summary>
|
|
/// Capture time selection before being dragged so we can revert etc.
|
|
/// </summary>
|
|
protected TimeSelection? OriginalSelection { get; private set; }
|
|
protected IModificationOptions? OriginalModificationOptions { get; private set; }
|
|
|
|
public MotionEditMode EditMode { get; }
|
|
|
|
protected TimeSelectionItem( MotionEditMode editMode )
|
|
{
|
|
EditMode = editMode;
|
|
|
|
HoverEvents = true;
|
|
Focusable = true;
|
|
Selectable = true;
|
|
}
|
|
|
|
public abstract void UpdatePosition( TimeSelection value, Rect viewRect );
|
|
|
|
public override bool Contains( Vector2 localPos )
|
|
{
|
|
return BoundingRect.Shrink( 0f, 24f, 0f, 24f ).IsInside( localPos );
|
|
}
|
|
|
|
public abstract MovieTimeRange TimeRange { get; }
|
|
|
|
public virtual (MovieTime? Min, MovieTime? Max) DragLimits => (-TimeRange.Start, null);
|
|
|
|
public virtual CursorShape DragCursor => CursorShape.Finger;
|
|
public virtual IEnumerable<MovieTime> SnapSources => [TimeRange.Start, TimeRange.End];
|
|
|
|
void IMovieDraggable.StartDrag()
|
|
{
|
|
OriginalSelection = EditMode.TimeSelection;
|
|
OriginalModificationOptions = EditMode.Modification?.Options;
|
|
|
|
OnStartDrag();
|
|
}
|
|
|
|
protected virtual void OnStartDrag() { }
|
|
|
|
void IMovieDraggable.Drag( MovieTime delta ) => OnDrag( delta );
|
|
|
|
protected virtual void OnDrag( MovieTime delta ) { }
|
|
|
|
void IMovieDraggable.EndDrag()
|
|
{
|
|
try
|
|
{
|
|
OnEndDrag();
|
|
}
|
|
finally
|
|
{
|
|
OriginalSelection = null;
|
|
OriginalModificationOptions = null;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnEndDrag() { }
|
|
|
|
bool IMovieDraggable.SnapFilter( ISnapSource source ) => OnSnapFilter( source );
|
|
|
|
protected virtual bool OnSnapFilter( ISnapSource source ) => source is not TimeSelectionItem;
|
|
|
|
public void ShowContextMenu( ContextMenuEvent ev )
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection )
|
|
{
|
|
return;
|
|
}
|
|
|
|
ev.Title = selection.TotalTimeRange.ToString();
|
|
ev.Accepted = true;
|
|
|
|
EditMode.AddTimeSelectionContextMenu( ev, selection );
|
|
|
|
OnShowContextMenu( ev );
|
|
|
|
EditMode.AddClipboardContextMenu( ev, selection.TotalTimeRange );
|
|
}
|
|
|
|
protected virtual void OnShowContextMenu( ContextMenuEvent ev )
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inner region of the timeline selection. Dragging it moves the whole selection left / right.
|
|
/// </summary>
|
|
private sealed class TimeSelectionPeakItem : TimeSelectionItem
|
|
{
|
|
public override MovieTimeRange TimeRange => EditMode.TimeSelection?.PeakTimeRange ?? default;
|
|
|
|
public override (MovieTime? Min, MovieTime? Max) DragLimits => (-EditMode.TimeSelection?.TotalStart, null);
|
|
|
|
public override IEnumerable<MovieTime> SnapSources
|
|
{
|
|
get
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection ) return [];
|
|
|
|
return [selection.TotalStart, selection.PeakStart, selection.PeakEnd, selection.TotalEnd];
|
|
}
|
|
}
|
|
|
|
public TimeSelectionPeakItem( MotionEditMode editMode )
|
|
: base( editMode )
|
|
{
|
|
ZIndex = 10000;
|
|
Cursor = CursorShape.Finger;
|
|
}
|
|
|
|
protected override void OnDrag( MovieTime delta )
|
|
{
|
|
if ( EditMode.TimeSelection is not { } current ) return;
|
|
|
|
if ( EditMode.Modification?.Options is ITranslatableOptions translatable )
|
|
{
|
|
EditMode.Modification.Options = translatable.WithOffset( translatable.Offset + delta );
|
|
}
|
|
|
|
EditMode.TimeSelection = current + delta;
|
|
}
|
|
|
|
public override void UpdatePosition( TimeSelection value, Rect viewRect )
|
|
{
|
|
PrepareGeometryChange();
|
|
|
|
var timeRange = value.PeakTimeRange;
|
|
|
|
Position = new Vector2( EditMode.Session.TimeToPixels( timeRange.Start ), viewRect.Top );
|
|
Size = new Vector2( EditMode.Session.TimeToPixels( timeRange.Duration ), viewRect.Height );
|
|
|
|
Update();
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
var color = EditMode.SelectionColor;
|
|
|
|
if ( Hovered ) color = color.Lighten( 0.1f );
|
|
|
|
Paint.Antialiasing = true;
|
|
|
|
Paint.SetBrush( color );
|
|
Paint.SetPen( Color.White.WithAlpha( 0.5f ), 0.5f );
|
|
Paint.DrawRect( LocalRect.Grow( 0f, 16f ) );
|
|
|
|
if ( EditMode.LastActionIcon is { } icon && EditMode._lastActionTime < 1f )
|
|
{
|
|
var t = 1f - EditMode._lastActionTime;
|
|
|
|
Paint.SetPen( Color.White.WithAlpha( t * t * t * t ) );
|
|
Paint.DrawIcon( LocalRect.Grow( 32f, 0f ), icon, 32f );
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum FadeKind
|
|
{
|
|
FadeIn,
|
|
FadeOut
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fade in / out region of the timeline selection. Dragging it moves the fade left / right.
|
|
/// If the selection has a zero-width peak (it fades out right after fading in), then
|
|
/// you can move the whole selection by starting a drag in the direction of the other
|
|
/// fade item.
|
|
/// </summary>
|
|
private sealed class TimeSelectionFadeItem : TimeSelectionItem
|
|
{
|
|
private bool? _moveWholeSelection;
|
|
|
|
public FadeKind Kind { get; }
|
|
|
|
public override MovieTimeRange TimeRange
|
|
{
|
|
get
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection )
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if ( _moveWholeSelection is not false )
|
|
{
|
|
return selection.TotalTimeRange;
|
|
}
|
|
|
|
return Kind == FadeKind.FadeIn
|
|
? selection.FadeInTimeRange
|
|
: selection.FadeOutTimeRange;
|
|
}
|
|
}
|
|
|
|
public InterpolationMode? Interpolation
|
|
{
|
|
get => Kind == FadeKind.FadeIn
|
|
? EditMode.TimeSelection?.FadeIn.Interpolation
|
|
: EditMode.TimeSelection?.FadeOut.Interpolation;
|
|
|
|
set
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection ) return;
|
|
if ( value is not { } mode ) return;
|
|
|
|
EditMode.TimeSelection = Kind == FadeKind.FadeIn
|
|
? selection with { FadeIn = selection.FadeIn with { Interpolation = mode } }
|
|
: selection with { FadeOut = selection.FadeOut with { Interpolation = mode } };
|
|
}
|
|
}
|
|
|
|
public override (MovieTime? Min, MovieTime? Max) DragLimits
|
|
{
|
|
get
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection )
|
|
{
|
|
return default;
|
|
}
|
|
|
|
if ( _moveWholeSelection is not false )
|
|
{
|
|
return (-selection.TotalStart, null);
|
|
}
|
|
|
|
return Kind == FadeKind.FadeIn
|
|
? (-selection.TotalStart, selection.PeakTimeRange.Duration)
|
|
: (-selection.PeakTimeRange.Duration, null);
|
|
}
|
|
}
|
|
|
|
public override IEnumerable<MovieTime> SnapSources
|
|
{
|
|
get
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection )
|
|
{
|
|
return [];
|
|
}
|
|
|
|
return _moveWholeSelection is not false
|
|
? [selection.TotalStart, selection.PeakStart, selection.TotalEnd]
|
|
: base.SnapSources;
|
|
}
|
|
}
|
|
|
|
public TimeSelectionFadeItem( MotionEditMode editMode, FadeKind kind )
|
|
: base( editMode )
|
|
{
|
|
Kind = kind;
|
|
ZIndex = 10001;
|
|
HandlePosition = kind == FadeKind.FadeIn ? new Vector2( 1f, 0f ) : new Vector2( 0f, 0f );
|
|
Cursor = CursorShape.Finger;
|
|
}
|
|
|
|
public override void UpdatePosition( TimeSelection value, Rect viewRect )
|
|
{
|
|
PrepareGeometryChange();
|
|
|
|
if ( EditMode.TimeSelection is not { } selection )
|
|
{
|
|
Position = new Vector2( -50000f, 0f );
|
|
}
|
|
else
|
|
{
|
|
var timeRange = Kind == FadeKind.FadeIn
|
|
? selection.FadeInTimeRange
|
|
: selection.FadeOutTimeRange;
|
|
|
|
Position = new Vector2( EditMode.Session.TimeToPixels( Kind == FadeKind.FadeIn ? timeRange.End : timeRange.Start ), viewRect.Top );
|
|
Size = new Vector2( EditMode.Session.TimeToPixels( timeRange.Duration ), viewRect.Height );
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
protected override void OnStartDrag()
|
|
{
|
|
if ( OriginalSelection is not { } selection ) return;
|
|
|
|
// We can't move the whole selection if there's a peak range between the fade in / fade out.
|
|
|
|
_moveWholeSelection = selection.PeakTimeRange.IsEmpty ? null : false;
|
|
}
|
|
|
|
protected override void OnDrag( MovieTime delta )
|
|
{
|
|
if ( EditMode.TimeSelection is not { } selection ) return;
|
|
|
|
// We want to move the whole time selection if the fade in and fade out are adjacent,
|
|
// and we start trying to "push" the fade in into the fade out (or vice versa).
|
|
|
|
_moveWholeSelection ??= Kind == FadeKind.FadeIn && delta.IsPositive || Kind == FadeKind.FadeOut && delta.IsNegative;
|
|
|
|
if ( _moveWholeSelection is true )
|
|
{
|
|
if ( EditMode.Modification?.Options is ITranslatableOptions translatable )
|
|
{
|
|
EditMode.Modification.Options = translatable.WithOffset( translatable.Offset + delta );
|
|
}
|
|
|
|
EditMode.TimeSelection = selection + delta;
|
|
}
|
|
else if ( Kind == FadeKind.FadeIn )
|
|
{
|
|
EditMode.TimeSelection = selection.WithTimes( totalStart: selection.TotalStart + delta, peakStart: selection.PeakStart + delta );
|
|
}
|
|
else
|
|
{
|
|
EditMode.TimeSelection = selection.WithTimes( peakEnd: selection.PeakEnd + delta, totalEnd: selection.TotalEnd + delta );
|
|
}
|
|
}
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
if ( Width < 1f || Interpolation is not { } interpolation ) return;
|
|
|
|
var color = EditMode.SelectionColor;
|
|
|
|
if ( Hovered ) color = color.Lighten( 0.1f );
|
|
|
|
var fadeColor = color.WithAlpha( 0.02f );
|
|
|
|
var (x0, x1) = Kind == FadeKind.FadeIn
|
|
? (0f, Width)
|
|
: (Width, 0f);
|
|
|
|
Paint.Antialiasing = true;
|
|
|
|
Paint.SetBrushLinear( new Vector2( x0, 0f ), new Vector2( x1, 0f ), fadeColor, color );
|
|
Paint.SetPen( color.WithAlpha( 0.5f ), 0.5f );
|
|
PaintExtensions.PaintMirroredCurve( t => interpolation.Apply( t ), LocalRect, ScrubBar.Height, Kind == FadeKind.FadeOut );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// One of the boundaries between the 3 parts of the selection (fade in / peak / fade out). Drag it to move
|
|
/// just that boundary. If sections of the selection are zero-width (no peak / no fade in / no fade out),
|
|
/// then we work out which handle you wanted to drag based on the direction the drag starts. You can't move
|
|
/// these handles past one another.
|
|
/// </summary>
|
|
private sealed class TimeSelectionHandleItem : TimeSelectionItem, ISnapSource
|
|
{
|
|
private enum Index
|
|
{
|
|
TotalStart,
|
|
PeakStart,
|
|
PeakEnd,
|
|
TotalEnd
|
|
}
|
|
|
|
private Index _minIndex;
|
|
private Index _maxIndex;
|
|
|
|
private MovieTime? _dragTime;
|
|
|
|
public override CursorShape DragCursor => CursorShape.SizeH;
|
|
|
|
public TimeSelectionHandleItem( MotionEditMode editMode )
|
|
: base( editMode )
|
|
{
|
|
HandlePosition = new Vector2( 0.5f, 0f );
|
|
|
|
ZIndex = 10002;
|
|
}
|
|
|
|
public override MovieTimeRange TimeRange
|
|
{
|
|
get
|
|
{
|
|
if ( EditMode.TimeSelection is not { } current )
|
|
{
|
|
return default;
|
|
}
|
|
|
|
return _dragTime ?? GetTime( current, GetIndexRange().Min );
|
|
}
|
|
}
|
|
|
|
public override (MovieTime? Min, MovieTime? Max) DragLimits
|
|
{
|
|
get
|
|
{
|
|
if ( OriginalSelection is not { } original || EditMode.TimeSelection is not { } current )
|
|
{
|
|
return base.DragLimits;
|
|
}
|
|
|
|
// Limit dragging to neighbouring handles
|
|
|
|
var curTime = GetTime( current, _minIndex );
|
|
var minTime = GetTime( original, _minIndex - 1 );
|
|
var maxTime = GetTime( original, _maxIndex + 1 );
|
|
|
|
return (minTime - curTime, maxTime - curTime);
|
|
}
|
|
}
|
|
|
|
protected override void OnStartDrag()
|
|
{
|
|
(_minIndex, _maxIndex) = GetIndexRange();
|
|
|
|
if ( OriginalSelection is not { } original ) return;
|
|
|
|
_dragTime = GetTime( original, _minIndex );
|
|
}
|
|
|
|
protected override void OnDrag( MovieTime delta )
|
|
{
|
|
if ( OriginalSelection is not { } original ) return;
|
|
if ( EditMode.TimeSelection is not { } current ) return;
|
|
if ( _dragTime is not { } time ) return;
|
|
|
|
time += delta;
|
|
|
|
_dragTime = time;
|
|
|
|
// If it's ambiguous which handle we are, pick a side
|
|
// based on which direction we're dragged
|
|
|
|
if ( delta.IsNegative ) _maxIndex = _minIndex;
|
|
if ( delta.IsPositive ) _minIndex = _maxIndex;
|
|
|
|
EditMode.TimeSelection = SetTime( original, _minIndex, time );
|
|
}
|
|
|
|
protected override void OnEndDrag()
|
|
{
|
|
_dragTime = null;
|
|
}
|
|
|
|
private static MovieTime GetTime( TimeSelection value, Index index ) => index switch
|
|
{
|
|
Index.TotalStart => value.TotalStart,
|
|
Index.PeakStart => value.PeakStart,
|
|
Index.PeakEnd => value.PeakEnd,
|
|
Index.TotalEnd => value.TotalEnd,
|
|
< 0 => MovieTime.Zero,
|
|
> Index.TotalEnd => MovieTime.MaxValue
|
|
};
|
|
|
|
private static TimeSelection SetTime( TimeSelection value, Index index, MovieTime time ) => index switch
|
|
{
|
|
Index.TotalStart => value.WithTimes( totalStart: time ),
|
|
Index.PeakStart => value.WithTimes( peakStart: time ),
|
|
Index.PeakEnd => value.WithTimes( peakEnd: time ),
|
|
Index.TotalEnd => value.WithTimes( totalEnd: time ),
|
|
_ => value
|
|
};
|
|
|
|
public override void UpdatePosition( TimeSelection value, Rect viewRect )
|
|
{
|
|
PrepareGeometryChange();
|
|
|
|
var time = GetTime( value, GetIndexRange().Min );
|
|
var margin = _maxIndex == Index.TotalStart || _minIndex == Index.TotalEnd
|
|
? ScrubBar.Height
|
|
: 0f;
|
|
|
|
Position = new Vector2( EditMode.Session.TimeToPixels( time ), viewRect.Top + margin );
|
|
Size = new Vector2( 8f, viewRect.Height - margin * 2f );
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the possible range of handles this could be, if the time selection has
|
|
/// overlapping control points.
|
|
/// </summary>
|
|
private (Index Min, Index Max) GetIndexRange()
|
|
{
|
|
if ( EditMode.TimeSelection is not { } value ) return default;
|
|
|
|
var handles = EditMode.Timeline.Items.OfType<TimeSelectionHandleItem>()
|
|
.OrderBy( x => x.Position.x );
|
|
|
|
var index = Index.TotalStart + handles.TakeWhile( x => x != this ).Count();
|
|
|
|
var minIndex = index;
|
|
var maxIndex = index;
|
|
|
|
var time = GetTime( value, index );
|
|
|
|
while ( minIndex > Index.TotalStart && GetTime( value, minIndex - 1 ) == time )
|
|
{
|
|
minIndex -= 1;
|
|
}
|
|
|
|
while ( maxIndex < Index.TotalEnd && GetTime( value, maxIndex + 1 ) == time )
|
|
{
|
|
maxIndex += 1;
|
|
}
|
|
|
|
return (minIndex, maxIndex);
|
|
}
|
|
|
|
public IEnumerable<SnapTarget> GetSnapTargets( MovieTime sourceTime, bool isPrimary ) => [TimeRange.Start];
|
|
|
|
protected override void OnPaint()
|
|
{
|
|
if ( Hovered )
|
|
{
|
|
Paint.SetPen( EditMode.SelectionColor.Lighten( 0.1f ).WithAlpha( 1f ) );
|
|
Paint.DrawLine( new Vector2( LocalRect.Center.x, LocalRect.Top ), new Vector2( LocalRect.Center.x, LocalRect.Bottom ) );
|
|
}
|
|
}
|
|
}
|
|
}
|