A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEngine;
5using UnityEngine.Timeline;
6
7namespace UnityEditor.Timeline
8{
9 enum ManipulateEdges
10 {
11 Left,
12 Right,
13 Both
14 }
15
16 class SnapEngine
17 {
18 static readonly float k_MagnetInfluenceInPixels = 10.0f;
19
20 class SnapInfo
21 {
22 public double time { get; set; }
23
24 public bool showSnapHint { get; set; }
25
26 public bool IsInInfluenceZone(double currentTime, WindowState state)
27 {
28 var pos = state.TimeToPixel(currentTime);
29 var magnetPos = state.TimeToPixel(time);
30
31 return Math.Abs(pos - magnetPos) < k_MagnetInfluenceInPixels;
32 }
33 }
34
35 struct TimeBoundaries
36 {
37 public TimeBoundaries(double l, double r)
38 {
39 left = l;
40 right = r;
41 }
42
43 public readonly double left;
44 public readonly double right;
45
46 public TimeBoundaries Translate(double d)
47 {
48 return new TimeBoundaries(left + d, right + d);
49 }
50 }
51
52 public static bool displayDebugLayout;
53
54 readonly IAttractable m_Attractable;
55 readonly IAttractionHandler m_AttractionHandler;
56 readonly ManipulateEdges m_ManipulateEdges;
57
58 readonly WindowState m_State;
59
60 double m_GrabbedTime;
61 TimeBoundaries m_GrabbedTimes;
62
63 TimeBoundaries m_CurrentTimes;
64
65 readonly List<SnapInfo> m_Magnets = new List<SnapInfo>();
66
67 bool m_SnapEnabled;
68
69 public SnapEngine(IAttractable attractable, IAttractionHandler attractionHandler, ManipulateEdges manipulateEdges, WindowState state,
70 Vector2 mousePosition, IEnumerable<ISnappable> snappables = null)
71 {
72 m_Attractable = attractable;
73 m_ManipulateEdges = manipulateEdges;
74
75 m_AttractionHandler = attractionHandler;
76 m_State = state;
77
78 m_CurrentTimes = m_GrabbedTimes = new TimeBoundaries(m_Attractable.start, m_Attractable.end);
79 m_GrabbedTime = m_State.PixelToTime(mousePosition.x);
80
81 // Add Time zero as Magnet
82 AddMagnet(0.0, true, state);
83
84 // Add current Time as Magnet
85 // case1157280 only add current time as magnet if visible
86 if (TimelineWindow.instance.currentMode.ShouldShowTimeCursor(m_State))
87 AddMagnet(state.editSequence.time, true, state);
88
89 if (state.IsEditingASubTimeline())
90 {
91 // Add start and end of evaluable range as Magnets
92 // This includes the case where the master timeline has a fixed length
93 var range = state.editSequence.GetEvaluableRange();
94 AddMagnet(range.start, true, state);
95 AddMagnet(range.end, true, state);
96 }
97 else if (state.masterSequence.asset.durationMode == TimelineAsset.DurationMode.FixedLength)
98 {
99 // Add end sequence Time as Magnet
100 AddMagnet(state.masterSequence.asset.duration, true, state);
101 }
102
103
104 if (snappables == null)
105 snappables = GetVisibleSnappables(m_State);
106
107 foreach (var snappable in snappables)
108 {
109 if (!attractable.ShouldSnapTo(snappable))
110 continue;
111
112 var edges = snappable.SnappableEdgesFor(attractable, manipulateEdges);
113 foreach (var edge in edges)
114 AddMagnet(edge.time, edge.showSnapHint, state);
115 }
116 }
117
118 public static IEnumerable<ISnappable> GetVisibleSnappables(WindowState state)
119 {
120 Rect rect = TimelineWindow.instance.state.timeAreaRect;
121 rect.height = float.MaxValue;
122 return state.spacePartitioner.GetItemsInArea<ISnappable>(rect).ToArray();
123 }
124
125 void AddMagnet(double magnetTime, bool showSnapHint, WindowState state)
126 {
127 var magnet = m_Magnets.FirstOrDefault(m => m.time.Equals(magnetTime));
128 if (magnet == null)
129 {
130 if (IsMagnetInShownArea(magnetTime, state))
131 m_Magnets.Add(new SnapInfo { time = magnetTime, showSnapHint = showSnapHint });
132 }
133 else
134 {
135 magnet.showSnapHint |= showSnapHint;
136 }
137 }
138
139 static bool IsMagnetInShownArea(double time, WindowState state)
140 {
141 var shownArea = state.timeAreaShownRange;
142 return time >= shownArea.x && time <= shownArea.y;
143 }
144
145 SnapInfo GetMagnetAt(double time)
146 {
147 return m_Magnets.FirstOrDefault(m => m.time.Equals(time));
148 }
149
150 SnapInfo ClosestMagnet(double time)
151 {
152 SnapInfo candidate = null;
153 var min = double.MaxValue;
154 foreach (var magnetInfo in m_Magnets)
155 {
156 var m = Math.Abs(magnetInfo.time - time);
157 if (m < min)
158 {
159 candidate = magnetInfo;
160 min = m;
161 }
162 }
163
164 if (candidate != null && candidate.IsInInfluenceZone(time, m_State))
165 return candidate;
166
167 return null;
168 }
169
170 public void Snap(Vector2 currentMousePosition, EventModifiers modifiers)
171 {
172 var d = m_State.PixelToTime(currentMousePosition.x) - m_GrabbedTime;
173
174 m_CurrentTimes = m_GrabbedTimes.Translate(d);
175
176 bool isLeft = m_ManipulateEdges == ManipulateEdges.Left || m_ManipulateEdges == ManipulateEdges.Both;
177 bool isRight = m_ManipulateEdges == ManipulateEdges.Right || m_ManipulateEdges == ManipulateEdges.Both;
178
179 bool attracted = false;
180
181 m_SnapEnabled = modifiers == ManipulatorsUtils.actionModifier ? !m_State.edgeSnaps : m_State.edgeSnaps;
182
183 if (m_SnapEnabled)
184 {
185 SnapInfo leftActiveMagnet = null;
186 SnapInfo rightActiveMagnet = null;
187
188 if (isLeft)
189 leftActiveMagnet = ClosestMagnet(m_CurrentTimes.left);
190
191 if (isRight)
192 rightActiveMagnet = ClosestMagnet(m_CurrentTimes.right);
193
194 if (leftActiveMagnet != null || rightActiveMagnet != null)
195 {
196 attracted = true;
197
198 bool leftAttraction = false;
199
200 if (rightActiveMagnet == null)
201 {
202 // Attracted by a left magnet only.
203 leftAttraction = true;
204 }
205 else
206 {
207 if (leftActiveMagnet != null)
208 {
209 // Attracted by both magnets, choose the closest one.
210 var leftDistance = Math.Abs(leftActiveMagnet.time - m_CurrentTimes.left);
211 var rightDistance = Math.Abs(rightActiveMagnet.time - m_CurrentTimes.right);
212
213 leftAttraction = leftDistance <= rightDistance;
214 }
215 // else, Attracted by right magnet only
216 }
217
218 if (leftAttraction)
219 {
220 m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Left, leftActiveMagnet.time);
221 }
222 else
223 {
224 m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.Right, rightActiveMagnet.time);
225 }
226 }
227 }
228
229 if (!attracted)
230 {
231 var time = isLeft ? m_CurrentTimes.left : m_CurrentTimes.right;
232
233 time = TimeReferenceUtility.SnapToFrameIfRequired(time);
234
235 m_AttractionHandler.OnAttractedEdge(m_Attractable, m_ManipulateEdges, AttractedEdge.None, time);
236 }
237 }
238
239 public void OnGUI(bool showLeft = true, bool showRight = true)
240 {
241 if (displayDebugLayout)
242 {
243 // Display Magnet influence zone
244 foreach (var m in m_Magnets)
245 {
246 var window = TimelineWindow.instance;
247 var rect = new Rect(m_State.TimeToPixel(m.time) - k_MagnetInfluenceInPixels, window.state.timeAreaRect.yMax, 2f * k_MagnetInfluenceInPixels, m_State.windowHeight);
248 EditorGUI.DrawRect(rect, new Color(1f, 0f, 0f, 0.4f));
249 }
250
251 // Display Cursor position
252 var mousePos = Event.current.mousePosition;
253 var time = m_State.PixelToTime(mousePos.x);
254 var p = new Vector2(m_State.TimeToPixel(time), TimelineWindow.instance.state.timeAreaRect.yMax);
255 var s = new Vector2(1f, m_State.windowHeight);
256 EditorGUI.DrawRect(new Rect(p, s), Color.blue);
257
258 p = new Vector2(m_State.TimeToPixel(m_GrabbedTime), TimelineWindow.instance.state.timeAreaRect.yMax);
259 s = new Vector2(1f, m_State.windowHeight);
260 EditorGUI.DrawRect(new Rect(p, s), Color.red);
261
262 p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.left), TimelineWindow.instance.state.timeAreaRect.yMax);
263 s = new Vector2(1f, m_State.windowHeight);
264 EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
265
266 p = new Vector2(m_State.TimeToPixel(m_CurrentTimes.right), TimelineWindow.instance.state.timeAreaRect.yMax);
267 EditorGUI.DrawRect(new Rect(p, s), Color.yellow);
268 }
269
270 if (m_SnapEnabled)
271 {
272 if (showLeft)
273 DrawMagnetLineAt(m_Attractable.start);
274
275 if (showRight)
276 DrawMagnetLineAt(m_Attractable.end);
277 }
278 }
279
280 void DrawMagnetLineAt(double time)
281 {
282 var magnet = GetMagnetAt(time);
283
284 if (magnet != null && magnet.showSnapHint)
285 Graphics.DrawLineAtTime(m_State, magnet.time, Color.white);
286 }
287 }
288}