215 lines
8.8 KiB
C#
215 lines
8.8 KiB
C#
#nullable enable
|
|
using ImGuiNET;
|
|
using T3.Core.Animation;
|
|
using T3.Core.DataTypes.Vector;
|
|
using T3.Core.Operator;
|
|
using T3.Core.Utils;
|
|
using T3.Editor.Gui.Interaction;
|
|
using T3.Editor.Gui.Interaction.Keyboard;
|
|
using T3.Editor.Gui.Input;
|
|
using T3.Editor.Gui.Interaction.Snapping;
|
|
using T3.Editor.Gui.Styling;
|
|
using T3.Editor.Gui.UiHelpers;
|
|
using T3.Editor.UiModel.Commands;
|
|
using T3.Editor.UiModel.ProjectHandling;
|
|
using T3.Editor.UiModel.Selection;
|
|
|
|
namespace T3.Editor.Gui.Windows.TimeLine.TimeClips;
|
|
|
|
/// <summary>
|
|
/// The clip-editing area inside the timeline window. Orchestrates per-frame layout,
|
|
/// renders op-backed <see cref="TimeClip"/>s (including <c>[AudioClip]</c>, which is itself a
|
|
/// TimeClip op), dispatches keyboard actions, and implements the public
|
|
/// <see cref="ITimeObjectManipulation"/> / <see cref="IValueSnapAttractor"/> interfaces
|
|
/// consumed by <see cref="TimeLineCanvas"/>. Interaction work is delegated to
|
|
/// <see cref="TimeClipInteractions"/> (selection, drag lifecycle, delete-with-children,
|
|
/// split-at-time, snap).
|
|
/// </summary>
|
|
internal sealed class ClipArea : ITimeObjectManipulation, IValueSnapAttractor
|
|
{
|
|
public ClipArea(TimeLineCanvas timeLineCanvas,
|
|
Func<Instance> getCompositionOp,
|
|
Func<Guid, bool> requestChildCompositionFunc,
|
|
ValueSnapHandler snapHandlerForU)
|
|
{
|
|
_context = new LayerContext(new ClipSelection(timeLineCanvas.NodeSelection),
|
|
requestChildCompositionFunc,
|
|
timeLineCanvas,
|
|
snapHandlerForU);
|
|
OpClips = new TimeClipInteractions(_context, getCompositionOp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Per-frame shared state passed into <see cref="TimeClipItem"/> and the interaction helpers.
|
|
/// </summary>
|
|
internal sealed record LayerContext(
|
|
ClipSelection ClipSelection,
|
|
Func<Guid, bool> RequestChildComposition,
|
|
TimeLineCanvas TimeCanvas,
|
|
ValueSnapHandler SnapHandler);
|
|
|
|
public TimeClipInteractions OpClips { get; }
|
|
|
|
public void Draw(Instance compositionOp, Playback playback, ValueSnapHandler snapHandler)
|
|
{
|
|
_drawList = ImGui.GetWindowDrawList();
|
|
OpClips.SetPlayback(playback);
|
|
|
|
ImGui.BeginGroup();
|
|
{
|
|
_context.ClipSelection.UpdateForComposition(compositionOp);
|
|
_minScreenPos = ImGui.GetCursorScreenPos();
|
|
|
|
DrawAllLayers(compositionOp);
|
|
OpClips.DrawContextMenuItems(compositionOp);
|
|
HandleKeyboardActions(compositionOp);
|
|
ClipTimingEditor.DrawPopUp(_context);
|
|
if (_context.ClipSelection.AllClipIds.Count > 0)
|
|
FormInputs.AddVerticalSpace(1);
|
|
}
|
|
ImGui.EndGroup();
|
|
|
|
// Files / assets dropped onto the clip area become the matching timeline-clip op (AudioClip,
|
|
// VideoClip, LoadDataClip, …) at the drop time / layer — see AssetType.TimelineClipOperator.
|
|
TimelineClipDrop.Handle(compositionOp, _context.TimeCanvas, _minScreenPos.Y, _minLayerIndex);
|
|
|
|
// Layer-area height drag handle. Only meaningful when there is at least one op clip to size.
|
|
if (_context.ClipSelection.AllClipIds.Count > 0)
|
|
{
|
|
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - 2);
|
|
ImGui.Button("##layerHeight", new Vector2(20, 3) * T3Ui.UiScaleFactor);
|
|
if (ImGui.IsItemActivated())
|
|
{
|
|
_layerHeightOnDragStart = UserSettings.Config.LayerHeight;
|
|
_mouseYOnDragStart = ImGui.GetMousePos().Y;
|
|
}
|
|
if (ImGui.IsItemActive())
|
|
{
|
|
var layerCount = (_maxLayerIndex - _minLayerIndex + 1).Clamp(1, 999999);
|
|
var mouseDeltaY = ImGui.GetMousePos().Y - _mouseYOnDragStart;
|
|
var heightDelta = mouseDeltaY / layerCount;
|
|
UserSettings.Config.LayerHeight = (_layerHeightOnDragStart + heightDelta).Clamp(4, 50);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleKeyboardActions(Instance compositionOp)
|
|
{
|
|
if (UserActions.SplitSelectedOrHoveredClips.Triggered())
|
|
OpClips.SplitClipsAtTime(compositionOp);
|
|
|
|
if (UserActions.DeleteSelection.Triggered())
|
|
{
|
|
OpClips.DeleteSelectedClips(compositionOp);
|
|
}
|
|
}
|
|
|
|
private void DrawAllLayers(Instance compositionOp)
|
|
{
|
|
var opClips = _context.ClipSelection.CompositionTimeClips.Values;
|
|
if (opClips.Count == 0)
|
|
{
|
|
LastHeight = 0;
|
|
return;
|
|
}
|
|
|
|
_minLayerIndex = int.MaxValue;
|
|
_maxLayerIndex = int.MinValue;
|
|
foreach (var clip in opClips)
|
|
{
|
|
_minLayerIndex = Math.Min(clip.LayerIndex, _minLayerIndex);
|
|
_maxLayerIndex = Math.Max(clip.LayerIndex, _maxLayerIndex);
|
|
}
|
|
|
|
var min = ImGui.GetCursorScreenPos() + new Vector2(0, LayerHeight * 0.5f);
|
|
var max = min + new Vector2(ImGui.GetContentRegionAvail().X,
|
|
LayerHeight * (_maxLayerIndex - _minLayerIndex + 1) + 1);
|
|
LastHeight = max.Y - min.Y + 5;
|
|
_drawList.AddRectFilled(new Vector2(min.X, max.Y - 2),
|
|
new Vector2(max.X, max.Y), UiColors.GridLines.Fade(0.6f));
|
|
|
|
var layerRect = new ImRect(min, max);
|
|
OpClips.DrawClips(compositionOp, layerRect, _minLayerIndex, _drawList);
|
|
|
|
// Span the surrounding BeginGroup bbox over the whole layer area so the layer-height
|
|
// drag handle and hit-testing cover the full width/height, not just the union of the
|
|
// emitted clip-body buttons.
|
|
ImGui.SetCursorScreenPos(min);
|
|
ImGui.Dummy(max - min);
|
|
|
|
ImGui.SetCursorScreenPos(min + new Vector2(0, LayerHeight));
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// External API (consumed by TimeLineCanvas, TimeWarpDrag, etc.)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public float LastHeight { get; private set; }
|
|
|
|
public void UpdateSelectionForArea(ImRect screenArea, SelectionFence.SelectModes selectMode)
|
|
{
|
|
OpClips.UpdateSelectionForArea(screenArea, selectMode, _minScreenPos, _minLayerIndex);
|
|
}
|
|
|
|
public IEnumerable<TimeClip> EnumerateSelectedClips() => OpClips.EnumerateSelectedClips();
|
|
public bool TryGetBounds(out ImRect bounds, bool useAllIfNonSelected = true)
|
|
=> OpClips.TryGetBounds(out bounds, useAllIfNonSelected);
|
|
public bool HasSelectedClips => OpClips.HasSelectedClips;
|
|
public bool HasAnyClips => OpClips.HasAnyClips;
|
|
public void SelectAllClips() => OpClips.SelectAllClips();
|
|
public TimeRange GetSelectionTimeRange() => OpClips.GetSelectionTimeRange();
|
|
public TimeRange GetAllClipsTimeRange() => OpClips.GetAllClipsTimeRange();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ITimeObjectManipulation
|
|
// ---------------------------------------------------------------------------
|
|
|
|
public void ClearSelection()
|
|
{
|
|
OpClips.ClearSelection();
|
|
}
|
|
|
|
public ICommand StartDragCommand(in Guid compositionSymbolId) => OpClips.StartDragCommand();
|
|
|
|
void ITimeObjectManipulation.UpdateDragCommand(double dt, double dy) => OpClips.UpdateDragCommand(dt, dy);
|
|
|
|
public void UpdateDragAtStartPointCommand(double dt, double dv) => OpClips.UpdateDragAtStartPointCommand(dt, dv);
|
|
|
|
public void UpdateDragAtEndPointCommand(double dt, double dv) => OpClips.UpdateDragAtEndPointCommand(dt, dv);
|
|
|
|
void ITimeObjectManipulation.UpdateDragStretchCommand(double scaleU, double scaleV, double originU, double originV)
|
|
=> OpClips.UpdateDragStretchCommand(scaleU, scaleV, originU, originV);
|
|
|
|
void ITimeObjectManipulation.CompleteDragCommand() => OpClips.CompleteDragCommand();
|
|
|
|
void ITimeObjectManipulation.DeleteSelectedElements(Instance instance)
|
|
{
|
|
//TODO: Implement deleting of layers with delete key
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// IValueSnapAttractor
|
|
// ---------------------------------------------------------------------------
|
|
|
|
void IValueSnapAttractor.CheckForSnap(ref SnapResult snapResult)
|
|
{
|
|
OpClips.CheckForSnap(ref snapResult);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Frame state
|
|
// ---------------------------------------------------------------------------
|
|
|
|
internal static float LayerHeight => UserSettings.Config.LayerHeight.Clamp(4, 40);
|
|
|
|
private readonly LayerContext _context;
|
|
|
|
private int _minLayerIndex = int.MaxValue;
|
|
private int _maxLayerIndex = int.MinValue;
|
|
private Vector2 _minScreenPos;
|
|
private ImDrawListPtr _drawList;
|
|
|
|
private static float _layerHeightOnDragStart;
|
|
private static float _mouseYOnDragStart;
|
|
}
|