| using System; |
| using System.Collections.Generic; |
| using System.Linq; |
| using UnityEngine; |
| using UnityEngine.Timeline; |
|
|
| namespace UnityEditor.Timeline |
| { |
| enum ManipulateEdges |
| { |
| Left, |
| Right, |
| Both |
| } |
|
|
| class SnapEngine |
| { |
| static readonly float k_MagnetInfluenceInPixels = 10.0f; |
|
|
| class SnapInfo |
| { |
| public double time { get; set; } |
|
|
| public bool showSnapHint { get; set; } |
|
|
| public bool IsInInfluenceZone(double currentTime, WindowState state) |
| { |
| var pos = state.TimeToPixel(currentTime); |
| var magnetPos = state.TimeToPixel(time); |
|
|
| return Math.Abs(pos - magnetPos) < k_MagnetInfluenceInPixels; |
| } |
| } |
|
|
| struct TimeBoundaries |
| { |
| public TimeBoundaries(double l, double r) |
| { |
| left = l; |
| right = r; |
| } |
|
|
| public readonly double left; |
| public readonly double right; |
|
|
| public TimeBoundaries Translate(double d) |
| { |
| return new TimeBoundaries(left + d, right + d); |
| } |
| } |
|
|
| public static bool displayDebugLayout; |
|
|
| readonly IAttractable m_Attractable; |
| readonly IAttractionHandler m_AttractionHandler; |
| readonly ManipulateEdges m_ManipulateEdges; |
|
|
| readonly WindowState m_State; |
|
|
| double m_GrabbedTime; |
| TimeBoundaries m_GrabbedTimes; |
|
|
| TimeBoundaries m_CurrentTimes; |
|
|
| readonly List<SnapInfo> m_Magnets = new List<SnapInfo>(); |
|
|
| bool m_SnapEnabled; |
|
|
| public SnapEngine(IAttractable attractable, IAttractionHandler attractionHandler, ManipulateEdges manipulateEdges, WindowState state, |
| Vector2 mousePosition, IEnumerable<ISnappable> snappables = null) |
| { |
| m_Attractable = attractable; |
| m_ManipulateEdges = manipulateEdges; |
|
|
| m_AttractionHandler = attractionHandler; |
| m_State = state; |
|
|
| m_CurrentTimes = m_GrabbedTimes = new TimeBoundaries(m_Attractable.start, m_Attractable.end); |
| m_GrabbedTime = m_State.PixelToTime(mousePosition.x); |
|
|
| |
| AddMagnet(0.0, true, state); |
|
|
| |
| |
| if (TimelineWindow.instance.currentMode.ShouldShowTimeCursor(m_State)) |
| AddMagnet(state.editSequence.time, true, state); |
|
|
| if (state.IsEditingASubTimeline()) |
| { |
| |
| |
| var range = state.editSequence.GetEvaluableRange(); |
| AddMagnet(range.start, true, state); |
| AddMagnet(range.end, true, state); |
| } |
| else if (state.masterSequence.asset.durationMode == TimelineAsset.DurationMode.FixedLength) |
| { |
| |
| AddMagnet(state.masterSequence.asset.duration, true, state); |
| } |
|
|
|
|
| if (snappables == null) |
| snappables = GetVisibleSnappables(m_State); |
|
|
| foreach (var snappable in snappables) |
| { |
| if (!attractable.ShouldSnapTo(snappable)) |
| continue; |
|
|
| var edges = snappable.SnappableEdgesFor(attractable, manipulateEdges); |
| foreach (var edge in edges) |
| AddMagnet(edge.time, edge.showSnapHint, state); |
| } |
| } |
|
|
| public static IEnumerable<ISnappable> GetVisibleSnappables(WindowState state) |
| { |
| Rect rect = TimelineWindow.instance.state.timeAreaRect; |
| rect.height = float.MaxValue; |
| return state.spacePartitioner.GetItemsInArea<ISnappable>(rect).ToArray(); |
| } |
|
|
| void AddMagnet(double magnetTime, bool showSnapHint, WindowState state) |
| { |
| var magnet = m_Magnets.FirstOrDefault(m => m.time.Equals(magnetTime)); |
| if (magnet == null) |
| { |
| if (IsMagnetInShownArea(magnetTime, state)) |
| m_Magnets.Add(new SnapInfo { time = magnetTime, showSnapHint = showSnapHint }); |
| } |
| else |
| { |
| magnet.showSnapHint |= showSnapHint; |
| } |
| } |
|
|
| static bool IsMagnetInShownArea(double time, WindowState state) |
| { |
| var shownArea = state.timeAreaShownRange; |
| return time >= shownArea.x && time <= shownArea.y; |
| } |
|
|
| SnapInfo GetMagnetAt(double time) |
| { |
| return m_Magnets.FirstOrDefault(m => m.time.Equals(time)); |
| } |
|
|
| SnapInfo ClosestMagnet(double time) |
| { |
| SnapInfo candidate = null; |
| var min = double.MaxValue; |
| foreach (var magnetInfo in m_Magnets) |
| { |
| var m = Math.Abs(magnetInfo.time - time); |
| if (m < min) |
| { |
| candidate = magnetInfo; |
| min = m; |
| } |
| } |
|
|
| if (candidate != null && candidate.IsInInfluenceZone(time, m_State)) |
| return candidate; |
|
|
| return null; |
| } |
|
|
| public void Snap(Vector2 currentMousePosition, EventModifiers modifiers) |
| { |
| var d = m_State.PixelToTime(currentMousePosition.x) - m_GrabbedTime; |
|
|
| m_CurrentTimes = m_GrabbedTimes.Translate(d); |
|
|
| bool isLeft = m_ManipulateEdges == ManipulateEdges.Left || m_ManipulateEdges == ManipulateEdges.Both; |
| bool isRight = m_ManipulateEdges == ManipulateEdges.Right || m_ManipulateEdges == ManipulateEdges.Both; |
|
|
| bool attracted = false; |
|
|
| m_SnapEnabled = modifiers == ManipulatorsUtils.actionModifier ? !m_State.edgeSnaps : m_State.edgeSnaps; |
|
|
| if (m_SnapEnabled) |
| { |
| SnapInfo leftActiveMagnet = null; |
| SnapInfo rightActiveMagnet = null; |
|
|
| if (isLeft) |
| leftActiveMagnet = ClosestMagnet(m_CurrentTimes.left); |
|
|
| if (isRight) |
| rightActiveMagnet = ClosestMagnet(m_CurrentTimes.right); |
|
|
| if (leftActiveMagnet != null || rightActiveMagnet != null) |
| { |
| attracted = true; |
|
|
| bool leftAttraction = false; |
|
|
| if (rightActiveMagnet == null) |
| { |
| |
| leftAttraction = true; |
| } |
| else |
| { |
| if (leftActiveMagnet != null) |
| { |
| |
| var leftDistance = Math.Abs(leftActiveMagnet.time - m_CurrentTimes.left); |
| var rightDistance = Math.Abs(rightActiveMagnet.time - m_CurrentTimes.right); |
|
|
| leftAttraction = leftDistance <= rightDistance; |
| } |
| |
| } |
|
|
| if (leftAttraction) |
| { |
| m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Left, leftActiveMagnet.time); |
| } |
| else |
| { |
| m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Right, rightActiveMagnet.time); |
| } |
| } |
| } |
|
|
| if (!attracted) |
| { |
| var time = isLeft ? m_CurrentTimes.left : m_CurrentTimes.right; |
|
|
| time = TimeReferenceUtility.SnapToFrameIfRequired(time); |
|
|
| m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.None, time); |
| } |
| } |
|
|
| public void OnGUI(bool showLeft = true, bool showRight = true) |
| { |
| if (displayDebugLayout) |
| { |
| |
| foreach (var m in m_Magnets) |
| { |
| var window = TimelineWindow.instance; |
| var rect = new Rect(m_State.TimeToPixel(m.time) - k_MagnetInfluenceInPixels, window.state.timeAreaRect.yMax, 2f * k_MagnetInfluenceInPixels, m_State.windowHeight); |
| EditorGUI.DrawRect(rect, new Color(1f, 0f, 0f, 0.4f)); |
| } |
|
|
| |
| var mousePos = Event.current.mousePosition; |
| var time = m_State.PixelToTime(mousePos.x); |
| var p = new Vector2(m_State.TimeToPixel(time), TimelineWindow.instance.state.timeAreaRect.yMax); |
| var s = new Vector2(1f, m_State.windowHeight); |
| EditorGUI.DrawRect(new Rect(p, s), Color.blue); |
|
|
| p = new Vector2(m_State.TimeToPixel(m_GrabbedTime), TimelineWindow.instance.state.timeAreaRect.yMax); |
| s = new Vector2(1f, m_State.windowHeight); |
| EditorGUI.DrawRect(new Rect(p, s), Color.red); |
|
|
| p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.left), TimelineWindow.instance.state.timeAreaRect.yMax); |
| s = new Vector2(1f, m_State.windowHeight); |
| EditorGUI.DrawRect(new Rect(p, s), Color.yellow); |
|
|
| p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.right), TimelineWindow.instance.state.timeAreaRect.yMax); |
| EditorGUI.DrawRect(new Rect(p, s), Color.yellow); |
| } |
|
|
| if (m_SnapEnabled) |
| { |
| if (showLeft) |
| DrawMagnetLineAt(m_Attractable.start); |
|
|
| if (showRight) |
| DrawMagnetLineAt(m_Attractable.end); |
| } |
| } |
|
|
| void DrawMagnetLineAt(double time) |
| { |
| var magnet = GetMagnetAt(time); |
|
|
| if (magnet != null && magnet.showSnapHint) |
| Graphics.DrawLineAtTime(m_State, magnet.time, Color.white); |
| } |
| } |
| } |
|
|