Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: db34f310723c62440a05d3e69f262a70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEditor.Timeline
{
class AddDeleteItemModeMix : IAddDeleteItemMode
{
public void InsertItemsAtTime(IEnumerable<ItemsPerTrack> itemsGroups, double requestedTime)
{
ItemsUtils.SetItemsStartTime(itemsGroups, requestedTime);
EditModeMixUtils.PrepareItemsForInsertion(itemsGroups);
if (!EditModeMixUtils.CanInsert(itemsGroups))
{
var validTime = itemsGroups.Select(c => c.targetTrack).Max(parent => parent.end);
ItemsUtils.SetItemsStartTime(itemsGroups, validTime);
}
}
public void RemoveItems(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41e14f40b915ca743a3dffd18ffc65ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
class AddDeleteItemModeReplace : IAddDeleteItemMode
{
public void InsertItemsAtTime(IEnumerable<ItemsPerTrack> itemsGroups, double requestedTime)
{
ItemsUtils.SetItemsStartTime(itemsGroups, requestedTime);
EditModeReplaceUtils.Insert(itemsGroups);
}
public void RemoveItems(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea28dc637ae40484da709200d3328587
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
class AddDeleteItemModeRipple : IAddDeleteItemMode
{
public void InsertItemsAtTime(IEnumerable<ItemsPerTrack> itemsGroups, double requestedTime)
{
ItemsUtils.SetItemsStartTime(itemsGroups, requestedTime);
EditModeRippleUtils.Insert(itemsGroups);
}
public void RemoveItems(IEnumerable<ItemsPerTrack> itemsGroups)
{
EditModeRippleUtils.Remove(itemsGroups);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 88338eb35defad644a48718188e8f219
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace UnityEditor.Timeline
{
interface IAddDeleteItemMode
{
void InsertItemsAtTime(IEnumerable<ItemsPerTrack> itemsGroups, double requestedTime);
void RemoveItems(IEnumerable<ItemsPerTrack> itemsGroups);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4db13e1060deaae48b30246ed63b7c9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4ad09461bf994e54da846f726a23118e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,95 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimelineCursors
{
public enum CursorType
{
MixBoth,
MixLeft,
MixRight,
Replace,
Ripple,
Pan
}
class CursorInfo
{
public readonly string assetPath;
public readonly Vector2 hotSpot;
public readonly MouseCursor mouseCursorType;
public CursorInfo(string assetPath, Vector2 hotSpot, MouseCursor mouseCursorType)
{
this.assetPath = assetPath;
this.hotSpot = hotSpot;
this.mouseCursorType = mouseCursorType;
}
}
const string k_CursorAssetRoot = "Cursors/";
const string k_CursorAssetsNamespace = "Timeline.";
const string k_CursorAssetExtension = ".png";
const string k_MixBothCursorAssetName = k_CursorAssetsNamespace + "MixBoth" + k_CursorAssetExtension;
const string k_MixLeftCursorAssetName = k_CursorAssetsNamespace + "MixLeft" + k_CursorAssetExtension;
const string k_MixRightCursorAssetName = k_CursorAssetsNamespace + "MixRight" + k_CursorAssetExtension;
const string k_ReplaceCursorAssetName = k_CursorAssetsNamespace + "Replace" + k_CursorAssetExtension;
const string k_RippleCursorAssetName = k_CursorAssetsNamespace + "Ripple" + k_CursorAssetExtension;
static readonly string s_PlatformPath = (Application.platform == RuntimePlatform.WindowsEditor) ? "Windows/" : "macOS/";
static readonly string s_CursorAssetDirectory = k_CursorAssetRoot + s_PlatformPath;
static readonly Dictionary<CursorType, CursorInfo> s_CursorInfoLookup = new Dictionary<CursorType, CursorInfo>
{
{CursorType.MixBoth, new CursorInfo(s_CursorAssetDirectory + k_MixBothCursorAssetName, new Vector2(16, 18), MouseCursor.CustomCursor)},
{CursorType.MixLeft, new CursorInfo(s_CursorAssetDirectory + k_MixLeftCursorAssetName, new Vector2(7, 18), MouseCursor.CustomCursor)},
{CursorType.MixRight, new CursorInfo(s_CursorAssetDirectory + k_MixRightCursorAssetName, new Vector2(25, 18), MouseCursor.CustomCursor)},
{CursorType.Replace, new CursorInfo(s_CursorAssetDirectory + k_ReplaceCursorAssetName, new Vector2(16, 28), MouseCursor.CustomCursor)},
{CursorType.Ripple, new CursorInfo(s_CursorAssetDirectory + k_RippleCursorAssetName, new Vector2(26, 19), MouseCursor.CustomCursor)},
{CursorType.Pan, new CursorInfo(null, Vector2.zero, MouseCursor.Pan)}
};
static readonly Dictionary<string, Texture2D> s_CursorAssetCache = new Dictionary<string, Texture2D>();
static CursorType? s_CurrentCursor;
public static void SetCursor(CursorType cursorType)
{
if (s_CurrentCursor.HasValue && s_CurrentCursor.Value == cursorType) return;
s_CurrentCursor = cursorType;
var cursorInfo = s_CursorInfoLookup[cursorType];
Texture2D cursorAsset = null;
if (cursorInfo.mouseCursorType == MouseCursor.CustomCursor)
{
cursorAsset = LoadCursorAsset(cursorInfo.assetPath);
}
EditorGUIUtility.SetCurrentViewCursor(cursorAsset, cursorInfo.hotSpot, cursorInfo.mouseCursorType);
}
public static void ClearCursor()
{
if (!s_CurrentCursor.HasValue) return;
EditorGUIUtility.ClearCurrentViewCursor();
s_CurrentCursor = null;
}
static Texture2D LoadCursorAsset(string assetPath)
{
if (!s_CursorAssetCache.ContainsKey(assetPath))
{
s_CursorAssetCache.Add(assetPath, (Texture2D)EditorGUIUtility.Load(assetPath));
}
return s_CursorAssetCache[assetPath];
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f16e09785c984c445a0467e30f845636
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,344 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class EditMode
{
public enum EditType
{
None = -1,
Mix = 0,
Ripple = 1,
Replace = 2
}
interface ISubEditMode
{
IMoveItemMode moveItemMode { get; }
IMoveItemDrawer moveItemDrawer { get; }
ITrimItemMode trimItemMode { get; }
ITrimItemDrawer trimItemDrawer { get; }
IAddDeleteItemMode addDeleteItemMode { get; }
Color color { get; }
KeyCode clutchKey { get; }
void Reset();
}
class SubEditMode<TMoveMode, TTrimMode, TAddDeleteMode>: ISubEditMode
where TMoveMode : class, IMoveItemMode, IMoveItemDrawer, new()
where TTrimMode : class, ITrimItemMode, ITrimItemDrawer, new()
where TAddDeleteMode : class, IAddDeleteItemMode, new()
{
public SubEditMode(Color guiColor, KeyCode key)
{
color = guiColor;
clutchKey = key;
Reset();
}
public void Reset()
{
m_MoveItemMode = new TMoveMode();
m_TrimItemMode = new TTrimMode();
m_AddDeleteItemMode = new TAddDeleteMode();
}
TMoveMode m_MoveItemMode;
TTrimMode m_TrimItemMode;
TAddDeleteMode m_AddDeleteItemMode;
public IMoveItemMode moveItemMode { get { return m_MoveItemMode; } }
public IMoveItemDrawer moveItemDrawer { get { return m_MoveItemMode; } }
public ITrimItemMode trimItemMode { get { return m_TrimItemMode; } }
public ITrimItemDrawer trimItemDrawer { get { return m_TrimItemMode; } }
public IAddDeleteItemMode addDeleteItemMode { get { return m_AddDeleteItemMode; } }
public Color color { get; }
public KeyCode clutchKey { get; }
}
static readonly Dictionary<EditType, ISubEditMode> k_EditModes = new Dictionary<EditType, ISubEditMode>
{
{ EditType.Mix, new SubEditMode<MoveItemModeMix, TrimItemModeMix, AddDeleteItemModeMix>(DirectorStyles.kMixToolColor, KeyCode.Alpha1) },
{ EditType.Ripple, new SubEditMode<MoveItemModeRipple, TrimItemModeRipple, AddDeleteItemModeRipple>(DirectorStyles.kRippleToolColor, KeyCode.Alpha2) },
{ EditType.Replace, new SubEditMode<MoveItemModeReplace, TrimItemModeReplace, AddDeleteItemModeReplace>(DirectorStyles.kReplaceToolColor, KeyCode.Alpha3) }
};
static EditType s_CurrentEditType = EditType.Mix;
static EditType s_OverrideEditType = EditType.None;
static ITrimmable s_CurrentTrimItem;
static TrimEdge s_CurrentTrimDirection;
static MoveItemHandler s_CurrentMoveItemHandler;
static EditModeInputHandler s_InputHandler = new EditModeInputHandler();
static ITrimItemMode trimMode
{
get { return GetSubEditMode(editType).trimItemMode; }
}
static ITrimItemDrawer trimDrawer
{
get { return GetSubEditMode(editType).trimItemDrawer; }
}
static IMoveItemMode moveMode
{
get { return GetSubEditMode(editType).moveItemMode; }
}
static IMoveItemDrawer moveDrawer
{
get { return GetSubEditMode(editType).moveItemDrawer; }
}
static IAddDeleteItemMode addDeleteMode
{
get { return GetSubEditMode(editType).addDeleteItemMode; }
}
public static EditModeInputHandler inputHandler
{
get { return s_InputHandler; }
}
static Color modeColor
{
get { return GetSubEditMode(editType).color; }
}
public static EditType editType
{
get
{
if (s_OverrideEditType != EditType.None)
return s_OverrideEditType;
var window = TimelineWindow.instance;
if (window != null)
s_CurrentEditType = window.state.editType;
return s_CurrentEditType;
}
set
{
s_CurrentEditType = value;
var window = TimelineWindow.instance;
if (window != null)
window.state.editType = value;
s_OverrideEditType = EditType.None;
}
}
static ISubEditMode GetSubEditMode(EditType type)
{
var subEditMode = k_EditModes[type];
if (subEditMode != null)
return subEditMode;
Debug.LogError("Unsupported editmode type");
return null;
}
static EditType GetSubEditType(KeyCode key)
{
foreach (var subEditMode in k_EditModes)
{
if (subEditMode.Value.clutchKey == key)
return subEditMode.Key;
}
return EditType.None;
}
public static void ClearEditMode()
{
k_EditModes[editType].Reset();
}
public static void BeginTrim(ITimelineItem item, TrimEdge trimDirection)
{
var itemToTrim = item as ITrimmable;
if (itemToTrim == null) return;
s_CurrentTrimItem = itemToTrim;
s_CurrentTrimDirection = trimDirection;
trimMode.OnBeforeTrim(itemToTrim, trimDirection);
UndoExtensions.RegisterTrack(itemToTrim.parentTrack, L10n.Tr("Trim Clip"));
}
public static void TrimStart(ITimelineItem item, double time, bool affectTimeScale)
{
var itemToTrim = item as ITrimmable;
if (itemToTrim == null) return;
trimMode.TrimStart(itemToTrim, time, affectTimeScale);
}
public static void TrimEnd(ITimelineItem item, double time, bool affectTimeScale)
{
var itemToTrim = item as ITrimmable;
if (itemToTrim == null) return;
trimMode.TrimEnd(itemToTrim, time, affectTimeScale);
}
public static void DrawTrimGUI(WindowState state, TimelineItemGUI item, TrimEdge edge)
{
trimDrawer.DrawGUI(state, item.rect, modeColor, edge);
}
public static void FinishTrim()
{
s_CurrentTrimItem = null;
TimelineCursors.ClearCursor();
ClearEditMode();
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
public static void BeginMove(MoveItemHandler moveItemHandler)
{
s_CurrentMoveItemHandler = moveItemHandler;
moveMode.BeginMove(s_CurrentMoveItemHandler.movingItems);
}
public static void UpdateMove()
{
moveMode.UpdateMove(s_CurrentMoveItemHandler.movingItems);
}
public static void OnTrackDetach(IEnumerable<ItemsPerTrack> grabbedTrackItems)
{
moveMode.OnTrackDetach(grabbedTrackItems);
}
public static void HandleTrackSwitch(IEnumerable<ItemsPerTrack> grabbedTrackItems)
{
moveMode.HandleTrackSwitch(grabbedTrackItems);
}
public static bool AllowTrackSwitch()
{
return moveMode.AllowTrackSwitch();
}
public static double AdjustStartTime(WindowState state, ItemsPerTrack itemsGroup, double time)
{
return moveMode.AdjustStartTime(state, itemsGroup, time);
}
public static bool ValidateDrag(ItemsPerTrack itemsGroup)
{
return moveMode.ValidateMove(itemsGroup);
}
public static void DrawMoveGUI(WindowState state, IEnumerable<MovingItems> movingItems)
{
moveDrawer.DrawGUI(state, movingItems, modeColor);
}
public static void FinishMove()
{
var manipulatedItemsList = s_CurrentMoveItemHandler.movingItems;
moveMode.FinishMove(manipulatedItemsList);
foreach (var itemsGroup in manipulatedItemsList)
foreach (var item in itemsGroup.items)
item.parentTrack = itemsGroup.targetTrack;
s_CurrentMoveItemHandler = null;
TimelineCursors.ClearCursor();
ClearEditMode();
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
public static void FinalizeInsertItemsAtTime(IEnumerable<ItemsPerTrack> newItems, double requestedTime)
{
addDeleteMode.InsertItemsAtTime(newItems, requestedTime);
}
public static void PrepareItemsDelete(IEnumerable<ItemsPerTrack> newItems)
{
addDeleteMode.RemoveItems(newItems);
}
public static void HandleModeClutch()
{
if (Event.current.type == EventType.KeyDown && EditorGUI.IsEditingTextField())
return;
var prevType = editType;
if (Event.current.type == EventType.KeyDown)
{
var clutchEditType = GetSubEditType(Event.current.keyCode);
if (clutchEditType != EditType.None)
{
s_OverrideEditType = clutchEditType;
Event.current.Use();
}
}
else if (Event.current.type == EventType.KeyUp)
{
var clutchEditType = GetSubEditType(Event.current.keyCode);
if (clutchEditType == s_OverrideEditType)
{
s_OverrideEditType = EditType.None;
Event.current.Use();
}
}
if (prevType != editType)
{
if (s_CurrentTrimItem != null)
{
trimMode.OnBeforeTrim(s_CurrentTrimItem, s_CurrentTrimDirection);
}
else if (s_CurrentMoveItemHandler != null)
{
if (s_CurrentMoveItemHandler.movingItems == null)
{
s_CurrentMoveItemHandler = null;
return;
}
foreach (var movingItems in s_CurrentMoveItemHandler.movingItems)
{
if (movingItems != null && movingItems.HasAnyDetachedParents())
{
foreach (var items in movingItems.items)
{
items.parentTrack = movingItems.originalTrack;
}
}
}
var movingSelection = s_CurrentMoveItemHandler.movingItems;
// Handle clutch key transition if needed
GetSubEditMode(prevType).moveItemMode.OnModeClutchExit(movingSelection);
moveMode.OnModeClutchEnter(movingSelection);
moveMode.BeginMove(movingSelection);
moveMode.HandleTrackSwitch(movingSelection);
UpdateMove();
s_CurrentMoveItemHandler.RefreshPreviewItems();
TimelineWindow.instance.state.rebuildGraph = true; // TODO Rebuild only if parent changed
}
TimelineWindow.instance.Repaint(); // TODO Refresh the toolbar without doing a full repaint?
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dfd084fea478f3148b7de3d83bab1d8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,199 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace UnityEditor.Timeline
{
class EditModeInputHandler
{
readonly MoveInputHandler m_MoveHandler;
readonly TrimInputHandler m_TrimHandler;
public EditModeInputHandler()
{
m_MoveHandler = new MoveInputHandler();
m_TrimHandler = new TrimInputHandler();
}
public void ProcessMove(InputEvent action, double value)
{
if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
ProcessInputAction(m_MoveHandler, action, value, TimelineWindow.instance.state);
}
public void ProcessTrim(InputEvent action, double value, bool stretch)
{
if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
{
m_TrimHandler.stretch = stretch;
ProcessInputAction(m_TrimHandler, action, value, TimelineWindow.instance.state);
}
}
public void SetValueForEdge(IEnumerable<ITimelineItem> items, AttractedEdge edge, double value)
{
if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
MoveInputHandler.SetValueForEdge(items, edge, value, TimelineWindow.instance.state);
}
public void OnGUI(WindowState state, Event evt)
{
if (TimelineWindow.instance != null && TimelineWindow.instance.state != null)
{
m_MoveHandler.OnGUI(evt);
m_TrimHandler.OnGUI(state);
}
}
static void ProcessInputAction(IInputHandler handler, InputEvent action, double value, WindowState state)
{
var items = SelectionManager.SelectedItems();
switch (action)
{
case InputEvent.None:
return;
case InputEvent.DragEnter:
handler.OnEnterDrag(items, state);
break;
case InputEvent.Drag:
handler.OnDrag(value, state);
break;
case InputEvent.DragExit:
handler.OnExitDrag();
break;
case InputEvent.KeyboardInput:
handler.OnSetValue(items, value, state);
break;
default:
return;
}
}
interface IInputHandler
{
void OnEnterDrag(IEnumerable<ITimelineItem> items, WindowState state);
void OnDrag(double value, WindowState state);
void OnExitDrag();
void OnSetValue(IEnumerable<ITimelineItem> items, double value, WindowState state);
}
class TrimInputHandler : IInputHandler
{
bool isDragging { get; set; }
public bool stretch { get; set; }
IEnumerable<ITimelineItem> grabbedItems { get; set; }
public void OnEnterDrag(IEnumerable<ITimelineItem> items, WindowState state)
{
grabbedItems = items.OfType<ITrimmable>().ToArray();
foreach (var item in grabbedItems)
{
EditMode.BeginTrim(item, TrimEdge.End);
}
isDragging = true;
}
public void OnDrag(double endValue, WindowState state)
{
var trimValue = endValue;
trimValue = TimeReferenceUtility.SnapToFrameIfRequired(trimValue);
foreach (var item in grabbedItems)
{
EditMode.TrimEnd(item, trimValue, stretch);
}
state.UpdateRootPlayableDuration(state.editSequence.duration);
}
public void OnExitDrag()
{
isDragging = false;
EditMode.FinishTrim();
TimelineWindow.instance.Repaint();
}
public void OnSetValue(IEnumerable<ITimelineItem> items, double endValue, WindowState state)
{
foreach (var item in items.OfType<ITrimmable>())
{
EditMode.BeginTrim(item, TrimEdge.End);
EditMode.TrimEnd(item, endValue, stretch);
EditMode.FinishTrim();
}
state.UpdateRootPlayableDuration(state.editSequence.duration);
}
public void OnGUI(WindowState state)
{
if (!isDragging) return;
foreach (var item in grabbedItems)
{
EditMode.DrawTrimGUI(state, item.gui, TrimEdge.End);
}
}
}
class MoveInputHandler : IInputHandler
{
MoveItemHandler m_MoveItemHandler;
public void OnEnterDrag(IEnumerable<ITimelineItem> items, WindowState state)
{
if (items.Any())
{
m_MoveItemHandler = new MoveItemHandler(state);
m_MoveItemHandler.Grab(items, items.First().parentTrack);
}
}
public void OnDrag(double value, WindowState state)
{
if (m_MoveItemHandler == null) return;
var startValue = value;
startValue = TimeReferenceUtility.SnapToFrameIfRequired(startValue);
m_MoveItemHandler.OnAttractedEdge(null, ManipulateEdges.Both, AttractedEdge.None, startValue);
}
public void OnExitDrag()
{
if (m_MoveItemHandler == null) return;
m_MoveItemHandler.Drop();
m_MoveItemHandler = null;
GUIUtility.ExitGUI();
}
public void OnSetValue(IEnumerable<ITimelineItem> items, double value, WindowState state)
{
if (!items.Any()) return;
m_MoveItemHandler = new MoveItemHandler(state);
m_MoveItemHandler.Grab(items, items.First().parentTrack);
m_MoveItemHandler.OnAttractedEdge(null, ManipulateEdges.Both, AttractedEdge.None, value);
m_MoveItemHandler.Drop();
m_MoveItemHandler = null;
}
public void OnGUI(Event evt)
{
if (m_MoveItemHandler != null)
m_MoveItemHandler.OnGUI(evt);
}
public static void SetValueForEdge(IEnumerable<ITimelineItem> items, AttractedEdge edge, double value, WindowState state)
{
var handler = new MoveItemHandler(state);
foreach (var item in items)
{
handler.Grab(new[] {item}, item.parentTrack);
handler.OnAttractedEdge(null, ManipulateEdges.Both, edge, value);
handler.Drop();
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 459f6a07ee4a58b42ba2568b097c3ec4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,49 @@
using System;
using UnityEngine;
namespace UnityEditor.Timeline
{
class HeaderSplitterManipulator : Manipulator
{
bool m_Captured;
protected override bool MouseDown(Event evt, WindowState state)
{
Rect headerSplitterRect = state.GetWindow().headerSplitterRect;
if (headerSplitterRect.Contains(evt.mousePosition))
{
m_Captured = true;
state.AddCaptured(this);
return true;
}
return false;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (!m_Captured)
return false;
state.sequencerHeaderWidth = evt.mousePosition.x;
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_Captured)
return false;
state.RemoveCaptured(this);
m_Captured = false;
return true;
}
public override void Overlay(Event evt, WindowState state)
{
Rect rect = state.GetWindow().sequenceRect;
EditorGUIUtility.AddCursorRect(rect, MouseCursor.SplitResizeLeftRight);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7db6186effeb4d159963cad6defd26e5
timeCreated: 1604439091

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bd3ffb9a97575a44a82f9ca086813154
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,28 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Timeline
{
interface IMoveItemMode
{
void OnTrackDetach(IEnumerable<ItemsPerTrack> itemsGroups);
void HandleTrackSwitch(IEnumerable<ItemsPerTrack> itemsGroups);
bool AllowTrackSwitch();
double AdjustStartTime(WindowState state, ItemsPerTrack itemsGroup, double time);
void OnModeClutchEnter(IEnumerable<ItemsPerTrack> itemsGroups);
void OnModeClutchExit(IEnumerable<ItemsPerTrack> itemsGroups);
void BeginMove(IEnumerable<ItemsPerTrack> itemsGroups);
void UpdateMove(IEnumerable<ItemsPerTrack> itemsGroups);
void FinishMove(IEnumerable<ItemsPerTrack> itemsGroups);
bool ValidateMove(ItemsPerTrack itemsGroup);
}
interface IMoveItemDrawer
{
void DrawGUI(WindowState state, IEnumerable<MovingItems> movingItems, Color color);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3ff3d24ea34f9f74cb138e435f5f491e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,311 @@
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();
}
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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6bd368ab00d75c459e2582e017191e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,161 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class MoveItemModeMix : IMoveItemMode, IMoveItemDrawer
{
TimelineClip[] m_ClipsMoved;
Dictionary<TimelineClip, double> m_OriginalEaseInDuration = new Dictionary<TimelineClip, double>();
Dictionary<TimelineClip, double> m_OriginalEaseOutDuration = new Dictionary<TimelineClip, double>();
public void OnTrackDetach(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public void HandleTrackSwitch(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
{
var targetTrack = itemsGroup.targetTrack;
if (targetTrack != null && itemsGroup.items.Any())
{
var compatible = itemsGroup.items.First().IsCompatibleWithTrack(targetTrack) &&
!EditModeUtils.IsInfiniteTrack(targetTrack);
var track = compatible ? targetTrack : null;
UndoExtensions.RegisterTrack(track, L10n.Tr("Move Items"));
EditModeUtils.SetParentTrack(itemsGroup.items, track);
}
else
{
EditModeUtils.SetParentTrack(itemsGroup.items, null);
}
}
}
public bool AllowTrackSwitch()
{
return true;
}
public double AdjustStartTime(WindowState state, ItemsPerTrack itemsGroup, double time)
{
return time;
}
public void OnModeClutchEnter(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public void OnModeClutchExit(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public void BeginMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
m_ClipsMoved = itemsGroups.SelectMany(i => i.clips).ToArray();
foreach (var clip in m_ClipsMoved)
{
m_OriginalEaseInDuration[clip] = clip.easeInDuration;
m_OriginalEaseOutDuration[clip] = clip.easeOutDuration;
}
}
public void UpdateMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
//Compute Blends before updating ease values.
foreach(var t in itemsGroups.Select(i=>i.targetTrack).Where(t => t != null))
t.ComputeBlendsFromOverlaps();
//Reset to original ease values. The trim operation will calculate the proper blend values.
foreach(var clip in m_ClipsMoved)
{
clip.easeInDuration = m_OriginalEaseInDuration[clip];
clip.easeOutDuration = m_OriginalEaseOutDuration[clip];
EditorUtility.SetDirty(clip.asset);
}
}
public void FinishMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
var allClips = itemsGroups.Select(i=>i.targetTrack)
.Where(t=>t != null).SelectMany(t => t.clips);
// update easeIn easeOut durations to apply any modifications caused by blends created or modified by clip move.
foreach (var clip in allClips)
{
clip.easeInDuration = clip.easeInDuration;
clip.easeOutDuration = clip.easeOutDuration;
}
}
public bool ValidateMove(ItemsPerTrack itemsGroup)
{
var track = itemsGroup.targetTrack;
var items = itemsGroup.items;
if (EditModeUtils.IsInfiniteTrack(track))
{
double startTime;
double stopTime;
EditModeUtils.GetInfiniteClipBoundaries(track, out startTime, out stopTime);
return items.All(item =>
!EditModeUtils.IsItemWithinRange(item, startTime, stopTime) &&
!EditModeUtils.IsRangeWithinItem(startTime, stopTime, item));
}
var siblings = ItemsUtils.GetItemsExcept(itemsGroup.targetTrack, items);
return items.All(item => EditModeMixUtils.GetPlacementValidity(item, siblings) == PlacementValidity.Valid);
}
public void DrawGUI(WindowState state, IEnumerable<MovingItems> movingItems, Color color)
{
var selectionHasAnyBlendIn = false;
var selectionHasAnyBlendOut = false;
foreach (var grabbedItems in movingItems)
{
var bounds = grabbedItems.onTrackItemsBounds;
var counter = 0;
foreach (var item in grabbedItems.items.OfType<IBlendable>())
{
if (item.hasLeftBlend)
{
EditModeGUIUtils.DrawBoundsEdge(bounds[counter], color, TrimEdge.Start);
selectionHasAnyBlendIn = true;
}
if (item.hasRightBlend)
{
EditModeGUIUtils.DrawBoundsEdge(bounds[counter], color, TrimEdge.End);
selectionHasAnyBlendOut = true;
}
counter++;
}
}
if (selectionHasAnyBlendIn && selectionHasAnyBlendOut)
{
TimelineCursors.SetCursor(TimelineCursors.CursorType.MixBoth);
}
else if (selectionHasAnyBlendIn)
{
TimelineCursors.SetCursor(TimelineCursors.CursorType.MixLeft);
}
else if (selectionHasAnyBlendOut)
{
TimelineCursors.SetCursor(TimelineCursors.CursorType.MixRight);
}
else
{
TimelineCursors.ClearCursor();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2a8aecb05814e644abbb070fbd91156
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,99 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.Timeline
{
class MoveItemModeReplace : IMoveItemMode, IMoveItemDrawer
{
public void OnTrackDetach(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public void HandleTrackSwitch(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public bool AllowTrackSwitch()
{
return true;
}
public double AdjustStartTime(WindowState state, ItemsPerTrack itemsGroup, double time)
{
return time;
}
public void OnModeClutchEnter(IEnumerable<ItemsPerTrack> itemsGroups)
{
// TODO
}
public void OnModeClutchExit(IEnumerable<ItemsPerTrack> itemsGroups)
{
// TODO
}
public void BeginMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
{
EditModeUtils.SetParentTrack(itemsGroup.items, null);
}
}
public void UpdateMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
// Nothing
}
public void FinishMove(IEnumerable<ItemsPerTrack> itemsGroups)
{
EditModeReplaceUtils.Insert(itemsGroups);
}
public bool ValidateMove(ItemsPerTrack itemsGroup)
{
return true;
}
public void DrawGUI(WindowState state, IEnumerable<MovingItems> movingItems, Color color)
{
var operationWillReplace = false;
foreach (var itemsPerTrack in movingItems)
{
var bounds = itemsPerTrack.onTrackItemsBounds;
var counter = 0;
foreach (var item in itemsPerTrack.items)
{
if (EditModeUtils.GetFirstIntersectedItem(itemsPerTrack.items, item.start) != null)
{
EditModeGUIUtils.DrawBoundsEdge(bounds[counter], color, TrimEdge.Start);
operationWillReplace = true;
}
if (EditModeUtils.GetFirstIntersectedItem(itemsPerTrack.items, item.end) != null)
{
EditModeGUIUtils.DrawBoundsEdge(bounds[counter], color, TrimEdge.End);
operationWillReplace = true;
}
counter++;
// TODO Display swallowed clips?
}
}
if (operationWillReplace)
{
TimelineCursors.SetCursor(TimelineCursors.CursorType.Replace);
}
else
{
TimelineCursors.ClearCursor();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea5e2240e8a7d9046a651557deec40b2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,271 @@
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<Object, List<ITimelineItem>> m_NextItems = new Dictionary<Object, List<ITimelineItem>>();
readonly Dictionary<Object, PrevItemInfo> m_PreviousItem = new Dictionary<Object, PrevItemInfo>();
double m_PreviousEnd;
bool m_TrackLocked;
bool m_Detached;
public void OnTrackDetach(IEnumerable<ItemsPerTrack> 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<ItemsPerTrack> 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<ItemsPerTrack> itemsGroups)
{
m_Detached = true;
foreach (var itemsGroup in itemsGroups)
EditModeUtils.SetParentTrack(itemsGroup.items, null);
}
public void OnModeClutchEnter(IEnumerable<ItemsPerTrack> itemsGroups)
{
StartDetachMode(itemsGroups);
m_TrackLocked = false;
}
public void OnModeClutchExit(IEnumerable<ItemsPerTrack> itemsGroups)
{
m_Detached = false;
m_TrackLocked = false;
}
public void BeginMove(IEnumerable<ItemsPerTrack> 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<ItemsPerTrack> 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<ITimelineItem> orderedSelection, List<ITimelineItem> orderedSiblings, IEnumerable<Type> 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<ItemsPerTrack> itemsGroups)
{
if (m_Detached)
EditModeRippleUtils.Insert(itemsGroups);
m_Detached = false;
m_TrackLocked = false;
}
public void DrawGUI(WindowState state, IEnumerable<MovingItems> 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);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eebde5009793ce948bf5d4c4435b89b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,137 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class MovingItems : ItemsPerTrack
{
TrackAsset m_ReferenceTrack;
readonly bool m_AllowTrackSwitch;
readonly Rect[] m_ItemsBoundsOnTrack;
readonly Vector2[] m_ItemsMouseOffsets;
static readonly Rect s_InvisibleBounds = new Rect(float.MaxValue, float.MaxValue, 0.0f, 0.0f);
public TrackAsset originalTrack { get; }
public override TrackAsset targetTrack
{
get
{
if (m_AllowTrackSwitch)
return m_ReferenceTrack;
return originalTrack;
}
}
public bool canDrop;
public double start
{
get { return m_ItemsGroup.start; }
set { m_ItemsGroup.start = value; }
}
public double end
{
get { return m_ItemsGroup.end; }
}
public Rect[] onTrackItemsBounds
{
get { return m_ItemsBoundsOnTrack; }
}
public MovingItems(WindowState state, TrackAsset parentTrack, ITimelineItem[] items, TrackAsset referenceTrack, Vector2 mousePosition, bool allowTrackSwitch)
: base(parentTrack, items)
{
originalTrack = parentTrack;
m_ReferenceTrack = referenceTrack;
m_AllowTrackSwitch = allowTrackSwitch;
m_ItemsBoundsOnTrack = new Rect[items.Length];
m_ItemsMouseOffsets = new Vector2[items.Length];
for (int i = 0; i < items.Length; ++i)
{
var itemGUi = items[i].gui;
if (itemGUi != null)
{
m_ItemsBoundsOnTrack[i] = itemGUi.rect;
m_ItemsMouseOffsets[i] = mousePosition - m_ItemsBoundsOnTrack[i].position;
}
}
canDrop = true;
}
public void SetReferenceTrack(TrackAsset track)
{
m_ReferenceTrack = track;
}
public bool HasAnyDetachedParents()
{
return m_ItemsGroup.items.Any(x => x.parentTrack == null);
}
public void RefreshBounds(WindowState state, Vector2 mousePosition)
{
for (int i = 0; i < m_ItemsGroup.items.Length; ++i)
{
var item = m_ItemsGroup.items[i];
var itemGUI = item.gui;
if (item.parentTrack != null)
{
m_ItemsBoundsOnTrack[i] = itemGUI.visible ? itemGUI.rect : s_InvisibleBounds;
}
else
{
if (targetTrack != null)
{
var trackGUI = (TimelineTrackGUI)TimelineWindow.instance.allTracks.FirstOrDefault(t => t.track == targetTrack);
if (trackGUI == null) return;
var trackRect = trackGUI.boundingRect;
m_ItemsBoundsOnTrack[i] = itemGUI.RectToTimeline(trackRect, state);
}
else
{
m_ItemsBoundsOnTrack[i].position = mousePosition - m_ItemsMouseOffsets[i];
}
}
}
}
public void Draw(bool isValid)
{
for (int i = 0; i < m_ItemsBoundsOnTrack.Length; ++i)
{
var rect = m_ItemsBoundsOnTrack[i];
DrawItemInternal(m_ItemsGroup.items[i], rect, isValid);
}
}
static void DrawItemInternal(ITimelineItem item, Rect rect, bool isValid)
{
var clipGUI = item.gui as TimelineClipGUI;
if (clipGUI != null)
{
if (isValid)
{
clipGUI.DrawGhostClip(rect);
}
else
{
clipGUI.DrawInvalidClip(rect);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81a142c61a4e14d46bb21b02548ad24d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dec586c160776104da4d9a4e472662bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,140 @@
using System;
using System.Text;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class EaseClip : Manipulator
{
bool m_IsCaptured;
bool m_UndoSaved;
TimelineClipHandle m_EaseClipHandler;
ManipulateEdges m_Edges;
TimelineClip m_Clip;
StringBuilder m_OverlayText = new StringBuilder("");
double m_OriginalValue;
public static readonly string EaseInClipText = L10n.Tr("Ease In Clip");
public static readonly string EaseOutClipText = L10n.Tr("Ease Out Clip");
public static readonly string EaseInText = L10n.Tr("Ease In");
public static readonly string EaseOutText = L10n.Tr("Ease Out");
public static readonly string DurationText = L10n.Tr("Duration: ");
protected override bool MouseDown(Event evt, WindowState state)
{
if (evt.modifiers != ManipulatorsUtils.actionModifier)
return false;
return MouseDownInternal(evt, state, PickerUtils.TopmostPickedItem() as TimelineClipHandle);
}
protected bool MouseDownInternal(Event evt, WindowState state, TimelineClipHandle handle)
{
if (handle == null)
return false;
if (handle.clipGUI.clip != null && !handle.clipGUI.clip.clipCaps.HasAny(ClipCaps.Blending))
return false;
m_Edges = ManipulateEdges.Right;
if (handle.trimDirection == TrimEdge.Start)
m_Edges = ManipulateEdges.Left;
if (m_Edges == ManipulateEdges.Left && handle.clipGUI.clip.hasBlendIn || m_Edges == ManipulateEdges.Right && handle.clipGUI.clip.hasBlendOut)
return false;
m_IsCaptured = true;
m_UndoSaved = false;
m_EaseClipHandler = handle;
m_Clip = handle.clipGUI.clip;
m_OriginalValue = m_Edges == ManipulateEdges.Left ? m_Clip.easeInDuration : m_Clip.easeOutDuration;
// Change cursor only when OnGUI Process (not in test)
if (GUIUtility.guiDepth > 0)
TimelineCursors.SetCursor(m_Edges == ManipulateEdges.Left ? TimelineCursors.CursorType.MixRight : TimelineCursors.CursorType.MixLeft);
state.AddCaptured(this);
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_IsCaptured)
return false;
m_IsCaptured = false;
m_UndoSaved = false;
state.captured.Clear();
// Clear cursor only when OnGUI Process (not in test)
if (GUIUtility.guiDepth > 0)
TimelineCursors.ClearCursor();
return true;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (!m_IsCaptured)
return false;
if (!m_UndoSaved)
{
var uiClip = m_EaseClipHandler.clipGUI;
string undoName = m_Edges == ManipulateEdges.Left ? EaseInClipText : EaseOutClipText;
UndoExtensions.RegisterClip(uiClip.clip, undoName);
m_UndoSaved = true;
}
double d = state.PixelDeltaToDeltaTime(evt.delta.x);
var duration = m_Clip.duration;
var easeInDurationLimit = duration - m_Clip.easeOutDuration;
var easeOutDurationLimit = duration - m_Clip.easeInDuration;
if (m_Edges == ManipulateEdges.Left)
{
m_Clip.easeInDuration = Math.Min(easeInDurationLimit, Math.Max(0, m_Clip.easeInDuration + d));
}
else if (m_Edges == ManipulateEdges.Right)
{
m_Clip.easeOutDuration = Math.Min(easeOutDurationLimit, Math.Max(0, m_Clip.easeOutDuration - d));
}
RefreshOverlayStrings(m_EaseClipHandler, state);
return true;
}
public override void Overlay(Event evt, WindowState state)
{
if (!m_IsCaptured)
return;
if (m_OverlayText.Length > 0)
{
int stringLength = m_OverlayText.Length;
var r = new Rect(evt.mousePosition.x - (stringLength / 2.0f),
m_EaseClipHandler.clipGUI.rect.yMax,
stringLength, 20);
GUI.Label(r, m_OverlayText.ToString(), TimelineWindow.styles.tinyFont);
}
}
void RefreshOverlayStrings(TimelineClipHandle handle, WindowState state)
{
m_OverlayText.Length = 0;
m_OverlayText.Append(m_Edges == ManipulateEdges.Left ? EaseInText : EaseOutText);
var easeDuration = m_Edges == ManipulateEdges.Left ? m_Clip.easeInDuration : m_Clip.easeOutDuration;
var deltaDuration = easeDuration - m_OriginalValue;
// round to frame so we don't show partial time codes due to no frame snapping
if (state.timeFormat == TimeFormat.Timecode)
{
easeDuration = TimeUtility.RoundToFrame(easeDuration, state.referenceSequence.frameRate);
deltaDuration = TimeUtility.RoundToFrame(deltaDuration, state.referenceSequence.frameRate);
}
m_OverlayText.Append(DurationText);
m_OverlayText.Append(state.timeFormat.ToTimeStringWithDelta(easeDuration, state.referenceSequence.frameRate, deltaDuration));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b7cabea05434bb9479aee1e121b0d103
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,61 @@
using UnityEditor.ShortcutManagement;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class Jog : Manipulator
{
Vector2 m_MouseDownOrigin = Vector2.zero;
[ClutchShortcut("Timeline/Jog", typeof(TimelineWindow), KeyCode.J)]
static void JogShortcut(ShortcutArguments args)
{
if (args.stage == ShortcutStage.Begin)
{
(args.context as TimelineWindow).state.isJogging = true;
}
else if (args.stage == ShortcutStage.End)
{
(args.context as TimelineWindow).state.isJogging = false;
}
}
protected override bool MouseDown(Event evt, WindowState state)
{
if (!state.isJogging)
return false;
m_MouseDownOrigin = evt.mousePosition;
state.playbackSpeed = 0.0f;
state.Play();
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!state.isJogging)
{
return false;
}
m_MouseDownOrigin = evt.mousePosition;
state.playbackSpeed = 0.0f;
state.Play();
return false;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (!state.isJogging)
return false;
var distance = evt.mousePosition - m_MouseDownOrigin;
state.playbackSpeed = distance.x * 0.002f;
state.Play();
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 949b7e126b3f27940885a6808a15458e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,36 @@
using System;
using UnityEngine;
namespace UnityEditor.Timeline
{
class MarkerHeaderTrackManipulator : Manipulator
{
protected override bool ContextClick(Event evt, WindowState state)
{
if (!IsMouseOverMarkerHeader(evt.mousePosition, state))
return false;
SelectionManager.SelectOnly(state.editSequence.asset.markerTrack);
SequencerContextMenu.ShowTrackContextMenu(evt.mousePosition);
return true;
}
protected override bool MouseDown(Event evt, WindowState state)
{
if (evt.button != 0 || !IsMouseOverMarkerHeader(evt.mousePosition, state))
return false;
SelectionManager.SelectOnly(state.editSequence.asset.markerTrack);
return true;
}
static bool IsMouseOverMarkerHeader(Vector2 mousePosition, WindowState state)
{
if (!state.showMarkerHeader)
return false;
return state.GetWindow().markerHeaderRect.Contains(mousePosition)
|| state.GetWindow().markerContentRect.Contains(mousePosition);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e74ddf4132f3401409c824bed60280ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,36 @@
using System.Linq;
using UnityEngine;
namespace UnityEditor.Timeline
{
class RectangleSelect : RectangleTool
{
protected override bool enableAutoPan { get { return false; } }
protected override bool CanStartRectangle(Event evt)
{
if (evt.button != 0 || evt.alt)
return false;
return PickerUtils.pickedElements.All(e => e is IRowGUI);
}
protected override bool OnFinish(Event evt, WindowState state, Rect rect)
{
var selectables = state.spacePartitioner.GetItemsInArea<ISelectable>(rect).ToList();
if (!selectables.Any())
return false;
if (ItemSelection.CanClearSelection(evt))
SelectionManager.Clear();
foreach (var selectable in selectables)
{
ItemSelection.HandleItemSelection(evt, selectable);
}
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edd4f4b395430604d935bcf0b14c7d42
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,175 @@
using System;
using UnityEngine;
namespace UnityEditor.Timeline
{
abstract class RectangleTool
{
readonly struct TimelinePoint
{
readonly double m_Time;
readonly float m_YPos;
readonly float m_YScrollPos;
readonly WindowState m_State;
readonly TimelineTreeViewGUI m_TreeViewGUI;
public TimelinePoint(WindowState state, Vector2 mousePosition)
{
m_State = state;
m_TreeViewGUI = state.GetWindow().treeView;
m_Time = m_State.PixelToTime(mousePosition.x);
m_YPos = mousePosition.y;
m_YScrollPos = m_TreeViewGUI.scrollPosition.y;
}
public Vector2 ToPixel()
{
return new Vector2(m_State.TimeToPixel(m_Time), m_YPos - (m_TreeViewGUI.scrollPosition.y - m_YScrollPos));
}
}
const float k_HeaderSplitterOverlap = WindowConstants.headerSplitterWidth / 2;
TimeAreaAutoPanner m_TimeAreaAutoPanner;
TimelinePoint m_StartPoint;
Vector2 m_EndPixel = Vector2.zero;
Rect m_ActiveRect;
protected abstract bool enableAutoPan { get; }
protected abstract bool CanStartRectangle(Event evt);
protected abstract bool OnFinish(Event evt, WindowState state, Rect rect);
int m_Id;
public void OnGUI(WindowState state, EventType rawType, Vector2 mousePosition)
{
if (m_Id == 0)
m_Id = GUIUtility.GetPermanentControlID();
if (state == null || state.GetWindow().treeView == null)
return;
var evt = Event.current;
if (rawType == EventType.MouseDown || evt.type == EventType.MouseDown)
{
if (state.IsCurrentEditingASequencerTextField())
return;
m_ActiveRect = TimelineWindow.instance.sequenceContentRect;
//remove the track header splitter overlap
m_ActiveRect.x += k_HeaderSplitterOverlap;
m_ActiveRect.width -= k_HeaderSplitterOverlap;
if (!m_ActiveRect.Contains(mousePosition))
return;
if (!CanStartRectangle(evt))
return;
if (enableAutoPan)
m_TimeAreaAutoPanner = new TimeAreaAutoPanner(state);
m_StartPoint = new TimelinePoint(state, mousePosition);
m_EndPixel = mousePosition;
GUIUtility.hotControl = m_Id; //HACK: Because the treeView eats all the events, steal the hotControl if necessary...
evt.Use();
return;
}
switch (evt.GetTypeForControl(m_Id))
{
case EventType.KeyDown:
{
if (GUIUtility.hotControl == m_Id)
{
if (evt.keyCode == KeyCode.Escape)
{
m_TimeAreaAutoPanner = null;
GUIUtility.hotControl = 0;
evt.Use();
}
}
return;
}
case EventType.MouseDrag:
{
if (GUIUtility.hotControl != m_Id)
return;
m_EndPixel = mousePosition;
evt.Use();
return;
}
case EventType.MouseUp:
{
if (GUIUtility.hotControl != m_Id)
return;
m_TimeAreaAutoPanner = null;
var rect = CurrentRectangle();
if (IsValidRect(rect))
OnFinish(evt, state, rect);
GUIUtility.hotControl = 0;
evt.Use();
return;
}
}
if (GUIUtility.hotControl == m_Id)
{
if (evt.type == EventType.Repaint)
{
var r = CurrentRectangle();
if (IsValidRect(r))
{
using (new GUIViewportScope(m_ActiveRect))
{
DrawRectangle(r);
}
}
}
if (m_TimeAreaAutoPanner != null)
m_TimeAreaAutoPanner.OnGUI(evt);
}
}
static void DrawRectangle(Rect rect)
{
EditorStyles.selectionRect.Draw(rect, GUIContent.none, false, false, false, false);
}
static bool IsValidRect(Rect rect)
{
return rect.width >= 1.0f && rect.height >= 1.0f;
}
Rect CurrentRectangle()
{
var startPixel = m_StartPoint.ToPixel();
return Rect.MinMaxRect(
Math.Min(startPixel.x, m_EndPixel.x),
Math.Min(startPixel.y, m_EndPixel.y),
Math.Max(startPixel.x, m_EndPixel.x),
Math.Max(startPixel.y, m_EndPixel.y));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24a7ce8b48db53747a4e8abbda77eac4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,23 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
class RectangleZoom : RectangleTool
{
protected override bool enableAutoPan { get { return true; } }
protected override bool CanStartRectangle(Event evt)
{
return evt.button == 1 && evt.modifiers == (EventModifiers.Alt | EventModifiers.Shift);
}
protected override bool OnFinish(Event evt, WindowState state, Rect rect)
{
var x = state.PixelToTime(rect.xMin);
var y = state.PixelToTime(rect.xMax);
state.SetTimeAreaShownRange(x, y);
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5aa8f57287fc17149bcd798be813180b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,297 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class ClearSelection : Manipulator
{
protected override bool MouseDown(Event evt, WindowState state)
{
// If we hit this point this means no one used the mouse down events. We can safely clear the selection if needed
if (evt.button != 0)
return false;
var window = state.GetWindow();
if (!window.sequenceRect.Contains(evt.mousePosition))
return false;
if (ItemSelection.CanClearSelection(evt))
{
SelectionManager.Clear();
return true;
}
return false;
}
}
static class ItemSelection
{
public static bool CanClearSelection(Event evt)
{
return !evt.control && !evt.command && !evt.shift;
}
public static void RangeSelectItems(ITimelineItem lastItemToSelect)
{
var selectSorted = SelectionManager.SelectedItems().ToList();
var firstSelect = selectSorted.FirstOrDefault();
if (firstSelect == null)
{
SelectionManager.Add(lastItemToSelect);
return;
}
var allTracks = TimelineEditor.inspectedAsset.flattenedTracks;
var allItems = allTracks.SelectMany(ItemsUtils.GetItems).ToList();
TimelineHelpers.RangeSelect(allItems, selectSorted, lastItemToSelect, SelectionManager.Add, SelectionManager.Remove);
}
public static ISelectable HandleSingleSelection(Event evt)
{
var item = PickerUtils.TopmostPickedItemOfType<ISelectable>(i => i.CanSelect(evt));
if (item != null)
{
var selected = item.IsSelected();
if (!selected && CanClearSelection(evt))
SelectionManager.Clear();
if (evt.modifiers == EventModifiers.Shift)
{
if (!selected)
RangeSelectItems((item as TimelineItemGUI)?.item);
}
else
{
HandleItemSelection(evt, item);
}
}
return item;
}
public static void HandleItemSelection(Event evt, ISelectable item)
{
if (evt.modifiers == ManipulatorsUtils.actionModifier)
{
if (item.IsSelected())
item.Deselect();
else
item.Select();
}
else
{
if (!item.IsSelected())
item.Select();
}
}
}
class SelectAndMoveItem : Manipulator
{
bool m_Dragged;
SnapEngine m_SnapEngine;
TimeAreaAutoPanner m_TimeAreaAutoPanner;
Vector2 m_MouseDownPosition;
bool m_HorizontalMovementDone;
bool m_VerticalMovementDone;
MoveItemHandler m_MoveItemHandler;
bool m_CycleMarkersPending;
protected override bool MouseDown(Event evt, WindowState state)
{
if (evt.alt || evt.button != 0)
return false;
m_Dragged = false;
// Cycling markers and selection are mutually exclusive operations
if (!HandleMarkerCycle() && !HandleSingleSelection(evt))
return false;
m_MouseDownPosition = evt.mousePosition;
m_VerticalMovementDone = false;
m_HorizontalMovementDone = false;
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_Dragged)
{
var item = PickerUtils.TopmostPickedItem() as ISelectable;
if (item == null)
return false;
if (!item.IsSelected())
return false;
// Re-selecting an item part of a multi-selection should only keep this item selected.
if (SelectionManager.Count() > 1 && ItemSelection.CanClearSelection(evt))
{
SelectionManager.Clear();
item.Select();
return true;
}
if (m_CycleMarkersPending)
{
m_CycleMarkersPending = false;
TimelineMarkerClusterGUI.CycleMarkers();
return true;
}
return false;
}
m_TimeAreaAutoPanner = null;
DropItems();
m_SnapEngine = null;
m_MoveItemHandler = null;
state.Evaluate();
state.RemoveCaptured(this);
m_Dragged = false;
TimelineCursors.ClearCursor();
return true;
}
protected override bool DoubleClick(Event evt, WindowState state)
{
return MouseDown(evt, state) && MouseUp(evt, state);
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (state.editSequence.isReadOnly)
return false;
// case 1099285 - ctrl-click can cause no clips to be selected
var selectedItemsGUI = SelectionManager.SelectedItems();
if (!selectedItemsGUI.Any())
{
m_Dragged = false;
return false;
}
const float hDeadZone = 5.0f;
const float vDeadZone = 5.0f;
bool vDone = m_VerticalMovementDone || Math.Abs(evt.mousePosition.y - m_MouseDownPosition.y) > vDeadZone;
bool hDone = m_HorizontalMovementDone || Math.Abs(evt.mousePosition.x - m_MouseDownPosition.x) > hDeadZone;
m_CycleMarkersPending = false;
if (!m_Dragged)
{
var canStartMove = vDone || hDone;
if (canStartMove)
{
state.AddCaptured(this);
m_Dragged = true;
var referenceTrack = GetTrackDropTargetAt(state, m_MouseDownPosition);
foreach (var item in selectedItemsGUI)
item.gui.StartDrag();
m_MoveItemHandler = new MoveItemHandler(state);
m_MoveItemHandler.Grab(selectedItemsGUI, referenceTrack, m_MouseDownPosition);
m_SnapEngine = new SnapEngine(m_MoveItemHandler, m_MoveItemHandler, ManipulateEdges.Both,
state, m_MouseDownPosition);
m_TimeAreaAutoPanner = new TimeAreaAutoPanner(state);
}
}
if (!m_VerticalMovementDone)
{
m_VerticalMovementDone = vDone;
if (m_VerticalMovementDone)
m_MoveItemHandler.OnTrackDetach();
}
if (!m_HorizontalMovementDone)
{
m_HorizontalMovementDone = hDone;
}
if (m_Dragged)
{
if (m_HorizontalMovementDone)
m_SnapEngine.Snap(evt.mousePosition, evt.modifiers);
if (m_VerticalMovementDone)
{
var track = GetTrackDropTargetAt(state, evt.mousePosition);
m_MoveItemHandler.UpdateTrackTarget(track);
}
state.Evaluate();
}
return true;
}
public override void Overlay(Event evt, WindowState state)
{
if (!m_Dragged)
return;
if (m_TimeAreaAutoPanner != null)
m_TimeAreaAutoPanner.OnGUI(evt);
m_MoveItemHandler.OnGUI(evt);
if (!m_MoveItemHandler.allowTrackSwitch || m_MoveItemHandler.targetTrack != null)
{
TimeIndicator.Draw(state, m_MoveItemHandler.start, m_MoveItemHandler.end);
m_SnapEngine.OnGUI();
}
}
bool HandleMarkerCycle()
{
m_CycleMarkersPending = TimelineMarkerClusterGUI.CanCycleMarkers();
return m_CycleMarkersPending;
}
static bool HandleSingleSelection(Event evt)
{
return ItemSelection.HandleSingleSelection(evt) != null;
}
void DropItems()
{
// Order matters here: m_MoveItemHandler.movingItems is destroyed during call to Drop()
foreach (var movingItem in m_MoveItemHandler.movingItems)
{
foreach (var item in movingItem.items)
item.gui.StopDrag();
}
m_MoveItemHandler.Drop();
}
static TrackAsset GetTrackDropTargetAt(WindowState state, Vector2 point)
{
var track = state.spacePartitioner.GetItemsAtPosition<IRowGUI>(point).FirstOrDefault();
return track != null ? track.asset : null;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4f988528bbbb0846a4cb50efb4587a5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,20 @@
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrackZoom : Manipulator
{
// only handles 'vertical' zoom. horizontal is handled in timelineGUI
protected override bool MouseWheel(Event evt, WindowState state)
{
if (EditorGUI.actionKey)
{
state.trackScale = Mathf.Min(Mathf.Max(state.trackScale + (evt.delta.y * 0.1f), 1.0f), 100.0f);
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e7c80eefe2def5459e0b486b3ab96e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrimClip : Manipulator
{
private readonly string kDurationText = L10n.Tr("Duration:");
private readonly string kSpeedText = L10n.Tr("Speed:");
class TrimClipAttractionHandler : IAttractionHandler
{
public void OnAttractedEdge(IAttractable attractable, ManipulateEdges manipulateEdges, AttractedEdge edge, double time)
{
var clipGUI = attractable as TimelineClipGUI;
if (clipGUI == null)
return;
var clipItem = ItemsUtils.ToItem(clipGUI.clip);
if (manipulateEdges == ManipulateEdges.Right)
{
bool affectTimeScale = IsAffectingTimeScale(clipGUI.clip);
EditMode.TrimEnd(clipItem, time, affectTimeScale);
}
else if (manipulateEdges == ManipulateEdges.Left)
{
bool affectTimeScale = IsAffectingTimeScale(clipGUI.clip);
EditMode.TrimStart(clipItem, time, affectTimeScale);
}
}
private bool IsAffectingTimeScale(TimelineClip clip)
{
bool autoScale = (clip.clipCaps & ClipCaps.AutoScale) == ClipCaps.AutoScale;
// TODO Do not use Event.current from here.
bool affectTimeScale = (autoScale && (Event.current.modifiers != EventModifiers.Shift))
|| (!autoScale && (Event.current.modifiers == EventModifiers.Shift));
return affectTimeScale;
}
}
bool m_IsCaptured;
TimelineClipHandle m_TrimClipHandler;
double m_OriginalDuration;
double m_OriginalTimeScale;
double m_OriginalEaseInDuration;
double m_OriginalEaseOutDuration;
bool m_UndoSaved;
SnapEngine m_SnapEngine;
readonly List<string> m_OverlayStrings = new List<string>();
static readonly double kEpsilon = 0.0000001;
protected override bool MouseDown(Event evt, WindowState state)
{
var handle = PickerUtils.TopmostPickedItem() as TimelineClipHandle;
if (handle == null)
return false;
if (handle.clipGUI.clip.GetParentTrack() != null && handle.clipGUI.clip.GetParentTrack().lockedInHierarchy)
return false;
m_TrimClipHandler = handle;
m_IsCaptured = true;
state.AddCaptured(this);
m_UndoSaved = false;
var clip = m_TrimClipHandler.clipGUI.clip;
m_OriginalDuration = clip.duration;
m_OriginalTimeScale = clip.timeScale;
m_OriginalEaseInDuration = clip.easeInDuration;
m_OriginalEaseOutDuration = clip.easeOutDuration;
RefreshOverlayStrings(m_TrimClipHandler, state);
// in ripple trim, the right edge moves and needs to snap
var edges = ManipulateEdges.Right;
if (EditMode.editType != EditMode.EditType.Ripple && m_TrimClipHandler.trimDirection == TrimEdge.Start)
edges = ManipulateEdges.Left;
m_SnapEngine = new SnapEngine(m_TrimClipHandler.clipGUI, new TrimClipAttractionHandler(), edges, state,
evt.mousePosition);
EditMode.BeginTrim(ItemsUtils.ToItem(clip), m_TrimClipHandler.trimDirection);
return true;
}
protected override bool MouseUp(Event evt, WindowState state)
{
if (!m_IsCaptured)
return false;
m_IsCaptured = false;
m_TrimClipHandler = null;
m_UndoSaved = false;
m_SnapEngine = null;
EditMode.FinishTrim();
state.captured.Clear();
return true;
}
protected override bool MouseDrag(Event evt, WindowState state)
{
if (state.editSequence.isReadOnly)
return false;
if (!m_IsCaptured)
return false;
var uiClip = m_TrimClipHandler.clipGUI;
if (!m_UndoSaved)
{
UndoExtensions.RegisterClip(uiClip.clip, L10n.Tr("Trim Clip"));
if (TimelineUtility.IsRecordableAnimationClip(uiClip.clip))
{
TimelineUndo.PushUndo(uiClip.clip.animationClip, L10n.Tr("Trim Clip"));
}
m_UndoSaved = true;
}
//Reset to original ease values. The trim operation will calculate the proper blend values.
uiClip.clip.easeInDuration = m_OriginalEaseInDuration;
uiClip.clip.easeOutDuration = m_OriginalEaseOutDuration;
if (m_SnapEngine != null)
m_SnapEngine.Snap(evt.mousePosition, evt.modifiers);
RefreshOverlayStrings(m_TrimClipHandler, state);
if (Selection.activeObject != null)
EditorUtility.SetDirty(Selection.activeObject);
// updates the duration of the graph without rebuilding
state.UpdateRootPlayableDuration(state.editSequence.duration);
return true;
}
public override void Overlay(Event evt, WindowState state)
{
if (!m_IsCaptured)
return;
EditMode.DrawTrimGUI(state, m_TrimClipHandler.clipGUI, m_TrimClipHandler.trimDirection);
bool trimStart = m_TrimClipHandler.trimDirection == TrimEdge.Start;
TimeIndicator.Draw(state, trimStart ? m_TrimClipHandler.clipGUI.start : m_TrimClipHandler.clipGUI.end);
if (m_SnapEngine != null)
m_SnapEngine.OnGUI(trimStart, !trimStart);
if (m_OverlayStrings.Count > 0)
{
const float padding = 4.0f;
var labelStyle = TimelineWindow.styles.tinyFont;
var longestLine = labelStyle.CalcSize(
new GUIContent(m_OverlayStrings.Aggregate("", (max, cur) => max.Length > cur.Length ? max : cur)));
var stringLength = longestLine.x + padding;
var lineHeight = longestLine.y + padding;
var r = new Rect(evt.mousePosition.x - (stringLength / 2.0f),
m_TrimClipHandler.clipGUI.rect.yMax,
stringLength, lineHeight);
foreach (var s in m_OverlayStrings)
{
GUI.Label(r, s, labelStyle);
r.y += lineHeight;
}
}
}
void RefreshOverlayStrings(TimelineClipHandle handle, WindowState state)
{
m_OverlayStrings.Clear();
var differenceDuration = handle.clipGUI.clip.duration - m_OriginalDuration;
m_OverlayStrings.Add($"{kDurationText} {state.timeFormat.ToTimeStringWithDelta(handle.clipGUI.clip.duration, state.referenceSequence.frameRate, differenceDuration)}");
var differenceSpeed = m_OriginalTimeScale - handle.clipGUI.clip.timeScale;
if (Math.Abs(differenceSpeed) > kEpsilon)
{
var sign = differenceSpeed > 0 ? "+" : "";
var timeScale = handle.clipGUI.clip.timeScale.ToString("f2");
var deltaSpeed = differenceSpeed.ToString("p2");
m_OverlayStrings.Add($"{kSpeedText} {timeScale} ({sign}{deltaSpeed}) ");
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 511aa760b8728a940a41c29837945292
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,72 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
class TimeAreaAutoPanner
{
readonly WindowState m_State;
readonly TimelineWindow m_Window;
readonly Rect m_ViewRect;
const float k_PixelDistanceToMaxSpeed = 100.0f;
const float k_MaxPanSpeed = 30.0f;
public TimeAreaAutoPanner(WindowState state)
{
m_State = state;
m_Window = m_State.GetWindow();
var shownRange = m_State.timeAreaShownRange;
var trackViewBounds = m_Window.sequenceRect;
m_ViewRect = Rect.MinMaxRect(m_State.TimeToPixel(shownRange.x), trackViewBounds.yMin,
m_State.TimeToPixel(shownRange.y), trackViewBounds.yMax);
}
public void OnGUI(Event evt)
{
if (evt.type != EventType.Layout)
return;
var hFactor = 0.0f;
var vFactor = 0.0f;
bool horizontalPan = GetPanFactor(evt.mousePosition.x, m_ViewRect.xMin, m_ViewRect.xMax, out hFactor);
bool verticalPan = GetPanFactor(evt.mousePosition.y, m_ViewRect.yMin, m_ViewRect.yMax, out vFactor);
if (horizontalPan)
{
var translation = m_State.timeAreaTranslation;
translation.x += hFactor * k_MaxPanSpeed;
m_State.SetTimeAreaTransform(translation, m_State.timeAreaScale);
}
if (verticalPan)
{
var translation = m_Window.treeView.scrollPosition;
translation.y -= vFactor * k_MaxPanSpeed;
m_Window.treeView.scrollPosition = translation;
}
}
static bool GetPanFactor(float v, float min, float max, out float factor)
{
factor = 0.0f;
if (v < min)
{
factor = Mathf.Clamp01((min - v) / k_PixelDistanceToMaxSpeed);
return true;
}
if (v > max)
{
factor = -Mathf.Clamp01((v - max) / k_PixelDistanceToMaxSpeed);
return true;
}
return false;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02e1fe0a338b35545a5fed1345848332
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,47 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
static class TimeIndicator
{
static readonly Tooltip s_Tooltip = new Tooltip(DirectorStyles.Instance.displayBackground, DirectorStyles.Instance.tinyFont);
public static void Draw(WindowState state, double time)
{
var bounds = state.timeAreaRect;
bounds.xMin = Mathf.Max(bounds.xMin, state.TimeToTimeAreaPixel(time));
using (new GUIViewportScope(state.timeAreaRect))
{
s_Tooltip.text = TimeReferenceUtility.ToTimeString(time);
var tooltipBounds = s_Tooltip.bounds;
tooltipBounds.xMin = bounds.xMin - (tooltipBounds.width / 2.0f);
tooltipBounds.y = bounds.y;
s_Tooltip.bounds = tooltipBounds;
if (time >= 0)
s_Tooltip.Draw();
}
if (time >= 0)
{
Graphics.DrawLineAtTime(state, time, Color.black, true);
}
}
public static void Draw(WindowState state, double start, double end)
{
var bounds = state.timeAreaRect;
bounds.xMin = Mathf.Max(bounds.xMin, state.TimeToTimeAreaPixel(start));
bounds.xMax = Mathf.Min(bounds.xMax, state.TimeToTimeAreaPixel(end));
var color = DirectorStyles.Instance.selectedStyle.focused.textColor;
color.a = 0.12f;
EditorGUI.DrawRect(bounds, color);
Draw(state, start);
Draw(state, end);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d446e57147ca9b4183edfbbfa9bf206
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineClipGroup
{
readonly TimelineClip[] m_Clips;
readonly TimelineClip m_LeftMostClip;
readonly TimelineClip m_RightMostClip;
public TimelineClip[] clips
{
get { return m_Clips; }
}
public double start
{
get { return m_LeftMostClip.start; }
set
{
var offset = value - m_LeftMostClip.start;
foreach (var clip in m_Clips)
clip.start += offset;
}
}
public double end
{
get { return m_RightMostClip.end; }
}
public TimelineClipGroup(IEnumerable<TimelineClip> clips)
{
Debug.Assert(clips != null && clips.Any());
m_Clips = clips.ToArray();
m_LeftMostClip = null;
m_RightMostClip = null;
foreach (var clip in m_Clips)
{
if (m_LeftMostClip == null || clip.start < m_LeftMostClip.start)
m_LeftMostClip = clip;
if (m_RightMostClip == null || clip.end > m_RightMostClip.end)
m_RightMostClip = clip;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8c3bda3a988b5f4c910a5c3f722d0be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f6d64d8648793944dbadfd71f0f4b0a1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,23 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
enum TrimEdge
{
Start,
End
}
interface ITrimItemMode
{
void OnBeforeTrim(ITrimmable item, TrimEdge trimDirection);
void TrimStart(ITrimmable item, double time, bool affectTimeScale);
void TrimEnd(ITrimmable item, double time, bool affectTimeScale);
}
interface ITrimItemDrawer
{
void DrawGUI(WindowState state, Rect bounds, Color color, TrimEdge edge);
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b4a5ce78107bc38409a3bb5e8b3289ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,102 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrimItemModeMix : ITrimItemMode, ITrimItemDrawer
{
ITrimmable m_Item;
double m_Min;
double m_Max;
public void OnBeforeTrim(ITrimmable item, TrimEdge trimDirection)
{
m_Item = item;
var sortedItems = ItemsUtils.GetItemsExcept(item.parentTrack, new[] {item})
.OfType<ITrimmable>()
.OrderBy(c => c.start);
var itemStart = (DiscreteTime)item.start;
var itemEnd = (DiscreteTime)item.end;
var overlapped = sortedItems.LastOrDefault(c => (DiscreteTime)c.start == itemStart && (DiscreteTime)c.end == itemEnd);
ITrimmable nextItem;
ITrimmable prevItem;
m_Min = 0.0;
m_Max = double.PositiveInfinity;
var blendableItem = item as IBlendable;
if (blendableItem != null && blendableItem.supportsBlending)
{
if (trimDirection == TrimEdge.Start)
{
nextItem = sortedItems.FirstOrDefault(c => (DiscreteTime)c.start >= itemStart && (DiscreteTime)c.end > itemEnd);
prevItem = sortedItems.LastOrDefault(c => (DiscreteTime)c.start <= itemStart && (DiscreteTime)c.end < itemEnd);
if (prevItem != null)
m_Min = prevItem.start + EditModeUtils.BlendDuration(prevItem, TrimEdge.Start);
if (nextItem != null)
m_Max = nextItem.start;
}
else
{
nextItem = sortedItems.FirstOrDefault(c => c != overlapped && (DiscreteTime)c.start >= itemStart && (DiscreteTime)c.end >= itemEnd);
prevItem = sortedItems.LastOrDefault(c => c != overlapped && (DiscreteTime)c.start <= itemStart && (DiscreteTime)c.end <= itemEnd);
if (prevItem != null)
m_Min = prevItem.end;
if (nextItem != null)
m_Max = nextItem.end - EditModeUtils.BlendDuration(nextItem, TrimEdge.End);
}
}
else
{
nextItem = sortedItems.FirstOrDefault(c => (DiscreteTime)c.start > itemStart);
prevItem = sortedItems.LastOrDefault(c => (DiscreteTime)c.start < itemStart);
if (prevItem != null)
m_Min = prevItem.end;
if (nextItem != null)
m_Max = nextItem.start;
}
}
public void TrimStart(ITrimmable item, double time, bool affectTimeScale)
{
time = Math.Min(Math.Max(time, m_Min), m_Max);
item.SetStart(time, affectTimeScale);
}
public void TrimEnd(ITrimmable item, double time, bool affectTimeScale)
{
time = Math.Min(Math.Max(time, m_Min), m_Max);
item.SetEnd(time, affectTimeScale);
}
public void DrawGUI(WindowState state, Rect bounds, Color color, TrimEdge edge)
{
if (EditModeUtils.HasBlends(m_Item, edge))
{
EditModeGUIUtils.DrawBoundsEdge(bounds, color, edge);
var cursorType = (edge == TrimEdge.End)
? TimelineCursors.CursorType.MixRight
: TimelineCursors.CursorType.MixLeft;
TimelineCursors.SetCursor(cursorType);
}
else
{
TimelineCursors.ClearCursor();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ddd9f7d3cce6724696a33752ab2f5a4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,139 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrimItemModeReplace : ITrimItemMode, ITrimItemDrawer
{
ITrimmable m_Item;
ITrimmable m_ItemToBeReplaced;
double m_ClipOriginalEdgeValue;
bool m_TrimReplace;
double m_Min;
double m_Max;
public void OnBeforeTrim(ITrimmable item, TrimEdge trimDirection)
{
m_Item = item;
var sortedClips = ItemsUtils.GetItemsExcept(item.parentTrack, new[] { item })
.OfType<ITrimmable>()
.OrderBy(c => c.start);
var clipStart = (DiscreteTime)item.start;
var clipEnd = (DiscreteTime)item.end;
var overlapped = sortedClips.LastOrDefault(c => (DiscreteTime)c.start == clipStart && (DiscreteTime)c.end == clipEnd);
ITrimmable nextItem;
ITrimmable prevItem;
m_Min = 0.0;
m_Max = double.PositiveInfinity;
if (trimDirection == TrimEdge.Start)
{
nextItem = sortedClips.FirstOrDefault(c => (DiscreteTime)c.start >= clipStart && (DiscreteTime)c.end > clipEnd);
prevItem = sortedClips.LastOrDefault(c => (DiscreteTime)c.start <= clipStart && (DiscreteTime)c.end < clipEnd);
if (prevItem != null)
m_Min = prevItem.start + EditModeUtils.BlendDuration(prevItem, TrimEdge.Start) + TimelineClip.kMinDuration;
if (nextItem != null)
m_Max = nextItem.start;
m_ItemToBeReplaced = prevItem;
if (m_ItemToBeReplaced != null)
m_ClipOriginalEdgeValue = m_ItemToBeReplaced.end;
}
else
{
nextItem = sortedClips.FirstOrDefault(c => c != overlapped && (DiscreteTime)c.start >= clipStart && (DiscreteTime)c.end >= clipEnd);
prevItem = sortedClips.LastOrDefault(c => c != overlapped && (DiscreteTime)c.start <= clipStart && (DiscreteTime)c.end <= clipEnd);
if (prevItem != null)
m_Min = prevItem.end;
if (nextItem != null)
m_Max = nextItem.end - EditModeUtils.BlendDuration(nextItem, TrimEdge.End) - TimelineClip.kMinDuration;
m_ItemToBeReplaced = nextItem;
if (m_ItemToBeReplaced != null)
m_ClipOriginalEdgeValue = m_ItemToBeReplaced.start;
}
m_TrimReplace = false;
}
public void TrimStart(ITrimmable item, double time, bool affectTimeScale)
{
time = Math.Min(Math.Max(time, m_Min), m_Max);
if (m_ItemToBeReplaced != null)
{
if (!m_TrimReplace)
m_TrimReplace = item.start >= m_ItemToBeReplaced.end;
}
time = Math.Max(time, 0.0);
item.SetStart(time, affectTimeScale);
if (m_ItemToBeReplaced != null && m_TrimReplace)
{
var prevEnd = Math.Min(item.start, m_ClipOriginalEdgeValue);
m_ItemToBeReplaced.SetEnd(prevEnd, false);
}
}
public void TrimEnd(ITrimmable item, double time, bool affectTimeScale)
{
time = Math.Min(Math.Max(time, m_Min), m_Max);
if (m_ItemToBeReplaced != null)
{
if (!m_TrimReplace)
m_TrimReplace = item.end <= m_ItemToBeReplaced.start;
}
item.SetEnd(time, affectTimeScale);
if (m_ItemToBeReplaced != null && m_TrimReplace)
{
var nextStart = Math.Max(item.end, m_ClipOriginalEdgeValue);
m_ItemToBeReplaced.SetStart(nextStart, false);
}
}
public void DrawGUI(WindowState state, Rect bounds, Color color, TrimEdge edge)
{
bool shouldDraw = m_ItemToBeReplaced != null && (edge == TrimEdge.End && m_Item.end > m_ClipOriginalEdgeValue) ||
(edge == TrimEdge.Start && m_Item.start < m_ClipOriginalEdgeValue);
if (shouldDraw)
{
var cursorType = TimelineCursors.CursorType.Replace;
if (EditModeUtils.HasBlends(m_Item, edge))
{
color = DirectorStyles.kMixToolColor;
cursorType = (edge == TrimEdge.End)
? TimelineCursors.CursorType.MixRight
: TimelineCursors.CursorType.MixLeft;
}
EditModeGUIUtils.DrawBoundsEdge(bounds, color, edge);
TimelineCursors.SetCursor(cursorType);
}
else
{
TimelineCursors.ClearCursor();
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 769f6f5dd7c8f2d4c9ab1caba0bd2628
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,97 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TrimItemModeRipple : ITrimItemMode, ITrimItemDrawer
{
double m_OriginalClipStart;
double m_OriginalClipEnd;
ITrimmable[] m_NextItems;
double m_BlendDuration;
double m_TrimStartShift;
public void OnBeforeTrim(ITrimmable item, TrimEdge trimDirection)
{
m_OriginalClipStart = item.start;
m_OriginalClipEnd = item.end;
m_TrimStartShift = 0.0;
var sortedClips = ItemsUtils.GetItemsExcept(item.parentTrack, new[] { item })
.OfType<ITrimmable>()
.OrderBy(c => c.start);
var clipStart = (DiscreteTime)item.start;
var clipEnd = (DiscreteTime)item.end;
m_NextItems = sortedClips.Where(c => (DiscreteTime)c.start >= clipStart && (DiscreteTime)c.end >= clipEnd).ToArray();
var overlapped = sortedClips.LastOrDefault(c => (DiscreteTime)c.start == clipStart && (DiscreteTime)c.end == clipEnd);
if (overlapped != null)
{
m_BlendDuration = overlapped.end - overlapped.start;
}
else
{
m_BlendDuration = 0.0;
var prevClip = sortedClips.LastOrDefault(c => (DiscreteTime)c.start <= clipStart && (DiscreteTime)c.end <= clipEnd);
if (prevClip != null)
m_BlendDuration += Math.Max(prevClip.end - item.start, 0.0);
var nextClip = sortedClips.FirstOrDefault(c => (DiscreteTime)c.start >= clipStart && (DiscreteTime)c.end >= clipEnd);
if (nextClip != null)
m_BlendDuration += Math.Max(item.end - nextClip.start, 0.0);
}
}
public void TrimStart(ITrimmable item, double time, bool affectTimeScale)
{
var prevEnd = item.end;
// HACK If time is negative, make sure we shift the SetStart operation to a positive space.
if (time < 0.0)
m_TrimStartShift = Math.Max(-time, m_TrimStartShift);
item.start = m_OriginalClipEnd - item.duration + m_TrimStartShift;
time += m_TrimStartShift;
if (m_BlendDuration > 0.0)
time = Math.Min(time, item.end - m_BlendDuration);
item.SetStart(time, affectTimeScale);
item.start = m_OriginalClipStart;
var offset = item.end - prevEnd;
foreach (var timelineClip in m_NextItems)
timelineClip.start += offset;
}
public void TrimEnd(ITrimmable item, double time, bool affectTimeScale)
{
var prevEnd = item.end;
if (m_BlendDuration > 0.0)
time = Math.Max(time, item.start + m_BlendDuration);
item.SetEnd(time, affectTimeScale);
var offset = item.end - prevEnd;
foreach (var timelineClip in m_NextItems)
timelineClip.start += offset;
}
public void DrawGUI(WindowState state, Rect bounds, Color color, TrimEdge edge)
{
EditModeGUIUtils.DrawBoundsEdge(bounds, color, edge);
TimelineCursors.SetCursor(TimelineCursors.CursorType.Ripple);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20c8bb6b47a526c4c96ca73314fe2856
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 67e9c6cf60c57a54f9f4db1bc33fd2e3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,27 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
static class EditModeGUIUtils
{
public static void DrawBoundsEdge(Rect bounds, Color color, TrimEdge edge, float width = 4.0f)
{
var r = bounds;
r.yMin += 2.0f;
r.yMax -= 2.0f;
r.width = width;
r.x = edge == TrimEdge.End ? bounds.xMax : bounds.xMin - width;
EditorGUI.DrawRect(r, color);
}
public static void DrawOverlayRect(Rect bounds, Color overlayColor)
{
var c = overlayColor;
c.a = 0.2f;
EditorGUI.DrawRect(bounds, c);
EditorGUI.DrawOutline(bounds, 1.0f, new Color(1.0f, 1.0f, 1.0f, 0.5f));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8938e753b3f47374889d5cf3265b563c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,138 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class EditModeMixUtils
{
static readonly List<PlacementValidity> k_UnrecoverablePlacements = new List<PlacementValidity>
{
PlacementValidity.InvalidIsWithin,
PlacementValidity.InvalidStartsInBlend,
PlacementValidity.InvalidContainsBlend,
PlacementValidity.InvalidOverlapWithNonBlendableClip
};
public static bool CanInsert(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
{
List<ITimelineItem> siblings = ItemsUtils.GetItemsExcept(itemsGroup.targetTrack, itemsGroup.items).ToList();
foreach (var item in itemsGroup.items)
{
var placementValidity = GetPlacementValidity(item, siblings);
if (k_UnrecoverablePlacements.Contains(placementValidity))
{
return false;
}
}
}
return true;
}
//Corrects clips durations to fit at insertion point, if needed
public static void PrepareItemsForInsertion(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
{
var siblings = ItemsUtils.GetItemsExcept(itemsGroup.targetTrack, itemsGroup.items);
foreach (var item in itemsGroup.items.OfType<ITrimmable>())
{
var eatenItems = siblings.Where(c => EditModeUtils.IsItemWithinRange(c, item.start, item.end)).ToList();
var intersectedItem = EditModeUtils.GetFirstIntersectedItem(siblings, item.end);
if (intersectedItem != null)
eatenItems.Add(intersectedItem);
var blendableItems = eatenItems.OfType<IBlendable>();
if (blendableItems.Any())
{
var minTime = blendableItems.Min(c => c.end - c.rightBlendDuration);
if (item.end > minTime)
item.SetEnd(minTime, false);
}
}
}
}
public static PlacementValidity GetPlacementValidity(ITimelineItem item, IEnumerable<ITimelineItem> otherItems)
{
if (item.duration <= 0.0)
return PlacementValidity.Valid; //items without any duration can always be placed
var sortedItems = otherItems.Where(i => i.duration > 0.0).OrderBy(c => c.start);
var candidates = new List<ITimelineItem>();
foreach (var sortedItem in sortedItems)
{
if ((DiscreteTime)sortedItem.start >= (DiscreteTime)item.end)
{
// No need to process further
break;
}
if ((DiscreteTime)sortedItem.end <= (DiscreteTime)item.start)
{
// Skip
continue;
}
candidates.Add(sortedItem);
}
var discreteStart = (DiscreteTime)item.start;
var discreteEnd = (DiscreteTime)item.end;
// Note: Order of tests matters
for (int i = 0, n = candidates.Count; i < n; i++)
{
var candidate = candidates[i];
var blendItem = item as IBlendable;
if (blendItem != null && blendItem.supportsBlending)
{
if (EditModeUtils.Contains(candidate.start, candidate.end, item))
return PlacementValidity.InvalidIsWithin;
if (i < n - 1)
{
var nextCandidate = candidates[i + 1];
var discreteNextCandidateStart = (DiscreteTime)nextCandidate.start;
var discreteCandidateEnd = (DiscreteTime)candidate.end;
if (discreteCandidateEnd > discreteNextCandidateStart)
{
if (discreteStart >= discreteNextCandidateStart)
{
// Note: In case the placement is fully within a blend,
// InvalidStartsInBlend MUST have priority
return PlacementValidity.InvalidStartsInBlend;
}
if (discreteEnd > discreteNextCandidateStart && discreteEnd <= discreteCandidateEnd)
return PlacementValidity.InvalidEndsInBlend;
if (discreteStart < discreteNextCandidateStart && discreteEnd > discreteCandidateEnd)
return PlacementValidity.InvalidContainsBlend;
}
}
if (EditModeUtils.Contains(item.start, item.end, candidate))
return PlacementValidity.InvalidContains;
}
else
{
if (EditModeUtils.Overlaps(item, candidate.start, candidate.end)
|| EditModeUtils.Overlaps(candidate, item.start, item.end))
return PlacementValidity.InvalidOverlapWithNonBlendableClip;
}
}
return PlacementValidity.Valid;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 505965fb9ab352b4d88882d7c8d822bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class EditModeReplaceUtils
{
public static void Insert(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
{
Insert(itemsGroup.targetTrack, itemsGroup.items);
}
}
static void Insert(TrackAsset track, IEnumerable<ITimelineItem> items)
{
if (track == null) return;
var orderedItems = ItemsUtils.GetItemsExcept(track, items)
.OfType<ITrimmable>()
.OrderBy(i => i.start).ToArray();
foreach (var item in items.OfType<ITrimmable>())
{
var from = item.start;
var to = item.end;
var overlappedItems = orderedItems.Where(i => EditModeUtils.Overlaps(i, from, to));
foreach (var overlappedItem in overlappedItems)
{
if (EditModeUtils.IsItemWithinRange(overlappedItem, from, to))
{
overlappedItem.Delete();
}
else
{
if (overlappedItem.start >= from)
overlappedItem.TrimStart(to);
else
overlappedItem.TrimEnd(from);
}
}
var includingItems = orderedItems.Where(c => c.start<from && c.end> to);
foreach (var includingItem in includingItems)
{
var newItem = includingItem.CloneTo(track, includingItem.start) as ITrimmable;
includingItem.TrimStart(to);
if (newItem != null)
newItem.SetEnd(from, false);
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6ca745fb561cbf640b6e603f95662fa0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class EditModeRippleUtils
{
public static void Insert(IEnumerable<ItemsPerTrack> itemsGroups)
{
var start = double.MaxValue;
var end = double.MinValue;
foreach (var itemsGroup in itemsGroups)
{
start = Math.Min(start, itemsGroup.items.Min(c => c.start));
end = Math.Max(end, itemsGroup.items.Max(c => c.end));
}
var offset = 0.0;
var discreteStart = (DiscreteTime)start;
var discreteEnd = (DiscreteTime)end;
var itemTypes = ItemsUtils.GetItemTypes(itemsGroups);
var siblingsToRipple = new List<ITimelineItem>();
foreach (var itemsGroup in itemsGroups)
{
//can only ripple items of the same type as those selected
siblingsToRipple.AddRange(ItemsUtils.GetItemsExcept(itemsGroup.targetTrack, itemsGroup.items).Where(i => itemTypes.Contains(i.GetType())));
foreach (var item in siblingsToRipple)
{
var discreteItemStart = (DiscreteTime)item.start;
var discreteItemEnd = (DiscreteTime)item.end;
if ((discreteItemStart < discreteStart && discreteItemEnd > discreteStart) || (discreteItemStart >= discreteStart && discreteItemStart < discreteEnd))
offset = Math.Max(offset, end - item.start);
}
}
if (offset > 0.0)
{
foreach (var sibling in siblingsToRipple)
{
if ((DiscreteTime)sibling.end > (DiscreteTime)start)
sibling.start += offset;
}
}
}
public static void Remove(IEnumerable<ItemsPerTrack> itemsGroups)
{
foreach (var itemsGroup in itemsGroups)
Remove(itemsGroup.targetTrack, itemsGroup.items);
}
static void Remove(TrackAsset track, IEnumerable<ITimelineItem> items)
{
if (track == null) return;
//can only ripple items of the same type as those selected
var itemTypes = ItemsUtils.GetItemTypes(items);
var siblingsToRipple = ItemsUtils.GetItemsExcept(track, items)
.Where(i => itemTypes.Contains(i.GetType()))
.OrderBy(c => c.start)
.ToArray();
var orderedItems = items
.OrderBy(c => c.start)
.ToArray();
var cumulativeOffset = 0.0;
foreach (var item in orderedItems)
{
var offset = item.end - item.start;
var start = item.start - cumulativeOffset;
var end = item.end - cumulativeOffset;
var nextItem = siblingsToRipple.FirstOrDefault(c => (DiscreteTime)c.start > (DiscreteTime)start && (DiscreteTime)c.start < (DiscreteTime)end);
if (nextItem != null)
{
offset -= end - nextItem.start;
}
var prevItem = siblingsToRipple.FirstOrDefault(c => (DiscreteTime)c.end > (DiscreteTime)start && (DiscreteTime)c.end < (DiscreteTime)end);
if (prevItem != null)
{
offset -= prevItem.end - start;
}
if (offset <= 0.0)
continue;
cumulativeOffset += offset;
for (int i = siblingsToRipple.Length - 1; i >= 0; --i)
{
var c = siblingsToRipple[i];
if ((DiscreteTime)c.start < (DiscreteTime)start)
break;
c.start = c.start - offset;
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 495e2738ac7d88a41a158cd2e237d70b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class EditModeUtils
{
public static void Delete(IEnumerable<ITimelineItem> items)
{
if (items == null)
return;
foreach (var item in items)
item.Delete();
}
public static void SetStart(IEnumerable<ITimelineItem> items, double time)
{
var offset = time - items.Min(c => c.start);
foreach (var item in items)
item.start += offset;
}
public static void SetParentTrack(IEnumerable<ITimelineItem> items, TrackAsset parentTrack)
{
foreach (var item in items)
{
if (item.parentTrack == parentTrack)
continue;
item.parentTrack = parentTrack;
var clipGUI = item.gui as TimelineClipGUI;
if (clipGUI != null)
{
clipGUI.clipCurveEditor = null;
}
}
}
public static ITimelineItem GetFirstIntersectedItem(IEnumerable<ITimelineItem> items, double time)
{
return items.FirstOrDefault(c => Intersects(time, c.start, c.end));
}
static bool Intersects(double time, double start, double end)
{
var discreteTime = (DiscreteTime)time;
return discreteTime > (DiscreteTime)start && discreteTime < (DiscreteTime)end;
}
public static bool Overlaps(ITimelineItem item, double from, double to)
{
var discreteFrom = (DiscreteTime)from;
var discreteTo = (DiscreteTime)to;
var discreteStart = (DiscreteTime)item.start;
if (discreteStart >= discreteFrom && discreteStart < discreteTo)
return true;
var discreteEnd = (DiscreteTime)item.end;
if (discreteEnd > discreteFrom && discreteEnd <= discreteTo)
return true;
return false;
}
public static bool IsItemWithinRange(ITimelineItem item, double from, double to)
{
return (DiscreteTime)item.start >= (DiscreteTime)from && (DiscreteTime)item.end <= (DiscreteTime)to;
}
public static bool IsRangeWithinItem(double from, double to, ITimelineItem item)
{
return (DiscreteTime)from >= (DiscreteTime)item.start && (DiscreteTime)to <= (DiscreteTime)item.end;
}
public static bool Contains(double from, double to, ITimelineItem item)
{
return (DiscreteTime)from < (DiscreteTime)item.start && (DiscreteTime)to > (DiscreteTime)item.end;
}
public static bool HasBlends(ITimelineItem item, TrimEdge edge)
{
var blendable = item as IBlendable;
if (blendable == null) return false;
return edge == TrimEdge.Start && blendable.hasLeftBlend || edge == TrimEdge.End && blendable.hasRightBlend;
}
public static double BlendDuration(ITimelineItem item, TrimEdge edge)
{
var blendable = item as IBlendable;
if (blendable == null) return 0.0;
return edge == TrimEdge.Start ? blendable.leftBlendDuration : blendable.rightBlendDuration;
}
public static bool IsInfiniteTrack(TrackAsset track)
{
var aTrack = track as AnimationTrack;
return aTrack != null && aTrack.CanConvertToClipMode();
}
public static void GetInfiniteClipBoundaries(TrackAsset track, out double start, out double end)
{
var info = AnimationClipCurveCache.Instance.GetCurveInfo(((AnimationTrack)track).infiniteClip);
if (info.keyTimes.Length > 0)
{
start = info.keyTimes.Min();
end = info.keyTimes.Max();
}
else
{
start = end = 0.0f;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70eae1897c9d308448eb3bb0b5be9f58
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
using UnityEngine;
namespace UnityEditor.Timeline
{
static class ManipulatorsUtils
{
public static EventModifiers actionModifier
{
get
{
if (Application.platform == RuntimePlatform.OSXEditor ||
Application.platform == RuntimePlatform.OSXPlayer)
return EventModifiers.Command;
return EventModifiers.Control;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e177382a693dea644acd34e3e7a3feb3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,13 @@
namespace UnityEditor.Timeline
{
enum PlacementValidity
{
Valid,
InvalidContains,
InvalidIsWithin,
InvalidStartsInBlend,
InvalidEndsInBlend,
InvalidContainsBlend,
InvalidOverlapWithNonBlendableClip
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 335020228a0fe124897f51f25f6350ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: