A game about forced loneliness, made by TACStudios
1using System;
2using System.Linq;
3using UnityEditor.Timeline.Actions;
4using UnityEngine;
5using UnityEngine.Timeline;
6
7namespace UnityEditor.Timeline
8{
9 static class Gaps
10 {
11 static readonly string kInsertTime = L10n.Tr("Insert Time");
12
13 public static void Insert(TimelineAsset asset, double at, double amount, double tolerance)
14 {
15 var tracks = asset.flattenedTracks.Where(x => x.lockedInHierarchy == false).ToList();
16 // gather all clips
17 var clips = tracks.SelectMany(x => x.clips).Where(x => (x.start - at) >= -tolerance).ToList();
18 var markers = tracks.SelectMany(x => x.GetMarkers()).Where(x => (x.time - at) >= -tolerance).ToList();
19
20 // push undo on the tracks for the clips that are being modified
21 UndoExtensions.RegisterClips(clips, kInsertTime);
22
23 // push the clips
24 foreach (var clip in clips)
25 {
26 clip.start += amount;
27 }
28
29 // push undos and move the markers
30 UndoExtensions.RegisterMarkers(markers, kInsertTime);
31 foreach (var marker in markers)
32 {
33 marker.time += amount;
34 }
35
36 TimelineEditor.Refresh(RefreshReason.ContentsModified);
37 }
38 }
39
40 class PlayheadContextMenu : Manipulator
41 {
42 readonly TimeAreaItem m_TimeAreaItem;
43 static readonly int[] kFrameInsertionValues = { 5, 10, 25, 100 };
44 static readonly GUIContent[] kFrameInsertionValuesGuiContents =
45 {
46 L10n.TextContent("Insert/Frame/5 Frames"),
47 L10n.TextContent("Insert/Frame/10 Frames"),
48 L10n.TextContent("Insert/Frame/25 Frames"),
49 L10n.TextContent("Insert/Frame/100 Frames")
50 };
51
52 static readonly GUIContent kSingleFrameGuiContents = L10n.TextContent("Insert/Frame/Single");
53 static readonly GUIContent kSelectedTimeGuiContents = L10n.TextContent("Insert/Selected Time");
54
55 public PlayheadContextMenu(TimeAreaItem timeAreaItem)
56 {
57 m_TimeAreaItem = timeAreaItem;
58 }
59
60 protected override bool ContextClick(Event evt, WindowState state)
61 {
62 if (!m_TimeAreaItem.bounds.Contains(evt.mousePosition))
63 return false;
64
65 var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
66 var menu = new GenericMenu();
67
68 if (!TimelineWindow.instance.state.editSequence.isReadOnly)
69 {
70 menu.AddItem(kSingleFrameGuiContents, false, () =>
71 Gaps.Insert(state.editSequence.asset, state.editSequence.time,
72 1.0 / state.referenceSequence.frameRate, tolerance)
73 );
74
75 for (var i = 0; i != kFrameInsertionValues.Length; ++i)
76 {
77 double f = kFrameInsertionValues[i];
78 menu.AddItem(
79 kFrameInsertionValuesGuiContents[i],
80 false, () =>
81 Gaps.Insert(state.editSequence.asset, state.editSequence.time,
82 f / state.referenceSequence.frameRate, tolerance)
83 );
84 }
85
86 var playRangeTime = state.playRange;
87 if (playRangeTime.start > playRangeTime.end)
88 {
89 menu.AddItem(kSelectedTimeGuiContents, false, () =>
90 Gaps.Insert(state.editSequence.asset, playRangeTime.start, playRangeTime.end - playRangeTime.start,
91 TimeUtility.GetEpsilon(playRangeTime.start, state.referenceSequence.frameRate))
92 );
93 }
94 }
95
96 menu.AddItem(L10n.TextContent("Select/Clips Ending Before"), false, () => SelectClipsEndingBefore(state));
97 menu.AddItem(L10n.TextContent("Select/Clips Starting Before"), false, () => SelectClipsStartingBefore(state));
98 menu.AddItem(L10n.TextContent("Select/Clips Ending After"), false, () => SelectClipsEndingAfter(state));
99 menu.AddItem(L10n.TextContent("Select/Clips Starting After"), false, () => SelectClipsStartingAfter(state));
100 menu.AddItem(L10n.TextContent("Select/Clips Intersecting"), false, () => SelectClipsIntersecting(state));
101 menu.AddItem(L10n.TextContent("Select/Blends Intersecting"), false, () => SelectBlendsIntersecting(state));
102 menu.ShowAsContext();
103
104 return true;
105 }
106
107 internal static void SelectClipsEndingBefore(WindowState state)
108 {
109 var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
110 SelectMenuCallback(x => x.end < state.editSequence.time + tolerance, state);
111 }
112
113 internal static void SelectClipsStartingBefore(WindowState state)
114 {
115 var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
116 SelectMenuCallback(x => x.start < state.editSequence.time + tolerance, state);
117 }
118
119 internal static void SelectClipsEndingAfter(WindowState state)
120 {
121 var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
122 SelectMenuCallback(x => x.end - state.editSequence.time >= -tolerance, state);
123 }
124
125 internal static void SelectClipsStartingAfter(WindowState state)
126 {
127 var tolerance = TimeUtility.GetEpsilon(state.editSequence.time, state.referenceSequence.frameRate);
128 SelectMenuCallback(x => x.start - state.editSequence.time >= -tolerance, state);
129 }
130
131 internal static void SelectClipsIntersecting(WindowState state)
132 {
133 SelectMenuCallback(x => x.start <= state.editSequence.time && state.editSequence.time <= x.end, state);
134 }
135
136 internal static void SelectBlendsIntersecting(WindowState state)
137 {
138 SelectMenuCallback(x => SelectBlendingIntersecting(x, state.editSequence.time), state);
139 }
140
141 static bool SelectBlendingIntersecting(TimelineClip clip, double time)
142 {
143 return clip.start <= time && time <= clip.end && (
144 (time <= clip.start + clip.blendInDuration) ||
145 (time >= clip.end - clip.blendOutDuration)
146 );
147 }
148
149 static void SelectMenuCallback(Func<TimelineClip, bool> selector, WindowState state)
150 {
151 var allClips = state.GetWindow().treeView.allClipGuis;
152 if (allClips == null)
153 return;
154
155 SelectionManager.Clear();
156 for (var i = 0; i != allClips.Count; ++i)
157 {
158 var c = allClips[i];
159
160 if (c != null && c.clip != null && c.clip.GetParentTrack().lockedInHierarchy == false && selector(c.clip))
161 {
162 SelectionManager.Add(c.clip);
163 }
164 }
165 }
166 }
167
168 class TimeAreaContextMenu : Manipulator
169 {
170 protected override bool ContextClick(Event evt, WindowState state)
171 {
172 if (state.timeAreaRect.Contains(Event.current.mousePosition))
173 {
174 var menu = new GenericMenu();
175 AddTimeAreaMenuItems(menu, state);
176 menu.ShowAsContext();
177 return true;
178 }
179 return false;
180 }
181
182 internal static void AddTimeAreaMenuItems(GenericMenu menu, WindowState state)
183 {
184 foreach (var value in Enum.GetValues(typeof(TimelineAsset.DurationMode)))
185 {
186 var mode = (TimelineAsset.DurationMode)value;
187 var item = EditorGUIUtility.TextContent(string.Format(TimelineWindow.Styles.DurationModeText, L10n.Tr(ObjectNames.NicifyVariableName(mode.ToString()))));
188
189 if (state.recording || state.IsEditingASubTimeline() || state.editSequence.asset == null
190 || state.editSequence.isReadOnly)
191 menu.AddDisabledItem(item);
192 else
193 menu.AddItem(item, state.editSequence.asset.durationMode == mode, () => SelectDurationCallback(state, mode));
194
195 menu.AddItem(DirectorStyles.showMarkersOnTimeline, state.showMarkerHeader, () => state.showMarkerHeader = !state.showMarkerHeader);
196 }
197 }
198
199 static void SelectDurationCallback(WindowState state, TimelineAsset.DurationMode mode)
200 {
201 if (mode == state.editSequence.asset.durationMode)
202 return;
203
204 UndoExtensions.RegisterTimeline(state.editSequence.asset, "Duration Mode");
205
206
207 // if we switched from Auto to Fixed, use the auto duration as the new fixed duration so the end marker stay in the same position.
208 if (state.editSequence.asset.durationMode == TimelineAsset.DurationMode.BasedOnClips && mode == TimelineAsset.DurationMode.FixedLength)
209 {
210 state.editSequence.asset.UpdateFixedDurationWithItemsDuration();
211 }
212
213 state.editSequence.asset.durationMode = mode;
214 state.UpdateRootPlayableDuration(state.editSequence.duration);
215 }
216 }
217
218 class Scrub : Manipulator
219 {
220 readonly Func<Event, WindowState, bool> m_OnMouseDown;
221 readonly Action<double> m_OnMouseDrag;
222 readonly System.Action m_OnMouseUp;
223
224 bool m_IsCaptured;
225
226 public Scrub(Func<Event, WindowState, bool> onMouseDown, Action<double> onMouseDrag, System.Action onMouseUp)
227 {
228 m_OnMouseDown = onMouseDown;
229 m_OnMouseDrag = onMouseDrag;
230 m_OnMouseUp = onMouseUp;
231 }
232
233 protected override bool MouseDown(Event evt, WindowState state)
234 {
235 if (evt.button != 0)
236 return false;
237
238 if (!m_OnMouseDown(evt, state))
239 return false;
240
241 state.AddCaptured(this);
242 m_IsCaptured = true;
243
244 return true;
245 }
246
247 protected override bool MouseUp(Event evt, WindowState state)
248 {
249 if (!m_IsCaptured)
250 return false;
251
252 m_IsCaptured = false;
253 state.RemoveCaptured(this);
254
255 m_OnMouseUp();
256
257 return true;
258 }
259
260 protected override bool MouseDrag(Event evt, WindowState state)
261 {
262 if (!m_IsCaptured)
263 return false;
264
265 m_OnMouseDrag(state.GetSnappedTimeAtMousePosition(evt.mousePosition));
266
267 return true;
268 }
269 }
270
271 class TimeAreaItem : Control
272 {
273 public Color headColor { get; set; }
274 public Color lineColor { get; set; }
275 public bool drawLine { get; set; }
276 public bool drawHead { get; set; }
277 public bool canMoveHead { get; set; }
278 public string tooltip { get; set; }
279 public Vector2 boundOffset { get; set; }
280
281 readonly GUIContent m_HeaderContent = new GUIContent();
282 readonly GUIStyle m_Style;
283 readonly Tooltip m_Tooltip;
284
285 Rect m_BoundingRect;
286
287 float widgetHeight { get { return m_Style.fixedHeight; } }
288 float widgetWidth { get { return m_Style.fixedWidth; } }
289
290 public Rect bounds
291 {
292 get
293 {
294 Rect r = m_BoundingRect;
295 r.y = TimelineWindow.instance.state.timeAreaRect.yMax - widgetHeight;
296 r.position += boundOffset;
297
298 return r;
299 }
300 }
301
302 public GUIStyle style
303 {
304 get { return m_Style; }
305 }
306
307
308 public bool showTooltip { get; set; }
309
310 // is this the first frame the drag callback is being invoked
311 public bool firstDrag { get; private set; }
312
313 public TimeAreaItem(GUIStyle style, Action<double> onDrag)
314 {
315 m_Style = style;
316 headColor = Color.white;
317 var scrub = new Scrub(
318 (evt, state) =>
319 {
320 firstDrag = true;
321 return state.timeAreaRect.Contains(evt.mousePosition) && bounds.Contains(evt.mousePosition);
322 },
323 (d) =>
324 {
325 if (onDrag != null)
326 onDrag(d);
327 firstDrag = false;
328 },
329 () =>
330 {
331 showTooltip = false;
332 firstDrag = false;
333 }
334 );
335 AddManipulator(scrub);
336 lineColor = m_Style.normal.textColor;
337 drawLine = true;
338 drawHead = true;
339 canMoveHead = false;
340 tooltip = string.Empty;
341 boundOffset = Vector2.zero;
342 m_Tooltip = new Tooltip(DirectorStyles.Instance.displayBackground, DirectorStyles.Instance.tinyFont);
343 }
344
345 public void Draw(Rect rect, WindowState state, double time)
346 {
347 var clipRect = new Rect(0.0f, 0.0f, TimelineWindow.instance.position.width, TimelineWindow.instance.position.height);
348 clipRect.xMin += state.sequencerHeaderWidth;
349
350 using (new GUIViewportScope(clipRect))
351 {
352 Vector2 windowCoordinate = rect.min;
353 windowCoordinate.y += 4.0f;
354
355 windowCoordinate.x = state.TimeToPixel(time);
356
357 m_BoundingRect = new Rect((windowCoordinate.x - widgetWidth / 2.0f), windowCoordinate.y, widgetWidth, widgetHeight);
358
359 // Do not paint if the time cursor goes outside the timeline bounds...
360 if (Event.current.type == EventType.Repaint)
361 {
362 if (m_BoundingRect.xMax < state.timeAreaRect.xMin)
363 return;
364 if (m_BoundingRect.xMin > state.timeAreaRect.xMax)
365 return;
366 }
367
368 var top = new Vector3(windowCoordinate.x, rect.y - DirectorStyles.kDurationGuiThickness);
369 var bottom = new Vector3(windowCoordinate.x, rect.yMax);
370
371 if (drawLine)
372 {
373 Rect lineRect = Rect.MinMaxRect(top.x - 0.5f, top.y, bottom.x + 0.5f, bottom.y);
374 EditorGUI.DrawRect(lineRect, lineColor);
375 }
376
377 if (drawHead && Event.current.type == EventType.Repaint)
378 {
379 Color c = GUI.color;
380 GUI.color = headColor;
381 style.Draw(bounds, m_HeaderContent, false, false, false, false);
382 GUI.color = c;
383
384 if (canMoveHead)
385 EditorGUIUtility.AddCursorRect(bounds, MouseCursor.MoveArrow);
386 }
387
388 if (showTooltip)
389 {
390 m_Tooltip.text = TimeReferenceUtility.ToTimeString(time);
391
392 Vector2 position = bounds.position;
393 position.y = state.timeAreaRect.y;
394 position.y -= m_Tooltip.bounds.height;
395 position.x -= Mathf.Abs(m_Tooltip.bounds.width - bounds.width) / 2.0f;
396
397 Rect tooltipBounds = bounds;
398 tooltipBounds.position = position;
399 m_Tooltip.bounds = tooltipBounds;
400
401 m_Tooltip.Draw();
402 }
403 }
404 }
405 }
406}