#nullable enable
using T3.Core.Animation;
using T3.Core.DataTypes;
namespace T3.Editor.Gui.Windows.TimeLine;
///
/// Aggregates across every currently-active on the timeline
/// (DopeSheetArea, TimelineCurveEditor, …). One editor today, potentially several when the
/// dope-sheet and curve editor are shown side by side. The group mirrors the pattern
/// uses for ITimeObjectManipulation:
/// cross-editor state lives here so callers never need to pick one "active" editor.
///
internal sealed class KeyframeEditorGroup
{
public KeyframeEditorGroup(List editors)
{
_editors = editors;
}
public IReadOnlyList All => _editors;
public int Count => _editors.Count;
//
// Read-only aggregates ---------------------------------------------------
//
public TimeRange GetSelectionTimeRange()
{
var range = TimeRange.Undefined;
for (var i = 0; i < _editors.Count; i++)
{
var r = _editors[i].GetSelectionTimeRange();
if (r.IsValid)
{
range.Unite(r.Start);
range.Unite(r.End);
}
}
return range;
}
public TimeRange GetAllKeyframesTimeRange()
{
var range = TimeRange.Undefined;
for (var i = 0; i < _editors.Count; i++)
{
var r = _editors[i].GetAllKeyframesTimeRange();
if (r.IsValid)
{
range.Unite(r.Start);
range.Unite(r.End);
}
}
return range;
}
/// Sum of per-editor change counters so cache hashes refresh whenever any selection changes.
public int AggregateSelectionChangeCounter
{
get
{
var sum = 0;
for (var i = 0; i < _editors.Count; i++)
sum += _editors[i].SelectionChangeCounter;
return sum;
}
}
public bool IsKeyframeSelectedInAny(VDefinition v)
{
for (var i = 0; i < _editors.Count; i++)
if (_editors[i].IsKeyframeSelected(v))
return true;
return false;
}
public int TotalSelectedKeyframeCount
{
get
{
var n = 0;
for (var i = 0; i < _editors.Count; i++)
n += _editors[i].SelectedKeyframeCount;
return n;
}
}
//
// Enumerations -----------------------------------------------------------
//
public IEnumerable EnumerateAllSelectedKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
foreach (var v in _editors[i].EnumerateSelectedKeyframes())
yield return v;
}
public IEnumerable EnumerateAllKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
foreach (var v in _editors[i].EnumerateAllKeyframes())
yield return v;
}
public void CopyAllSelectedKeyframesTo(List buffer)
{
buffer.Clear();
for (var i = 0; i < _editors.Count; i++)
foreach (var v in _editors[i].EnumerateSelectedKeyframes())
buffer.Add(v);
}
public void CopyAllCurvesTo(List buffer)
{
buffer.Clear();
for (var i = 0; i < _editors.Count; i++)
foreach (var curve in EnumerateCurvesOf(_editors[i]))
buffer.Add(curve);
}
private static IEnumerable EnumerateCurvesOf(AnimationParameterEditing editor)
{
// CopyAllCurvesTo overrides its buffer; use a throwaway to iterate without clobbering caller's.
_scratchCurves.Clear();
editor.CopyAllCurvesTo(_scratchCurves);
for (var i = 0; i < _scratchCurves.Count; i++)
yield return _scratchCurves[i];
}
[ThreadStatic] private static List? _scratchCurvesBacking;
private static List _scratchCurves => _scratchCurvesBacking ??= new List(32);
//
// Mutations -------------------------------------------------------------
//
public void SelectAllKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].SelectAllKeyframes();
}
public void ReplaceKeyframeSelection(IEnumerable keys)
{
// Materialize once so every editor sees the same set (avoids re-enumerating a generator).
_scratchKeys.Clear();
foreach (var k in keys)
_scratchKeys.Add(k);
for (var i = 0; i < _editors.Count; i++)
_editors[i].ReplaceKeyframeSelection(_scratchKeys);
_scratchKeys.Clear();
}
public void AddToKeyframeSelection(IReadOnlyList keys)
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].AddToKeyframeSelection(keys);
}
public void RemoveFromKeyframeSelection(IReadOnlyList keys)
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].RemoveFromKeyframeSelection(keys);
}
public void ApplyKeyframeTimeOffset(IReadOnlyList keys, double deltaU)
{
// Editors share the same VDefinition instances via SharedSelectedKeyframes, so the U
// shift must be applied exactly once. Calling editor.ApplyKeyframeTimeOffset per editor
// would mutate keys[i].U += deltaU N times — visible as TimeSelectionArea drag jitter
// when the inline curve editor is open (DSA + TimelineCurveEditor both active → 2x).
for (var i = 0; i < keys.Count; i++)
keys[i].U += deltaU;
// Curve tables are per-editor, so each still needs to rebuild.
for (var i = 0; i < _editors.Count; i++)
_editors[i].RebuildCurves();
}
///
/// Consume the static flag
/// by rebuilding curve tables on every active editor. Each editor owns different parameter
/// subsets (and therefore different curves), so all need to rebuild before the flag is cleared.
///
public void ProcessPendingCurveTableRebuild()
{
if (!AnimationParameterEditing.CurvesTablesNeedsRefresh)
return;
for (var i = 0; i < _editors.Count; i++)
_editors[i].RebuildCurves();
AnimationParameterEditing.CurvesTablesNeedsRefresh = false;
}
private readonly List _editors;
private static readonly List _scratchKeys = new(64);
}