using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using Object = UnityEngine.Object; namespace UnityEditor.Timeline { class MoveItemModeRipple : IMoveItemMode, IMoveItemDrawer { const float k_SnapToEdgeDistance = 30.0f; class PrevItemInfo { public ITimelineItem item; public ITimelineItem firstSelectedItem; public bool blending; public PrevItemInfo(ITimelineItem item, ITimelineItem firstSelectedItem) { this.item = item; this.firstSelectedItem = firstSelectedItem; blending = item != null && item.end > firstSelectedItem.start; } } readonly Dictionary> m_NextItems = new Dictionary>(); readonly Dictionary m_PreviousItem = new Dictionary(); double m_PreviousEnd; bool m_TrackLocked; bool m_Detached; public void OnTrackDetach(IEnumerable itemsGroups) { if (m_TrackLocked) return; if (m_Detached) return; if (itemsGroups.Any(x => x.markers.Any())) return; // Ripple can either remove or not clips when detaching them from their track. // Keep it off for now. TODO: add clutch key to toggle this feature? //EditModeRippleUtils.Remove(manipulatedClipsList); StartDetachMode(itemsGroups); } public void HandleTrackSwitch(IEnumerable itemsGroups) { // Nothing } public bool AllowTrackSwitch() { return !m_TrackLocked; } public double AdjustStartTime(WindowState state, ItemsPerTrack itemsGroup, double time) { var track = itemsGroup.targetTrack; if (track == null) return time; double start; double end; if (EditModeUtils.IsInfiniteTrack(track)) { EditModeUtils.GetInfiniteClipBoundaries(track, out start, out end); } else { var siblings = ItemsUtils.GetItemsExcept(track, itemsGroup.items); var firstIntersectedItem = EditModeUtils.GetFirstIntersectedItem(siblings, time); if (firstIntersectedItem == null) return time; start = firstIntersectedItem.start; end = firstIntersectedItem.end; } var closestTime = Math.Abs(time - start) < Math.Abs(time - end) ? start : end; var pixelTime = state.TimeToPixel(time); var pixelClosestTime = state.TimeToPixel(closestTime); if (Math.Abs(pixelTime - pixelClosestTime) < k_SnapToEdgeDistance) return closestTime; return time; } void StartDetachMode(IEnumerable itemsGroups) { m_Detached = true; foreach (var itemsGroup in itemsGroups) EditModeUtils.SetParentTrack(itemsGroup.items, null); } public void OnModeClutchEnter(IEnumerable itemsGroups) { StartDetachMode(itemsGroups); m_TrackLocked = false; } public void OnModeClutchExit(IEnumerable itemsGroups) { m_Detached = false; m_TrackLocked = false; } public void BeginMove(IEnumerable itemsGroups) { m_NextItems.Clear(); m_PreviousItem.Clear(); var itemTypes = ItemsUtils.GetItemTypes(itemsGroups).ToList(); foreach (var itemsGroup in itemsGroups) { //can only ripple items of the same type as those selected var sortedSelectedItems = itemsGroup.items.OrderBy(i => i.start).ToList(); var siblings = itemsGroup.targetTrack.GetItemsExcept(itemsGroup.items); var sortedSiblingsToRipple = siblings.Where(i => itemTypes.Contains(i.GetType())).OrderBy(i => i.start).ToList(); var start = sortedSelectedItems.First().start; m_NextItems.Add(itemsGroup.targetTrack, sortedSiblingsToRipple.Where(i => i.start > start).ToList()); m_PreviousItem.Add(itemsGroup.targetTrack, CalculatePrevItemInfo(sortedSelectedItems, sortedSiblingsToRipple, itemTypes)); } m_PreviousEnd = itemsGroups.Max(m => m.items.Max(c => c.end)); } public void UpdateMove(IEnumerable itemsGroups) { if (m_Detached) return; m_TrackLocked = true; var overlap = 0.0; foreach (var itemsGroup in itemsGroups) { var track = itemsGroup.targetTrack; if (track == null) continue; var prevItemInfo = m_PreviousItem[track]; if (prevItemInfo.item != null) { var prevItem = prevItemInfo.item; var firstItem = prevItemInfo.firstSelectedItem; if (prevItemInfo.blending) prevItemInfo.blending = prevItem.end > firstItem.start; if (prevItemInfo.blending) { var b = EditModeUtils.BlendDuration(firstItem, TrimEdge.End); overlap = Math.Max(overlap, Math.Max(prevItem.start, prevItem.end - firstItem.end + firstItem.start + b) - firstItem.start); } else { overlap = Math.Max(overlap, prevItem.end - firstItem.start); } } } if (overlap > 0) { foreach (var itemsGroup in itemsGroups) { foreach (var item in itemsGroup.items) item.start += overlap; } } var newEnd = itemsGroups.Max(m => m.items.Max(c => c.end)); var offset = newEnd - m_PreviousEnd; m_PreviousEnd = newEnd; foreach (var itemsGroup in itemsGroups) { foreach (var item in m_NextItems[itemsGroup.targetTrack]) item.start += offset; } } static PrevItemInfo CalculatePrevItemInfo(List orderedSelection, List orderedSiblings, IEnumerable itemTypes) { ITimelineItem previousItem = null; ITimelineItem firstSelectedItem = null; var gap = double.PositiveInfinity; foreach (var type in itemTypes) { var firstSelectedItemOfType = orderedSelection.FirstOrDefault(i => i.GetType() == type); if (firstSelectedItemOfType == null) continue; var previousItemOfType = orderedSiblings.LastOrDefault(i => i.GetType() == type && i.start < firstSelectedItemOfType.start); if (previousItemOfType == null) continue; var currentGap = firstSelectedItemOfType.start - previousItemOfType.end; if (currentGap < gap) { gap = currentGap; firstSelectedItem = firstSelectedItemOfType; previousItem = previousItemOfType; } } return new PrevItemInfo(previousItem, firstSelectedItem); } public bool ValidateMove(ItemsPerTrack itemsGroup) { return true; } public void FinishMove(IEnumerable itemsGroups) { if (m_Detached) EditModeRippleUtils.Insert(itemsGroups); m_Detached = false; m_TrackLocked = false; } public void DrawGUI(WindowState state, IEnumerable movingItems, Color color) { if (m_Detached) { var xMin = float.MaxValue; var xMax = float.MinValue; foreach (var grabbedItems in movingItems) { xMin = Math.Min(xMin, grabbedItems.onTrackItemsBounds.Min(b => b.xMin)); // TODO Cache this? xMax = Math.Max(xMax, grabbedItems.onTrackItemsBounds.Max(b => b.xMax)); } foreach (var grabbedItems in movingItems) { var bounds = Rect.MinMaxRect(xMin, grabbedItems.onTrackItemsBounds[0].yMin, xMax, grabbedItems.onTrackItemsBounds[0].yMax); EditModeGUIUtils.DrawOverlayRect(bounds, new Color(1.0f, 1.0f, 1.0f, 0.5f)); EditModeGUIUtils.DrawBoundsEdge(bounds, color, TrimEdge.Start); } } else { foreach (var grabbedItems in movingItems) { var bounds = Rect.MinMaxRect(grabbedItems.onTrackItemsBounds.Min(b => b.xMin), grabbedItems.onTrackItemsBounds[0].yMin, grabbedItems.onTrackItemsBounds.Max(b => b.xMax), grabbedItems.onTrackItemsBounds[0].yMax); EditModeGUIUtils.DrawBoundsEdge(bounds, color, TrimEdge.Start); } } TimelineCursors.SetCursor(TimelineCursors.CursorType.Ripple); } } }