Editor/MovieMaker/BlockDisplay/SequenceBlockItem.cs
using Sandbox.MovieMaker;
using System.Linq;
namespace Editor.MovieMaker.BlockDisplays;
#nullable enable
public sealed class SequenceBlockItem : BlockItem<ProjectSequenceBlock>, IMovieDraggable, IMovieResizable
{
private RealTimeSince _lastClick;
private GraphicsItem? _ghost;
private BlockEdge? _resizeEdge;
public new ProjectSequenceTrack Track => (ProjectSequenceTrack)Parent.View.Track;
public string BlockTitle => Block.Resource.ResourceName.ToTitleCase();
public Rect FullSceneRect
{
get
{
var fullTimeRange = FullTimeRange.ClampStart( 0d );
var min = Parent.Session.TimeToPixels( fullTimeRange.Start );
var max = Parent.Session.TimeToPixels( fullTimeRange.End );
return SceneRect with { Left = min, Right = max };
}
}
public bool ShowFullTimeRange
{
get => _ghost.IsValid();
set
{
if ( _ghost.IsValid() == value ) return;
if ( !value )
{
_ghost?.Destroy();
_ghost = null;
return;
}
var fullSceneRect = FullSceneRect;
_ghost = new FullBlockGhostItem();
_ghost.Position = new Vector2( fullSceneRect.Left, Position.y );
_ghost.Size = new Vector2( fullSceneRect.Width, Height );
_ghost.Parent = Parent;
}
}
public SequenceBlockItem()
{
HoverEvents = true;
Selectable = true;
Cursor = CursorShape.Finger;
}
protected override void OnDestroy()
{
base.OnDestroy();
_ghost?.Destroy();
_ghost = null;
}
protected override void OnMousePressed( GraphicsMouseEvent e )
{
if ( Parent.View.IsLocked ) return;
if ( !e.LeftMouseButton && !e.RightMouseButton ) return;
// e.Accepted = true;
var time = Parent.Session.ScenePositionToTime( e.ScenePosition );
if ( e.RightMouseButton )
{
Parent.Session.PlayheadTime = time;
e.Accepted = true;
}
}
private MovieTimeRange FullTimeRange
{
get
{
var sourceTimeRange = new MovieTimeRange( 0d, Block.Resource.GetCompiled().Duration );
return new MovieTimeRange( Block.Transform * sourceTimeRange.Start, Block.Transform * sourceTimeRange.End );
}
}
private void OnSplit( MovieTime time )
{
if ( time <= TimeRange.Start ) return;
if ( time >= TimeRange.End ) return;
using ( Parent.Session.History.Push( $"Split Sequence ({BlockTitle})" ) )
{
Track.AddBlock( (time, Block.TimeRange.End), Block.Transform, Block.Resource );
Block.TimeRange = (Block.TimeRange.Start, time);
}
Layout();
Parent.View.MarkValueChanged();
}
protected override void OnMouseReleased( GraphicsMouseEvent e )
{
if ( e.RightMouseButton )
{
OnOpenContextMenu( e );
return;
}
if ( !e.LeftMouseButton ) return;
if ( _lastClick < 0.5f )
{
OnEdit();
}
_lastClick = 0f;
}
private void OnOpenContextMenu( GraphicsMouseEvent e )
{
e.Accepted = true;
Selected = true;
var time = Parent.Session.PlayheadTime;
var menu = new Menu();
menu.AddHeading( "Sequence Block" );
menu.AddOption( "Edit", "edit", OnEdit );
if ( time > TimeRange.Start && time < TimeRange.End )
{
menu.AddOption( "Split", "carpenter", () => OnSplit( time ) );
}
menu.AddOption( "Delete", "delete", OnDelete );
menu.OpenAt( e.ScreenPosition );
}
private void OnDelete()
{
using ( Parent.Session.History.Push( "Sequence Deleted" ) )
{
Track.RemoveBlock( Block );
if ( Track.IsEmpty )
{
Parent.View.Remove();
}
}
Parent.View.MarkValueChanged();
}
private void OnEdit()
{
if ( Block.Resource is { } resource )
{
Parent.Session.Editor.EnterSequence( resource, Block.Transform, Block.Transform.Inverse * Block.TimeRange );
}
}
protected override void OnPaint()
{
var isLocked = Parent.View.IsLocked;
var isSelected = !isLocked && Selected;
var isHovered = !isLocked && Paint.HasMouseOver;
var color = Theme.Primary.Desaturate( isLocked ? 0.25f : 0f ).Darken( isLocked ? 0.5f : isSelected ? 0f : isHovered ? 0.1f : 0.25f );
PaintExtensions.PaintFilmStrip( LocalRect.Shrink( 0f, 0f, 1f, 0f ), color );
var minX = LocalRect.Left;
var maxX = LocalRect.Right;
Paint.ClearBrush();
Paint.SetPen( Theme.TextControl.Darken( isLocked ? 0.25f : 0f ) );
var textRect = new Rect( minX + 8f, LocalRect.Top, maxX - minX - 16f, LocalRect.Height );
var fullTimeRange = FullTimeRange;
if ( _resizeEdge is BlockEdge.Start )
{
TryDrawText( ref textRect, $"{Block.TimeRange.End - fullTimeRange.Start}", TextFlag.RightCenter );
TryDrawText( ref textRect, $"{Block.TimeRange.Start - fullTimeRange.Start}", TextFlag.LeftCenter );
}
else if ( _resizeEdge is BlockEdge.End )
{
TryDrawText( ref textRect, $"{Block.TimeRange.Start - fullTimeRange.Start}", TextFlag.LeftCenter );
TryDrawText( ref textRect, $"{Block.TimeRange.End - fullTimeRange.Start}", TextFlag.RightCenter );
}
else
{
TryDrawText( ref textRect, BlockTitle, icon: "movie", flags: TextFlag.LeftCenter );
}
}
private void TryDrawText( ref Rect rect, string text, TextFlag flags = TextFlag.Center, string? icon = null, float iconSize = 16f )
{
var originalRect = rect;
if ( icon != null )
{
if ( rect.Width < iconSize ) return;
rect.Left += iconSize + 4f;
}
var textRect = Paint.MeasureText( rect, text, flags );
if ( textRect.Width > rect.Width )
{
if ( icon != null )
{
Paint.DrawIcon( originalRect, icon, iconSize, flags );
}
rect = default;
return;
}
if ( icon != null )
{
Paint.DrawIcon( new Rect( textRect.Left - iconSize - 4f, rect.Top, iconSize, rect.Height ), icon, iconSize, flags );
}
Paint.DrawText( rect, text, flags );
if ( (flags & TextFlag.Left) != 0 )
{
rect.Left = textRect.Right;
}
else if ( (flags & TextFlag.Right) != 0 )
{
rect.Right = textRect.Left;
}
}
ITrackBlock IMovieTrackItem.Block => Block;
MovieTimeRange IMovieTrackItem.TimeRange => Block.TimeRange;
MovieTimeRange? IMovieResizable.FullTimeRange => FullTimeRange;
void IMovieDraggable.Drag( MovieTime delta )
{
Block.TimeRange += delta;
Block.Transform += delta;
Layout();
}
void IMovieResizable.StartResize( BlockEdge edge )
{
ShowFullTimeRange = true;
_resizeEdge = edge;
}
void IMovieResizable.Resize( BlockEdge edge, MovieTime delta )
{
Block.TimeRange =
edge == BlockEdge.Start
? Block.TimeRange with { Start = Block.TimeRange.Start + delta }
: Block.TimeRange with { End = Block.TimeRange.End + delta };
Layout();
}
void IMovieResizable.EndResize()
{
ShowFullTimeRange = false;
_resizeEdge = null;
}
}
file sealed class FullBlockGhostItem : GraphicsItem
{
public FullBlockGhostItem()
{
ZIndex = -100;
}
protected override void OnPaint()
{
Paint.SetBrushAndPen( Timeline.Colors.ChannelBackground );
Paint.DrawRect( LocalRect, 2 );
}
}