Files
2026-07-13 13:13:17 +08:00

200 lines
6.6 KiB
C#

#nullable enable
using T3.Core.Animation;
using T3.Core.DataTypes;
namespace T3.Editor.Gui.Windows.TimeLine;
/// <summary>
/// Aggregates across every currently-active <see cref="AnimationParameterEditing"/> 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
/// <see cref="T3.Editor.Gui.Interaction.WithCurves.AnimationCanvas"/> uses for <c>ITimeObjectManipulation</c>:
/// cross-editor state lives here so callers never need to pick one "active" editor.
/// </summary>
internal sealed class KeyframeEditorGroup
{
public KeyframeEditorGroup(List<AnimationParameterEditing> editors)
{
_editors = editors;
}
public IReadOnlyList<AnimationParameterEditing> 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;
}
/// <summary>Sum of per-editor change counters so cache hashes refresh whenever any selection changes.</summary>
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<VDefinition> EnumerateAllSelectedKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
foreach (var v in _editors[i].EnumerateSelectedKeyframes())
yield return v;
}
public IEnumerable<VDefinition> EnumerateAllKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
foreach (var v in _editors[i].EnumerateAllKeyframes())
yield return v;
}
public void CopyAllSelectedKeyframesTo(List<VDefinition> 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<Curve> buffer)
{
buffer.Clear();
for (var i = 0; i < _editors.Count; i++)
foreach (var curve in EnumerateCurvesOf(_editors[i]))
buffer.Add(curve);
}
private static IEnumerable<Curve> 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<Curve>? _scratchCurvesBacking;
private static List<Curve> _scratchCurves => _scratchCurvesBacking ??= new List<Curve>(32);
//
// Mutations -------------------------------------------------------------
//
public void SelectAllKeyframes()
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].SelectAllKeyframes();
}
public void ReplaceKeyframeSelection(IEnumerable<VDefinition> 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<VDefinition> keys)
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].AddToKeyframeSelection(keys);
}
public void RemoveFromKeyframeSelection(IReadOnlyList<VDefinition> keys)
{
for (var i = 0; i < _editors.Count; i++)
_editors[i].RemoveFromKeyframeSelection(keys);
}
public void ApplyKeyframeTimeOffset(IReadOnlyList<VDefinition> 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();
}
/// <summary>
/// Consume the static <see cref="AnimationParameterEditing.CurvesTablesNeedsRefresh"/> 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.
/// </summary>
public void ProcessPendingCurveTableRebuild()
{
if (!AnimationParameterEditing.CurvesTablesNeedsRefresh)
return;
for (var i = 0; i < _editors.Count; i++)
_editors[i].RebuildCurves();
AnimationParameterEditing.CurvesTablesNeedsRefresh = false;
}
private readonly List<AnimationParameterEditing> _editors;
private static readonly List<VDefinition> _scratchKeys = new(64);
}