#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); }