Editor/TimelineEventControlWidget.cs
using System.Linq;
using Editor;
using Sandbox;
namespace Timeline;
[CustomEditor( typeof( EventTracks ) )]
public class TimelineEventControlWidget : ControlWidget
{
public Color HighlightColor = Theme.Blue;
public override bool SupportsMultiEdit => true;
public TimelineEventControlWidget( SerializedProperty property ) : base( property )
{
Cursor = CursorShape.Finger;
FixedHeight = 64; // Set a fixed height for the timeline preview
}
protected override void PaintOver()
{
var value = SerializedProperty.GetValue<EventTracks>();
var col = HighlightColor.WithAlpha( Paint.HasMouseOver ? 1 : 0.5f );
var inner = LocalRect.Shrink( 4.0f );
// Draw timeline background
Paint.SetBrush( Theme.ControlBackground );
Paint.ClearPen();
Paint.DrawRect( inner, 3 );
// Draw Event ID and duration info
var topInfoHeight = 14;
// Draw duration info
var duration = value?.Duration ?? 10.0f;
Paint.SetFont( "Roboto", 8, 400 );
Paint.SetPen( Theme.TextControl.WithAlpha( 0.7f ) );
var durationRect = new Rect(inner.Right - 80, inner.Top + 2, 76, 12);
Paint.DrawText( durationRect, $"{duration:F1}s", TextFlag.Right | TextFlag.Top );
// Timeline ruler (below the info text)
var rulerTop = inner.Top + topInfoHeight + 2;
Paint.SetPen( Theme.TextControl.WithAlpha( 0.2f ), 1 );
for ( int i = 0; i <= 10; i++ )
{
float x = inner.Left + (inner.Width * i / 10f);
float tickHeight = i % 5 == 0 ? 6 : 3;
Paint.DrawLine( new Vector2( x, rulerTop ), new Vector2( x, rulerTop + tickHeight ) );
Paint.DrawLine( new Vector2( x, inner.Bottom - 4 ), new Vector2( x, inner.Bottom - 4 - tickHeight ) );
// Draw time labels on major ticks
if ( i % 5 == 0 )
{
float timeAtTick = (i / 10f) * duration;
Paint.SetFont( "Roboto", 7, 400 );
Paint.SetPen( Theme.TextControl.WithAlpha( 0.5f ) );
Paint.DrawText( new Rect( x - 15, inner.Bottom - 15, 30, 8 ), $"{timeAtTick:F1}s", TextFlag.Center );
}
}
// Draw events
if ( value?.Events != null && value.Events.Count > 0 )
{
foreach ( var evt in value.Events )
{
// Calculate position based on absolute time and track duration
float normalizedTime = duration > 0 ? evt.Time / duration : 0;
float x = inner.Left + normalizedTime * inner.Width;
// Only draw if within visible bounds
if ( x >= inner.Left && x <= inner.Right )
{
var eventColor = GetEventIdColor(value.EventId);
// Draw event marker line
Paint.SetPen( eventColor, 2 );
Paint.DrawLine( new Vector2( x, rulerTop + 8 ), new Vector2( x, inner.Bottom - 18 ) );
// Draw event dot
var dotRect = new Rect( x - 3, inner.Center.y + 2, 6, 6 );
Paint.SetBrush( eventColor );
Paint.ClearPen();
Paint.DrawCircle( dotRect );
}
}
// Draw event count
Paint.SetFont( "Roboto", 8, 400 );
Paint.SetPen( Theme.TextControl.WithAlpha( 0.7f ) );
Paint.DrawText( inner.Shrink( 4, 2 ), $"{value.Events.Count} events", TextFlag.Left | TextFlag.Bottom );
}
else
{
// Draw "no events" text
Paint.SetFont( "Roboto", 10, 400 );
Paint.SetPen( Theme.TextControl.WithAlpha( 0.5f ) );
var messageRect = new Rect(inner.Left, rulerTop + 8, inner.Width, inner.Bottom - rulerTop - 24);
Paint.DrawText( messageRect, "Click to add events", TextFlag.Center );
}
// Draw border around timeline area
Paint.SetBrushAndPen( Color.Transparent, col, 2 );
Paint.DrawRect( inner, 3 );
if (!string.IsNullOrEmpty(value?.EventId))
{
Paint.SetFont( "Roboto", 9, 600 );
Paint.SetPen( Theme.Primary );
var labelRect = new Rect(inner.Left + 4, inner.Top + 2, inner.Width - 8, 12);
Paint.DrawText( labelRect, $"'{value.EventId}'", TextFlag.Left | TextFlag.Top );
}
}
Color GetEventIdColor( string eventId )
{
// Generate a consistent color based on the event ID
if (string.IsNullOrEmpty(eventId))
return Theme.Primary;
return eventId.ToLower() switch
{
"jump" => Color.Orange,
"footstep" => Color.Green,
"sound" => Color.Cyan,
"explosion" => Color.Red,
"effect" => Color.Blue,
"animation" => Color.Yellow,
"trigger" => Color.Yellow,
"camera" => Color.Magenta,
_ => new ColorHsv(eventId.GetHashCode() % 360, 0.7f, 0.8f) // Generate color from hash
};
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
if ( e.LeftMouseButton )
{
// Open timeline event editor popup
var popup = new TimelineEventPopup( this );
popup.Position = e.ScreenPosition - popup.Size * new Vector2( 0.5f, 0.5f );
popup.Visible = true;
// Constrain to screen bounds
popup.ConstrainToScreen();
popup.AddEventTracks( SerializedProperty, Update );
}
}
}