mathe/Library/PackageCache/com.unity.timeline@1.7.5/Editor/Manipulators/Move/MoveItemHandler.cs
2024-09-20 20:30:10 +02:00

313 lines
9.5 KiB
C#

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class MoveItemHandler : IAttractable, IAttractionHandler
{
bool m_Grabbing;
MovingItems m_LeftMostMovingItems;
MovingItems m_RightMostMovingItems;
HashSet<TimelineItemGUI> m_ItemGUIs;
ItemsGroup m_ItemsGroup;
public TrackAsset targetTrack { get; private set; }
public bool allowTrackSwitch { get; private set; }
int m_GrabbedModalUndoGroup = -1;
readonly WindowState m_State;
public MovingItems[] movingItems { get; private set; }
public MoveItemHandler(WindowState state)
{
m_State = state;
}
public void Grab(IEnumerable<ITimelineItem> items, TrackAsset referenceTrack)
{
Grab(items, referenceTrack, Vector2.zero);
}
public void Grab(IEnumerable<ITimelineItem> items, TrackAsset referenceTrack, Vector2 mousePosition)
{
if (items == null) return;
items = items.ToArray(); // Cache enumeration result
if (!items.Any()) return;
m_GrabbedModalUndoGroup = Undo.GetCurrentGroup();
var trackItems = items.GroupBy(c => c.parentTrack).ToArray();
var trackItemsCount = trackItems.Length;
var tracks = items.Select(c => c.parentTrack).Where(x => x != null).Distinct();
movingItems = new MovingItems[trackItemsCount];
allowTrackSwitch = trackItemsCount == 1 && !trackItems.SelectMany(x => x).Any(x => x is MarkerItem); // For now, track switch is only supported when all items are on the same track and there are no items
// one push per track handles all the clips on the track
UndoExtensions.RegisterTracks(tracks, L10n.Tr("Move Items"));
foreach (var sourceTrack in tracks)
{
// push all markers on the track because of ripple
UndoExtensions.RegisterMarkers(sourceTrack.GetMarkers(), L10n.Tr("Move Items"));
}
for (var i = 0; i < trackItemsCount; ++i)
{
var track = trackItems[i].Key;
var grabbedItems = new MovingItems(m_State, track, trackItems[i].ToArray(), referenceTrack, mousePosition, allowTrackSwitch);
movingItems[i] = grabbedItems;
}
m_LeftMostMovingItems = null;
m_RightMostMovingItems = null;
foreach (var grabbedTrackItems in movingItems)
{
if (m_LeftMostMovingItems == null || m_LeftMostMovingItems.start > grabbedTrackItems.start)
m_LeftMostMovingItems = grabbedTrackItems;
if (m_RightMostMovingItems == null || m_RightMostMovingItems.end < grabbedTrackItems.end)
m_RightMostMovingItems = grabbedTrackItems;
}
m_ItemGUIs = new HashSet<TimelineItemGUI>();
m_ItemsGroup = new ItemsGroup(items);
foreach (var item in items)
m_ItemGUIs.Add(item.gui);
targetTrack = referenceTrack;
EditMode.BeginMove(this);
m_Grabbing = true;
}
public void Drop()
{
if (IsValidDrop())
{
foreach (var grabbedItems in movingItems)
{
var track = grabbedItems.targetTrack;
UndoExtensions.RegisterTrack(track, L10n.Tr("Move Items"));
if (EditModeUtils.IsInfiniteTrack(track) && grabbedItems.clips.Any())
((AnimationTrack)track).ConvertToClipMode();
}
EditMode.FinishMove();
Done();
}
else
{
Cancel();
}
EditMode.ClearEditMode();
}
bool IsValidDrop()
{
return movingItems.All(g => g.canDrop);
}
void Cancel()
{
if (!m_Grabbing)
return;
// TODO fix undo reselection persistency
// identify the clips by their playable asset, since that reference will survive the undo
// This is a workaround, until a more persistent fix for selection of clips across Undo can be found
var assets = movingItems.SelectMany(x => x.clips).Select(x => x.asset);
Undo.RevertAllDownToGroup(m_GrabbedModalUndoGroup);
// reselect the clips from the original clip
var clipsToSelect = movingItems.Select(x => x.originalTrack).SelectMany(x => x.GetClips()).Where(x => assets.Contains(x.asset)).ToArray();
SelectionManager.RemoveTimelineSelection();
foreach (var c in clipsToSelect)
SelectionManager.Add(c);
Done();
}
void Done()
{
foreach (var movingItem in movingItems)
{
foreach (var item in movingItem.items)
{
if (item.gui != null)
item.gui.isInvalid = false;
}
}
movingItems = null;
m_LeftMostMovingItems = null;
m_RightMostMovingItems = null;
m_Grabbing = false;
m_State.Refresh();
m_State.analytics.SendManipulationEndedEvent();
}
public double start { get { return m_ItemsGroup.start; } }
public double end { get { return m_ItemsGroup.end; } }
public bool ShouldSnapTo(ISnappable snappable)
{
var itemGUI = snappable as TimelineItemGUI;
return itemGUI != null && !m_ItemGUIs.Contains(itemGUI);
}
public void UpdateTrackTarget(TrackAsset track)
{
if (!EditMode.AllowTrackSwitch())
return;
targetTrack = track;
var targetTracksChanged = false;
foreach (var grabbedItem in movingItems)
{
var prevTrackGUI = grabbedItem.targetTrack;
grabbedItem.SetReferenceTrack(track);
targetTracksChanged = grabbedItem.targetTrack != prevTrackGUI;
}
if (targetTracksChanged)
EditMode.HandleTrackSwitch(movingItems);
RefreshPreviewItems();
m_State.rebuildGraph |= targetTracksChanged;
}
public void OnGUI(Event evt)
{
if (!m_Grabbing)
return;
if (evt.type != EventType.Repaint)
return;
var isValid = IsValidDrop();
using (new GUIViewportScope(m_State.GetWindow().sequenceContentRect))
{
foreach (var grabbedClip in movingItems)
{
grabbedClip.RefreshBounds(m_State, evt.mousePosition);
if (!grabbedClip.HasAnyDetachedParents())
continue;
grabbedClip.Draw(isValid);
}
if (isValid)
{
EditMode.DrawMoveGUI(m_State, movingItems);
}
else
{
TimelineCursors.ClearCursor();
}
}
}
public void OnAttractedEdge(IAttractable attractable, ManipulateEdges manipulateEdges, AttractedEdge edge, double time)
{
double offset;
if (edge == AttractedEdge.Right)
{
var duration = end - start;
var startTime = time - duration;
startTime = EditMode.AdjustStartTime(m_State, m_RightMostMovingItems, startTime);
offset = startTime + duration - end;
}
else
{
if (edge == AttractedEdge.Left)
time = EditMode.AdjustStartTime(m_State, m_LeftMostMovingItems, time);
offset = time - start;
}
if (start + offset < 0.0)
offset = -start;
if (!offset.Equals(0.0))
{
foreach (var grabbedClips in movingItems)
grabbedClips.start += offset;
EditMode.UpdateMove();
RefreshPreviewItems();
}
}
public void RefreshPreviewItems()
{
foreach (var movingItemsGroup in movingItems)
{
// Check validity
var valid = ValidateItemDrag(movingItemsGroup);
foreach (var item in movingItemsGroup.items)
{
if (item.gui != null)
item.gui.isInvalid = !valid;
}
movingItemsGroup.canDrop = valid;
}
}
static bool ValidateItemDrag(ItemsPerTrack itemsGroup)
{
//TODO-marker: this is to prevent the drag operation from being canceled when moving only markers
if (itemsGroup.clips.Any())
{
if (itemsGroup.targetTrack == null)
return false;
if (itemsGroup.targetTrack.lockedInHierarchy)
return false;
if (itemsGroup.items.Any(i => !i.IsCompatibleWithTrack(itemsGroup.targetTrack)))
return false;
return EditMode.ValidateDrag(itemsGroup);
}
return true;
}
public void OnTrackDetach()
{
EditMode.OnTrackDetach(movingItems);
}
}
}