158 lines
4.9 KiB
C#
158 lines
4.9 KiB
C#
using System;
|
|
using System.Linq;
|
|
using T3.Core.Animation;
|
|
using T3.Core.IO;
|
|
using T3.Core.Logging;
|
|
using T3.Core.Settings;
|
|
using T3.Core.Utils;
|
|
|
|
// ReSharper disable ForCanBeConvertedToForeach
|
|
|
|
namespace T3.Core.Operator.Slots;
|
|
|
|
public interface ITimeClipProvider
|
|
{
|
|
TimeClip TimeClip { get; }
|
|
}
|
|
|
|
public interface IOutputDataUser
|
|
{
|
|
void SetOutputData(IOutputData data);
|
|
}
|
|
|
|
// This interface is mainly to extract the output data type while no instance of an implementer exists.
|
|
internal interface IOutputDataUser<T> : IOutputDataUser
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// An unfortunate workaround to allow flagging operators that should not update their source time region when being dragged
|
|
/// in the timeline. This primarily useful for ops like [TimeClip] that do not involve content nested in their sub elements.
|
|
/// Also see <see cref="TimeClip.UsedForRegionMapping"/>.
|
|
/// </summary>
|
|
public interface IPreventingTimeRemap;
|
|
|
|
/// <summary>
|
|
/// Marks a clip whose <see cref="TimeClip.SourceRange"/> is <b>content-time</b> — anchored at the source's start
|
|
/// (0), not the placement position. The editor uses this at creation to default SourceRange to <c>[0, duration]</c>
|
|
/// instead of the generic <c>SourceRange = TimeRange</c> (which is right only for region-mapping clips). Implemented
|
|
/// by media clips like [VideoClip]. (Future: pair with an instance-side AvailableSourceRange the editor can snap to.)
|
|
/// </summary>
|
|
public interface IContentTimeClip;
|
|
|
|
public sealed class TimeClipSlot<T> : Slot<T>, ITimeClipProvider, IOutputDataUser<TimeClip>
|
|
{
|
|
public TimeClip TimeClip { get; private set; }
|
|
|
|
public TimeClipSlot()
|
|
{
|
|
HasInvalidationOverride = true;
|
|
}
|
|
|
|
public void SetOutputData(IOutputData data)
|
|
{
|
|
TimeClip = data as TimeClip ?? new TimeClip();
|
|
TimeClip.Id = Parent.SymbolChildId;
|
|
TimeClip.UsedForRegionMapping = Parent is not IPreventingTimeRemap;
|
|
}
|
|
|
|
public UpdateStates LastUpdateStatus;
|
|
|
|
private void UpdateWithTimeRangeCheck(EvaluationContext context)
|
|
{
|
|
if ((context.LocalTime < TimeClip.TimeRange.Start) || (context.LocalTime >= TimeClip.TimeRange.End))
|
|
{
|
|
LastUpdateStatus = CoreSettings.Config.TimeClipSuspending ? UpdateStates.Suspended : UpdateStates.Active;
|
|
return;
|
|
}
|
|
|
|
// TODO: Setting local time should flag time accessors as dirty
|
|
var prevTime = context.LocalTime;
|
|
var prevFxTime = context.LocalFxTime;
|
|
|
|
context.LocalTime = prevTime.Remap(TimeClip.TimeRange.Start, TimeClip.TimeRange.End,
|
|
TimeClip.SourceRange.Start, TimeClip.SourceRange.End);
|
|
|
|
context.LocalFxTime = prevFxTime.Remap(TimeClip.TimeRange.Start, TimeClip.TimeRange.End,
|
|
TimeClip.SourceRange.Start, TimeClip.SourceRange.End);
|
|
|
|
if (_baseUpdateAction == null)
|
|
{
|
|
Log.Warning("Ignoring invalid time clip update action", Parent);
|
|
}
|
|
else
|
|
{
|
|
_baseUpdateAction(context);
|
|
}
|
|
|
|
context.LocalTime = prevTime;
|
|
context.LocalFxTime = prevFxTime;
|
|
LastUpdateStatus = UpdateStates.Active;
|
|
}
|
|
|
|
private Action<EvaluationContext> _baseUpdateAction;
|
|
|
|
public enum UpdateStates
|
|
{
|
|
Undefined,
|
|
Active,
|
|
Inactive, // Out of range
|
|
Suspended,
|
|
}
|
|
|
|
public override Action<EvaluationContext> UpdateAction
|
|
{
|
|
set
|
|
{
|
|
_baseUpdateAction = value;
|
|
base.UpdateAction = UpdateWithTimeRangeCheck;
|
|
}
|
|
}
|
|
|
|
protected override void SetDisabled(bool isDisabled)
|
|
{
|
|
if (isDisabled == _isDisabled)
|
|
return;
|
|
|
|
if (isDisabled)
|
|
{
|
|
_keepOriginalUpdateAction = _baseUpdateAction;
|
|
base.UpdateAction = EmptyAction;
|
|
DirtyFlag.Invalidate();
|
|
}
|
|
else
|
|
{
|
|
RestoreUpdateAction();
|
|
DirtyFlag.Invalidate();
|
|
}
|
|
|
|
_isDisabled = isDisabled;
|
|
}
|
|
|
|
protected override int InvalidationOverride()
|
|
{
|
|
// Slot is an output of a composition op
|
|
if (HasInputConnections)
|
|
{
|
|
return InputConnections[0].InvalidateGraph();
|
|
}
|
|
|
|
if (LastUpdateStatus == UpdateStates.Suspended)
|
|
{
|
|
return _dirtyFlag.Invalidate();
|
|
}
|
|
|
|
var isOutputDirty = _dirtyFlag.IsDirty;
|
|
var parentInputs = Parent.Inputs;
|
|
var parentInputCount = parentInputs.Count;
|
|
for (var index = 0; index < parentInputCount; index++)
|
|
{
|
|
var inputSlot = parentInputs[index];
|
|
// Let each input handle its own invalidation strategy (e.g. MultiInputSlot traverses all collected connections).
|
|
inputSlot.InvalidateGraph();
|
|
isOutputDirty |= inputSlot.IsDirty;
|
|
}
|
|
|
|
return isOutputDirty ? _dirtyFlag.Invalidate() : _dirtyFlag.SourceVersion;
|
|
}
|
|
} |