mirror of
https://github.com/Project-Redacted/Highscores-Server.git
synced 2025-05-19 09:54:56 +00:00
Add example Unity Project
This commit is contained in:
parent
fda7ff28dd
commit
e3acdb9d6b
7122 changed files with 505543 additions and 2 deletions
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8620e97e7e9859049934889a52248435
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,376 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using ClipAction = UnityEditor.Timeline.ItemAction<UnityEngine.Timeline.TimelineClip>;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Edit in Animation Window", MenuOrder.ClipEditAction.EditInAnimationWindow), UsedImplicitly]
|
||||
class EditClipInAnimationWindow : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (clips.Length == 1 && clips[0].animationClip != null)
|
||||
return MenuActionDisplayState.Visible;
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
var clip = clips[0];
|
||||
|
||||
if (clip.curves != null || clip.animationClip != null)
|
||||
{
|
||||
var clipToEdit = clip.animationClip != null ? clip.animationClip : clip.curves;
|
||||
if (clipToEdit == null)
|
||||
return false;
|
||||
|
||||
var gameObject = state.GetSceneReference(clip.parentTrack);
|
||||
var timeController = TimelineAnimationUtilities.CreateTimeController(state, clip);
|
||||
TimelineAnimationUtilities.EditAnimationClipWithTimeController(
|
||||
clipToEdit, timeController, clip.animationClip != null ? gameObject : null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Edit Sub-Timeline", MenuOrder.ClipEditAction.EditSubTimeline), UsedImplicitly]
|
||||
class EditSubTimeline : ClipAction
|
||||
{
|
||||
private static readonly string MultiItemPrefix = "Edit Sub-Timelines/";
|
||||
private static readonly string SingleItemPrefix = "Edit ";
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return IsValid(state, clips) ? MenuActionDisplayState.Visible : MenuActionDisplayState.Hidden;
|
||||
}
|
||||
|
||||
bool IsValid(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (clips.Length != 1 || state == null || state.editSequence.director == null) return false;
|
||||
var clip = clips[0];
|
||||
|
||||
var directors = TimelineUtility.GetSubTimelines(clip, state.editSequence.director);
|
||||
return directors.Any(x => x != null);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (!IsValid(state, clips)) return false;
|
||||
|
||||
var clip = clips[0];
|
||||
|
||||
var directors = TimelineUtility.GetSubTimelines(clip, state.editSequence.director);
|
||||
ExecuteInternal(state, directors, 0, clip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ExecuteInternal(WindowState state, IList<PlayableDirector> directors, int directorIndex, TimelineClip clip)
|
||||
{
|
||||
SelectionManager.Clear();
|
||||
state.GetWindow().SetCurrentTimeline(directors[directorIndex], clip);
|
||||
}
|
||||
|
||||
protected override void AddMenuItem(WindowState state, TimelineClip[] items, List<MenuActionItem> menuItems)
|
||||
{
|
||||
if (items == null || items.Length != 1)
|
||||
return;
|
||||
|
||||
var mode = TimelineWindow.instance.currentMode.mode;
|
||||
var menuItem = new MenuActionItem()
|
||||
{
|
||||
category = category,
|
||||
entryName = GetDisplayName(items),
|
||||
shortCut = string.Empty,
|
||||
isChecked = false,
|
||||
isActiveInMode = IsActionActiveInMode(this, mode),
|
||||
priority = priority,
|
||||
state = GetDisplayState(state, items),
|
||||
callback = null
|
||||
};
|
||||
|
||||
var subDirectors = TimelineUtility.GetSubTimelines(items[0], state.editSequence.director);
|
||||
if (subDirectors.Count == 1)
|
||||
{
|
||||
menuItem.entryName = SingleItemPrefix + DisplayNameHelper.GetDisplayName(subDirectors[0]);
|
||||
menuItem.callback = () => Execute(state, items);
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < subDirectors.Count; i++)
|
||||
{
|
||||
var index = i;
|
||||
menuItem.category = MultiItemPrefix;
|
||||
menuItem.entryName = DisplayNameHelper.GetDisplayName(subDirectors[i]);
|
||||
menuItem.callback = () => ExecuteInternal(state, subDirectors, index, items[0]);
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim Start", MenuOrder.ClipAction.TrimStart)]
|
||||
[Shortcut(Shortcuts.Clip.trimStart), UsedImplicitly]
|
||||
class TrimStart : ItemAction<TimelineClip>
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return clips.All(x => state.editSequence.time <= x.start || state.editSequence.time >= x.start + x.duration) ?
|
||||
MenuActionDisplayState.Disabled : MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.TrimStart(clips, state.editSequence.time);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim End", MenuOrder.ClipAction.TrimEnd), UsedImplicitly]
|
||||
[Shortcut(Shortcuts.Clip.trimEnd)]
|
||||
class TrimEnd : ItemAction<TimelineClip>
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return clips.All(x => state.editSequence.time <= x.start || state.editSequence.time >= x.start + x.duration) ?
|
||||
MenuActionDisplayState.Disabled : MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.TrimEnd(clips, state.editSequence.time);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Clip.split), MenuEntry("Editing/Split", MenuOrder.ClipAction.Split), UsedImplicitly]
|
||||
class Split : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return clips.All(x => state.editSequence.time <= x.start || state.editSequence.time >= x.start + x.duration) ?
|
||||
MenuActionDisplayState.Disabled : MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool success = ClipModifier.Split(clips, state.editSequence.time, state.editSequence.director);
|
||||
if (success)
|
||||
state.Refresh();
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Complete Last Loop", MenuOrder.ClipAction.CompleteLastLoop), UsedImplicitly]
|
||||
class CompleteLastLoop : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.CompleteLastLoop(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim Last Loop", MenuOrder.ClipAction.TrimLastLoop), UsedImplicitly]
|
||||
class TrimLastLoop : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.TrimLastLoop(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Match Duration", MenuOrder.ClipAction.MatchDuration), UsedImplicitly]
|
||||
class MatchDuration : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return clips.Length > 1 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.MatchDuration(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Double Speed", MenuOrder.ClipAction.DoubleSpeed), UsedImplicitly]
|
||||
class DoubleSpeed : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.DoubleSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Half Speed", MenuOrder.ClipAction.HalfSpeed), UsedImplicitly]
|
||||
class HalfSpeed : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.HalfSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset Duration", MenuOrder.ClipAction.ResetDuration), UsedImplicitly]
|
||||
class ResetDuration : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.ResetEditing(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset Speed", MenuOrder.ClipAction.ResetSpeed), UsedImplicitly]
|
||||
class ResetSpeed : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.ResetSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset All", MenuOrder.ClipAction.ResetAll), UsedImplicitly]
|
||||
class ResetAll : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration) ||
|
||||
clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
var speedResult = ClipModifier.ResetSpeed(clips);
|
||||
var editResult = ClipModifier.ResetEditing(clips);
|
||||
return speedResult || editResult;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Tile", MenuOrder.ClipAction.Tile), UsedImplicitly]
|
||||
class Tile : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return clips.Length > 1 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
return ClipModifier.Tile(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Find Source Asset", MenuOrder.ClipAction.FindSourceAsset), UsedImplicitly]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FindSourceAsset : ClipAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state,
|
||||
TimelineClip[] clips)
|
||||
{
|
||||
if (clips.Length > 1)
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
if (GetUnderlyingAsset(state, clips[0]) == null)
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
EditorGUIUtility.PingObject(GetUnderlyingAsset(state, clips[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UnityEngine.Object GetExternalPlayableAsset(TimelineClip clip)
|
||||
{
|
||||
if (clip.asset == null)
|
||||
return null;
|
||||
|
||||
if ((clip.asset.hideFlags & HideFlags.HideInHierarchy) != 0)
|
||||
return null;
|
||||
|
||||
return clip.asset;
|
||||
}
|
||||
|
||||
private static UnityEngine.Object GetUnderlyingAsset(WindowState state, TimelineClip clip)
|
||||
{
|
||||
var asset = clip.asset as ScriptableObject;
|
||||
if (asset == null)
|
||||
return null;
|
||||
|
||||
var fields = ObjectReferenceField.FindObjectReferences(asset.GetType());
|
||||
if (fields.Length == 0)
|
||||
return GetExternalPlayableAsset(clip);
|
||||
|
||||
// Find the first non-null field
|
||||
foreach (var field in fields)
|
||||
{
|
||||
// skip scene refs in asset mode
|
||||
if (state.editSequence.director == null && field.isSceneReference)
|
||||
continue;
|
||||
var obj = field.Find(asset, state.editSequence.director);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
}
|
||||
|
||||
return GetExternalPlayableAsset(clip);
|
||||
}
|
||||
}
|
||||
|
||||
class CopyClipsToClipboard : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
TimelineEditor.clipboard.CopyItems(clips.ToItems());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b721099b5d509d4093e516f59ad9ad6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,124 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
abstract class ItemAction<T> : MenuItemActionBase where T : class
|
||||
{
|
||||
public abstract bool Execute(WindowState state, T[] items);
|
||||
|
||||
protected virtual MenuActionDisplayState GetDisplayState(WindowState state, T[] items)
|
||||
{
|
||||
return items.Length > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
protected virtual string GetDisplayName(T[] items)
|
||||
{
|
||||
return menuName;
|
||||
}
|
||||
|
||||
public bool CanExecute(WindowState state, T[] items)
|
||||
{
|
||||
return GetDisplayState(state, items) == MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
protected virtual void AddMenuItem(WindowState state, T[] items, List<MenuActionItem> menuItem)
|
||||
{
|
||||
var mode = TimelineWindow.instance.currentMode.mode;
|
||||
menuItem.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = category,
|
||||
entryName = GetDisplayName(items),
|
||||
shortCut = this.shortCut,
|
||||
isChecked = false,
|
||||
isActiveInMode = IsActionActiveInMode(this, mode),
|
||||
priority = priority,
|
||||
state = GetDisplayState(state, items),
|
||||
callback = () => Execute(state, items)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static bool HandleShortcut(WindowState state, Event evt, T item)
|
||||
{
|
||||
T[] items = { item };
|
||||
|
||||
foreach (ItemAction<T> action in actions)
|
||||
{
|
||||
var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
|
||||
|
||||
foreach (ShortcutAttribute shortcut in attr)
|
||||
{
|
||||
if (shortcut.MatchesEvent(evt))
|
||||
{
|
||||
if (s_ShowActionTriggeredByShortcut)
|
||||
Debug.Log(action.GetType().Name);
|
||||
|
||||
if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
|
||||
return false;
|
||||
|
||||
var result = action.Execute(state, items);
|
||||
state.Refresh();
|
||||
state.Evaluate();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static List<ItemAction<T>> s_ActionClasses;
|
||||
|
||||
static List<ItemAction<T>> actions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_ActionClasses == null)
|
||||
{
|
||||
s_ActionClasses = GetActionsOfType(typeof(ItemAction<T>)).Select(x => (ItemAction<T>)x.GetConstructors()[0].Invoke(null)).ToList();
|
||||
}
|
||||
|
||||
return s_ActionClasses;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(T[] items, List<MenuActionItem> menuItems)
|
||||
{
|
||||
if (items == null || items.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (action.showInMenu)
|
||||
action.AddMenuItem(TimelineWindow.instance.state, items, menuItems);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool Invoke<TAction>(WindowState state, T[] items)
|
||||
where TAction : ItemAction<T>
|
||||
{
|
||||
var itemsDerived = items.ToArray();
|
||||
|
||||
if (!itemsDerived.Any())
|
||||
return false;
|
||||
|
||||
var action = actions.FirstOrDefault(x => x.GetType() == typeof(TAction));
|
||||
|
||||
if (action != null)
|
||||
return action.Execute(state, itemsDerived);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool Invoke<TAction>(WindowState state, T item)
|
||||
where TAction : ItemAction<T>
|
||||
{
|
||||
return Invoke<TAction>(state, new[] {item});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 84b5362754a9d934ba259398b757d0be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,17 @@
|
|||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using MarkerAction = UnityEditor.Timeline.ItemAction<UnityEngine.Timeline.IMarker>;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[UsedImplicitly]
|
||||
class CopyMarkersToClipboard : MarkerAction
|
||||
{
|
||||
public override bool Execute(WindowState state, IMarker[] markers)
|
||||
{
|
||||
TimelineEditor.clipboard.CopyItems(markers.ToItems());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5da77d4d078922b4c8466e9e35fb3f5e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 985eed4bc2fbee941b761b8816d9055d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
enum MenuActionDisplayState
|
||||
{
|
||||
Visible,
|
||||
Disabled,
|
||||
Hidden
|
||||
}
|
||||
|
||||
struct MenuActionItem
|
||||
{
|
||||
public string category;
|
||||
public string entryName;
|
||||
public string shortCut;
|
||||
public int priority;
|
||||
public bool isChecked;
|
||||
public bool isActiveInMode;
|
||||
public MenuActionDisplayState state;
|
||||
public GenericMenu.MenuFunction callback;
|
||||
}
|
||||
|
||||
class MenuItemActionBase
|
||||
{
|
||||
public Vector2? mousePosition { get; set; }
|
||||
|
||||
protected static bool s_ShowActionTriggeredByShortcut = false;
|
||||
|
||||
private static MenuEntryAttribute NoMenu = new MenuEntryAttribute(null, MenuOrder.DefaultPriority);
|
||||
private MenuEntryAttribute m_MenuInfo;
|
||||
private string m_ShortCut = null;
|
||||
|
||||
|
||||
public static IEnumerable<Type> GetActionsOfType(Type actionType)
|
||||
{
|
||||
var query = TypeCache.GetTypesDerivedFrom(actionType).Where(type => !type.IsGenericType && !type.IsNested && !type.IsAbstract);
|
||||
return query;
|
||||
}
|
||||
|
||||
public static ShortcutAttribute GetShortcutAttributeForAction(MenuItemActionBase action)
|
||||
{
|
||||
var shortcutAttributes = action.GetType()
|
||||
.GetCustomAttributes(typeof(ShortcutAttribute), true)
|
||||
.Cast<ShortcutAttribute>();
|
||||
|
||||
foreach (var shortcutAttribute in shortcutAttributes)
|
||||
{
|
||||
var shortcutOverride = shortcutAttribute as ShortcutPlatformOverrideAttribute;
|
||||
if (shortcutOverride != null)
|
||||
{
|
||||
if (shortcutOverride.MatchesCurrentPlatform())
|
||||
return shortcutOverride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return shortcutAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void BuildMenu(GenericMenu menu, List<MenuActionItem> items)
|
||||
{
|
||||
// sorted the outer menu by priority, then sort the innermenu by priority
|
||||
var sortedItems =
|
||||
items.GroupBy(x => string.IsNullOrEmpty(x.category) ? x.entryName : x.category).
|
||||
OrderBy(x => x.Min(y => y.priority)).
|
||||
SelectMany(x => x.OrderBy(z => z.priority));
|
||||
|
||||
int lastPriority = Int32.MinValue;
|
||||
string lastCategory = string.Empty;
|
||||
|
||||
foreach (var s in sortedItems)
|
||||
{
|
||||
if (s.state == MenuActionDisplayState.Hidden)
|
||||
continue;
|
||||
|
||||
var priority = s.priority;
|
||||
if (lastPriority == Int32.MinValue)
|
||||
{
|
||||
lastPriority = priority;
|
||||
}
|
||||
else if ((priority / MenuOrder.SeparatorAt) > (lastPriority / MenuOrder.SeparatorAt))
|
||||
{
|
||||
string path = String.Empty;
|
||||
if (lastCategory == s.category)
|
||||
path = s.category;
|
||||
menu.AddSeparator(path);
|
||||
}
|
||||
|
||||
lastPriority = priority;
|
||||
lastCategory = s.category;
|
||||
|
||||
string entry = s.category + s.entryName;
|
||||
if (!string.IsNullOrEmpty(s.shortCut))
|
||||
entry += " " + s.shortCut;
|
||||
|
||||
if (s.state == MenuActionDisplayState.Visible && s.isActiveInMode)
|
||||
menu.AddItem(new GUIContent(entry), s.isChecked, s.callback);
|
||||
else
|
||||
menu.AddDisabledItem(new GUIContent(entry));
|
||||
}
|
||||
}
|
||||
|
||||
public static ActiveInModeAttribute GetActiveInModeAttribute(MenuItemActionBase action)
|
||||
{
|
||||
var attr = action.GetType().GetCustomAttributes(typeof(ActiveInModeAttribute), true);
|
||||
|
||||
if (attr.Length > 0)
|
||||
return (attr[0] as ActiveInModeAttribute);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsActionActiveInMode(MenuItemActionBase action, TimelineModes mode)
|
||||
{
|
||||
ActiveInModeAttribute attr = GetActiveInModeAttribute(action);
|
||||
return attr != null && (attr.modes & mode) != 0;
|
||||
}
|
||||
|
||||
public int priority
|
||||
{
|
||||
get { return menuInfo.priority; }
|
||||
}
|
||||
|
||||
public string category
|
||||
{
|
||||
get { return menuInfo.subMenuPath; }
|
||||
}
|
||||
|
||||
public string menuName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(menuInfo.name))
|
||||
return L10n.Tr(GetType().Name);
|
||||
return menuInfo.name;
|
||||
}
|
||||
}
|
||||
|
||||
// shortcut used by the menu
|
||||
public string shortCut
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ShortCut == null)
|
||||
{
|
||||
var shortcutAttribute = GetShortcutAttributeForAction(this);
|
||||
m_ShortCut = shortcutAttribute == null ? string.Empty : shortcutAttribute.GetMenuShortcut();
|
||||
}
|
||||
return m_ShortCut;
|
||||
}
|
||||
}
|
||||
|
||||
public bool showInMenu
|
||||
{
|
||||
get { return menuInfo != NoMenu; }
|
||||
}
|
||||
|
||||
private MenuEntryAttribute menuInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_MenuInfo == null)
|
||||
m_MenuInfo = GetType().GetCustomAttributes(typeof(MenuEntryAttribute), false).OfType<MenuEntryAttribute>().DefaultIfEmpty(NoMenu).First();
|
||||
return m_MenuInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5882d0e4313310143acb11d1a66c597f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,448 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class SequencerContextMenu
|
||||
{
|
||||
static readonly TimelineAction[] MarkerHeaderCommonOperations =
|
||||
{
|
||||
new PasteAction()
|
||||
};
|
||||
|
||||
public static readonly TimelineAction[] MarkerHeaderMenuItems =
|
||||
TimelineAction.AllActions.OfType<MarkerHeaderAction>().
|
||||
Where(a => a.showInMenu).
|
||||
Union(MarkerHeaderCommonOperations).
|
||||
ToArray();
|
||||
|
||||
|
||||
static class Styles
|
||||
{
|
||||
public static readonly string addItemFromAssetTemplate = L10n.Tr("Add {0} From {1}");
|
||||
public static readonly string addSingleItemFromAssetTemplate = L10n.Tr("Add From {1}");
|
||||
public static readonly string addItemTemplate = L10n.Tr("Add {0}");
|
||||
public static readonly string typeSelectorTemplate = L10n.Tr("Select {0}");
|
||||
public static readonly string trackGroup = L10n.Tr("Track Group");
|
||||
public static readonly string trackSubGroup = L10n.Tr("Track Sub-Group");
|
||||
public static readonly string addTrackLayer = L10n.Tr("Add Layer");
|
||||
public static readonly string layerName = L10n.Tr("Layer {0}");
|
||||
}
|
||||
|
||||
public static void ShowMarkerHeaderContextMenu(Vector2? mousePosition, WindowState state)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
List<MenuActionItem> items = new List<MenuActionItem>(100);
|
||||
BuildMarkerHeaderContextMenu(items, mousePosition, state);
|
||||
MenuItemActionBase.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
List<MenuActionItem> items = new List<MenuActionItem>(100);
|
||||
BuildNewTracksContextMenu(items, tracks, state);
|
||||
MenuItemActionBase.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state, Rect rect)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
List<MenuActionItem> items = new List<MenuActionItem>(100);
|
||||
BuildNewTracksContextMenu(items, tracks, state);
|
||||
MenuItemActionBase.BuildMenu(menu, items);
|
||||
menu.DropDown(rect);
|
||||
}
|
||||
|
||||
public static void ShowTrackContextMenu(TrackAsset[] tracks, Vector2? mousePosition)
|
||||
{
|
||||
if (tracks == null || tracks.Length == 0)
|
||||
return;
|
||||
|
||||
var items = new List<MenuActionItem>();
|
||||
var menu = new GenericMenu();
|
||||
BuildTrackContextMenu(items, tracks, mousePosition);
|
||||
MenuItemActionBase.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void ShowItemContextMenu(Vector2 mousePosition, TimelineClip[] clips, IMarker[] markers)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
var items = new List<MenuActionItem>();
|
||||
BuildItemContextMenu(items, mousePosition, clips, markers);
|
||||
MenuItemActionBase.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
internal static void BuildItemContextMenu(List<MenuActionItem> items, Vector2 mousePosition, TimelineClip[] clips, IMarker[] markers)
|
||||
{
|
||||
var state = TimelineWindow.instance.state;
|
||||
|
||||
TimelineAction.GetMenuEntries(TimelineAction.MenuActions, mousePosition, items);
|
||||
ItemAction<TimelineClip>.GetMenuEntries(clips, items);
|
||||
ItemAction<IMarker>.GetMenuEntries(markers, items);
|
||||
|
||||
if (clips.Length > 0)
|
||||
AddMarkerMenuCommands(items, clips.Select(c => c.parentTrack).Distinct().ToList(), TimelineHelpers.GetCandidateTime(state, mousePosition));
|
||||
}
|
||||
|
||||
internal static void BuildNewTracksContextMenu(List<MenuActionItem> menuItems, ICollection<TrackAsset> parentTracks, WindowState state, string format = null)
|
||||
{
|
||||
if (parentTracks == null)
|
||||
parentTracks = new TrackAsset[0];
|
||||
|
||||
if (string.IsNullOrEmpty(format))
|
||||
format = "{0}";
|
||||
|
||||
// Add Group or SubGroup
|
||||
var title = string.Format(format, parentTracks.Any(t => t != null) ? Styles.trackSubGroup : Styles.trackGroup);
|
||||
var menuState = MenuActionDisplayState.Visible;
|
||||
if (state.editSequence.isReadOnly)
|
||||
menuState = MenuActionDisplayState.Disabled;
|
||||
if (parentTracks.Any() && parentTracks.Any(t => t != null && t.lockedInHierarchy))
|
||||
menuState = MenuActionDisplayState.Disabled;
|
||||
|
||||
GenericMenu.MenuFunction command = () =>
|
||||
{
|
||||
SelectionManager.Clear();
|
||||
if (parentTracks.Count == 0)
|
||||
Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(null, title));
|
||||
|
||||
foreach (var parentTrack in parentTracks)
|
||||
Selection.Add(TimelineHelpers.CreateTrack<GroupTrack>(parentTrack, title));
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
};
|
||||
|
||||
menuItems.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = string.Empty,
|
||||
entryName = title,
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = MenuOrder.AddGroupItemStart,
|
||||
state = menuState,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
var allTypes = TypeUtility.AllTrackTypes().Where(x => x != typeof(GroupTrack) && !TypeUtility.IsHiddenInMenu(x)).ToList();
|
||||
|
||||
int builtInPriority = MenuOrder.AddTrackItemStart;
|
||||
int customPriority = MenuOrder.AddCustomTrackItemStart;
|
||||
foreach (var trackType in allTypes)
|
||||
{
|
||||
var trackItemType = trackType;
|
||||
|
||||
command = () =>
|
||||
{
|
||||
SelectionManager.Clear();
|
||||
|
||||
if (parentTracks.Count == 0)
|
||||
SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, null));
|
||||
|
||||
foreach (var parentTrack in parentTracks)
|
||||
SelectionManager.Add(TimelineHelpers.CreateTrack((Type)trackItemType, parentTrack));
|
||||
};
|
||||
|
||||
menuItems.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = TimelineHelpers.GetTrackCategoryName(trackType),
|
||||
entryName = string.Format(format, TimelineHelpers.GetTrackMenuName(trackItemType)),
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = TypeUtility.IsBuiltIn(trackType) ? builtInPriority++ : customPriority++,
|
||||
state = menuState,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void BuildMarkerHeaderContextMenu(List<MenuActionItem> menu, Vector2? mousePosition, WindowState state)
|
||||
{
|
||||
TimelineAction.GetMenuEntries(MarkerHeaderMenuItems, null, menu);
|
||||
|
||||
var timeline = state.editSequence.asset;
|
||||
var time = TimelineHelpers.GetCandidateTime(state, mousePosition);
|
||||
var enabled = timeline.markerTrack == null || !timeline.markerTrack.lockedInHierarchy;
|
||||
|
||||
var addMarkerCommand = new Action<Type, Object>
|
||||
(
|
||||
(type, obj) => AddSingleMarkerCallback(type, time, timeline, state.editSequence.director, obj)
|
||||
);
|
||||
|
||||
AddMarkerMenuCommands(menu, new TrackAsset[] {timeline.markerTrack}, addMarkerCommand, enabled);
|
||||
}
|
||||
|
||||
internal static void BuildTrackContextMenu(List<MenuActionItem> items, TrackAsset[] tracks, Vector2? mousePosition)
|
||||
{
|
||||
if (tracks == null || tracks.Length == 0)
|
||||
return;
|
||||
|
||||
TimelineAction.GetMenuEntries(TimelineAction.MenuActions, mousePosition, items);
|
||||
TrackAction.GetMenuEntries(TimelineWindow.instance.state, mousePosition, tracks, items);
|
||||
AddLayeredTrackCommands(items, tracks);
|
||||
|
||||
var first = tracks.First().GetType();
|
||||
var allTheSame = tracks.All(t => t.GetType() == first);
|
||||
if (allTheSame)
|
||||
{
|
||||
if (first != typeof(GroupTrack))
|
||||
{
|
||||
var candidateTime = TimelineHelpers.GetCandidateTime(TimelineWindow.instance.state, mousePosition, tracks);
|
||||
AddClipMenuCommands(items, tracks, candidateTime);
|
||||
AddMarkerMenuCommands(items, tracks, candidateTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildNewTracksContextMenu(items, tracks, TimelineWindow.instance.state, Styles.addItemTemplate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddLayeredTrackCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Count == 0)
|
||||
return;
|
||||
|
||||
var layeredType = tracks.First().GetType();
|
||||
// animation tracks have a special menu.
|
||||
if (layeredType == typeof(AnimationTrack))
|
||||
return;
|
||||
|
||||
// must implement ILayerable
|
||||
if (!typeof(UnityEngine.Timeline.ILayerable).IsAssignableFrom(layeredType))
|
||||
return;
|
||||
|
||||
if (tracks.Any(t => t.GetType() != layeredType))
|
||||
return;
|
||||
|
||||
// only supported on the master track no nesting.
|
||||
if (tracks.Any(t => t.isSubTrack))
|
||||
return;
|
||||
|
||||
var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
int priority = MenuOrder.TrackAddMenu.AddLayerTrack;
|
||||
GenericMenu.MenuFunction menuCallback = () =>
|
||||
{
|
||||
foreach (var track in tracks)
|
||||
TimelineHelpers.CreateTrack(layeredType, track, string.Format(Styles.layerName, track.GetChildTracks().Count() + 1));
|
||||
};
|
||||
|
||||
var entryName = Styles.addTrackLayer;
|
||||
menuItems.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = string.Empty,
|
||||
entryName = entryName,
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = priority++,
|
||||
state = enabled ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled,
|
||||
callback = menuCallback
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static void AddClipMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return;
|
||||
|
||||
var trackAsset = tracks.First();
|
||||
var trackType = trackAsset.GetType();
|
||||
if (tracks.Any(t => t.GetType() != trackType))
|
||||
return;
|
||||
|
||||
var enabled = tracks.All(t => t != null && !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
var assetTypes = TypeUtility.GetPlayableAssetsHandledByTrack(trackType);
|
||||
var visibleAssetTypes = TypeUtility.GetVisiblePlayableAssetsHandledByTrack(trackType);
|
||||
|
||||
// skips the name if there is only a single type
|
||||
var commandNameTemplate = assetTypes.Count() == 1 ? Styles.addSingleItemFromAssetTemplate : Styles.addItemFromAssetTemplate;
|
||||
int builtInPriority = MenuOrder.AddClipItemStart;
|
||||
int customPriority = MenuOrder.AddCustomClipItemStart;
|
||||
foreach (var assetType in assetTypes)
|
||||
{
|
||||
var assetItemType = assetType;
|
||||
var category = TimelineHelpers.GetItemCategoryName(assetType);
|
||||
Action<Object> onObjectChanged = obj =>
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
foreach (var t in tracks)
|
||||
{
|
||||
TimelineHelpers.CreateClipOnTrack(assetItemType, obj, t, candidateTime);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var objectReference in TypeUtility.ObjectReferencesForType(assetType))
|
||||
{
|
||||
var isSceneReference = objectReference.isSceneReference;
|
||||
var dataType = objectReference.type;
|
||||
GenericMenu.MenuFunction menuCallback = () =>
|
||||
{
|
||||
ObjectSelector.get.Show(null, dataType, null, isSceneReference, null, (obj) => onObjectChanged(obj), null);
|
||||
ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(dataType)));
|
||||
};
|
||||
|
||||
menuItems.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = category,
|
||||
entryName = string.Format(commandNameTemplate, TypeUtility.GetDisplayName(assetType), TypeUtility.GetDisplayName(objectReference.type)),
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = TypeUtility.IsBuiltIn(assetType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled,
|
||||
callback = menuCallback
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var assetType in visibleAssetTypes)
|
||||
{
|
||||
var assetItemType = assetType;
|
||||
var category = TimelineHelpers.GetItemCategoryName(assetType);
|
||||
var commandName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(assetType));
|
||||
GenericMenu.MenuFunction command = () =>
|
||||
{
|
||||
foreach (var t in tracks)
|
||||
{
|
||||
TimelineHelpers.CreateClipOnTrack(assetItemType, t, candidateTime);
|
||||
}
|
||||
};
|
||||
|
||||
menuItems.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = category,
|
||||
entryName = commandName,
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = TypeUtility.IsBuiltIn(assetItemType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddMarkerMenuCommands(List<MenuActionItem> menu, IEnumerable<Type> markerTypes, Action<Type, Object> addMarkerCommand, bool enabled)
|
||||
{
|
||||
int builtInPriority = MenuOrder.AddMarkerItemStart;
|
||||
int customPriority = MenuOrder.AddCustomMarkerItemStart;
|
||||
foreach (var markerType in markerTypes)
|
||||
{
|
||||
var markerItemType = markerType;
|
||||
string category = TimelineHelpers.GetItemCategoryName(markerItemType);
|
||||
menu.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = category,
|
||||
entryName = string.Format(Styles.addItemTemplate, TypeUtility.GetDisplayName(markerType)),
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled,
|
||||
callback = () => addMarkerCommand(markerItemType, null)
|
||||
}
|
||||
);
|
||||
|
||||
foreach (var objectReference in TypeUtility.ObjectReferencesForType(markerType))
|
||||
{
|
||||
var isSceneReference = objectReference.isSceneReference;
|
||||
GenericMenu.MenuFunction menuCallback = () =>
|
||||
{
|
||||
var dataType = markerItemType;
|
||||
ObjectSelector.get.Show(null, dataType, null, isSceneReference, null, (obj) => addMarkerCommand(markerItemType, obj), null);
|
||||
ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(dataType)));
|
||||
};
|
||||
|
||||
menu.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = TimelineHelpers.GetItemCategoryName(markerItemType),
|
||||
entryName = string.Format(Styles.addItemFromAssetTemplate, TypeUtility.GetDisplayName(markerType), TypeUtility.GetDisplayName(objectReference.type)),
|
||||
shortCut = string.Empty,
|
||||
isActiveInMode = true,
|
||||
isChecked = false,
|
||||
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled,
|
||||
callback = menuCallback
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, double candidateTime)
|
||||
{
|
||||
if (tracks.Count == 0)
|
||||
return;
|
||||
|
||||
var enabled = tracks.All(t => !t.lockedInHierarchy) && !TimelineWindow.instance.state.editSequence.isReadOnly;
|
||||
var addMarkerCommand = new Action<Type, Object>((type, obj) => AddMarkersCallback(tracks, type, candidateTime, obj));
|
||||
|
||||
AddMarkerMenuCommands(menuItems, tracks, addMarkerCommand, enabled);
|
||||
}
|
||||
|
||||
static void AddMarkerMenuCommands(List<MenuActionItem> menuItems, ICollection<TrackAsset> tracks, Action<Type, Object> command, bool enabled)
|
||||
{
|
||||
var markerTypes = TypeUtility.GetBuiltInMarkerTypes().Union(TypeUtility.GetUserMarkerTypes());
|
||||
if (tracks != null)
|
||||
markerTypes = markerTypes.Where(x => tracks.All(track => (track == null) || TypeUtility.DoesTrackSupportMarkerType(track, x))); // null track indicates marker track to be created
|
||||
|
||||
AddMarkerMenuCommands(menuItems, markerTypes, command, enabled);
|
||||
}
|
||||
|
||||
static void AddMarkersCallback(ICollection<TrackAsset> targets, Type markerType, double time, Object obj)
|
||||
{
|
||||
SelectionManager.Clear();
|
||||
foreach (var target in targets)
|
||||
{
|
||||
var marker = TimelineHelpers.CreateMarkerOnTrack(markerType, obj, target, time);
|
||||
SelectionManager.Add(marker);
|
||||
}
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
}
|
||||
|
||||
static void AddSingleMarkerCallback(Type markerType, double time, TimelineAsset timeline, PlayableDirector director, Object assignableObject)
|
||||
{
|
||||
timeline.CreateMarkerTrack();
|
||||
var markerTrack = timeline.markerTrack;
|
||||
|
||||
SelectionManager.Clear();
|
||||
var marker = TimelineHelpers.CreateMarkerOnTrack(markerType, assignableObject, markerTrack, time);
|
||||
SelectionManager.Add(marker);
|
||||
|
||||
if (typeof(INotification).IsAssignableFrom(markerType) && director != null)
|
||||
{
|
||||
if (director != null && director.GetGenericBinding(markerTrack) == null)
|
||||
director.SetGenericBinding(markerTrack, director.gameObject);
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de86b4ed8106fd84a8bc2f5d69798d53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,946 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using MenuEntryPair = System.Collections.Generic.KeyValuePair<UnityEngine.GUIContent, UnityEditor.Timeline.TimelineAction>;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
abstract class TimelineAction : MenuItemActionBase
|
||||
{
|
||||
public abstract bool Execute(WindowState state);
|
||||
|
||||
public virtual MenuActionDisplayState GetDisplayState(WindowState state)
|
||||
{
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public virtual bool IsChecked(WindowState state)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected string GetDisplayName(WindowState state)
|
||||
{
|
||||
return menuName;
|
||||
}
|
||||
|
||||
bool CanExecute(WindowState state)
|
||||
{
|
||||
return GetDisplayState(state) == MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public static void Invoke<T>(WindowState state) where T : TimelineAction
|
||||
{
|
||||
var action = AllActions.FirstOrDefault(x => x.GetType() == typeof(T));
|
||||
if (action != null && action.CanExecute(state))
|
||||
action.Execute(state);
|
||||
}
|
||||
|
||||
// an instance of all TimelineActions
|
||||
public static readonly TimelineAction[] AllActions = GetActionsOfType(typeof(TimelineAction)).Select(x => (TimelineAction)x.GetConstructors()[0].Invoke(null)).ToArray();
|
||||
|
||||
// an instance of all TimelineActions that should appear in a regular contextMenu
|
||||
public static readonly TimelineAction[] MenuActions = AllActions.Where(a => a.showInMenu && !(a is MarkerHeaderAction)).ToArray();
|
||||
|
||||
public static void GetMenuEntries(IEnumerable<TimelineAction> actions, Vector2? mousePos, List<MenuActionItem> items)
|
||||
{
|
||||
var state = TimelineWindow.instance.state;
|
||||
var mode = TimelineWindow.instance.currentMode.mode;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var actionItem = action;
|
||||
action.mousePosition = mousePos;
|
||||
items.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = action.category,
|
||||
entryName = action.GetDisplayName(state),
|
||||
shortCut = action.shortCut,
|
||||
isChecked = action.IsChecked(state),
|
||||
isActiveInMode = IsActionActiveInMode(action, mode),
|
||||
priority = action.priority,
|
||||
state = action.GetDisplayState(state),
|
||||
callback = () =>
|
||||
{
|
||||
actionItem.mousePosition = mousePos;
|
||||
actionItem.Execute(state);
|
||||
actionItem.mousePosition = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
action.mousePosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HandleShortcut(WindowState state, Event evt)
|
||||
{
|
||||
if (EditorGUI.IsEditingTextField())
|
||||
return false;
|
||||
|
||||
foreach (var action in AllActions)
|
||||
{
|
||||
var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
|
||||
|
||||
foreach (ShortcutAttribute shortcut in attr)
|
||||
{
|
||||
if (shortcut.MatchesEvent(evt))
|
||||
{
|
||||
if (s_ShowActionTriggeredByShortcut)
|
||||
Debug.Log(action.GetType().Name);
|
||||
|
||||
if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
|
||||
return false;
|
||||
|
||||
var handled = action.Execute(state);
|
||||
if (handled)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static bool DoInternal(Type t, WindowState state)
|
||||
{
|
||||
var action = (TimelineAction)t.GetConstructors()[0].Invoke(null);
|
||||
|
||||
if (action.CanExecute(state))
|
||||
return action.Execute(state);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// indicates the action only applies to the marker header menu
|
||||
abstract class MarkerHeaderAction : TimelineAction
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[MenuEntry("Copy", MenuOrder.TimelineAction.Copy)]
|
||||
[Shortcut("Main Menu/Edit/Copy", EventCommandNames.Copy)]
|
||||
class CopyAction : TimelineAction
|
||||
{
|
||||
public static bool Do(WindowState state)
|
||||
{
|
||||
return DoInternal(typeof(CopyAction), state);
|
||||
}
|
||||
|
||||
public override MenuActionDisplayState GetDisplayState(WindowState state)
|
||||
{
|
||||
return SelectionManager.Count() > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
TimelineEditor.clipboard.Clear();
|
||||
|
||||
var clips = SelectionManager.SelectedClips().ToArray();
|
||||
if (clips.Length > 0)
|
||||
{
|
||||
ItemAction<TimelineClip>.Invoke<CopyClipsToClipboard>(state, clips);
|
||||
}
|
||||
var markers = SelectionManager.SelectedMarkers().ToArray();
|
||||
if (markers.Length > 0)
|
||||
{
|
||||
ItemAction<IMarker>.Invoke<CopyMarkersToClipboard>(state, markers);
|
||||
}
|
||||
var tracks = SelectionManager.SelectedTracks().ToArray();
|
||||
if (tracks.Length > 0)
|
||||
{
|
||||
CopyTracksToClipboard.Do(state, tracks);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Paste", MenuOrder.TimelineAction.Paste)]
|
||||
[Shortcut("Main Menu/Edit/Paste", EventCommandNames.Paste)]
|
||||
class PasteAction : TimelineAction
|
||||
{
|
||||
public static bool Do(WindowState state)
|
||||
{
|
||||
return DoInternal(typeof(PasteAction), state);
|
||||
}
|
||||
|
||||
public override MenuActionDisplayState GetDisplayState(WindowState state)
|
||||
{
|
||||
return CanPaste(state) ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (!CanPaste(state))
|
||||
return false;
|
||||
|
||||
PasteItems(state, mousePosition);
|
||||
PasteTracks(state);
|
||||
|
||||
state.Refresh();
|
||||
|
||||
mousePosition = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CanPaste(WindowState state)
|
||||
{
|
||||
var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
|
||||
|
||||
if (!copiedItems.Any())
|
||||
return TimelineEditor.clipboard.GetTracks().Any();
|
||||
|
||||
return CanPasteItems(copiedItems, state, mousePosition);
|
||||
}
|
||||
|
||||
static bool CanPasteItems(ICollection<ItemsPerTrack> itemsGroups, WindowState state, Vector2? mousePosition)
|
||||
{
|
||||
var hasItemsCopiedFromMultipleTracks = itemsGroups.Count > 1;
|
||||
var allItemsCopiedFromCurrentAsset = itemsGroups.All(x => x.targetTrack.timelineAsset == state.editSequence.asset);
|
||||
var hasUsedShortcut = mousePosition == null;
|
||||
var anySourceLocked = itemsGroups.Any(x => x.targetTrack != null && x.targetTrack.lockedInHierarchy);
|
||||
|
||||
var targetTrack = GetPickedTrack();
|
||||
if (targetTrack == null)
|
||||
targetTrack = SelectionManager.SelectedTracks().FirstOrDefault();
|
||||
|
||||
//do not paste if the user copied items from another timeline
|
||||
//if the copied items comes from > 1 track (since we do not know where to paste the copied items)
|
||||
//or if a keyboard shortcut was used (since the user will not see the paste result)
|
||||
if (!allItemsCopiedFromCurrentAsset)
|
||||
{
|
||||
var isSelectedTrackInCurrentAsset = targetTrack != null && targetTrack.timelineAsset == state.editSequence.asset;
|
||||
if (hasItemsCopiedFromMultipleTracks || (hasUsedShortcut && !isSelectedTrackInCurrentAsset))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasUsedShortcut)
|
||||
return !anySourceLocked; // copy/paste to same track
|
||||
|
||||
if (hasItemsCopiedFromMultipleTracks)
|
||||
{
|
||||
//do not paste if the track which received the paste action does not contain a copied clip
|
||||
return !anySourceLocked && itemsGroups.Select(x => x.targetTrack).Contains(targetTrack);
|
||||
}
|
||||
|
||||
var copiedItems = itemsGroups.SelectMany(i => i.items);
|
||||
return IsTrackValidForItems(targetTrack, copiedItems);
|
||||
}
|
||||
|
||||
static void PasteItems(WindowState state, Vector2? mousePosition)
|
||||
{
|
||||
var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
|
||||
var numberOfUniqueParentsInClipboard = copiedItems.Count();
|
||||
|
||||
if (numberOfUniqueParentsInClipboard == 0) return;
|
||||
List<ITimelineItem> newItems;
|
||||
|
||||
//if the copied items were on a single parent, then use the mouse position to get the parent OR the original parent
|
||||
if (numberOfUniqueParentsInClipboard == 1)
|
||||
{
|
||||
var itemsGroup = copiedItems.First();
|
||||
TrackAsset target = null;
|
||||
if (mousePosition.HasValue)
|
||||
target = GetPickedTrack();
|
||||
if (target == null)
|
||||
target = FindSuitableParentForSingleTrackPasteWithoutMouse(itemsGroup);
|
||||
|
||||
var candidateTime = TimelineHelpers.GetCandidateTime(state, mousePosition, target);
|
||||
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, itemsGroup, target, candidateTime, "Paste Items").ToList();
|
||||
}
|
||||
//if copied items were on multiple parents, then the destination parents are the same as the original parents
|
||||
else
|
||||
{
|
||||
var time = TimelineHelpers.GetCandidateTime(state, mousePosition, copiedItems.Select(c => c.targetTrack).ToArray());
|
||||
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, copiedItems, time, "Paste Items").ToList();
|
||||
}
|
||||
|
||||
TimelineHelpers.FrameItems(state, newItems);
|
||||
SelectionManager.RemoveTimelineSelection();
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
SelectionManager.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
static TrackAsset FindSuitableParentForSingleTrackPasteWithoutMouse(ItemsPerTrack itemsGroup)
|
||||
{
|
||||
var groupParent = itemsGroup.targetTrack; //set a main parent in the clipboard
|
||||
var selectedTracks = SelectionManager.SelectedTracks();
|
||||
|
||||
if (selectedTracks.Contains(groupParent))
|
||||
{
|
||||
return groupParent;
|
||||
}
|
||||
|
||||
//find a selected track suitable for all items
|
||||
var itemsToPaste = itemsGroup.items;
|
||||
var compatibleTrack = selectedTracks.FirstOrDefault(t => IsTrackValidForItems(t, itemsToPaste));
|
||||
return compatibleTrack != null ? compatibleTrack : groupParent;
|
||||
}
|
||||
|
||||
static bool IsTrackValidForItems(TrackAsset track, IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
if (track == null || track.lockedInHierarchy) return false;
|
||||
return items.All(i => i.IsCompatibleWithTrack(track));
|
||||
}
|
||||
|
||||
static TrackAsset GetPickedTrack()
|
||||
{
|
||||
var rowGUI = PickerUtils.pickedElements.OfType<IRowGUI>().FirstOrDefault();
|
||||
if (rowGUI != null)
|
||||
return rowGUI.asset;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void PasteTracks(WindowState state)
|
||||
{
|
||||
var trackData = TimelineEditor.clipboard.GetTracks().ToList();
|
||||
if (trackData.Any())
|
||||
{
|
||||
SelectionManager.RemoveTimelineSelection();
|
||||
}
|
||||
|
||||
foreach (var track in trackData)
|
||||
{
|
||||
var newTrack = track.item.Duplicate(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, TimelineEditor.inspectedAsset);
|
||||
SelectionManager.Add(newTrack);
|
||||
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
|
||||
{
|
||||
SelectionManager.Add(childTrack);
|
||||
}
|
||||
|
||||
if (track.parent != null && track.parent.timelineAsset == state.editSequence.asset)
|
||||
{
|
||||
TrackExtensions.ReparentTracks(new List<TrackAsset> { newTrack }, track.parent, track.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Duplicate", MenuOrder.TimelineAction.Duplicate)]
|
||||
[Shortcut("Main Menu/Edit/Duplicate", EventCommandNames.Duplicate)]
|
||||
class DuplicateAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return Execute(state, (item1, item2) => ItemsUtils.TimeGapBetweenItems(item1, item2, state));
|
||||
}
|
||||
|
||||
internal bool Execute(WindowState state, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
|
||||
{
|
||||
var selectedItems = SelectionManager.SelectedItems().ToItemsPerTrack().ToList();
|
||||
if (selectedItems.Any())
|
||||
{
|
||||
var requestedTime = CalculateDuplicateTime(selectedItems, gapBetweenItems);
|
||||
var duplicatedItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(state, TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector, selectedItems, requestedTime, "Duplicate Items");
|
||||
|
||||
TimelineHelpers.FrameItems(state, duplicatedItems);
|
||||
SelectionManager.RemoveTimelineSelection();
|
||||
foreach (var item in duplicatedItems)
|
||||
SelectionManager.Add(item);
|
||||
}
|
||||
|
||||
var tracks = SelectionManager.SelectedTracks().ToArray();
|
||||
if (tracks.Length > 0)
|
||||
TrackAction.Invoke<DuplicateTracks>(state, tracks);
|
||||
|
||||
state.Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
static double CalculateDuplicateTime(IEnumerable<ItemsPerTrack> duplicatedItems, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
|
||||
{
|
||||
//Find the end time of the rightmost item
|
||||
var itemsOnTracks = duplicatedItems.SelectMany(i => i.targetTrack.GetItems()).ToList();
|
||||
var time = itemsOnTracks.Max(i => i.end);
|
||||
|
||||
//From all the duplicated items, select the leftmost items
|
||||
var firstDuplicatedItems = duplicatedItems.Select(i => i.leftMostItem);
|
||||
var leftMostDuplicatedItems = firstDuplicatedItems.OrderBy(i => i.start).GroupBy(i => i.start).FirstOrDefault();
|
||||
if (leftMostDuplicatedItems == null) return 0.0;
|
||||
|
||||
foreach (var leftMostItem in leftMostDuplicatedItems)
|
||||
{
|
||||
var siblings = leftMostItem.parentTrack.GetItems();
|
||||
var rightMostSiblings = siblings.OrderByDescending(i => i.end).GroupBy(i => i.end).FirstOrDefault();
|
||||
if (rightMostSiblings == null) continue;
|
||||
|
||||
foreach (var sibling in rightMostSiblings)
|
||||
time = Math.Max(time, sibling.end + gapBetweenItems(leftMostItem, sibling));
|
||||
}
|
||||
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Delete", MenuOrder.TimelineAction.Delete)]
|
||||
[Shortcut("Main Menu/Edit/Delete", EventCommandNames.Delete)]
|
||||
[ShortcutPlatformOverride(RuntimePlatform.OSXEditor, KeyCode.Backspace, ShortcutModifiers.Action)]
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
class DeleteAction : TimelineAction
|
||||
{
|
||||
public override MenuActionDisplayState GetDisplayState(WindowState state)
|
||||
{
|
||||
return CanDelete(state) ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
static bool CanDelete(WindowState state)
|
||||
{
|
||||
if (state.editSequence.isReadOnly)
|
||||
return false;
|
||||
// All() returns true when empty
|
||||
return SelectionManager.SelectedTracks().All(x => !x.lockedInHierarchy) &&
|
||||
SelectionManager.SelectedItems().All(x => x.parentTrack == null || !x.parentTrack.lockedInHierarchy);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (SelectionManager.GetCurrentInlineEditorCurve() != null)
|
||||
return false;
|
||||
|
||||
if (!CanDelete(state))
|
||||
return false;
|
||||
|
||||
var selectedItems = SelectionManager.SelectedItems();
|
||||
DeleteItems(selectedItems);
|
||||
|
||||
var tracks = SelectionManager.SelectedTracks().ToArray();
|
||||
if (tracks.Any())
|
||||
TrackAction.Invoke<DeleteTracks>(state, tracks);
|
||||
|
||||
state.Refresh();
|
||||
return selectedItems.Any() || tracks.Length > 0;
|
||||
}
|
||||
|
||||
internal static void DeleteItems(IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
var tracks = items.GroupBy(c => c.parentTrack);
|
||||
|
||||
foreach (var track in tracks)
|
||||
TimelineUndo.PushUndo(track.Key, "Delete Items");
|
||||
|
||||
TimelineAnimationUtilities.UnlinkAnimationWindowFromClips(items.OfType<ClipItem>().Select(i => i.clip));
|
||||
|
||||
EditMode.PrepareItemsDelete(ItemsUtils.ToItemsPerTrack(items));
|
||||
EditModeUtils.Delete(items);
|
||||
|
||||
SelectionManager.RemoveAllClips();
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Match Content", MenuOrder.TimelineAction.MatchContent)]
|
||||
[Shortcut(Shortcuts.Timeline.matchContent)]
|
||||
class MatchContent : TimelineAction
|
||||
{
|
||||
public override MenuActionDisplayState GetDisplayState(WindowState state)
|
||||
{
|
||||
var clips = SelectionManager.SelectedClips().ToArray();
|
||||
|
||||
if (!clips.Any() || SelectionManager.GetCurrentInlineEditorCurve() != null)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
return clips.Any(TimelineHelpers.HasUsableAssetDuration)
|
||||
? MenuActionDisplayState.Visible
|
||||
: MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (SelectionManager.GetCurrentInlineEditorCurve() != null)
|
||||
return false;
|
||||
|
||||
var clips = SelectionManager.SelectedClips().ToArray();
|
||||
return clips.Length > 0 && ClipModifier.MatchContent(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.play)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PlayTimelineAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
var currentState = state.playing;
|
||||
state.SetPlaying(!currentState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectAllAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
// otherwise select all tracks.
|
||||
SelectionManager.Clear();
|
||||
state.GetWindow().allTracks.ForEach(x => SelectionManager.Add(x.track));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.previousFrame)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PreviousFrameAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
state.editSequence.frame--;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.nextFrame)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class NextFrameAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
state.editSequence.frame++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.frameAll)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FrameAllAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
if (inlineCurveEditor != null && inlineCurveEditor.inlineCurvesSelected)
|
||||
{
|
||||
FrameSelectedAction.FrameInlineCurves(inlineCurveEditor, state, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.IsEditingASubItem())
|
||||
return false;
|
||||
|
||||
var w = state.GetWindow();
|
||||
if (w == null || w.treeView == null)
|
||||
return false;
|
||||
|
||||
var visibleTracks = w.treeView.visibleTracks.ToList();
|
||||
if (state.editSequence.asset != null && state.editSequence.asset.markerTrack != null)
|
||||
visibleTracks.Add(state.editSequence.asset.markerTrack);
|
||||
|
||||
if (visibleTracks.Count == 0)
|
||||
return false;
|
||||
|
||||
var startTime = float.MaxValue;
|
||||
var endTime = float.MinValue;
|
||||
|
||||
foreach (var t in visibleTracks)
|
||||
{
|
||||
if (t == null)
|
||||
continue;
|
||||
|
||||
double trackStart, trackEnd;
|
||||
t.GetItemRange(out trackStart, out trackEnd);
|
||||
startTime = Mathf.Min(startTime, (float)trackStart);
|
||||
endTime = Mathf.Max(endTime, (float)(trackEnd));
|
||||
}
|
||||
|
||||
if (startTime != float.MinValue)
|
||||
{
|
||||
FrameSelectedAction.FrameRange(startTime, endTime, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FrameSelectedAction : TimelineAction
|
||||
{
|
||||
public static void FrameRange(float startTime, float endTime, WindowState state)
|
||||
{
|
||||
if (startTime > endTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var halfDuration = endTime - Math.Max(0.0f, startTime);
|
||||
|
||||
if (halfDuration > 0.0f)
|
||||
{
|
||||
state.SetTimeAreaShownRange(Mathf.Max(-10.0f, startTime - (halfDuration * 0.1f)),
|
||||
endTime + (halfDuration * 0.1f));
|
||||
}
|
||||
else
|
||||
{
|
||||
// start == end
|
||||
// keep the zoom level constant, only pan the time area to center the item
|
||||
var currentRange = state.timeAreaShownRange.y - state.timeAreaShownRange.x;
|
||||
state.SetTimeAreaShownRange(startTime - currentRange / 2, startTime + currentRange / 2);
|
||||
}
|
||||
|
||||
TimelineZoomManipulator.InvalidateWheelZoom();
|
||||
state.Evaluate();
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
if (inlineCurveEditor != null && inlineCurveEditor.inlineCurvesSelected)
|
||||
{
|
||||
FrameInlineCurves(inlineCurveEditor, state, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (state.IsEditingASubItem())
|
||||
return false;
|
||||
|
||||
if (SelectionManager.Count() == 0)
|
||||
return false;
|
||||
|
||||
var startTime = float.MaxValue;
|
||||
var endTime = float.MinValue;
|
||||
|
||||
var clips = SelectionManager.SelectedClipGUI();
|
||||
var markers = SelectionManager.SelectedMarkers();
|
||||
if (!clips.Any() && !markers.Any())
|
||||
return false;
|
||||
|
||||
foreach (var c in clips)
|
||||
{
|
||||
startTime = Mathf.Min(startTime, (float)c.clip.start);
|
||||
endTime = Mathf.Max(endTime, (float)c.clip.end);
|
||||
if (c.clipCurveEditor != null)
|
||||
{
|
||||
c.clipCurveEditor.FrameClip();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var marker in markers)
|
||||
{
|
||||
startTime = Mathf.Min(startTime, (float)marker.time);
|
||||
endTime = Mathf.Max(endTime, (float)marker.time);
|
||||
}
|
||||
|
||||
FrameRange(startTime, endTime, state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void FrameInlineCurves(IClipCurveEditorOwner curveEditorOwner, WindowState state, bool selectionOnly)
|
||||
{
|
||||
var curveEditor = curveEditorOwner.clipCurveEditor.curveEditor;
|
||||
var frameBounds = selectionOnly ? curveEditor.GetSelectionBounds() : curveEditor.GetClipBounds();
|
||||
|
||||
var clipGUI = curveEditorOwner as TimelineClipGUI;
|
||||
var areaOffset = 0.0f;
|
||||
|
||||
if (clipGUI != null)
|
||||
{
|
||||
areaOffset = (float)Math.Max(0.0, clipGUI.clip.FromLocalTimeUnbound(0.0));
|
||||
|
||||
var timeScale = (float)clipGUI.clip.timeScale; // Note: The getter for clip.timeScale is guaranteed to never be zero.
|
||||
|
||||
// Apply scaling
|
||||
var newMin = frameBounds.min.x / timeScale;
|
||||
var newMax = (frameBounds.max.x - frameBounds.min.x) / timeScale + newMin;
|
||||
|
||||
frameBounds.SetMinMax(
|
||||
new Vector3(newMin, frameBounds.min.y, frameBounds.min.z),
|
||||
new Vector3(newMax, frameBounds.max.y, frameBounds.max.z));
|
||||
}
|
||||
|
||||
curveEditor.Frame(frameBounds, true, true);
|
||||
|
||||
var area = curveEditor.shownAreaInsideMargins;
|
||||
area.x += areaOffset;
|
||||
|
||||
var curveStart = curveEditorOwner.clipCurveEditor.dataSource.start;
|
||||
FrameRange(curveStart + frameBounds.min.x, curveStart + frameBounds.max.x, state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.previousKey)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PrevKeyAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
var keyTraverser = new Utilities.KeyTraverser(state.editSequence.asset, 0.01f / state.referenceSequence.frameRate);
|
||||
var time = keyTraverser.GetPrevKey((float)state.editSequence.time, state.dirtyStamp);
|
||||
if (time != state.editSequence.time)
|
||||
{
|
||||
state.editSequence.time = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.nextKey)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class NextKeyAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
var keyTraverser = new Utilities.KeyTraverser(state.editSequence.asset, 0.01f / state.referenceSequence.frameRate);
|
||||
var time = keyTraverser.GetNextKey((float)state.editSequence.time, state.dirtyStamp);
|
||||
if (time != state.editSequence.time)
|
||||
{
|
||||
state.editSequence.time = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.goToStart)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class GotoStartAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
state.editSequence.time = 0.0f;
|
||||
state.EnsurePlayHeadIsVisible();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.goToEnd)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class GotoEndAction : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
state.editSequence.time = state.editSequence.duration;
|
||||
state.EnsurePlayHeadIsVisible();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.zoomIn)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ZoomIn : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
TimelineZoomManipulator.Instance.DoZoom(1.15f, state);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.zoomOut)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ZoomOut : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
TimelineZoomManipulator.Instance.DoZoom(0.85f, state);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.collapseGroup)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class CollapseGroup : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.CollapseGroup(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.unCollapseGroup)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class UnCollapseGroup : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.UnCollapseGroup(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectLeftItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectLeftClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
// Switches to track header if no left track exists
|
||||
return KeyboardNavigation.SelectLeftItem(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectRightItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectRightClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectRightItem(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectUpItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectUpClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpItem(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectUpTrack)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectUpTrack : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpTrack();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectDownItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectDownClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectDownItem(state);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectDownTrack)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectDownTrack : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (!KeyboardNavigation.ClipAreaActive() && !KeyboardNavigation.TrackHeadActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem(state);
|
||||
else
|
||||
return KeyboardNavigation.SelectDownTrack();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectLeft)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectLeftClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectLeftItem(state, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectRight)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectRightClip : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectRightItem(state, true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectUp)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectUpTrack : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpTrack(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectDown)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectDownTrack : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
return KeyboardNavigation.SelectDownTrack(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.toggleClipTrackArea)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ToggleClipTrackArea : TimelineAction
|
||||
{
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (KeyboardNavigation.TrackHeadActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem(state, SelectionManager.SelectedTracks());
|
||||
|
||||
if (!KeyboardNavigation.ClipAreaActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem(state);
|
||||
|
||||
var item = KeyboardNavigation.GetVisibleSelectedItems().LastOrDefault();
|
||||
if (item != null)
|
||||
SelectionManager.SelectOnly(item.parentTrack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Mute", MenuOrder.TrackAction.MuteTrack)]
|
||||
class ToggleMuteMarkersOnTimeline : MarkerHeaderAction
|
||||
{
|
||||
public override bool IsChecked(WindowState state)
|
||||
{
|
||||
return IsMarkerTrackValid(state) && state.editSequence.asset.markerTrack.muted;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
if (state.showMarkerHeader)
|
||||
ToggleMute(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ToggleMute(WindowState state)
|
||||
{
|
||||
var timeline = state.editSequence.asset;
|
||||
timeline.CreateMarkerTrack();
|
||||
|
||||
TimelineUndo.PushUndo(timeline.markerTrack, "Toggle Mute");
|
||||
timeline.markerTrack.muted = !timeline.markerTrack.muted;
|
||||
}
|
||||
|
||||
static bool IsMarkerTrackValid(WindowState state)
|
||||
{
|
||||
var timeline = state.editSequence.asset;
|
||||
return timeline != null && timeline.markerTrack != null;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Show Markers", MenuOrder.TrackAction.ShowHideMarkers)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ToggleShowMarkersOnTimeline : MarkerHeaderAction
|
||||
{
|
||||
public override bool IsChecked(WindowState state)
|
||||
{
|
||||
return state.showMarkerHeader;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state)
|
||||
{
|
||||
ToggleShow(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ToggleShow(WindowState state)
|
||||
{
|
||||
state.GetWindow().SetShowMarkerHeader(!state.showMarkerHeader);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b1c789407b55e3a4c9cc86135a714e33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,521 @@
|
|||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
abstract class TrackAction : MenuItemActionBase
|
||||
{
|
||||
public abstract bool Execute(WindowState state, TrackAsset[] tracks);
|
||||
|
||||
protected virtual MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.Length > 0 ? MenuActionDisplayState.Visible : MenuActionDisplayState.Disabled;
|
||||
}
|
||||
|
||||
protected virtual bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual string GetDisplayName(TrackAsset[] tracks)
|
||||
{
|
||||
return menuName;
|
||||
}
|
||||
|
||||
public static void Invoke<T>(WindowState state, TrackAsset[] tracks) where T : TrackAction
|
||||
{
|
||||
actions.First(x => x.GetType() == typeof(T)).Execute(state, tracks);
|
||||
}
|
||||
|
||||
static List<TrackAction> s_ActionClasses;
|
||||
|
||||
static List<TrackAction> actions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_ActionClasses == null)
|
||||
s_ActionClasses =
|
||||
GetActionsOfType(typeof(TrackAction))
|
||||
.Select(x => (TrackAction)x.GetConstructors()[0].Invoke(null))
|
||||
.OrderBy(x => x.priority).ThenBy(x => x.category)
|
||||
.ToList();
|
||||
|
||||
return s_ActionClasses;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(WindowState state, Vector2? mousePos, TrackAsset[] tracks, List<MenuActionItem> items)
|
||||
{
|
||||
var mode = TimelineWindow.instance.currentMode.mode;
|
||||
foreach (var action in actions)
|
||||
{
|
||||
if (!action.showInMenu)
|
||||
continue;
|
||||
|
||||
var actionItem = action;
|
||||
items.Add(
|
||||
new MenuActionItem()
|
||||
{
|
||||
category = action.category,
|
||||
entryName = action.GetDisplayName(tracks),
|
||||
shortCut = action.shortCut,
|
||||
isChecked = action.IsChecked(state, tracks),
|
||||
isActiveInMode = IsActionActiveInMode(action, mode),
|
||||
priority = action.priority,
|
||||
state = action.GetDisplayState(state, tracks),
|
||||
callback = () =>
|
||||
{
|
||||
actionItem.mousePosition = mousePos;
|
||||
actionItem.Execute(state, tracks);
|
||||
actionItem.mousePosition = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HandleShortcut(WindowState state, Event evt, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var action in actions)
|
||||
{
|
||||
var attr = action.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true);
|
||||
|
||||
foreach (ShortcutAttribute shortcut in attr)
|
||||
{
|
||||
if (shortcut.MatchesEvent(evt))
|
||||
{
|
||||
if (s_ShowActionTriggeredByShortcut)
|
||||
Debug.Log(action.GetType().Name);
|
||||
|
||||
if (!IsActionActiveInMode(action, TimelineWindow.instance.currentMode.mode))
|
||||
return false;
|
||||
|
||||
return action.Execute(state, tracks);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// For testing
|
||||
internal MenuActionDisplayState InternalGetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return GetDisplayState(state, tracks);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Edit in Animation Window", MenuOrder.TrackAction.EditInAnimationWindow)]
|
||||
class EditTrackInAnimationWindow : TrackAction
|
||||
{
|
||||
public static bool Do(WindowState state, TrackAsset track)
|
||||
{
|
||||
AnimationClip clipToEdit = null;
|
||||
|
||||
AnimationTrack animationTrack = track as AnimationTrack;
|
||||
if (animationTrack != null)
|
||||
{
|
||||
if (!animationTrack.CanConvertToClipMode())
|
||||
return false;
|
||||
|
||||
clipToEdit = animationTrack.infiniteClip;
|
||||
}
|
||||
else if (track.hasCurves)
|
||||
{
|
||||
clipToEdit = track.curves;
|
||||
}
|
||||
|
||||
if (clipToEdit == null)
|
||||
return false;
|
||||
|
||||
var gameObject = state.GetSceneReference(track);
|
||||
var timeController = TimelineAnimationUtilities.CreateTimeController(state, CreateTimeControlClipData(track));
|
||||
TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks[0] is AnimationTrack)
|
||||
{
|
||||
var animTrack = tracks[0] as AnimationTrack;
|
||||
if (animTrack.CanConvertToClipMode())
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
else if (tracks[0].hasCurves)
|
||||
{
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return Do(state, tracks[0]);
|
||||
}
|
||||
|
||||
static TimelineWindowTimeControl.ClipData CreateTimeControlClipData(TrackAsset track)
|
||||
{
|
||||
var data = new TimelineWindowTimeControl.ClipData();
|
||||
data.track = track;
|
||||
data.start = track.start;
|
||||
data.duration = track.duration;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Lock selected track only", MenuOrder.TrackAction.LockSelected)]
|
||||
class LockSelectedTrack : TrackAction
|
||||
{
|
||||
public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only");
|
||||
public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only");
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack ||
|
||||
!track.subTracksObjects.Any()))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||||
Lock(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override string GetDisplayName(TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText;
|
||||
}
|
||||
|
||||
public static void Lock(WindowState state, TrackAsset[] tracks, bool shouldlock)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t)))
|
||||
{
|
||||
TimelineUndo.PushUndo(track, "Lock Tracks");
|
||||
track.locked = shouldlock;
|
||||
}
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Lock", MenuOrder.TrackAction.LockTrack)]
|
||||
[Shortcut(Shortcuts.Timeline.toggleLock)]
|
||||
class LockTrack : TrackAction
|
||||
{
|
||||
public static readonly string UnlockText = L10n.Tr("Unlock");
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
bool hasUnlockableTracks = tracks.Any(x => TimelineUtility.IsLockedFromGroup(x));
|
||||
if (hasUnlockableTracks)
|
||||
return MenuActionDisplayState.Disabled;
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
protected override string GetDisplayName(TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.Any(x => !x.locked) ? base.GetDisplayName(tracks) : UnlockText;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||||
SetLockState(tracks, hasUnlockedTracks, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SetLockState(TrackAsset[] tracks, bool shouldLock, WindowState state = null)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (TimelineUtility.IsLockedFromGroup(track))
|
||||
continue;
|
||||
|
||||
if (track as GroupTrack == null)
|
||||
SetLockState(track.GetChildTracks().ToArray(), shouldLock, state);
|
||||
|
||||
TimelineUndo.PushUndo(track, "Lock Tracks");
|
||||
track.locked = shouldLock;
|
||||
}
|
||||
|
||||
if (state != null)
|
||||
{
|
||||
// find the tracks we've locked. unselect anything locked and remove recording.
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (TimelineUtility.IsLockedFromGroup(track) || !track.locked)
|
||||
continue;
|
||||
|
||||
var flattenedChildTracks = track.GetFlattenedChildTracks();
|
||||
foreach (var i in track.clips)
|
||||
SelectionManager.Remove(i);
|
||||
state.UnarmForRecord(track);
|
||||
foreach (var child in flattenedChildTracks)
|
||||
{
|
||||
SelectionManager.Remove(child);
|
||||
state.UnarmForRecord(child);
|
||||
foreach (var clip in child.GetClips())
|
||||
SelectionManager.Remove(clip);
|
||||
}
|
||||
}
|
||||
|
||||
// no need to rebuild, just repaint (including inspectors)
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
state.editorWindow.Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[MenuEntry("Show Markers", MenuOrder.TrackAction.ShowHideMarkers)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ShowHideMarkers : TrackAction
|
||||
{
|
||||
protected override bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.All(x => x.GetShowMarkers());
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers());
|
||||
ShowHide(state, tracks, hasUnlockedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ShowHide(WindowState state, TrackAsset[] tracks, bool shouldLock)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
var window = state.GetWindow();
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
window.SetShowTrackMarkers(track, shouldLock);
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Mute selected track only", MenuOrder.TrackAction.MuteSelected), UsedImplicitly]
|
||||
class MuteSelectedTrack : TrackAction
|
||||
{
|
||||
public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only");
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack ||
|
||||
!track.subTracksObjects.Any()))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return false;
|
||||
|
||||
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||||
Mute(state, tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override string GetDisplayName(TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.All(t => t.muted) ? UnmuteSelectedText : base.GetDisplayName(tracks);
|
||||
}
|
||||
|
||||
public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t)))
|
||||
{
|
||||
TimelineUndo.PushUndo(track, "Mute Tracks");
|
||||
track.muted = shouldMute;
|
||||
}
|
||||
|
||||
state.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Mute", MenuOrder.TrackAction.MuteTrack)]
|
||||
[Shortcut(Shortcuts.Timeline.toggleMute)]
|
||||
class MuteTrack : TrackAction
|
||||
{
|
||||
public static readonly string UnMuteText = L10n.Tr("Unmute");
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(track => TimelineUtility.IsParentMuted(track)))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
protected override string GetDisplayName(TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.Any(x => !x.muted) ? base.GetDisplayName(tracks) : UnMuteText;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (!tracks.Any() || tracks.Any(track => TimelineUtility.IsParentMuted(track)))
|
||||
return false;
|
||||
|
||||
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||||
Mute(state, tracks, hasUnmutedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Mute(WindowState state, TrackAsset[] tracks, bool shouldMute)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (track as GroupTrack == null)
|
||||
Mute(state, track.GetChildTracks().ToArray(), shouldMute);
|
||||
TimelineUndo.PushUndo(track, "Mute Tracks");
|
||||
track.muted = shouldMute;
|
||||
}
|
||||
|
||||
state.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteTracks : TrackAction
|
||||
{
|
||||
public static void Do(TimelineAsset timeline, TrackAsset track)
|
||||
{
|
||||
SelectionManager.Remove(track);
|
||||
TrackModifier.DeleteTrack(timeline, track);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
// disable preview mode so deleted tracks revert to default state
|
||||
// Case 956129: Disable preview mode _before_ deleting the tracks, since clip data is still needed
|
||||
state.previewMode = false;
|
||||
|
||||
TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks);
|
||||
|
||||
foreach (var track in tracks)
|
||||
Do(state.editSequence.asset, track);
|
||||
|
||||
state.Refresh();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CopyTracksToClipboard : TrackAction
|
||||
{
|
||||
public static bool Do(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
var action = new CopyTracksToClipboard();
|
||||
|
||||
return action.Execute(state, tracks);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
TimelineEditor.clipboard.CopyTracks(tracks);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicateTracks : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any())
|
||||
{
|
||||
SelectionManager.RemoveTimelineSelection();
|
||||
}
|
||||
|
||||
foreach (var track in TrackExtensions.FilterTracks(tracks))
|
||||
{
|
||||
var newTrack = track.Duplicate(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector);
|
||||
SelectionManager.Add(newTrack);
|
||||
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
|
||||
{
|
||||
SelectionManager.Add(childTrack);
|
||||
}
|
||||
}
|
||||
|
||||
state.Refresh();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Remove Invalid Markers", MenuOrder.TrackAction.RemoveInvalidMarkers), UsedImplicitly]
|
||||
class RemoveInvalidMarkersAction : TrackAction
|
||||
{
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count()))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
bool anyRemoved = false;
|
||||
foreach (var target in tracks)
|
||||
{
|
||||
var invalids = target.GetMarkersRaw().Where(x => !(x is IMarker)).ToList();
|
||||
foreach (var m in invalids)
|
||||
{
|
||||
anyRemoved = true;
|
||||
target.DeleteMarkerRaw(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (anyRemoved)
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return anyRemoved;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fda82b5ca7a4c5f40b497c4f5f4bd950
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b124f0b8ca43e6e46bdc0322fad15ea3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,56 @@
|
|||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[UsedImplicitly]
|
||||
[CustomTimelineEditor(typeof(ActivationTrack))]
|
||||
class ActivationTrackEditor : TrackEditor
|
||||
{
|
||||
static readonly string ClipText = LocalizationDatabase.GetLocalizedString("Active");
|
||||
|
||||
static readonly string k_ErrorParentString = LocalizationDatabase.GetLocalizedString("The bound GameObject is a parent of the PlayableDirector.");
|
||||
static readonly string k_ErrorString = LocalizationDatabase.GetLocalizedString("The bound GameObject contains the PlayableDirector.");
|
||||
|
||||
public override TrackDrawOptions GetTrackOptions(TrackAsset track, Object binding)
|
||||
{
|
||||
var options = base.GetTrackOptions(track, binding);
|
||||
options.errorText = GetErrorText(track, binding);
|
||||
return options;
|
||||
}
|
||||
|
||||
string GetErrorText(TrackAsset track, Object binding)
|
||||
{
|
||||
var gameObject = binding as GameObject;
|
||||
var currentDirector = TimelineEditor.inspectedDirector;
|
||||
if (gameObject != null && currentDirector != null)
|
||||
{
|
||||
var director = gameObject.GetComponent<PlayableDirector>();
|
||||
if (currentDirector == director)
|
||||
{
|
||||
return k_ErrorString;
|
||||
}
|
||||
|
||||
if (currentDirector.gameObject.transform.IsChildOf(gameObject.transform))
|
||||
{
|
||||
return k_ErrorParentString;
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetErrorText(track, binding, TrackBindingErrors.PrefabBound);
|
||||
}
|
||||
|
||||
public override void OnCreate(TrackAsset track, TrackAsset copiedFrom)
|
||||
{
|
||||
// Add a default clip to the newly created track
|
||||
if (copiedFrom == null)
|
||||
{
|
||||
var clip = track.CreateClip(0);
|
||||
clip.displayName = ClipText;
|
||||
clip.duration = System.Math.Max(clip.duration, track.timelineAsset.duration * 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4fbcc9b1f6ace8c4f8724a88dccca5f8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,43 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(ActivationTrack))]
|
||||
class ActivationTrackInspector : TrackAssetInspector
|
||||
{
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent PostPlaybackStateText = EditorGUIUtility.TrTextContent("Post-playback state");
|
||||
}
|
||||
|
||||
SerializedProperty m_PostPlaybackProperty;
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
using (new EditorGUI.DisabledScope(IsTrackLocked()))
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
if (m_PostPlaybackProperty != null)
|
||||
EditorGUILayout.PropertyField(m_PostPlaybackProperty, Styles.PostPlaybackStateText);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
var activationTrack = target as ActivationTrack;
|
||||
if (activationTrack != null)
|
||||
activationTrack.UpdateTrackMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
m_PostPlaybackProperty = serializedObject.FindProperty("m_PostPlaybackState");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c46b007a3762fc84cb1ee7ca30060f0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 57ff740bce4ab0c498ada374a8ca1dc0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Build;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Analytics
|
||||
{
|
||||
class TimelineSceneInfo
|
||||
{
|
||||
public Dictionary<string, int> trackCount = new Dictionary<string, int>
|
||||
{
|
||||
{"ActivationTrack", 0},
|
||||
{"AnimationTrack", 0},
|
||||
{"AudioTrack", 0},
|
||||
{"ControlTrack", 0},
|
||||
{"PlayableTrack", 0},
|
||||
{"UserType", 0},
|
||||
{"Other", 0}
|
||||
};
|
||||
|
||||
public Dictionary<string, int> userTrackTypesCount = new Dictionary<string, int>();
|
||||
public HashSet<TimelineAsset> uniqueDirectors = new HashSet<TimelineAsset>();
|
||||
public int numTracks = 0;
|
||||
public int minDuration = int.MaxValue;
|
||||
public int maxDuration = int.MinValue;
|
||||
public int minNumTracks = int.MaxValue;
|
||||
public int maxNumTracks = int.MinValue;
|
||||
public int numRecorded = 0;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct TrackInfo
|
||||
{
|
||||
public string name;
|
||||
public double percent;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class TimelineEventInfo
|
||||
{
|
||||
public int num_timelines;
|
||||
public int min_duration, max_duration;
|
||||
public int min_num_tracks, max_num_tracks;
|
||||
public double recorded_percent;
|
||||
public List<TrackInfo> track_info = new List<TrackInfo>();
|
||||
public string most_popular_user_track = string.Empty;
|
||||
|
||||
public TimelineEventInfo(TimelineSceneInfo sceneInfo)
|
||||
{
|
||||
num_timelines = sceneInfo.uniqueDirectors.Count;
|
||||
min_duration = sceneInfo.minDuration;
|
||||
max_duration = sceneInfo.maxDuration;
|
||||
min_num_tracks = sceneInfo.minNumTracks;
|
||||
max_num_tracks = sceneInfo.maxNumTracks;
|
||||
recorded_percent = Math.Round(100.0 * sceneInfo.numRecorded / sceneInfo.numTracks, 1);
|
||||
|
||||
foreach (KeyValuePair<string, int> kv in sceneInfo.trackCount.Where(x => x.Value > 0))
|
||||
{
|
||||
track_info.Add(new TrackInfo()
|
||||
{
|
||||
name = kv.Key,
|
||||
percent = Math.Round(100.0 * kv.Value / sceneInfo.numTracks, 1)
|
||||
});
|
||||
}
|
||||
|
||||
if (sceneInfo.userTrackTypesCount.Any())
|
||||
{
|
||||
most_popular_user_track = sceneInfo.userTrackTypesCount
|
||||
.First(x => x.Value == sceneInfo.userTrackTypesCount.Values.Max()).Key;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsUserType(Type t)
|
||||
{
|
||||
string nameSpace = t.Namespace;
|
||||
return string.IsNullOrEmpty(nameSpace) || !nameSpace.StartsWith("UnityEngine.Timeline");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class TimelineAnalytics
|
||||
{
|
||||
static TimelineSceneInfo _timelineSceneInfo = new TimelineSceneInfo();
|
||||
|
||||
class TimelineAnalyticsPreProcess : IPreprocessBuildWithReport
|
||||
{
|
||||
public int callbackOrder { get { return 0; } }
|
||||
public void OnPreprocessBuild(BuildReport report)
|
||||
{
|
||||
_timelineSceneInfo = new TimelineSceneInfo();
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineAnalyticsProcess : IProcessSceneWithReport
|
||||
{
|
||||
public int callbackOrder
|
||||
{
|
||||
get { return 0; }
|
||||
}
|
||||
|
||||
public void OnProcessScene(Scene scene, BuildReport report)
|
||||
{
|
||||
var timelines = UnityEngine.Object.FindObjectsOfType<PlayableDirector>().Select(pd => pd.playableAsset).OfType<TimelineAsset>().Distinct();
|
||||
|
||||
foreach (var timeline in timelines)
|
||||
{
|
||||
if (_timelineSceneInfo.uniqueDirectors.Add(timeline))
|
||||
{
|
||||
_timelineSceneInfo.numTracks += timeline.flattenedTracks.Count();
|
||||
_timelineSceneInfo.minDuration = Math.Min(_timelineSceneInfo.minDuration, (int)(timeline.duration * 1000));
|
||||
_timelineSceneInfo.maxDuration = Math.Max(_timelineSceneInfo.maxDuration, (int)(timeline.duration * 1000));
|
||||
_timelineSceneInfo.minNumTracks = Math.Min(_timelineSceneInfo.minNumTracks, timeline.flattenedTracks.Count());
|
||||
_timelineSceneInfo.maxNumTracks = Math.Max(_timelineSceneInfo.maxNumTracks, timeline.flattenedTracks.Count());
|
||||
|
||||
foreach (var track in timeline.flattenedTracks)
|
||||
{
|
||||
string key = track.GetType().Name;
|
||||
if (_timelineSceneInfo.trackCount.ContainsKey(key))
|
||||
{
|
||||
_timelineSceneInfo.trackCount[key]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TimelineEventInfo.IsUserType(track.GetType()))
|
||||
{
|
||||
_timelineSceneInfo.trackCount["UserType"]++;
|
||||
if (_timelineSceneInfo.userTrackTypesCount.ContainsKey(key))
|
||||
_timelineSceneInfo.userTrackTypesCount[key]++;
|
||||
else
|
||||
_timelineSceneInfo.userTrackTypesCount[key] = 1;
|
||||
}
|
||||
else
|
||||
_timelineSceneInfo.trackCount["Other"]++;
|
||||
}
|
||||
|
||||
if (track.clips.Any(x => x.recordable))
|
||||
_timelineSceneInfo.numRecorded++;
|
||||
else
|
||||
{
|
||||
var animationTrack = track as AnimationTrack;
|
||||
if (animationTrack != null)
|
||||
{
|
||||
if (animationTrack.CanConvertToClipMode())
|
||||
_timelineSceneInfo.numRecorded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineAnalyticsPostProcess : IPostprocessBuildWithReport
|
||||
{
|
||||
public int callbackOrder {get { return 0; }}
|
||||
public void OnPostprocessBuild(BuildReport report)
|
||||
{
|
||||
if (_timelineSceneInfo.uniqueDirectors.Count > 0)
|
||||
{
|
||||
var timelineEvent = new TimelineEventInfo(_timelineSceneInfo);
|
||||
EditorAnalytics.SendEventTimelineInfo(timelineEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 10ba9bc9317e315439b0223674162c52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3174898fbcdf12448963cdb5f5b60a33
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,96 @@
|
|||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using ClipAction = UnityEditor.Timeline.ItemAction<UnityEngine.Timeline.TimelineClip>;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Match Offsets To Previous Clip", MenuOrder.CustomClipAction.AnimClipMatchPrevious), UsedImplicitly]
|
||||
class MatchOffsetsPreviousAction : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToPrevious(state, items.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.parentTrack != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.parentTrack.clips.Any(x => x.start < clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.parentTrack) != null;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (items.Any(c => IsValidClip(c, director)))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Match Offsets To Next Clip", MenuOrder.CustomClipAction.AnimClipMatchNext), UsedImplicitly]
|
||||
class MatchOffsetsNextAction : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToNext(state, items.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.parentTrack != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.parentTrack.clips.Any(x => x.start > clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.parentTrack) != null;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (items.Any(c => IsValidClip(c, director)))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Reset Offsets", MenuOrder.CustomClipAction.AnimClipResetOffset), UsedImplicitly]
|
||||
class ResetOffsets : ClipAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
AnimationOffsetMenu.ResetClipOffsets(state, items.Where(TimelineAnimationUtilities.IsAnimationClip).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TimelineClip[] items)
|
||||
{
|
||||
if (!items.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf22284ca28e7ef4490033b61e9b52cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,436 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
struct CurveBindingPair
|
||||
{
|
||||
public EditorCurveBinding binding;
|
||||
public AnimationCurve curve;
|
||||
public ObjectReferenceKeyframe[] objectCurve;
|
||||
}
|
||||
|
||||
class CurveBindingGroup
|
||||
{
|
||||
public CurveBindingPair[] curveBindingPairs { get; set; }
|
||||
public Vector2 timeRange { get; set; }
|
||||
public Vector2 valueRange { get; set; }
|
||||
|
||||
public bool isFloatCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||||
curveBindingPairs[0].curve != null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool isObjectCurve
|
||||
{
|
||||
get
|
||||
{
|
||||
return curveBindingPairs != null && curveBindingPairs.Length > 0 &&
|
||||
curveBindingPairs[0].objectCurve != null;
|
||||
}
|
||||
}
|
||||
|
||||
public int count
|
||||
{
|
||||
get
|
||||
{
|
||||
if (curveBindingPairs == null)
|
||||
return 0;
|
||||
return curveBindingPairs.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AnimationClipCurveInfo
|
||||
{
|
||||
bool m_CurveDirty = true;
|
||||
bool m_KeysDirty = true;
|
||||
|
||||
public bool dirty
|
||||
{
|
||||
get { return m_CurveDirty; }
|
||||
set
|
||||
{
|
||||
m_CurveDirty = value;
|
||||
if (m_CurveDirty)
|
||||
{
|
||||
m_KeysDirty = true;
|
||||
if (m_groupings != null)
|
||||
m_groupings.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationCurve[] curves;
|
||||
public EditorCurveBinding[] bindings;
|
||||
|
||||
public EditorCurveBinding[] objectBindings;
|
||||
public List<ObjectReferenceKeyframe[]> objectCurves;
|
||||
|
||||
Dictionary<string, CurveBindingGroup> m_groupings;
|
||||
|
||||
// to tell whether the cache has changed
|
||||
public int version { get; private set; }
|
||||
|
||||
float[] m_KeyTimes;
|
||||
|
||||
Dictionary<EditorCurveBinding, float[]> m_individualBindinsKey;
|
||||
|
||||
public float[] keyTimes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_KeysDirty || m_KeyTimes == null)
|
||||
{
|
||||
RebuildKeyCache();
|
||||
}
|
||||
return m_KeyTimes;
|
||||
}
|
||||
}
|
||||
|
||||
public float[] GetCurveTimes(EditorCurveBinding curve)
|
||||
{
|
||||
return GetCurveTimes(new[] { curve });
|
||||
}
|
||||
|
||||
public float[] GetCurveTimes(EditorCurveBinding[] curves)
|
||||
{
|
||||
if (m_KeysDirty || m_KeyTimes == null)
|
||||
{
|
||||
RebuildKeyCache();
|
||||
}
|
||||
|
||||
var keyTimes = new List<float>();
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
var c = curves[i];
|
||||
if (m_individualBindinsKey.ContainsKey(c))
|
||||
{
|
||||
keyTimes.AddRange(m_individualBindinsKey[c]);
|
||||
}
|
||||
}
|
||||
return keyTimes.ToArray();
|
||||
}
|
||||
|
||||
void RebuildKeyCache()
|
||||
{
|
||||
m_individualBindinsKey = new Dictionary<EditorCurveBinding, float[]>();
|
||||
|
||||
List<float> keys = curves.SelectMany(y => y.keys).Select(z => z.time).ToList();
|
||||
for (int i = 0; i < objectCurves.Count; i++)
|
||||
{
|
||||
var kf = objectCurves[i];
|
||||
keys.AddRange(kf.Select(x => x.time));
|
||||
}
|
||||
|
||||
for (int b = 0; b < bindings.Count(); b++)
|
||||
{
|
||||
m_individualBindinsKey.Add(bindings[b], curves[b].keys.Select(k => k.time).Distinct().ToArray());
|
||||
}
|
||||
|
||||
m_KeyTimes = keys.OrderBy(x => x).Distinct().ToArray();
|
||||
m_KeysDirty = false;
|
||||
}
|
||||
|
||||
public void Update(AnimationClip clip)
|
||||
{
|
||||
List<EditorCurveBinding> postfilter = new List<EditorCurveBinding>();
|
||||
var clipBindings = AnimationUtility.GetCurveBindings(clip);
|
||||
for (int i = 0; i < clipBindings.Length; i++)
|
||||
{
|
||||
var bind = clipBindings[i];
|
||||
if (!bind.propertyName.Contains("LocalRotation.w"))
|
||||
postfilter.Add(RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(bind, clip));
|
||||
}
|
||||
bindings = postfilter.ToArray();
|
||||
|
||||
curves = new AnimationCurve[bindings.Length];
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
curves[i] = AnimationUtility.GetEditorCurve(clip, bindings[i]);
|
||||
}
|
||||
|
||||
objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
|
||||
objectCurves = new List<ObjectReferenceKeyframe[]>(objectBindings.Length);
|
||||
for (int i = 0; i < objectBindings.Length; i++)
|
||||
{
|
||||
objectCurves.Add(AnimationUtility.GetObjectReferenceCurve(clip, objectBindings[i]));
|
||||
}
|
||||
|
||||
m_CurveDirty = false;
|
||||
m_KeysDirty = true;
|
||||
|
||||
version = version + 1;
|
||||
}
|
||||
|
||||
public bool GetBindingForCurve(AnimationCurve curve, ref EditorCurveBinding binding)
|
||||
{
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
if (curve == curves[i])
|
||||
{
|
||||
binding = bindings[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public AnimationCurve GetCurveForBinding(EditorCurveBinding binding)
|
||||
{
|
||||
for (int i = 0; i < curves.Length; i++)
|
||||
{
|
||||
if (binding.Equals(bindings[i]))
|
||||
{
|
||||
return curves[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public ObjectReferenceKeyframe[] GetObjectCurveForBinding(EditorCurveBinding binding)
|
||||
{
|
||||
if (objectCurves == null)
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < objectCurves.Count; i++)
|
||||
{
|
||||
if (binding.Equals(objectBindings[i]))
|
||||
{
|
||||
return objectCurves[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// given a groupID, get the list of curve bindings
|
||||
public CurveBindingGroup GetGroupBinding(string groupID)
|
||||
{
|
||||
if (m_groupings == null)
|
||||
m_groupings = new Dictionary<string, CurveBindingGroup>();
|
||||
|
||||
CurveBindingGroup result = null;
|
||||
if (!m_groupings.TryGetValue(groupID, out result))
|
||||
{
|
||||
result = new CurveBindingGroup();
|
||||
result.timeRange = new Vector2(float.MaxValue, float.MinValue);
|
||||
result.valueRange = new Vector2(float.MaxValue, float.MinValue);
|
||||
List<CurveBindingPair> found = new List<CurveBindingPair>();
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
if (bindings[i].GetGroupID() == groupID)
|
||||
{
|
||||
CurveBindingPair pair = new CurveBindingPair();
|
||||
pair.binding = bindings[i];
|
||||
pair.curve = curves[i];
|
||||
found.Add(pair);
|
||||
|
||||
for (int k = 0; k < curves[i].keys.Length; k++)
|
||||
{
|
||||
var key = curves[i].keys[k];
|
||||
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||||
result.valueRange = new Vector2(Mathf.Min(key.value, result.valueRange.x), Mathf.Max(key.value, result.valueRange.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < objectBindings.Length; i++)
|
||||
{
|
||||
if (objectBindings[i].GetGroupID() == groupID)
|
||||
{
|
||||
CurveBindingPair pair = new CurveBindingPair();
|
||||
pair.binding = objectBindings[i];
|
||||
pair.objectCurve = objectCurves[i];
|
||||
found.Add(pair);
|
||||
|
||||
for (int k = 0; k < objectCurves[i].Length; k++)
|
||||
{
|
||||
var key = objectCurves[i][k];
|
||||
result.timeRange = new Vector2(Mathf.Min(key.time, result.timeRange.x), Mathf.Max(key.time, result.timeRange.y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.curveBindingPairs = found.OrderBy(x => AnimationWindowUtility.GetComponentIndex(x.binding.propertyName)).ToArray();
|
||||
|
||||
m_groupings.Add(groupID, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for storing the animation clip data
|
||||
class AnimationClipCurveCache
|
||||
{
|
||||
static AnimationClipCurveCache s_Instance;
|
||||
Dictionary<AnimationClip, AnimationClipCurveInfo> m_ClipCache = new Dictionary<AnimationClip, AnimationClipCurveInfo>();
|
||||
bool m_IsEnabled;
|
||||
|
||||
|
||||
public static AnimationClipCurveCache Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
s_Instance = new AnimationClipCurveCache();
|
||||
}
|
||||
|
||||
return s_Instance;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
if (!m_IsEnabled)
|
||||
{
|
||||
AnimationUtility.onCurveWasModified += OnCurveWasModified;
|
||||
m_IsEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
if (m_IsEnabled)
|
||||
{
|
||||
AnimationUtility.onCurveWasModified -= OnCurveWasModified;
|
||||
m_IsEnabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// callback when a curve is edited. Force the cache to update next time it's accessed
|
||||
void OnCurveWasModified(AnimationClip clip, EditorCurveBinding binding, AnimationUtility.CurveModifiedType modification)
|
||||
{
|
||||
if (modification == AnimationUtility.CurveModifiedType.CurveDeleted)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationClipCurveInfo data;
|
||||
if (m_ClipCache.TryGetValue(clip, out data))
|
||||
{
|
||||
data.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnimationClipCurveInfo GetCurveInfo(AnimationClip clip)
|
||||
{
|
||||
AnimationClipCurveInfo data;
|
||||
if (clip == null)
|
||||
return null;
|
||||
if (!m_ClipCache.TryGetValue(clip, out data))
|
||||
{
|
||||
data = new AnimationClipCurveInfo();
|
||||
data.dirty = true;
|
||||
m_ClipCache[clip] = data;
|
||||
}
|
||||
if (data.dirty)
|
||||
{
|
||||
data.Update(clip);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
public void ClearCachedProxyClips()
|
||||
{
|
||||
var toRemove = new List<AnimationClip>();
|
||||
foreach (var entry in m_ClipCache)
|
||||
{
|
||||
var clip = entry.Key;
|
||||
if (clip != null && (clip.hideFlags & HideFlags.HideAndDontSave) == HideFlags.HideAndDontSave)
|
||||
toRemove.Add(clip);
|
||||
}
|
||||
|
||||
foreach (var clip in toRemove)
|
||||
{
|
||||
m_ClipCache.Remove(clip);
|
||||
Object.DestroyImmediate(clip, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ClearCachedProxyClips();
|
||||
m_ClipCache.Clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class EditorCurveBindingExtension
|
||||
{
|
||||
// identifier to generate an id thats the same for all curves in the same group
|
||||
public static string GetGroupID(this EditorCurveBinding binding)
|
||||
{
|
||||
return binding.type + AnimationWindowUtility.GetPropertyGroupName(binding.propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class CurveBindingGroupExtensions
|
||||
{
|
||||
// Extentions to determine curve types
|
||||
public static bool IsEnableGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
return curves.isFloatCurve && curves.count == 1 && curves.curveBindingPairs[0].binding.propertyName == "m_Enabled";
|
||||
}
|
||||
|
||||
public static bool IsVectorGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
if (!curves.isFloatCurve)
|
||||
return false;
|
||||
if (curves.count <= 1 || curves.count > 4)
|
||||
return false;
|
||||
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||||
return l == 'x' || l == 'y' || l == 'z' || l == 'w';
|
||||
}
|
||||
|
||||
public static bool IsColorGroup(this CurveBindingGroup curves)
|
||||
{
|
||||
if (!curves.isFloatCurve)
|
||||
return false;
|
||||
if (curves.count != 3 && curves.count != 4)
|
||||
return false;
|
||||
char l = curves.curveBindingPairs[0].binding.propertyName.Last();
|
||||
return l == 'r' || l == 'g' || l == 'b' || l == 'a';
|
||||
}
|
||||
|
||||
public static string GetDescription(this CurveBindingGroup group, float t)
|
||||
{
|
||||
string result = string.Empty;
|
||||
if (group.isFloatCurve)
|
||||
{
|
||||
if (group.count > 1)
|
||||
{
|
||||
result += "(" + group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||||
for (int j = 1; j < group.curveBindingPairs.Length; j++)
|
||||
{
|
||||
result += "," + group.curveBindingPairs[j].curve.Evaluate(t).ToString("0.##");
|
||||
}
|
||||
result += ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
result = group.curveBindingPairs[0].curve.Evaluate(t).ToString("0.##");
|
||||
}
|
||||
}
|
||||
else if (group.isObjectCurve)
|
||||
{
|
||||
Object obj = null;
|
||||
if (group.curveBindingPairs[0].objectCurve.Length > 0)
|
||||
obj = CurveEditUtility.Evaluate(group.curveBindingPairs[0].objectCurve, t);
|
||||
result = (obj == null ? "None" : obj.name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 07a967d2fca95324f8922df8394a5655
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,82 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationOffsetMenu
|
||||
{
|
||||
public static GUIContent MatchPreviousMenuItem = EditorGUIUtility.TrTextContent("Match Offsets To Previous Clip");
|
||||
public static GUIContent MatchNextMenuItem = EditorGUIUtility.TrTextContent("Match Offsets To Next Clip");
|
||||
public static string MatchFieldsPrefix = "Match Offsets Fields/";
|
||||
public static GUIContent ResetOffsetMenuItem = EditorGUIUtility.TrTextContent("Reset Offsets");
|
||||
|
||||
static bool EnforcePreviewMode(WindowState state)
|
||||
{
|
||||
state.previewMode = true; // try and set the preview mode
|
||||
if (!state.previewMode)
|
||||
{
|
||||
Debug.LogError("Match clips cannot be completed because preview mode cannot be enabed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void MatchClipsToPrevious(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode(state))
|
||||
return;
|
||||
|
||||
clips = clips.OrderBy(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(state.editSequence.director, clip.parentTrack);
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Match Clip");
|
||||
TimelineAnimationUtilities.MatchPrevious(clip, sceneObject.transform, state.editSequence.director);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
internal static void MatchClipsToNext(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode(state))
|
||||
return;
|
||||
|
||||
clips = clips.OrderByDescending(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(state.editSequence.director, clip.parentTrack);
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Match Clip");
|
||||
TimelineAnimationUtilities.MatchNext(clip, sceneObject.transform, state.editSequence.director);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
public static void ResetClipOffsets(WindowState state, TimelineClip[] clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
if (clip.asset is AnimationPlayableAsset)
|
||||
{
|
||||
TimelineUndo.PushUndo(clip.asset, "Reset Offsets");
|
||||
var playableAsset = (AnimationPlayableAsset)clip.asset;
|
||||
playableAsset.ResetOffsets();
|
||||
}
|
||||
}
|
||||
state.rebuildGraph = true;
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9ace5095cc37ed849b52109d2ee305d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,65 @@
|
|||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(AnimationPlayableAsset)), UsedImplicitly]
|
||||
class AnimationPlayableAssetEditor : ClipEditor
|
||||
{
|
||||
public static readonly string k_NoClipAssignedError = LocalizationDatabase.GetLocalizedString("No animation clip assigned");
|
||||
public static readonly string k_LegacyClipError = LocalizationDatabase.GetLocalizedString("Legacy animation clips are not supported");
|
||||
static readonly string k_MotionCurveError = LocalizationDatabase.GetLocalizedString("You are using motion curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
static readonly string k_RootCurveError = LocalizationDatabase.GetLocalizedString("You are using root curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
|
||||
{
|
||||
var clipOptions = base.GetClipOptions(clip);
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
|
||||
if (asset != null)
|
||||
clipOptions.errorText = GetErrorText(asset, clip.parentTrack as AnimationTrack, clipOptions.errorText);
|
||||
|
||||
if (clip.recordable)
|
||||
clipOptions.highlightColor = DirectorStyles.Instance.customSkin.colorAnimationRecorded;
|
||||
|
||||
return clipOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)
|
||||
{
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset != null && asset.clip != null && asset.clip.legacy)
|
||||
{
|
||||
asset.clip = null;
|
||||
Debug.LogError("Legacy Animation Clips are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
string GetErrorText(AnimationPlayableAsset animationAsset, AnimationTrack track, string defaultError)
|
||||
{
|
||||
if (animationAsset.clip == null)
|
||||
return k_NoClipAssignedError;
|
||||
if (animationAsset.clip.legacy)
|
||||
return k_LegacyClipError;
|
||||
if (animationAsset.clip.hasMotionCurves || animationAsset.clip.hasRootCurves)
|
||||
{
|
||||
if (track != null && track.trackOffset == TrackOffset.Auto)
|
||||
{
|
||||
var animator = track.GetBinding(TimelineEditor.inspectedDirector);
|
||||
if (animator != null && !animator.applyRootMotion && !animationAsset.clip.hasGenericRootTransform)
|
||||
{
|
||||
if (animationAsset.clip.hasMotionCurves)
|
||||
return k_MotionCurveError;
|
||||
return k_RootCurveError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultError;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f7fed0d9d0f7a7f41a8525aa79e790b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,151 @@
|
|||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Add Override Track", MenuOrder.CustomTrackAction.AnimAddOverrideTrack), UsedImplicitly]
|
||||
class AddOverrideTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
TimelineHelpers.CreateTrack(typeof(AnimationTrack), animTrack, "Override " + animTrack.GetChildTracks().Count());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => t.isSubTrack || !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Clip Track", MenuOrder.CustomTrackAction.AnimConvertToClipMode), UsedImplicitly]
|
||||
class ConvertToClipModeAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertToClipMode();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertToClipMode()))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Infinite Clip", MenuOrder.CustomTrackAction.AnimConvertFromClipMode), UsedImplicitly]
|
||||
class ConvertFromClipTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertFromClipMode(state.editSequence.asset);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertFromClipMode()))
|
||||
return MenuActionDisplayState.Visible;
|
||||
|
||||
return MenuActionDisplayState.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrackOffsetBaseAction : TrackAction
|
||||
{
|
||||
public abstract TrackOffset trackOffset { get; }
|
||||
|
||||
protected override MenuActionDisplayState GetDisplayState(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return MenuActionDisplayState.Hidden;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return MenuActionDisplayState.Disabled;
|
||||
|
||||
return MenuActionDisplayState.Visible;
|
||||
}
|
||||
|
||||
protected override bool IsChecked(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
return tracks.OfType<AnimationTrack>().All(t => t.trackOffset == trackOffset);
|
||||
}
|
||||
|
||||
public override bool Execute(WindowState state, TrackAsset[] tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
state.UnarmForRecord(animTrack);
|
||||
TimelineUndo.PushUndo(animTrack, "Set Transform Offsets");
|
||||
animTrack.trackOffset = trackOffset;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Transform Offsets", MenuOrder.CustomTrackAction.AnimApplyTrackOffset), UsedImplicitly]
|
||||
class ApplyTransformOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplyTransformOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Scene Offsets", MenuOrder.CustomTrackAction.AnimApplySceneOffset), UsedImplicitly]
|
||||
class ApplySceneOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplySceneOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Auto (Deprecated)", MenuOrder.CustomTrackAction.AnimApplyAutoOffset), UsedImplicitly]
|
||||
class ApplyAutoAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.Auto; }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d4553f2006f48b6448553cb525d2876e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,224 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
class BindingSelector
|
||||
{
|
||||
TreeViewController m_TreeView;
|
||||
public TreeViewController treeViewController
|
||||
{
|
||||
get { return m_TreeView; }
|
||||
}
|
||||
|
||||
TreeViewState m_TrackGlobalTreeViewState;
|
||||
TreeViewState m_TreeViewState;
|
||||
BindingTreeViewDataSource m_TreeViewDataSource;
|
||||
CurveDataSource m_CurveDataSource;
|
||||
TimelineWindow m_Window;
|
||||
CurveEditor m_CurveEditor;
|
||||
ReorderableList m_DopeLines;
|
||||
string[] m_StringList = {};
|
||||
int[] m_Selection;
|
||||
bool m_PartOfSelection;
|
||||
public BindingSelector(EditorWindow window, CurveEditor curveEditor, TreeViewState trackGlobalTreeViewState)
|
||||
{
|
||||
m_Window = window as TimelineWindow;
|
||||
m_CurveEditor = curveEditor;
|
||||
m_TrackGlobalTreeViewState = trackGlobalTreeViewState;
|
||||
|
||||
m_DopeLines = new ReorderableList(m_StringList, typeof(string), false, false, false, false);
|
||||
m_DopeLines.drawElementBackgroundCallback = null;
|
||||
m_DopeLines.showDefaultBackground = false;
|
||||
m_DopeLines.index = 0;
|
||||
m_DopeLines.headerHeight = 0;
|
||||
m_DopeLines.elementHeight = 20;
|
||||
m_DopeLines.draggable = false;
|
||||
}
|
||||
|
||||
public bool selectable { get { return true; } }
|
||||
|
||||
public object selectableObject
|
||||
{
|
||||
get { return this; }
|
||||
}
|
||||
|
||||
public bool selected
|
||||
{
|
||||
get { return m_PartOfSelection; }
|
||||
set
|
||||
{
|
||||
m_PartOfSelection = value;
|
||||
|
||||
if (!m_PartOfSelection)
|
||||
{
|
||||
m_DopeLines.index = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Delete(WindowState state)
|
||||
{
|
||||
// we dont support deleting the summary
|
||||
if (m_DopeLines.index < 1)
|
||||
return;
|
||||
|
||||
if (m_CurveDataSource == null)
|
||||
return;
|
||||
|
||||
var clip = m_CurveDataSource.animationClip;
|
||||
if (clip == null)
|
||||
return;
|
||||
|
||||
int curveIndexToDelete = m_DopeLines.index - 1;
|
||||
var bindings = AnimationUtility.GetCurveBindings(clip);
|
||||
|
||||
if (curveIndexToDelete >= bindings.Length)
|
||||
return;
|
||||
|
||||
TimelineUndo.PushUndo(clip, "Delete Curve");
|
||||
AnimationUtility.SetEditorCurve(clip, bindings[m_DopeLines.index - 1], null);
|
||||
state.rebuildGraph = true;
|
||||
}
|
||||
|
||||
public void OnGUI(Rect targetRect)
|
||||
{
|
||||
if (m_TreeView == null)
|
||||
return;
|
||||
|
||||
m_TreeView.OnEvent();
|
||||
m_TreeView.OnGUI(targetRect, GUIUtility.GetControlID(FocusType.Passive));
|
||||
}
|
||||
|
||||
public void InitIfNeeded(Rect rect, CurveDataSource dataSource, bool isNewSelection)
|
||||
{
|
||||
if (Event.current.type != EventType.Layout)
|
||||
return;
|
||||
|
||||
m_CurveDataSource = dataSource;
|
||||
var clip = dataSource.animationClip;
|
||||
|
||||
List<EditorCurveBinding> allBindings = new List<EditorCurveBinding>();
|
||||
allBindings.Add(new EditorCurveBinding { propertyName = "Summary" });
|
||||
if (clip != null)
|
||||
allBindings.AddRange(AnimationUtility.GetCurveBindings(clip));
|
||||
|
||||
m_DopeLines.list = allBindings.ToArray();
|
||||
|
||||
if (m_TreeViewState != null)
|
||||
{
|
||||
if (isNewSelection)
|
||||
RefreshAll();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_TreeViewState = m_TrackGlobalTreeViewState != null ? m_TrackGlobalTreeViewState : new TreeViewState();
|
||||
|
||||
m_TreeView = new TreeViewController(m_Window, m_TreeViewState)
|
||||
{
|
||||
useExpansionAnimation = false,
|
||||
deselectOnUnhandledMouseDown = true
|
||||
};
|
||||
|
||||
m_TreeView.selectionChangedCallback += OnItemSelectionChanged;
|
||||
|
||||
m_TreeViewDataSource = new BindingTreeViewDataSource(m_TreeView, clip, m_CurveDataSource);
|
||||
|
||||
m_TreeView.Init(rect, m_TreeViewDataSource, new BindingTreeViewGUI(m_TreeView), null);
|
||||
|
||||
m_TreeViewDataSource.UpdateData();
|
||||
|
||||
RefreshSelection();
|
||||
}
|
||||
|
||||
void OnItemSelectionChanged(int[] selection)
|
||||
{
|
||||
RefreshSelection(selection);
|
||||
}
|
||||
|
||||
void RefreshAll()
|
||||
{
|
||||
RefreshTree();
|
||||
RefreshSelection();
|
||||
}
|
||||
|
||||
void RefreshSelection()
|
||||
{
|
||||
RefreshSelection(m_TreeViewState.selectedIDs != null ? m_TreeViewState.selectedIDs.ToArray() : null);
|
||||
}
|
||||
|
||||
void RefreshSelection(int[] selection)
|
||||
{
|
||||
if (selection == null || selection.Length == 0)
|
||||
{
|
||||
// select all.
|
||||
if (m_TreeViewDataSource.GetRows().Count > 0)
|
||||
{
|
||||
m_Selection = m_TreeViewDataSource.GetRows().Select(r => r.id).ToArray();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Selection = selection;
|
||||
}
|
||||
|
||||
RefreshCurves();
|
||||
}
|
||||
|
||||
public void RefreshCurves()
|
||||
{
|
||||
if (m_CurveDataSource == null || m_Selection == null)
|
||||
return;
|
||||
|
||||
var bindings = new List<EditorCurveBinding>();
|
||||
foreach (int s in m_Selection)
|
||||
{
|
||||
var item = (CurveTreeViewNode)m_TreeView.FindItem(s);
|
||||
if (item != null && item.bindings != null)
|
||||
bindings.AddRange(item.bindings);
|
||||
}
|
||||
|
||||
var wrappers = m_CurveDataSource.GenerateWrappers(bindings);
|
||||
m_CurveEditor.animationCurves = wrappers.ToArray();
|
||||
}
|
||||
|
||||
public void RefreshTree()
|
||||
{
|
||||
if (m_TreeViewDataSource == null)
|
||||
return;
|
||||
|
||||
if (m_Selection == null)
|
||||
m_Selection = new int[0];
|
||||
|
||||
// get the names of the previous items
|
||||
var selected = m_Selection.Select(x => m_TreeViewDataSource.FindItem(x)).Where(t => t != null).Select(c => c.displayName).ToArray();
|
||||
|
||||
// update the source
|
||||
m_TreeViewDataSource.UpdateData();
|
||||
|
||||
// find the same items
|
||||
var reselected = m_TreeViewDataSource.GetRows().Where(x => selected.Contains(x.displayName)).Select(x => x.id).ToArray();
|
||||
if (!reselected.Any())
|
||||
{
|
||||
if (m_TreeViewDataSource.GetRows().Count > 0)
|
||||
{
|
||||
reselected = new[] { m_TreeViewDataSource.GetItem(0).id };
|
||||
}
|
||||
}
|
||||
|
||||
// update the selection
|
||||
OnItemSelectionChanged(reselected);
|
||||
}
|
||||
|
||||
internal virtual bool IsRenamingNodeAllowed(TreeViewItem node)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c171b9ca03610ea4faa426e082a1075d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,139 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditorInternal
|
||||
{
|
||||
class BindingTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
public const int RootID = int.MinValue;
|
||||
public const int GroupID = -1;
|
||||
|
||||
AnimationClip m_Clip;
|
||||
CurveDataSource m_CurveDataSource;
|
||||
|
||||
public BindingTreeViewDataSource(
|
||||
TreeViewController treeView, AnimationClip clip, CurveDataSource curveDataSource)
|
||||
: base(treeView)
|
||||
{
|
||||
m_Clip = clip;
|
||||
showRootItem = false;
|
||||
m_CurveDataSource = curveDataSource;
|
||||
}
|
||||
|
||||
void SetupRootNodeSettings()
|
||||
{
|
||||
showRootItem = false;
|
||||
SetExpanded(RootID, true);
|
||||
SetExpanded(GroupID, true);
|
||||
}
|
||||
|
||||
static string GroupName(EditorCurveBinding binding)
|
||||
{
|
||||
string property = AnimationWindowUtility.NicifyPropertyGroupName(binding.type, binding.propertyName);
|
||||
if (!string.IsNullOrEmpty(binding.path))
|
||||
{
|
||||
property = binding.path + " : " + property;
|
||||
}
|
||||
|
||||
int lastArrayIdx = property.LastIndexOf("Array.");
|
||||
if (lastArrayIdx != -1)
|
||||
{
|
||||
property = property.Substring(0, lastArrayIdx - 1);
|
||||
}
|
||||
return property;
|
||||
}
|
||||
|
||||
static string PropertyName(EditorCurveBinding binding, string arrayPrefixToRemove = "")
|
||||
{
|
||||
string propertyName = AnimationWindowUtility.GetPropertyDisplayName(binding.propertyName);
|
||||
if (propertyName.Contains("Array"))
|
||||
{
|
||||
propertyName = propertyName.Replace("Array.", "");
|
||||
propertyName = propertyName.Replace(arrayPrefixToRemove, "");
|
||||
propertyName = propertyName.TrimStart('.');
|
||||
}
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return;
|
||||
|
||||
var bindings = AnimationUtility.GetCurveBindings(m_Clip)
|
||||
.Union(AnimationUtility.GetObjectReferenceCurveBindings(m_Clip))
|
||||
.ToArray();
|
||||
|
||||
var results = bindings.GroupBy(p => GroupName(p), p => p, (key, g) => new
|
||||
{
|
||||
parent = key,
|
||||
bindings = g.ToList()
|
||||
}).OrderBy(t =>
|
||||
{
|
||||
//Force transform order first
|
||||
if (t.parent == "Position") return -3;
|
||||
if (t.parent == "Rotation") return -2;
|
||||
if (t.parent == "Scale") return -1;
|
||||
return 0;
|
||||
}).ThenBy(t => t.parent);
|
||||
|
||||
m_RootItem = new CurveTreeViewNode(RootID, null, "root", null)
|
||||
{
|
||||
children = new List<TreeViewItem>(1)
|
||||
};
|
||||
|
||||
var groupingNode = new CurveTreeViewNode(GroupID, m_RootItem, m_CurveDataSource.groupingName, bindings)
|
||||
{
|
||||
children = new List<TreeViewItem>()
|
||||
};
|
||||
|
||||
m_RootItem.children.Add(groupingNode);
|
||||
|
||||
foreach (var r in results)
|
||||
{
|
||||
var newNode = new CurveTreeViewNode(r.parent.GetHashCode(), groupingNode, r.parent, r.bindings.ToArray());
|
||||
groupingNode.children.Add(newNode);
|
||||
if (r.bindings.Count > 1)
|
||||
{
|
||||
for (int b = 0; b < r.bindings.Count; b++)
|
||||
{
|
||||
if (newNode.children == null)
|
||||
newNode.children = new List<TreeViewItem>();
|
||||
|
||||
var binding = r.bindings[b];
|
||||
var bindingNode = new CurveTreeViewNode(binding.GetHashCode(), newNode, PropertyName(binding, newNode.displayName), new[] {binding});
|
||||
newNode.children.Add(bindingNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SetupRootNodeSettings();
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
m_TreeView.ReloadData();
|
||||
}
|
||||
}
|
||||
|
||||
class CurveTreeViewNode : TreeViewItem
|
||||
{
|
||||
EditorCurveBinding[] m_Bindings;
|
||||
|
||||
public EditorCurveBinding[] bindings
|
||||
{
|
||||
get { return m_Bindings; }
|
||||
}
|
||||
|
||||
public CurveTreeViewNode(int id, TreeViewItem parent, string displayName, EditorCurveBinding[] bindings)
|
||||
: base(id, parent != null ? parent.depth + 1 : -1, parent, displayName)
|
||||
{
|
||||
m_Bindings = bindings;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c2177aaf0fde92439246adc2dc0bfa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,80 @@
|
|||
using UnityEditor;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditorInternal
|
||||
{
|
||||
class BindingTreeViewGUI : TreeViewGUI
|
||||
{
|
||||
static readonly float s_RowRightOffset = 10;
|
||||
static readonly float s_ColorIndicatorTopMargin = 3;
|
||||
static readonly Color s_KeyColorForNonCurves = new Color(0.7f, 0.7f, 0.7f, 0.5f);
|
||||
static readonly Color s_ChildrenCurveLabelColor = new Color(1.0f, 1.0f, 1.0f, 0.7f);
|
||||
|
||||
public BindingTreeViewGUI(TreeViewController treeView)
|
||||
: base(treeView, true)
|
||||
{
|
||||
k_IconWidth = 13.0f;
|
||||
}
|
||||
|
||||
public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused)
|
||||
{
|
||||
Color originalColor = GUI.color;
|
||||
GUI.color = node.parent == null ||
|
||||
node.parent.id == BindingTreeViewDataSource.RootID ||
|
||||
node.parent.id == BindingTreeViewDataSource.GroupID ?
|
||||
Color.white :
|
||||
s_ChildrenCurveLabelColor;
|
||||
|
||||
base.OnRowGUI(rowRect, node, row, selected, focused);
|
||||
|
||||
GUI.color = originalColor;
|
||||
DoCurveColorIndicator(rowRect, node as CurveTreeViewNode);
|
||||
}
|
||||
|
||||
protected override bool IsRenaming(int id)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool BeginRename(TreeViewItem item, float delay)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void DoCurveColorIndicator(Rect rect, CurveTreeViewNode node)
|
||||
{
|
||||
if (node == null)
|
||||
return;
|
||||
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
Color originalColor = GUI.color;
|
||||
|
||||
if (node.bindings.Length == 1 && !node.bindings[0].isPPtrCurve)
|
||||
GUI.color = CurveUtility.GetPropertyColor(node.bindings[0].propertyName);
|
||||
else
|
||||
GUI.color = s_KeyColorForNonCurves;
|
||||
|
||||
Texture icon = CurveUtility.GetIconCurve();
|
||||
rect = new Rect(rect.xMax - s_RowRightOffset - (icon.width * 0.5f) - 5, rect.yMin + s_ColorIndicatorTopMargin, icon.width, icon.height);
|
||||
|
||||
GUI.DrawTexture(rect, icon, ScaleMode.ScaleToFit, true, 1);
|
||||
|
||||
GUI.color = originalColor;
|
||||
}
|
||||
|
||||
protected override Texture GetIconForItem(TreeViewItem item)
|
||||
{
|
||||
var node = item as CurveTreeViewNode;
|
||||
if (node == null)
|
||||
return null;
|
||||
|
||||
if (node.bindings == null || node.bindings.Length == 0)
|
||||
return null;
|
||||
|
||||
return AssetPreview.GetMiniTypeThumbnail(node.bindings[0].type);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c09dc5cd0a70cf40856b7d406106ee1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,342 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
class ClipCurveEditor
|
||||
{
|
||||
internal readonly CurveEditor m_CurveEditor;
|
||||
static readonly CurveEditorSettings s_CurveEditorSettings = new CurveEditorSettings
|
||||
{
|
||||
hSlider = false,
|
||||
vSlider = false,
|
||||
hRangeLocked = false,
|
||||
vRangeLocked = false,
|
||||
scaleWithWindow = true,
|
||||
hRangeMin = 0.0f,
|
||||
showAxisLabels = true,
|
||||
allowDeleteLastKeyInCurve = true,
|
||||
rectangleToolFlags = CurveEditorSettings.RectangleToolFlags.MiniRectangleTool
|
||||
};
|
||||
|
||||
static readonly float s_GridLabelWidth = 40.0f;
|
||||
|
||||
readonly BindingSelector m_BindingHierarchy;
|
||||
public BindingSelector bindingHierarchy
|
||||
{
|
||||
get { return m_BindingHierarchy; }
|
||||
}
|
||||
|
||||
public Rect shownAreaInsideMargins
|
||||
{
|
||||
get { return m_CurveEditor != null ? m_CurveEditor.shownAreaInsideMargins : new Rect(1, 1, 1, 1); }
|
||||
}
|
||||
|
||||
Vector2 m_ScrollPosition = Vector2.zero;
|
||||
|
||||
readonly CurveDataSource m_DataSource;
|
||||
|
||||
float m_LastFrameRate = 30.0f;
|
||||
int m_LastClipVersion = -1;
|
||||
int m_LastCurveCount = -1;
|
||||
TrackViewModelData m_ViewModel;
|
||||
bool m_ShouldRestoreShownArea;
|
||||
|
||||
bool isNewSelection
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ViewModel == null || m_DataSource == null)
|
||||
return true;
|
||||
|
||||
return m_ViewModel.lastInlineCurveDataID != m_DataSource.id;
|
||||
}
|
||||
}
|
||||
|
||||
internal CurveEditor curveEditor
|
||||
{
|
||||
get { return m_CurveEditor; }
|
||||
}
|
||||
|
||||
public ClipCurveEditor(CurveDataSource dataSource, TimelineWindow parentWindow, TrackAsset hostTrack)
|
||||
{
|
||||
m_DataSource = dataSource;
|
||||
|
||||
m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], false);
|
||||
|
||||
s_CurveEditorSettings.vTickStyle = new TickStyle
|
||||
{
|
||||
tickColor = { color = DirectorStyles.Instance.customSkin.colorInlineCurveVerticalLines },
|
||||
distLabel = 20,
|
||||
stubs = true
|
||||
};
|
||||
|
||||
s_CurveEditorSettings.hTickStyle = new TickStyle
|
||||
{
|
||||
// hide horizontal lines by giving them a transparent color
|
||||
tickColor = { color = new Color(0.0f, 0.0f, 0.0f, 0.0f) },
|
||||
distLabel = 0
|
||||
};
|
||||
|
||||
m_CurveEditor.settings = s_CurveEditorSettings;
|
||||
|
||||
m_ViewModel = TimelineWindowViewPrefs.GetTrackViewModelData(hostTrack);
|
||||
|
||||
m_ShouldRestoreShownArea = true;
|
||||
m_CurveEditor.ignoreScrollWheelUntilClicked = true;
|
||||
m_CurveEditor.curvesUpdated = OnCurvesUpdated;
|
||||
|
||||
m_BindingHierarchy = new BindingSelector(parentWindow, m_CurveEditor, m_ViewModel.inlineCurvesState);
|
||||
}
|
||||
|
||||
public void SelectAllKeys()
|
||||
{
|
||||
m_CurveEditor.SelectAll();
|
||||
}
|
||||
|
||||
public void FrameClip()
|
||||
{
|
||||
m_CurveEditor.InvalidateBounds();
|
||||
m_CurveEditor.FrameClip(false, true);
|
||||
}
|
||||
|
||||
public CurveDataSource dataSource
|
||||
{
|
||||
get { return m_DataSource; }
|
||||
}
|
||||
|
||||
internal void OnCurvesUpdated()
|
||||
{
|
||||
if (m_DataSource == null)
|
||||
return;
|
||||
|
||||
if (m_CurveEditor == null)
|
||||
return;
|
||||
|
||||
if (m_CurveEditor.animationCurves.Length == 0)
|
||||
return;
|
||||
|
||||
List<CurveWrapper> curvesToUpdate = m_CurveEditor.animationCurves.Where(c => c.changed).ToList();
|
||||
|
||||
// nothing changed, return.
|
||||
if (curvesToUpdate.Count == 0)
|
||||
return;
|
||||
|
||||
AnimationClip clip = m_DataSource.animationClip;
|
||||
|
||||
// something changed, manage the undo properly.
|
||||
Undo.RegisterCompleteObjectUndo(clip, "Edit Clip Curve");
|
||||
|
||||
foreach (CurveWrapper c in curvesToUpdate)
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(clip, c.binding, c.curve);
|
||||
c.changed = false;
|
||||
}
|
||||
|
||||
m_DataSource.UpdateCurves(curvesToUpdate);
|
||||
}
|
||||
|
||||
public void DrawHeader(Rect headerRect)
|
||||
{
|
||||
m_BindingHierarchy.InitIfNeeded(headerRect, m_DataSource, isNewSelection);
|
||||
|
||||
try
|
||||
{
|
||||
GUILayout.BeginArea(headerRect);
|
||||
m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
|
||||
m_BindingHierarchy.OnGUI(new Rect(0, 0, headerRect.width, headerRect.height));
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
class FrameFormatCurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat
|
||||
{
|
||||
get { return TimeArea.TimeFormat.Frame; }
|
||||
}
|
||||
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||||
public bool rippleTime { get { return false; } }
|
||||
}
|
||||
|
||||
class UnformattedCurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat
|
||||
{
|
||||
get { return TimeArea.TimeFormat.None; }
|
||||
}
|
||||
public Vector2 timeRange { get { return new Vector2(0, 1); } }
|
||||
public bool rippleTime { get { return false; } }
|
||||
}
|
||||
|
||||
void UpdateCurveEditorIfNeeded(WindowState state)
|
||||
{
|
||||
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null) || (m_DataSource.animationClip == null))
|
||||
return;
|
||||
|
||||
AnimationClipCurveInfo curveInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_DataSource.animationClip);
|
||||
int version = curveInfo.version;
|
||||
if (version != m_LastClipVersion)
|
||||
{
|
||||
// tree has changed
|
||||
if (m_LastCurveCount != curveInfo.curves.Length)
|
||||
{
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
m_LastCurveCount = curveInfo.curves.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
// update just the curves
|
||||
m_BindingHierarchy.RefreshCurves();
|
||||
}
|
||||
m_LastClipVersion = version;
|
||||
}
|
||||
|
||||
if (state.timeInFrames)
|
||||
m_CurveEditor.state = new FrameFormatCurveEditorState();
|
||||
else
|
||||
m_CurveEditor.state = new UnformattedCurveEditorState();
|
||||
|
||||
m_CurveEditor.invSnap = state.referenceSequence.frameRate;
|
||||
}
|
||||
|
||||
public void DrawCurveEditor(Rect rect, WindowState state, Vector2 clipRange, bool loop, bool selected)
|
||||
{
|
||||
SetupMarginsAndRect(rect, state);
|
||||
UpdateCurveEditorIfNeeded(state);
|
||||
|
||||
if (m_ShouldRestoreShownArea)
|
||||
RestoreShownArea();
|
||||
|
||||
var curveVisibleTimeRange = CalculateCurveVisibleTimeRange(state.timeAreaShownRange, m_DataSource);
|
||||
m_CurveEditor.SetShownHRangeInsideMargins(curveVisibleTimeRange.x, curveVisibleTimeRange.y); //align the curve with the clip.
|
||||
|
||||
if (m_LastFrameRate != state.referenceSequence.frameRate)
|
||||
{
|
||||
m_CurveEditor.hTicks.SetTickModulosForFrameRate(state.referenceSequence.frameRate);
|
||||
m_LastFrameRate = state.referenceSequence.frameRate;
|
||||
}
|
||||
|
||||
foreach (var cw in m_CurveEditor.animationCurves)
|
||||
cw.renderer.SetWrap(WrapMode.Default, loop ? WrapMode.Loop : WrapMode.Default);
|
||||
|
||||
using (new GUIGroupScope(rect))
|
||||
{
|
||||
var localRect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
||||
var localClipRange = new Vector2(Mathf.Floor(clipRange.x - rect.xMin), Mathf.Ceil(clipRange.y - rect.xMin));
|
||||
var curveStartPosX = Mathf.Floor(state.TimeToPixel(m_DataSource.start) - rect.xMin);
|
||||
|
||||
EditorGUI.DrawRect(new Rect(curveStartPosX, 0.0f, 1.0f, rect.height), new Color(1.0f, 1.0f, 1.0f, 0.5f));
|
||||
DrawCurveEditorBackground(localRect);
|
||||
|
||||
if (selected)
|
||||
{
|
||||
var selectionRect = new Rect(localClipRange.x, 0.0f, localClipRange.y - localClipRange.x, localRect.height);
|
||||
DrawOutline(selectionRect);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
{
|
||||
var evt = Event.current;
|
||||
if (evt.type == EventType.Layout || evt.type == EventType.Repaint || selected)
|
||||
m_CurveEditor.CurveGUI();
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
OnCurvesUpdated();
|
||||
|
||||
DrawOverlay(localRect, localClipRange, DirectorStyles.Instance.customSkin.colorInlineCurveOutOfRangeOverlay);
|
||||
DrawGrid(localRect, curveStartPosX);
|
||||
}
|
||||
}
|
||||
|
||||
static Vector2 CalculateCurveVisibleTimeRange(Vector2 timeAreaShownRange, CurveDataSource curve)
|
||||
{
|
||||
var curveVisibleTimeRange = new Vector2
|
||||
{
|
||||
x = Math.Max(0.0f, timeAreaShownRange.x - curve.start),
|
||||
y = timeAreaShownRange.y - curve.start
|
||||
};
|
||||
return curveVisibleTimeRange * curve.timeScale;
|
||||
}
|
||||
|
||||
void SetupMarginsAndRect(Rect rect, WindowState state)
|
||||
{
|
||||
var startX = state.TimeToPixel(m_DataSource.start) - rect.x;
|
||||
var timelineWidth = state.timeAreaRect.width;
|
||||
m_CurveEditor.rect = new Rect(0.0f, 0.0f, timelineWidth, rect.height);
|
||||
m_CurveEditor.leftmargin = Math.Max(startX, 0.0f);
|
||||
m_CurveEditor.rightmargin = 0.0f;
|
||||
m_CurveEditor.topmargin = m_CurveEditor.bottommargin = CalculateTopMargin(rect.height);
|
||||
}
|
||||
|
||||
void RestoreShownArea()
|
||||
{
|
||||
if (isNewSelection)
|
||||
FrameClip();
|
||||
else
|
||||
m_CurveEditor.shownAreaInsideMargins = m_ViewModel.inlineCurvesShownAreaInsideMargins;
|
||||
m_ShouldRestoreShownArea = false;
|
||||
}
|
||||
|
||||
static void DrawCurveEditorBackground(Rect rect)
|
||||
{
|
||||
if (EditorGUIUtility.isProSkin)
|
||||
return;
|
||||
|
||||
var animEditorBackgroundRect = Rect.MinMaxRect(0.0f, rect.yMin, rect.xMax, rect.yMax);
|
||||
|
||||
// Curves are not legible in Personal Skin so we need to darken the background a bit.
|
||||
EditorGUI.DrawRect(animEditorBackgroundRect, DirectorStyles.Instance.customSkin.colorInlineCurvesBackground);
|
||||
}
|
||||
|
||||
static float CalculateTopMargin(float height)
|
||||
{
|
||||
return Mathf.Clamp(0.15f * height, 10.0f, 40.0f);
|
||||
}
|
||||
|
||||
static void DrawOutline(Rect rect, float thickness = 2.0f)
|
||||
{
|
||||
// Draw top selected lines.
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), Color.white);
|
||||
|
||||
// Draw bottom selected lines.
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), Color.white);
|
||||
|
||||
// Draw Left Selected Lines
|
||||
EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), Color.white);
|
||||
|
||||
// Draw Right Selected Lines
|
||||
EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), Color.white);
|
||||
}
|
||||
|
||||
static void DrawOverlay(Rect rect, Vector2 clipRange, Color color)
|
||||
{
|
||||
var leftSide = new Rect(rect.xMin, rect.yMin, clipRange.x - rect.xMin, rect.height);
|
||||
EditorGUI.DrawRect(leftSide, color);
|
||||
|
||||
var rightSide = new Rect(Mathf.Max(0.0f, clipRange.y), rect.yMin, rect.xMax, rect.height);
|
||||
EditorGUI.DrawRect(rightSide, color);
|
||||
}
|
||||
|
||||
void DrawGrid(Rect rect, float curveXPosition)
|
||||
{
|
||||
var gridXPos = Mathf.Max(curveXPosition - s_GridLabelWidth, rect.xMin);
|
||||
var gridRect = new Rect(gridXPos, rect.y, s_GridLabelWidth, rect.height);
|
||||
var originalRect = m_CurveEditor.rect;
|
||||
|
||||
m_CurveEditor.rect = new Rect(0.0f, 0.0f, rect.width, rect.height);
|
||||
using (new GUIGroupScope(gridRect))
|
||||
m_CurveEditor.GridGUI();
|
||||
m_CurveEditor.rect = originalRect;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d49b2ed20045e034f9cdf6a6d95e6183
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,272 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
abstract class CurveDataSource
|
||||
{
|
||||
public static CurveDataSource Create(IRowGUI trackGUI)
|
||||
{
|
||||
if (trackGUI.asset is AnimationTrack)
|
||||
return new InfiniteClipCurveDataSource(trackGUI);
|
||||
|
||||
return new TrackParametersCurveDataSource(trackGUI);
|
||||
}
|
||||
|
||||
public static CurveDataSource Create(TimelineClipGUI clipGUI)
|
||||
{
|
||||
if (clipGUI.clip.animationClip != null)
|
||||
return new ClipAnimationCurveDataSource(clipGUI);
|
||||
|
||||
return new ClipParametersCurveDataSource(clipGUI);
|
||||
}
|
||||
|
||||
int? m_ID = null;
|
||||
public int id
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_ID.HasValue)
|
||||
m_ID = CreateHashCode();
|
||||
|
||||
return m_ID.Value;
|
||||
}
|
||||
}
|
||||
|
||||
readonly IRowGUI m_TrackGUI;
|
||||
protected IRowGUI trackGUI { get { return m_TrackGUI; } }
|
||||
|
||||
protected CurveDataSource(IRowGUI trackGUI)
|
||||
{
|
||||
m_TrackGUI = trackGUI;
|
||||
}
|
||||
|
||||
public abstract AnimationClip animationClip { get; }
|
||||
public abstract float start { get; }
|
||||
public abstract float timeScale { get; }
|
||||
public abstract string groupingName { get; }
|
||||
public virtual void UpdateCurves(List<CurveWrapper> updatedCurves) {}
|
||||
public virtual void RebuildCurves() {} // Only necessary when using proxies
|
||||
|
||||
public Rect GetBackgroundRect(WindowState state)
|
||||
{
|
||||
var trackRect = m_TrackGUI.boundingRect;
|
||||
return new Rect(
|
||||
state.timeAreaTranslation.x + trackRect.xMin,
|
||||
trackRect.y,
|
||||
(float)state.editSequence.asset.duration * state.timeAreaScale.x,
|
||||
trackRect.height
|
||||
);
|
||||
}
|
||||
|
||||
public List<CurveWrapper> GenerateWrappers(List<EditorCurveBinding> bindings)
|
||||
{
|
||||
var wrappers = new List<CurveWrapper>(bindings.Count);
|
||||
int curveWrapperId = 0;
|
||||
|
||||
foreach (EditorCurveBinding b in bindings)
|
||||
{
|
||||
// General configuration
|
||||
var wrapper = new CurveWrapper
|
||||
{
|
||||
id = curveWrapperId++,
|
||||
binding = b,
|
||||
groupId = -1,
|
||||
hidden = false,
|
||||
readOnly = false,
|
||||
getAxisUiScalarsCallback = () => new Vector2(1, 1)
|
||||
};
|
||||
|
||||
// Specific configuration
|
||||
ConfigureCurveWrapper(wrapper);
|
||||
|
||||
wrappers.Add(wrapper);
|
||||
}
|
||||
|
||||
return wrappers;
|
||||
}
|
||||
|
||||
protected virtual void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
wrapper.color = CurveUtility.GetPropertyColor(wrapper.binding.propertyName);
|
||||
wrapper.renderer = new NormalCurveRenderer(AnimationUtility.GetEditorCurve(animationClip, wrapper.binding));
|
||||
wrapper.renderer.SetCustomRange(0.0f, animationClip.length);
|
||||
}
|
||||
|
||||
protected virtual int CreateHashCode()
|
||||
{
|
||||
return m_TrackGUI.asset.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
class ClipAnimationCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Animated Values");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
|
||||
public ClipAnimationCurveDataSource(TimelineClipGUI clipGUI) : base(clipGUI.parent)
|
||||
{
|
||||
m_ClipGUI = clipGUI;
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_ClipGUI.clip.animationClip; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.FromLocalTimeUnbound(0.0); }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.timeScale; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
class ClipParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Clip Properties");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
public ClipParametersCurveDataSource(TimelineClipGUI clipGUI) : base(clipGUI.parent)
|
||||
{
|
||||
m_ClipGUI = clipGUI;
|
||||
m_CurvesProxy = new CurvesProxy(clipGUI.clip);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.FromLocalTimeUnbound(0.0); }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return (float)m_ClipGUI.clip.timeScale; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
public override void UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
public override void RebuildCurves()
|
||||
{
|
||||
m_CurvesProxy.RebuildCurves();
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
class InfiniteClipCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Animated Values");
|
||||
|
||||
readonly AnimationTrack m_AnimationTrack;
|
||||
|
||||
public InfiniteClipCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_AnimationTrack = trackGui.asset as AnimationTrack;
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_AnimationTrack.infiniteClip; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return 0.0f; }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return 1.0f; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
}
|
||||
|
||||
class TrackParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Track Properties");
|
||||
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
public TrackParametersCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_CurvesProxy = new CurvesProxy(trackGui.asset);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
public override float start
|
||||
{
|
||||
get { return 0.0f; }
|
||||
}
|
||||
|
||||
public override float timeScale
|
||||
{
|
||||
get { return 1.0f; }
|
||||
}
|
||||
|
||||
public override string groupingName
|
||||
{
|
||||
get { return k_GroupingName; }
|
||||
}
|
||||
|
||||
public override void UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
public override void RebuildCurves()
|
||||
{
|
||||
m_CurvesProxy.RebuildCurves();
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87a1ae9719ec25d44a4dbec20ec0f892
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,302 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class CurvesProxy : ICurvesOwner
|
||||
{
|
||||
public AnimationClip curves
|
||||
{
|
||||
get { return proxyCurves != null ? proxyCurves : m_OriginalOwner.curves; }
|
||||
}
|
||||
|
||||
public bool hasCurves
|
||||
{
|
||||
get { return m_IsAnimatable || m_OriginalOwner.hasCurves; }
|
||||
}
|
||||
|
||||
public double duration
|
||||
{
|
||||
get { return m_OriginalOwner.duration; }
|
||||
}
|
||||
|
||||
public string defaultCurvesName
|
||||
{
|
||||
get { return m_OriginalOwner.defaultCurvesName; }
|
||||
}
|
||||
|
||||
public UnityObject asset
|
||||
{
|
||||
get { return m_OriginalOwner.asset; }
|
||||
}
|
||||
|
||||
public UnityObject assetOwner
|
||||
{
|
||||
get { return m_OriginalOwner.assetOwner; }
|
||||
}
|
||||
|
||||
public TrackAsset targetTrack
|
||||
{
|
||||
get { return m_OriginalOwner.targetTrack; }
|
||||
}
|
||||
|
||||
readonly ICurvesOwner m_OriginalOwner;
|
||||
readonly bool m_IsAnimatable;
|
||||
readonly Dictionary<EditorCurveBinding, SerializedProperty> m_PropertiesMap = new Dictionary<EditorCurveBinding, SerializedProperty>();
|
||||
int m_ProxyIsRebuilding = 0;
|
||||
|
||||
AnimationClip m_ProxyCurves;
|
||||
AnimationClip proxyCurves
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_IsAnimatable) return null;
|
||||
|
||||
if (m_ProxyCurves == null)
|
||||
RebuildProxyCurves();
|
||||
|
||||
return m_ProxyCurves;
|
||||
}
|
||||
}
|
||||
|
||||
List<SerializedProperty> m_AllAnimatableParameters;
|
||||
List<SerializedProperty> allAnimatableParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
var so = AnimatedParameterUtility.GetSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
if (so == null)
|
||||
return null;
|
||||
|
||||
so.UpdateIfRequiredOrScript();
|
||||
|
||||
if (m_AllAnimatableParameters == null)
|
||||
m_AllAnimatableParameters = m_OriginalOwner.GetAllAnimatableParameters().ToList();
|
||||
|
||||
return m_AllAnimatableParameters;
|
||||
}
|
||||
}
|
||||
|
||||
public CurvesProxy([NotNull] ICurvesOwner originalOwner)
|
||||
{
|
||||
m_OriginalOwner = originalOwner;
|
||||
m_IsAnimatable = originalOwner.HasAnyAnimatableParameters();
|
||||
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void CreateCurves(string curvesClipName)
|
||||
{
|
||||
m_OriginalOwner.CreateCurves(curvesClipName);
|
||||
}
|
||||
|
||||
public void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
var color = CurveUtility.GetPropertyColor(wrapper.binding.propertyName);
|
||||
wrapper.color = color;
|
||||
|
||||
float h, s, v;
|
||||
Color.RGBToHSV(color, out h, out s, out v);
|
||||
wrapper.wrapColorMultiplier = Color.HSVToRGB(h, s * 0.33f, v * 1.15f);
|
||||
|
||||
var curve = AnimationUtility.GetEditorCurve(proxyCurves, wrapper.binding);
|
||||
|
||||
wrapper.renderer = new NormalCurveRenderer(curve);
|
||||
|
||||
// Use curve length instead of animation clip length
|
||||
wrapper.renderer.SetCustomRange(0.0f, curve.keys.Last().time);
|
||||
}
|
||||
|
||||
public void RebuildCurves()
|
||||
{
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void UpdateCurves(List<CurveWrapper> updatedCurves)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.asset, "Edit Clip Curve");
|
||||
|
||||
if (m_OriginalOwner.curves != null)
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, "Edit Clip Curve");
|
||||
|
||||
foreach (var curve in updatedCurves)
|
||||
{
|
||||
UpdateCurve(curve.binding, curve.curve);
|
||||
}
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
}
|
||||
|
||||
void UpdateCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
ApplyConstraints(binding, curve);
|
||||
|
||||
if (curve.length == 0)
|
||||
{
|
||||
HandleAllKeysDeleted(binding);
|
||||
}
|
||||
else if (curve.length == 1)
|
||||
{
|
||||
HandleConstantCurveValueChanged(binding, curve);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleCurveUpdated(binding, curve);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyConstraints(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
if (curve.length == 0)
|
||||
return;
|
||||
|
||||
var curveUpdated = false;
|
||||
|
||||
var property = m_PropertiesMap[binding];
|
||||
if (property.propertyType == SerializedPropertyType.Boolean)
|
||||
{
|
||||
TimelineAnimationUtilities.ConstrainCurveToBooleanValues(curve);
|
||||
curveUpdated = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var range = AnimatedParameterUtility.GetAttributeForProperty<RangeAttribute>(property);
|
||||
if (range != null)
|
||||
{
|
||||
TimelineAnimationUtilities.ConstrainCurveToRange(curve, range.min, range.max);
|
||||
curveUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!curveUpdated)
|
||||
return;
|
||||
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, curve);
|
||||
}
|
||||
}
|
||||
|
||||
void HandleCurveUpdated(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
if (!m_OriginalOwner.hasCurves)
|
||||
m_OriginalOwner.CreateCurves(null);
|
||||
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, updatedCurve);
|
||||
}
|
||||
|
||||
void HandleConstantCurveValueChanged(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
var prop = m_PropertiesMap[binding];
|
||||
if (prop == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(prop.serializedObject.targetObject, "Edit Clip Curve");
|
||||
prop.serializedObject.UpdateIfRequiredOrScript();
|
||||
CurveEditUtility.SetFromKeyValue(prop, updatedCurve.keys[0].value);
|
||||
prop.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
void HandleAllKeysDeleted(EditorCurveBinding binding)
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
// Remove curve from original asset
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
}
|
||||
|
||||
// Ensure proxy still has constant value
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
void RebuildProxyCurves()
|
||||
{
|
||||
if (!m_IsAnimatable)
|
||||
return;
|
||||
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_ProxyCurves == null)
|
||||
{
|
||||
m_ProxyCurves = new AnimationClip
|
||||
{
|
||||
legacy = true,
|
||||
name = "Constant Curves",
|
||||
hideFlags = HideFlags.HideAndDontSave,
|
||||
frameRate = m_OriginalOwner.targetTrack.timelineAsset == null
|
||||
? TimelineAsset.EditorSettings.kDefaultFps
|
||||
: m_OriginalOwner.targetTrack.timelineAsset.editorSettings.fps
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ProxyCurves.ClearCurves();
|
||||
}
|
||||
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
|
||||
foreach (var param in allAnimatableParameters)
|
||||
CreateProxyCurve(param, m_ProxyCurves, m_OriginalOwner.asset, param.propertyPath);
|
||||
|
||||
AnimationClipCurveCache.Instance.GetCurveInfo(m_ProxyCurves).dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateProxyCurve(SerializedProperty prop, AnimationClip clip, UnityObject owner, string propertyName)
|
||||
{
|
||||
var binding = AnimatedParameterUtility.GetCurveBinding(owner, propertyName);
|
||||
|
||||
var originalCurve = m_OriginalOwner.hasCurves
|
||||
? AnimationUtility.GetEditorCurve(m_OriginalOwner.curves, binding)
|
||||
: null;
|
||||
|
||||
if (originalCurve != null)
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(clip, binding, originalCurve);
|
||||
}
|
||||
else
|
||||
{
|
||||
var curve = new AnimationCurve();
|
||||
|
||||
CurveEditUtility.AddKeyFrameToCurve(
|
||||
curve, 0.0f, clip.frameRate, CurveEditUtility.GetKeyValue(prop),
|
||||
prop.propertyType == SerializedPropertyType.Boolean);
|
||||
|
||||
AnimationUtility.SetEditorCurve(clip, binding, curve);
|
||||
}
|
||||
|
||||
m_PropertiesMap[binding] = prop;
|
||||
}
|
||||
|
||||
struct RebuildGuard : IDisposable
|
||||
{
|
||||
CurvesProxy m_Owner;
|
||||
AnimationUtility.OnCurveWasModified m_Callback;
|
||||
|
||||
public RebuildGuard(CurvesProxy owner)
|
||||
{
|
||||
m_Callback = AnimationUtility.onCurveWasModified;
|
||||
AnimationUtility.onCurveWasModified = null;
|
||||
m_Owner = owner;
|
||||
m_Owner.m_ProxyIsRebuilding++;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AnimationUtility.onCurveWasModified = m_Callback;
|
||||
m_Owner.m_ProxyIsRebuilding--;
|
||||
m_Owner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d72ccd2c66ea846fc842adf682b11526
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,435 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngineInternal;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class TimelineAnimationUtilities
|
||||
{
|
||||
public enum OffsetEditMode
|
||||
{
|
||||
None = -1,
|
||||
Translation = 0,
|
||||
Rotation = 1
|
||||
}
|
||||
|
||||
public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator)
|
||||
{
|
||||
if (director == null || animator == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static TimelineClip GetPreviousClip(TimelineClip clip)
|
||||
{
|
||||
TimelineClip previousClip = null;
|
||||
foreach (var c in clip.parentTrack.clips)
|
||||
{
|
||||
if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
|
||||
previousClip = c;
|
||||
}
|
||||
return previousClip;
|
||||
}
|
||||
|
||||
public static TimelineClip GetNextClip(TimelineClip clip)
|
||||
{
|
||||
return clip.parentTrack.clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault();
|
||||
}
|
||||
|
||||
public struct RigidTransform
|
||||
{
|
||||
public Vector3 position;
|
||||
public Quaternion rotation;
|
||||
|
||||
public static RigidTransform Compose(Vector3 pos, Quaternion rot)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.position = pos;
|
||||
ret.rotation = rot;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform Mul(RigidTransform a, RigidTransform b)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.rotation = a.rotation * b.rotation;
|
||||
ret.position = a.position + a.rotation * b.position;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform Inverse(RigidTransform a)
|
||||
{
|
||||
RigidTransform ret;
|
||||
ret.rotation = Quaternion.Inverse(a.rotation);
|
||||
ret.position = ret.rotation * (-a.position);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static RigidTransform identity
|
||||
{
|
||||
get { return Compose(Vector3.zero, Quaternion.identity); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track)
|
||||
{
|
||||
Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one);
|
||||
|
||||
// in scene off mode, the track offsets are set to the preview position which is stored in the track
|
||||
if (track.trackOffset == TrackOffset.ApplySceneOffsets)
|
||||
{
|
||||
trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one);
|
||||
}
|
||||
|
||||
// put the parent transform on to the track matrix
|
||||
if (transform.parent != null)
|
||||
{
|
||||
trackMatrix = transform.parent.localToWorldMatrix * trackMatrix;
|
||||
}
|
||||
|
||||
return trackMatrix;
|
||||
}
|
||||
|
||||
// Given a world space position and rotation, updates the clip offsets to match that
|
||||
public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation)
|
||||
{
|
||||
Matrix4x4 worldToLocal = transform.worldToLocalMatrix;
|
||||
Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one);
|
||||
Matrix4x4 trackMatrix = GetTrackMatrix(transform, track);
|
||||
|
||||
|
||||
// Use the transform to find the proper goal matrix with scale taken into account
|
||||
var oldPos = transform.position;
|
||||
var oldRot = transform.rotation;
|
||||
transform.position = globalPosition;
|
||||
transform.rotation = globalRotation;
|
||||
Matrix4x4 goal = transform.localToWorldMatrix;
|
||||
transform.position = oldPos;
|
||||
transform.rotation = oldRot;
|
||||
|
||||
// compute the new clip matrix.
|
||||
Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix;
|
||||
return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip));
|
||||
}
|
||||
|
||||
public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform)
|
||||
{
|
||||
Vector3 position = track.position;
|
||||
Quaternion rotation = track.rotation;
|
||||
if (transform != null && transform.parent != null)
|
||||
{
|
||||
position = transform.parent.TransformPoint(position);
|
||||
rotation = transform.parent.rotation * rotation;
|
||||
MathUtils.QuaternionNormalize(ref rotation);
|
||||
}
|
||||
|
||||
return RigidTransform.Compose(position, rotation);
|
||||
}
|
||||
|
||||
public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets)
|
||||
{
|
||||
if (transform != null && transform.parent != null)
|
||||
{
|
||||
offsets.position = transform.parent.InverseTransformPoint(offsets.position);
|
||||
offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation;
|
||||
MathUtils.QuaternionNormalize(ref offsets.rotation);
|
||||
}
|
||||
|
||||
track.position = offsets.position;
|
||||
track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY);
|
||||
track.UpdateClipOffsets();
|
||||
}
|
||||
|
||||
static MatchTargetFields GetMatchFields(TimelineClip clip)
|
||||
{
|
||||
var track = clip.parentTrack as AnimationTrack;
|
||||
if (track == null)
|
||||
return MatchTargetFieldConstants.None;
|
||||
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
var fields = track.matchTargetFields;
|
||||
if (asset != null && !asset.useTrackMatchFields)
|
||||
fields = asset.matchTargetFields;
|
||||
return fields;
|
||||
}
|
||||
|
||||
static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields)
|
||||
{
|
||||
Vector3 position = asset.position;
|
||||
|
||||
position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x;
|
||||
position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y;
|
||||
position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z;
|
||||
|
||||
asset.position = position;
|
||||
|
||||
// check first to avoid unnecessary conversion errors
|
||||
if (fields.HasAny(MatchTargetFieldConstants.Rotation))
|
||||
{
|
||||
Vector3 eulers = asset.eulerAngles;
|
||||
Vector3 resultEulers = result.rotation.eulerAngles;
|
||||
|
||||
eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x;
|
||||
eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y;
|
||||
eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z;
|
||||
|
||||
asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY);
|
||||
}
|
||||
}
|
||||
|
||||
public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
|
||||
{
|
||||
const double timeEpsilon = 0.00001;
|
||||
MatchTargetFields matchFields = GetMatchFields(currentClip);
|
||||
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
|
||||
return;
|
||||
|
||||
double cachedTime = director.time;
|
||||
|
||||
// finds previous clip
|
||||
TimelineClip previousClip = GetPreviousClip(currentClip);
|
||||
if (previousClip == null || currentClip == previousClip)
|
||||
return;
|
||||
|
||||
// make sure the transform is properly updated before modifying the graph
|
||||
director.Evaluate();
|
||||
|
||||
var parentTrack = currentClip.parentTrack as AnimationTrack;
|
||||
|
||||
var blendIn = currentClip.blendInDuration;
|
||||
currentClip.blendInDuration = 0;
|
||||
var blendOut = previousClip.blendOutDuration;
|
||||
previousClip.blendOutDuration = 0;
|
||||
|
||||
//evaluate previous without current
|
||||
parentTrack.RemoveClip(currentClip);
|
||||
director.RebuildGraph();
|
||||
double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start;
|
||||
director.time = previousEndTime - timeEpsilon;
|
||||
director.Evaluate(); // add port to evaluate only track
|
||||
|
||||
var targetPosition = matchPoint.position;
|
||||
var targetRotation = matchPoint.rotation;
|
||||
|
||||
// evaluate current without previous
|
||||
parentTrack.AddClip(currentClip);
|
||||
parentTrack.RemoveClip(previousClip);
|
||||
director.RebuildGraph();
|
||||
director.time = currentClip.start + timeEpsilon;
|
||||
director.Evaluate();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//compute offsets
|
||||
|
||||
var animationPlayable = currentClip.asset as AnimationPlayableAsset;
|
||||
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
|
||||
WriteMatchFields(animationPlayable, match, matchFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
currentClip.blendInDuration = blendIn;
|
||||
previousClip.blendOutDuration = blendOut;
|
||||
|
||||
parentTrack.AddClip(previousClip);
|
||||
director.RebuildGraph();
|
||||
director.time = cachedTime;
|
||||
director.Evaluate();
|
||||
}
|
||||
|
||||
public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director)
|
||||
{
|
||||
const double timeEpsilon = 0.00001;
|
||||
MatchTargetFields matchFields = GetMatchFields(currentClip);
|
||||
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null)
|
||||
return;
|
||||
|
||||
double cachedTime = director.time;
|
||||
|
||||
// finds next clip
|
||||
TimelineClip nextClip = GetNextClip(currentClip);
|
||||
if (nextClip == null || currentClip == nextClip)
|
||||
return;
|
||||
|
||||
// make sure the transform is properly updated before modifying the graph
|
||||
director.Evaluate();
|
||||
|
||||
var parentTrack = currentClip.parentTrack as AnimationTrack;
|
||||
|
||||
var blendOut = currentClip.blendOutDuration;
|
||||
var blendIn = nextClip.blendInDuration;
|
||||
currentClip.blendOutDuration = 0;
|
||||
nextClip.blendInDuration = 0;
|
||||
|
||||
//evaluate previous without current
|
||||
parentTrack.RemoveClip(currentClip);
|
||||
director.RebuildGraph();
|
||||
director.time = nextClip.start + timeEpsilon;
|
||||
director.Evaluate(); // add port to evaluate only track
|
||||
|
||||
var targetPosition = matchPoint.position;
|
||||
var targetRotation = matchPoint.rotation;
|
||||
|
||||
// evaluate current without next
|
||||
parentTrack.AddClip(currentClip);
|
||||
parentTrack.RemoveClip(nextClip);
|
||||
director.RebuildGraph();
|
||||
director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon);
|
||||
director.Evaluate();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//compute offsets
|
||||
|
||||
var animationPlayable = currentClip.asset as AnimationPlayableAsset;
|
||||
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation);
|
||||
WriteMatchFields(animationPlayable, match, matchFields);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
currentClip.blendOutDuration = blendOut;
|
||||
nextClip.blendInDuration = blendIn;
|
||||
|
||||
parentTrack.AddClip(nextClip);
|
||||
director.RebuildGraph();
|
||||
director.time = cachedTime;
|
||||
director.Evaluate();
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(WindowState state, TimelineClip clip)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clip);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(WindowState state, TimelineWindowTimeControl.ClipData clipData)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clipData);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
animationWindow.EditSequencerClip(animationClip, sourceObject, timeController);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
var clips = new List<AnimationClip>();
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
var animationTrack = track as AnimationTrack;
|
||||
if (animationTrack != null && animationTrack.infiniteClip != null)
|
||||
clips.Add(animationTrack.infiniteClip);
|
||||
|
||||
GetAnimationClips(track.GetClips(), clips);
|
||||
}
|
||||
UnlinkAnimationWindowFromAnimationClips(clips);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips)
|
||||
{
|
||||
var clips = new List<AnimationClip>();
|
||||
GetAnimationClips(timelineClips, clips);
|
||||
UnlinkAnimationWindowFromAnimationClips(clips);
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips)
|
||||
{
|
||||
if (clips.Count == 0)
|
||||
return;
|
||||
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
|
||||
foreach (var animWindow in windows.OfType<AnimationWindow>())
|
||||
{
|
||||
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip))
|
||||
animWindow.UnlinkSequencer();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnlinkAnimationWindow()
|
||||
{
|
||||
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow));
|
||||
foreach (var animWindow in windows.OfType<AnimationWindow>())
|
||||
{
|
||||
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer)
|
||||
animWindow.UnlinkSequencer();
|
||||
}
|
||||
}
|
||||
|
||||
private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips)
|
||||
{
|
||||
foreach (var timelineClip in timelineClips)
|
||||
{
|
||||
if (timelineClip.curves != null)
|
||||
clips.Add(timelineClip.curves);
|
||||
AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset;
|
||||
if (apa != null && apa.clip != null)
|
||||
clips.Add(apa.clip);
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetAnimationWindowCurrentFrame()
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
if (animationWindow)
|
||||
return animationWindow.state.currentFrame;
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static void SetAnimationWindowCurrentFrame(int frame)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
if (animationWindow)
|
||||
animationWindow.state.currentFrame = frame;
|
||||
}
|
||||
|
||||
public static void ConstrainCurveToBooleanValues(AnimationCurve curve)
|
||||
{
|
||||
// Clamp the values first
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
key.value = key.value < 0.5f ? 0.0f : 1.0f;
|
||||
keys[i] = key;
|
||||
}
|
||||
curve.keys = keys;
|
||||
|
||||
// Update the tangents once all the values are clamped
|
||||
for (var i = 0; i < curve.length; i++)
|
||||
{
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue)
|
||||
{
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var key = keys[i];
|
||||
key.value = Mathf.Clamp(key.value, minValue, maxValue);
|
||||
keys[i] = key;
|
||||
}
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
public static bool IsAnimationClip(TimelineClip clip)
|
||||
{
|
||||
return clip != null && (clip.asset as AnimationPlayableAsset) != null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9685354eb873b8d4699078b307b0f260
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 919d97c1a707113409177d498d31cf51
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
class ActiveInModeAttribute : Attribute
|
||||
{
|
||||
public TimelineModes modes { get; private set; }
|
||||
public ActiveInModeAttribute(TimelineModes timelineModes)
|
||||
{
|
||||
modes = timelineModes;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3a784fb721704576b3b4c3a7f3324264
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate path and priority of classes that are auto added to the menu
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class MenuEntryAttribute : Attribute
|
||||
{
|
||||
public readonly int priority;
|
||||
public readonly string name;
|
||||
public readonly string subMenuPath;
|
||||
|
||||
public MenuEntryAttribute(string path, int priority)
|
||||
{
|
||||
path = path ?? string.Empty;
|
||||
path = L10n.Tr(path);
|
||||
this.priority = priority;
|
||||
|
||||
int index = path.LastIndexOf('/');
|
||||
if (index >= 0)
|
||||
{
|
||||
name = (index == path.Length - 1) ? string.Empty : path.Substring(index + 1);
|
||||
subMenuPath = path.Substring(0, index + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = path;
|
||||
subMenuPath = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e6870f707805737429a719f575621041
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,71 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
|
||||
class ShortcutAttribute : Attribute
|
||||
{
|
||||
readonly string m_Identifier;
|
||||
readonly string m_EventCommandName;
|
||||
readonly string m_MenuShortcut;
|
||||
|
||||
public ShortcutAttribute(string identifier)
|
||||
{
|
||||
m_Identifier = identifier;
|
||||
m_EventCommandName = identifier;
|
||||
}
|
||||
|
||||
public ShortcutAttribute(string identifier, string commandName)
|
||||
{
|
||||
m_Identifier = identifier;
|
||||
m_EventCommandName = commandName;
|
||||
}
|
||||
|
||||
public ShortcutAttribute(KeyCode key, ShortcutModifiers modifiers = ShortcutModifiers.None)
|
||||
{
|
||||
m_MenuShortcut = new KeyCombination(key, modifiers).ToMenuShortcutString();
|
||||
}
|
||||
|
||||
public string GetMenuShortcut()
|
||||
{
|
||||
if (m_MenuShortcut != null)
|
||||
return m_MenuShortcut;
|
||||
|
||||
//find the mapped shortcut in the shortcut manager
|
||||
var shortcut = ShortcutIntegration.instance.directory.FindShortcutEntry(m_Identifier);
|
||||
if (shortcut != null && shortcut.combinations.Any())
|
||||
{
|
||||
return KeyCombination.SequenceToMenuString(shortcut.combinations);
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public bool MatchesEvent(Event evt)
|
||||
{
|
||||
if (evt.type != EventType.ExecuteCommand)
|
||||
return false;
|
||||
return evt.commandName == m_EventCommandName;
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
|
||||
class ShortcutPlatformOverrideAttribute : ShortcutAttribute
|
||||
{
|
||||
RuntimePlatform platform { get; }
|
||||
|
||||
public ShortcutPlatformOverrideAttribute(RuntimePlatform platform, KeyCode key, ShortcutModifiers modifiers = ShortcutModifiers.None)
|
||||
: base(key, modifiers)
|
||||
{
|
||||
this.platform = platform;
|
||||
}
|
||||
|
||||
public bool MatchesCurrentPlatform()
|
||||
{
|
||||
return Application.platform == platform;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c50a694a8232898498c1cdd47ce9873f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e091bb444874ef244b1ba4a813fc1e34
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(AudioClipProperties))]
|
||||
class AudioClipPropertiesDrawer : PropertyDrawer
|
||||
{
|
||||
[UsedImplicitly] // Also used by tests
|
||||
internal static class Styles
|
||||
{
|
||||
public const string VolumeControl = "AudioClipPropertiesDrawer.volume";
|
||||
|
||||
const string k_Indent = " ";
|
||||
public const string valuesFormatter = "0.###";
|
||||
public static string mixedPropertiesInfo = L10n.Tr("The final {3} is {0}\n" +
|
||||
"Calculated from:\n" +
|
||||
k_Indent + "Clip: {1}\n" +
|
||||
k_Indent + "Track: {2}");
|
||||
|
||||
public static string audioSourceContribution = L10n.Tr(k_Indent + "AudioSource: {0}");
|
||||
}
|
||||
|
||||
static StringBuilder s_MixInfoBuilder = new StringBuilder();
|
||||
|
||||
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
|
||||
{
|
||||
var volumeProp = property.FindPropertyRelative("volume");
|
||||
|
||||
GUI.SetNextControlName(Styles.VolumeControl);
|
||||
EditorGUI.Slider(position, volumeProp, 0.0f, 1.0f, AudioSourceInspector.Styles.volumeLabel);
|
||||
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
// Nothing more to do in asset mode
|
||||
return;
|
||||
|
||||
var clip = SelectionManager.SelectedClips().FirstOrDefault(c => c.asset == property.serializedObject.targetObject);
|
||||
|
||||
if (clip == null || clip.parentTrack == null)
|
||||
return;
|
||||
|
||||
var clipVolume = volumeProp.floatValue;
|
||||
var trackVolume = new SerializedObject(clip.parentTrack).FindProperty("m_TrackProperties.volume").floatValue;
|
||||
var binding = TimelineEditor.inspectedDirector.GetGenericBinding(clip.parentTrack) as AudioSource;
|
||||
|
||||
if (Math.Abs(clipVolume) < float.Epsilon &&
|
||||
Math.Abs(trackVolume) < float.Epsilon &&
|
||||
(binding == null || Math.Abs(binding.volume) < float.Epsilon))
|
||||
return;
|
||||
|
||||
if (Math.Abs(clipVolume - 1) < float.Epsilon &&
|
||||
Math.Abs(trackVolume - 1) < float.Epsilon &&
|
||||
(binding == null || Math.Abs(binding.volume - 1) < float.Epsilon))
|
||||
return;
|
||||
|
||||
s_MixInfoBuilder.Length = 0;
|
||||
|
||||
var audioSourceVolume = binding == null ? 1.0f : binding.volume;
|
||||
|
||||
s_MixInfoBuilder.AppendFormat(
|
||||
Styles.mixedPropertiesInfo,
|
||||
(clipVolume * trackVolume * audioSourceVolume).ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
clipVolume.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
trackVolume.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
AudioSourceInspector.Styles.volumeLabel.text);
|
||||
|
||||
if (binding != null)
|
||||
s_MixInfoBuilder.Append("\n")
|
||||
.AppendFormat(Styles.audioSourceContribution,
|
||||
audioSourceVolume.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture));
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox(new GUIContent(s_MixInfoBuilder.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5b6cac4a98010394791c66942a33caf4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,84 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(AudioPlayableAsset)), UsedImplicitly]
|
||||
class AudioPlayableAssetEditor : ClipEditor
|
||||
{
|
||||
readonly string k_NoClipAssignedError = LocalizationDatabase.GetLocalizedString("No audio clip assigned");
|
||||
readonly Dictionary<TimelineClip, WaveformPreview> m_PersistentPreviews = new Dictionary<TimelineClip, WaveformPreview>();
|
||||
ColorSpace m_ColorSpace = ColorSpace.Uninitialized;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
|
||||
{
|
||||
var clipOptions = base.GetClipOptions(clip);
|
||||
var audioAsset = clip.asset as AudioPlayableAsset;
|
||||
if (audioAsset != null && audioAsset.clip == null)
|
||||
clipOptions.errorText = k_NoClipAssignedError;
|
||||
return clipOptions;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)
|
||||
{
|
||||
if (!TimelineWindow.instance.state.showAudioWaveform)
|
||||
return;
|
||||
|
||||
var rect = region.position;
|
||||
if (rect.width <= 0)
|
||||
return;
|
||||
|
||||
var audioClip = clip.asset as AudioClip;
|
||||
if (audioClip == null)
|
||||
{
|
||||
var audioPlayableAsset = clip.asset as AudioPlayableAsset;
|
||||
if (audioPlayableAsset != null)
|
||||
audioClip = audioPlayableAsset.clip;
|
||||
}
|
||||
|
||||
if (audioClip == null)
|
||||
return;
|
||||
|
||||
var quantizedRect = new Rect(Mathf.Ceil(rect.x), Mathf.Ceil(rect.y), Mathf.Ceil(rect.width), Mathf.Ceil(rect.height));
|
||||
WaveformPreview preview;
|
||||
|
||||
if (QualitySettings.activeColorSpace != m_ColorSpace)
|
||||
{
|
||||
m_ColorSpace = QualitySettings.activeColorSpace;
|
||||
m_PersistentPreviews.Clear();
|
||||
}
|
||||
|
||||
if (!m_PersistentPreviews.TryGetValue(clip, out preview) || audioClip != preview.presentedObject)
|
||||
{
|
||||
preview = m_PersistentPreviews[clip] = WaveformPreviewFactory.Create((int)quantizedRect.width, audioClip);
|
||||
Color waveColour = GammaCorrect(DirectorStyles.Instance.customSkin.colorAudioWaveform);
|
||||
Color transparent = waveColour;
|
||||
transparent.a = 0;
|
||||
preview.backgroundColor = transparent;
|
||||
preview.waveColor = waveColour;
|
||||
preview.SetChannelMode(WaveformPreview.ChannelMode.MonoSum);
|
||||
preview.updated += () => TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
|
||||
preview.looping = clip.SupportsLooping();
|
||||
preview.SetTimeInfo(region.startTime, region.endTime - region.startTime);
|
||||
preview.OptimizeForSize(quantizedRect.size);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
preview.ApplyModifications();
|
||||
preview.Render(quantizedRect);
|
||||
}
|
||||
}
|
||||
|
||||
static Color GammaCorrect(Color color)
|
||||
{
|
||||
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.gamma : color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 74374298effb78d47b85450f7f724cef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,27 @@
|
|||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(AudioPlayableAsset))]
|
||||
class AudioPlayableAssetInspector : BasicAssetInspector
|
||||
{
|
||||
public override void ApplyChanges()
|
||||
{
|
||||
// At this point, we are guaranteed that the Timeline window is focused on
|
||||
// the correct asset and that a single clip is selected (see ClipInspector)
|
||||
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
// Do nothing if in asset mode
|
||||
return;
|
||||
|
||||
var asset = (AudioPlayableAsset)target;
|
||||
|
||||
if (TimelineEditor.inspectedDirector.state == PlayState.Playing)
|
||||
asset.LiveLink();
|
||||
else
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 23884ce4c1de32846adafea2d53a4cee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[CustomEditor(typeof(AudioTrack))]
|
||||
[CanEditMultipleObjects]
|
||||
class AudioTrackInspector : TrackAssetInspector
|
||||
{
|
||||
[UsedImplicitly] // Also used by tests
|
||||
internal static class Styles
|
||||
{
|
||||
public const string VolumeControl = "AudioTrackInspector.volume";
|
||||
public const string StereoPanControl = "AudioTrackInspector.stereoPan";
|
||||
public const string SpatialBlendControl = "AudioTrackInspector.spatialBlend";
|
||||
|
||||
const string k_Indent = " ";
|
||||
public const string valuesFormatter = "0.###";
|
||||
public const string mixInfoSectionSeparator = "\n\n";
|
||||
public static string mixedPropertiesInfo = L10n.Tr("The final {3} is {0}\n" +
|
||||
"Calculated from:\n" +
|
||||
k_Indent + "Track: {1}\n" +
|
||||
k_Indent + "AudioSource: {2}");
|
||||
}
|
||||
|
||||
static StringBuilder s_MixInfoBuilder = new StringBuilder();
|
||||
|
||||
SerializedProperty m_VolumeProperty;
|
||||
SerializedProperty m_StereoPanProperty;
|
||||
SerializedProperty m_SpatialBlendProperty;
|
||||
PlayableDirector m_Director;
|
||||
|
||||
public override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (((AudioTrack)target).timelineAsset == TimelineEditor.inspectedAsset)
|
||||
m_Director = TimelineEditor.inspectedDirector;
|
||||
|
||||
m_VolumeProperty = serializedObject.FindProperty("m_TrackProperties.volume");
|
||||
m_StereoPanProperty = serializedObject.FindProperty("m_TrackProperties.stereoPan");
|
||||
m_SpatialBlendProperty = serializedObject.FindProperty("m_TrackProperties.spatialBlend");
|
||||
}
|
||||
|
||||
protected override void DrawTrackProperties()
|
||||
{
|
||||
// Volume
|
||||
GUI.SetNextControlName(Styles.VolumeControl);
|
||||
EditorGUILayout.Slider(m_VolumeProperty, 0.0f, 1.0f, AudioSourceInspector.Styles.volumeLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Stereo Pan
|
||||
GUI.SetNextControlName(Styles.StereoPanControl);
|
||||
EditorGUIUtility.sliderLabels.SetLabels(AudioSourceInspector.Styles.panLeftLabel, AudioSourceInspector.Styles.panRightLabel);
|
||||
EditorGUILayout.Slider(m_StereoPanProperty, -1.0f, 1.0f, AudioSourceInspector.Styles.panStereoLabel);
|
||||
EditorGUIUtility.sliderLabels.SetLabels(null, null);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// Spatial Blend
|
||||
using (new EditorGUI.DisabledScope(ShouldDisableSpatialBlend()))
|
||||
{
|
||||
GUI.SetNextControlName(Styles.SpatialBlendControl);
|
||||
EditorGUIUtility.sliderLabels.SetLabels(AudioSourceInspector.Styles.spatialLeftLabel, AudioSourceInspector.Styles.spatialRightLabel);
|
||||
EditorGUILayout.Slider(m_SpatialBlendProperty, 0.0f, 1.0f, AudioSourceInspector.Styles.spatialBlendLabel);
|
||||
EditorGUIUtility.sliderLabels.SetLabels(null, null);
|
||||
}
|
||||
|
||||
DrawMixInfoSection();
|
||||
}
|
||||
|
||||
void DrawMixInfoSection()
|
||||
{
|
||||
if (m_Director == null || targets.Length > 1)
|
||||
return;
|
||||
|
||||
var binding = m_Director.GetGenericBinding(target) as AudioSource;
|
||||
if (binding == null)
|
||||
return;
|
||||
|
||||
var audioSourceVolume = binding.volume;
|
||||
var audioSourcePan = binding.panStereo;
|
||||
var audioSourceBlend = binding.spatialBlend;
|
||||
|
||||
var trackVolume = m_VolumeProperty.floatValue;
|
||||
var trackPan = m_StereoPanProperty.floatValue;
|
||||
var trackBlend = m_SpatialBlendProperty.floatValue;
|
||||
|
||||
// Skip sections when result is obvious
|
||||
|
||||
var skipVolumeInfo = Math.Abs(audioSourceVolume) < float.Epsilon && Math.Abs(trackVolume) < float.Epsilon || // All muted
|
||||
Math.Abs(audioSourceVolume - 1) < float.Epsilon && Math.Abs(trackVolume - 1) < float.Epsilon; // All max volume
|
||||
|
||||
var skipPanInfo = Math.Abs(audioSourcePan) < float.Epsilon && Math.Abs(trackPan) < float.Epsilon || // All centered
|
||||
Math.Abs(audioSourcePan - 1) < float.Epsilon && Math.Abs(trackPan - 1) < float.Epsilon || // All right
|
||||
Math.Abs(audioSourcePan - (-1.0f)) < float.Epsilon && Math.Abs(trackPan - (-1.0f)) < float.Epsilon; // All left
|
||||
|
||||
var skipBlendInfo = Math.Abs(audioSourceBlend) < float.Epsilon && Math.Abs(trackBlend) < float.Epsilon || // All 2D
|
||||
Math.Abs(audioSourceBlend - 1) < float.Epsilon && Math.Abs(trackBlend - 1) < float.Epsilon; // All 3D
|
||||
|
||||
if (skipVolumeInfo && skipPanInfo && skipBlendInfo)
|
||||
return;
|
||||
|
||||
s_MixInfoBuilder.Length = 0;
|
||||
|
||||
if (!skipVolumeInfo)
|
||||
s_MixInfoBuilder.AppendFormat(
|
||||
Styles.mixedPropertiesInfo,
|
||||
(audioSourceVolume * trackVolume).ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
trackVolume.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
audioSourceVolume.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
AudioSourceInspector.Styles.volumeLabel.text);
|
||||
|
||||
if (!skipVolumeInfo && !skipPanInfo)
|
||||
s_MixInfoBuilder.Append(Styles.mixInfoSectionSeparator);
|
||||
|
||||
if (!skipPanInfo)
|
||||
s_MixInfoBuilder.AppendFormat(
|
||||
Styles.mixedPropertiesInfo,
|
||||
Mathf.Clamp(audioSourcePan + trackPan, -1.0f, 1.0f).ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
trackPan.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
audioSourcePan.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
AudioSourceInspector.Styles.panStereoLabel.text);
|
||||
|
||||
if ((!skipVolumeInfo || !skipPanInfo) && !skipBlendInfo)
|
||||
s_MixInfoBuilder.Append(Styles.mixInfoSectionSeparator);
|
||||
|
||||
if (!skipBlendInfo)
|
||||
s_MixInfoBuilder.AppendFormat(
|
||||
Styles.mixedPropertiesInfo,
|
||||
Mathf.Clamp01(audioSourceBlend + trackBlend).ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
trackBlend.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
audioSourceBlend.ToString(Styles.valuesFormatter, CultureInfo.InvariantCulture),
|
||||
AudioSourceInspector.Styles.spatialBlendLabel.text);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.HelpBox(new GUIContent(s_MixInfoBuilder.ToString()));
|
||||
}
|
||||
|
||||
protected override void ApplyChanges()
|
||||
{
|
||||
var track = (AudioTrack)target;
|
||||
|
||||
if (TimelineEditor.inspectedAsset != track.timelineAsset || TimelineEditor.inspectedDirector == null)
|
||||
return;
|
||||
|
||||
if (TimelineEditor.inspectedDirector.state == PlayState.Playing)
|
||||
track.LiveLink();
|
||||
else
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
bool ShouldDisableSpatialBlend()
|
||||
{
|
||||
return m_Director == null ||
|
||||
targets.Any(selectedTrack => m_Director.GetGenericBinding(selectedTrack) == null);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 57acdaad593b8d143b8fb5052a09d7d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a24ec4b5c3e08e47bf50c8298c1fe0d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,69 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(ControlPlayableAsset))]
|
||||
class ControlPlayableAssetEditor : ClipEditor
|
||||
{
|
||||
static readonly Texture2D[] s_ParticleSystemIcon = {AssetPreview.GetMiniTypeThumbnail(typeof(ParticleSystem))};
|
||||
|
||||
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
|
||||
{
|
||||
var asset = (ControlPlayableAsset)clip.asset;
|
||||
var options = base.GetClipOptions(clip);
|
||||
if (asset.updateParticle && TimelineEditor.inspectedDirector != null && asset.controllingParticles)
|
||||
options.icons = s_ParticleSystemIcon;
|
||||
return options;
|
||||
}
|
||||
|
||||
public override void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)
|
||||
{
|
||||
var asset = (ControlPlayableAsset)clip.asset;
|
||||
GameObject sourceObject = null;
|
||||
|
||||
// go by sourceObject first, then by prefab
|
||||
if (TimelineEditor.inspectedDirector != null)
|
||||
sourceObject = asset.sourceGameObject.Resolve(TimelineEditor.inspectedDirector);
|
||||
|
||||
if (sourceObject == null && asset.prefabGameObject != null)
|
||||
sourceObject = asset.prefabGameObject;
|
||||
|
||||
if (sourceObject)
|
||||
{
|
||||
var directors = asset.GetComponent<PlayableDirector>(sourceObject);
|
||||
var particleSystems = asset.GetComponent<ParticleSystem>(sourceObject);
|
||||
|
||||
// update the duration and loop values (used for UI purposes) here
|
||||
// so they are tied to the latest gameObject bound
|
||||
asset.UpdateDurationAndLoopFlag(directors, particleSystems);
|
||||
|
||||
clip.displayName = sourceObject.name;
|
||||
}
|
||||
}
|
||||
|
||||
public override void GetSubTimelines(TimelineClip clip, PlayableDirector director, List<PlayableDirector> subTimelines)
|
||||
{
|
||||
var asset = (ControlPlayableAsset)clip.asset;
|
||||
|
||||
// If there is a prefab, it will override the source GameObject
|
||||
if (!asset.updateDirector || asset.prefabGameObject != null || director == null)
|
||||
return;
|
||||
|
||||
var go = asset.sourceGameObject.Resolve(director);
|
||||
if (go == null)
|
||||
return;
|
||||
|
||||
foreach (var subTimeline in asset.GetComponent<PlayableDirector>(go))
|
||||
{
|
||||
if (subTimeline == director || subTimeline == TimelineEditor.masterDirector || subTimeline == TimelineEditor.inspectedDirector)
|
||||
continue;
|
||||
|
||||
if (subTimeline.playableAsset is TimelineAsset)
|
||||
subTimelines.Add(subTimeline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b5489bb3cd68836439785588fffc67a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,657 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Utility class for editing animation clips from serialized properties
|
||||
static class CurveEditUtility
|
||||
{
|
||||
static bool IsRotationKey(EditorCurveBinding binding)
|
||||
{
|
||||
return binding.propertyName.Contains("localEulerAnglesRaw");
|
||||
}
|
||||
|
||||
public static void AddKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
|
||||
{
|
||||
if (sourceBinding.isPPtrCurve)
|
||||
{
|
||||
AddObjectKey(clip, sourceBinding, prop, time);
|
||||
}
|
||||
else if (IsRotationKey(sourceBinding))
|
||||
{
|
||||
AddRotationKey(clip, sourceBinding, prop, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddFloatKey(clip, sourceBinding, prop, time);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddObjectKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
|
||||
{
|
||||
if (prop.propertyType != SerializedPropertyType.ObjectReference)
|
||||
return;
|
||||
|
||||
ObjectReferenceKeyframe[] curve = null;
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
|
||||
if (curveIndex >= 0)
|
||||
{
|
||||
curve = info.objectCurves[curveIndex];
|
||||
|
||||
// where in the array does the evaluation land?
|
||||
var evalIndex = EvaluateIndex(curve, (float)time);
|
||||
|
||||
if (KeyCompare(curve[evalIndex].time, (float)time, clip.frameRate) == 0)
|
||||
{
|
||||
curve[evalIndex].value = prop.objectReferenceValue;
|
||||
}
|
||||
// check the next key (always return the minimum value)
|
||||
else if (evalIndex < curve.Length - 1 && KeyCompare(curve[evalIndex + 1].time, (float)time, clip.frameRate) == 0)
|
||||
{
|
||||
curve[evalIndex + 1].value = prop.objectReferenceValue;
|
||||
}
|
||||
// resize the array
|
||||
else
|
||||
{
|
||||
if (time > curve[0].time)
|
||||
evalIndex++;
|
||||
var key = new ObjectReferenceKeyframe();
|
||||
key.time = (float)time;
|
||||
key.value = prop.objectReferenceValue;
|
||||
ArrayUtility.Insert(ref curve, evalIndex, key);
|
||||
}
|
||||
}
|
||||
else // curve doesn't exist, add it
|
||||
{
|
||||
curve = new ObjectReferenceKeyframe[1];
|
||||
curve[0].time = (float)time;
|
||||
curve[0].value = prop.objectReferenceValue;
|
||||
}
|
||||
|
||||
AnimationUtility.SetObjectReferenceCurve(clip, sourceBinding, curve);
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
|
||||
static void AddRotationKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
|
||||
{
|
||||
if (prop.propertyType != SerializedPropertyType.Quaternion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var updateCurves = new List<AnimationCurve>();
|
||||
var updateBindings = new List<EditorCurveBinding>();
|
||||
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
for (var i = 0; i < info.bindings.Length; i++)
|
||||
{
|
||||
if (sourceBind.type != info.bindings[i].type)
|
||||
continue;
|
||||
|
||||
if (info.bindings[i].propertyName.Contains("localEuler"))
|
||||
{
|
||||
updateBindings.Add(info.bindings[i]);
|
||||
updateCurves.Add(info.curves[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// use this instead of serialized properties because the editor will attempt to maintain
|
||||
// correct localeulers
|
||||
var eulers = ((Transform)prop.serializedObject.targetObject).localEulerAngles;
|
||||
if (updateBindings.Count == 0)
|
||||
{
|
||||
var propName = AnimationWindowUtility.GetPropertyGroupName(sourceBind.propertyName);
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".x"));
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".y"));
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".z"));
|
||||
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
AddKeyFrameToCurve(curveX, (float)time, clip.frameRate, eulers.x, false);
|
||||
AddKeyFrameToCurve(curveY, (float)time, clip.frameRate, eulers.y, false);
|
||||
AddKeyFrameToCurve(curveZ, (float)time, clip.frameRate, eulers.z, false);
|
||||
|
||||
updateCurves.Add(curveX);
|
||||
updateCurves.Add(curveY);
|
||||
updateCurves.Add(curveZ);
|
||||
}
|
||||
|
||||
for (var i = 0; i < updateBindings.Count; i++)
|
||||
{
|
||||
var c = updateBindings[i].propertyName.Last();
|
||||
var value = eulers.x;
|
||||
if (c == 'y') value = eulers.y;
|
||||
else if (c == 'z') value = eulers.z;
|
||||
AddKeyFrameToCurve(updateCurves[i], (float)time, clip.frameRate, value, false);
|
||||
}
|
||||
|
||||
UpdateEditorCurves(clip, updateBindings, updateCurves);
|
||||
}
|
||||
|
||||
// Add a floating point curve key
|
||||
static void AddFloatKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
|
||||
{
|
||||
var updateCurves = new List<AnimationCurve>();
|
||||
var updateBindings = new List<EditorCurveBinding>();
|
||||
|
||||
var updated = false;
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
for (var i = 0; i < info.bindings.Length; i++)
|
||||
{
|
||||
var binding = info.bindings[i];
|
||||
if (binding.type != sourceBind.type)
|
||||
continue;
|
||||
|
||||
SerializedProperty valProp = null;
|
||||
var curve = info.curves[i];
|
||||
|
||||
// perfect match on property path, editting a float
|
||||
if (prop.propertyPath.Equals(binding.propertyName))
|
||||
{
|
||||
valProp = prop;
|
||||
}
|
||||
// this is a child object
|
||||
else if (binding.propertyName.Contains(prop.propertyPath))
|
||||
{
|
||||
valProp = prop.serializedObject.FindProperty(binding.propertyName);
|
||||
}
|
||||
|
||||
if (valProp != null)
|
||||
{
|
||||
var value = GetKeyValue(valProp);
|
||||
if (!float.IsNaN(value)) // Nan indicates an error retrieving the property value
|
||||
{
|
||||
updated = true;
|
||||
AddKeyFrameToCurve(curve, (float)time, clip.frameRate, value, valProp.propertyType == SerializedPropertyType.Boolean);
|
||||
updateCurves.Add(curve);
|
||||
updateBindings.Add(binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Curves don't exist, add them
|
||||
if (!updated)
|
||||
{
|
||||
var propName = AnimationWindowUtility.GetPropertyGroupName(sourceBind.propertyName);
|
||||
if (!prop.hasChildren)
|
||||
{
|
||||
var value = GetKeyValue(prop);
|
||||
if (!float.IsNaN(value))
|
||||
{
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, sourceBind.propertyName));
|
||||
var curve = new AnimationCurve();
|
||||
AddKeyFrameToCurve(curve, (float)time, clip.frameRate, value, prop.propertyType == SerializedPropertyType.Boolean);
|
||||
updateCurves.Add(curve);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// special case because subproperties on color aren't 'visible' so you can't iterate over them
|
||||
if (prop.propertyType == SerializedPropertyType.Color)
|
||||
{
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".r"));
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".g"));
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".b"));
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, propName + ".a"));
|
||||
|
||||
var c = prop.colorValue;
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var curve = new AnimationCurve();
|
||||
AddKeyFrameToCurve(curve, (float)time, clip.frameRate, c[i], prop.propertyType == SerializedPropertyType.Boolean);
|
||||
updateCurves.Add(curve);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prop = prop.Copy();
|
||||
foreach (SerializedProperty cp in prop)
|
||||
{
|
||||
updateBindings.Add(EditorCurveBinding.FloatCurve(sourceBind.path, sourceBind.type, cp.propertyPath));
|
||||
var curve = new AnimationCurve();
|
||||
AddKeyFrameToCurve(curve, (float)time, clip.frameRate, GetKeyValue(cp), cp.propertyType == SerializedPropertyType.Boolean);
|
||||
updateCurves.Add(curve);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UpdateEditorCurves(clip, updateBindings, updateCurves);
|
||||
}
|
||||
|
||||
public static void RemoveKey(AnimationClip clip, EditorCurveBinding sourceBinding, SerializedProperty prop, double time)
|
||||
{
|
||||
if (sourceBinding.isPPtrCurve)
|
||||
{
|
||||
RemoveObjectKey(clip, sourceBinding, time);
|
||||
}
|
||||
else if (IsRotationKey(sourceBinding))
|
||||
{
|
||||
RemoveRotationKey(clip, sourceBinding, prop, time);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveFloatKey(clip, sourceBinding, prop, time);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveObjectKey(AnimationClip clip, EditorCurveBinding sourceBinding, double time)
|
||||
{
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
|
||||
if (curveIndex >= 0)
|
||||
{
|
||||
var curve = info.objectCurves[curveIndex];
|
||||
var evalIndex = GetKeyframeAtTime(curve, (float)time, clip.frameRate);
|
||||
if (evalIndex >= 0)
|
||||
{
|
||||
ArrayUtility.RemoveAt(ref curve, evalIndex);
|
||||
AnimationUtility.SetObjectReferenceCurve(clip, sourceBinding, curve.Length == 0 ? null : curve);
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetObjectKeyCount(AnimationClip clip, EditorCurveBinding sourceBinding)
|
||||
{
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
var curveIndex = Array.IndexOf(info.objectBindings, sourceBinding);
|
||||
if (curveIndex >= 0)
|
||||
{
|
||||
var curve = info.objectCurves[curveIndex];
|
||||
return curve.Length;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void RemoveRotationKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
|
||||
{
|
||||
if (prop.propertyType != SerializedPropertyType.Quaternion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var updateCurves = new List<AnimationCurve>();
|
||||
var updateBindings = new List<EditorCurveBinding>();
|
||||
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
for (var i = 0; i < info.bindings.Length; i++)
|
||||
{
|
||||
if (sourceBind.type != info.bindings[i].type)
|
||||
continue;
|
||||
|
||||
if (info.bindings[i].propertyName.Contains("localEuler"))
|
||||
{
|
||||
updateBindings.Add(info.bindings[i]);
|
||||
updateCurves.Add(info.curves[i]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var c in updateCurves)
|
||||
{
|
||||
RemoveKeyFrameFromCurve(c, (float)time, clip.frameRate);
|
||||
}
|
||||
|
||||
UpdateEditorCurves(clip, updateBindings, updateCurves);
|
||||
}
|
||||
|
||||
// Removes the float keys from curves
|
||||
static void RemoveFloatKey(AnimationClip clip, EditorCurveBinding sourceBind, SerializedProperty prop, double time)
|
||||
{
|
||||
var updateCurves = new List<AnimationCurve>();
|
||||
var updateBindings = new List<EditorCurveBinding>();
|
||||
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
for (var i = 0; i < info.bindings.Length; i++)
|
||||
{
|
||||
var binding = info.bindings[i];
|
||||
if (binding.type != sourceBind.type)
|
||||
continue;
|
||||
|
||||
SerializedProperty valProp = null;
|
||||
var curve = info.curves[i];
|
||||
|
||||
// perfect match on property path, editting a float
|
||||
if (prop.propertyPath.Equals(binding.propertyName))
|
||||
{
|
||||
valProp = prop;
|
||||
}
|
||||
// this is a child object
|
||||
else if (binding.propertyName.Contains(prop.propertyPath))
|
||||
{
|
||||
valProp = prop.serializedObject.FindProperty(binding.propertyName);
|
||||
}
|
||||
if (valProp != null)
|
||||
{
|
||||
RemoveKeyFrameFromCurve(curve, (float)time, clip.frameRate);
|
||||
updateCurves.Add(curve);
|
||||
updateBindings.Add(binding);
|
||||
}
|
||||
}
|
||||
|
||||
// update the curve. Do this last to not mess with the curve caches we are iterating over
|
||||
UpdateEditorCurves(clip, updateBindings, updateCurves);
|
||||
}
|
||||
|
||||
static void UpdateEditorCurve(AnimationClip clip, EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
if (curve.keys.Length == 0)
|
||||
AnimationUtility.SetEditorCurve(clip, binding, null);
|
||||
else
|
||||
AnimationUtility.SetEditorCurve(clip, binding, curve);
|
||||
}
|
||||
|
||||
static void UpdateEditorCurves(AnimationClip clip, List<EditorCurveBinding> bindings, List<AnimationCurve> curves)
|
||||
{
|
||||
if (curves.Count == 0)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < curves.Count; i++)
|
||||
{
|
||||
UpdateEditorCurve(clip, bindings[i], curves[i]);
|
||||
}
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
|
||||
public static void RemoveCurves(AnimationClip clip, SerializedProperty prop)
|
||||
{
|
||||
if (clip == null || prop == null)
|
||||
return;
|
||||
|
||||
var toRemove = new List<EditorCurveBinding>();
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
for (var i = 0; i < info.bindings.Length; i++)
|
||||
{
|
||||
var binding = info.bindings[i];
|
||||
|
||||
// check if we match directly, or with a child object
|
||||
if (prop.propertyPath.Equals(binding.propertyName) || binding.propertyName.Contains(prop.propertyPath))
|
||||
{
|
||||
toRemove.Add(binding);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < toRemove.Count; i++)
|
||||
{
|
||||
AnimationUtility.SetEditorCurve(clip, toRemove[i], null);
|
||||
}
|
||||
}
|
||||
|
||||
// adds a stepped key frame to the given curve
|
||||
public static void AddKeyFrameToCurve(AnimationCurve curve, float time, float framerate, float value, bool stepped)
|
||||
{
|
||||
var key = new Keyframe();
|
||||
|
||||
bool add = true;
|
||||
var keyIndex = GetKeyframeAtTime(curve, time, framerate);
|
||||
if (keyIndex != -1)
|
||||
{
|
||||
add = false;
|
||||
key = curve[keyIndex]; // retain the tangents and mode
|
||||
curve.RemoveKey(keyIndex);
|
||||
}
|
||||
|
||||
key.value = value;
|
||||
key.time = GetKeyTime(time, framerate);
|
||||
keyIndex = curve.AddKey(key);
|
||||
|
||||
if (stepped)
|
||||
{
|
||||
AnimationUtility.SetKeyBroken(curve, keyIndex, stepped);
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, keyIndex, AnimationUtility.TangentMode.Constant);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, keyIndex, AnimationUtility.TangentMode.Constant);
|
||||
key.outTangent = Mathf.Infinity;
|
||||
key.inTangent = Mathf.Infinity;
|
||||
}
|
||||
else if (add)
|
||||
{
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, keyIndex, AnimationUtility.TangentMode.ClampedAuto);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, keyIndex, AnimationUtility.TangentMode.ClampedAuto);
|
||||
}
|
||||
|
||||
if (keyIndex != -1 && !stepped)
|
||||
{
|
||||
AnimationUtility.UpdateTangentsFromModeSurrounding(curve, keyIndex);
|
||||
AnimationUtility.SetKeyBroken(curve, keyIndex, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Removes a keyframe at the given time from the animation curve
|
||||
public static bool RemoveKeyFrameFromCurve(AnimationCurve curve, float time, float framerate)
|
||||
{
|
||||
var keyIndex = GetKeyframeAtTime(curve, time, framerate);
|
||||
if (keyIndex == -1)
|
||||
return false;
|
||||
|
||||
curve.RemoveKey(keyIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
// gets the value of the key
|
||||
public static float GetKeyValue(SerializedProperty prop)
|
||||
{
|
||||
switch (prop.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Integer:
|
||||
return prop.intValue;
|
||||
case SerializedPropertyType.Boolean:
|
||||
return prop.boolValue ? 1.0f : 0.0f;
|
||||
case SerializedPropertyType.Float:
|
||||
return prop.floatValue;
|
||||
default:
|
||||
Debug.LogError("Could not convert property type " + prop.propertyType.ToString() + " to float");
|
||||
break;
|
||||
}
|
||||
return float.NaN;
|
||||
}
|
||||
|
||||
public static void SetFromKeyValue(SerializedProperty prop, float keyValue)
|
||||
{
|
||||
switch (prop.propertyType)
|
||||
{
|
||||
case SerializedPropertyType.Float:
|
||||
{
|
||||
prop.floatValue = keyValue;
|
||||
return;
|
||||
}
|
||||
case SerializedPropertyType.Integer:
|
||||
{
|
||||
prop.intValue = (int)keyValue;
|
||||
return;
|
||||
}
|
||||
case SerializedPropertyType.Boolean:
|
||||
{
|
||||
prop.boolValue = Math.Abs(keyValue) > 0.001f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.LogError("Could not convert float to property type " + prop.propertyType.ToString());
|
||||
}
|
||||
|
||||
// gets the index of the key, -1 if not found
|
||||
public static int GetKeyframeAtTime(AnimationCurve curve, float time, float frameRate)
|
||||
{
|
||||
var range = 0.5f / frameRate;
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
{
|
||||
var k = keys[i];
|
||||
if (k.time >= time - range && k.time < time + range)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int GetKeyframeAtTime(ObjectReferenceKeyframe[] curve, float time, float frameRate)
|
||||
{
|
||||
if (curve == null || curve.Length == 0)
|
||||
return -1;
|
||||
|
||||
var range = 0.5f / frameRate;
|
||||
for (var i = 0; i < curve.Length; i++)
|
||||
{
|
||||
var t = curve[i].time;
|
||||
if (t >= time - range && t < time + range)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static float GetKeyTime(float time, float frameRate)
|
||||
{
|
||||
return Mathf.Round(time * frameRate) / frameRate;
|
||||
}
|
||||
|
||||
public static int KeyCompare(float timeA, float timeB, float frameRate)
|
||||
{
|
||||
if (Mathf.Abs(timeA - timeB) <= 0.5f / frameRate)
|
||||
return 0;
|
||||
return timeA < timeB ? -1 : 1;
|
||||
}
|
||||
|
||||
// Evaluates an object (bool curve)
|
||||
public static Object Evaluate(ObjectReferenceKeyframe[] curve, float time)
|
||||
{
|
||||
return curve[EvaluateIndex(curve, time)].value;
|
||||
}
|
||||
|
||||
// returns the index from evaluation
|
||||
public static int EvaluateIndex(ObjectReferenceKeyframe[] curve, float time)
|
||||
{
|
||||
if (curve == null || curve.Length == 0)
|
||||
throw new InvalidOperationException("Can not evaluate a PPtr curve with no entries");
|
||||
|
||||
// clamp conditions
|
||||
if (time <= curve[0].time)
|
||||
return 0;
|
||||
if (time >= curve.Last().time)
|
||||
return curve.Length - 1;
|
||||
|
||||
// binary search
|
||||
var max = curve.Length - 1;
|
||||
var min = 0;
|
||||
while (max - min > 1)
|
||||
{
|
||||
var imid = (min + max) / 2;
|
||||
if (Mathf.Approximately(curve[imid].time, time))
|
||||
return imid;
|
||||
if (curve[imid].time < time)
|
||||
min = imid;
|
||||
else if (curve[imid].time > time)
|
||||
max = imid;
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
// Shifts the animation clip so the time start at 0
|
||||
public static void ShiftBySeconds(this AnimationClip clip, float time)
|
||||
{
|
||||
var floatBindings = AnimationUtility.GetCurveBindings(clip);
|
||||
var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
|
||||
|
||||
// update the float curves
|
||||
foreach (var bind in floatBindings)
|
||||
{
|
||||
var curve = AnimationUtility.GetEditorCurve(clip, bind);
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
keys[i].time += time;
|
||||
curve.keys = keys;
|
||||
AnimationUtility.SetEditorCurve(clip, bind, curve);
|
||||
}
|
||||
|
||||
// update the PPtr curves
|
||||
foreach (var bind in objectBindings)
|
||||
{
|
||||
var curve = AnimationUtility.GetObjectReferenceCurve(clip, bind);
|
||||
for (var i = 0; i < curve.Length; i++)
|
||||
curve[i].time += time;
|
||||
AnimationUtility.SetObjectReferenceCurve(clip, bind, curve);
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
|
||||
public static void ScaleTime(this AnimationClip clip, float scale)
|
||||
{
|
||||
var floatBindings = AnimationUtility.GetCurveBindings(clip);
|
||||
var objectBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
|
||||
|
||||
// update the float curves
|
||||
foreach (var bind in floatBindings)
|
||||
{
|
||||
var curve = AnimationUtility.GetEditorCurve(clip, bind);
|
||||
var keys = curve.keys;
|
||||
for (var i = 0; i < keys.Length; i++)
|
||||
keys[i].time *= scale;
|
||||
curve.keys = keys.OrderBy(x => x.time).ToArray();
|
||||
AnimationUtility.SetEditorCurve(clip, bind, curve);
|
||||
}
|
||||
|
||||
// update the PPtr curves
|
||||
foreach (var bind in objectBindings)
|
||||
{
|
||||
var curve = AnimationUtility.GetObjectReferenceCurve(clip, bind);
|
||||
for (var i = 0; i < curve.Length; i++)
|
||||
curve[i].time *= scale;
|
||||
curve = curve.OrderBy(x => x.time).ToArray();
|
||||
AnimationUtility.SetObjectReferenceCurve(clip, bind, curve);
|
||||
}
|
||||
|
||||
EditorUtility.SetDirty(clip);
|
||||
}
|
||||
|
||||
// Creates an opposing blend curve that matches the given curve to make sure the result is normalized
|
||||
public static AnimationCurve CreateMatchingCurve(AnimationCurve curve)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
|
||||
for (var i = 0; i != keys.Length; i++)
|
||||
{
|
||||
if (!Single.IsPositiveInfinity(keys[i].inTangent))
|
||||
keys[i].inTangent = -keys[i].inTangent;
|
||||
if (!Single.IsPositiveInfinity(keys[i].outTangent))
|
||||
keys[i].outTangent = -keys[i].outTangent;
|
||||
keys[i].value = 1.0f - keys[i].value;
|
||||
}
|
||||
return new AnimationCurve(keys);
|
||||
}
|
||||
|
||||
// Sanitizes the keys on an animation to force the property to be normalized
|
||||
public static Keyframe[] SanitizeCurveKeys(Keyframe[] keys, bool easeIn)
|
||||
{
|
||||
if (keys.Length < 2)
|
||||
{
|
||||
if (easeIn)
|
||||
keys = new[] { new Keyframe(0, 0), new Keyframe(1, 1) };
|
||||
else
|
||||
keys = new[] { new Keyframe(0, 1), new Keyframe(1, 0) };
|
||||
}
|
||||
else if (easeIn)
|
||||
{
|
||||
keys[0].time = 0;
|
||||
keys[keys.Length - 1].time = 1;
|
||||
keys[keys.Length - 1].value = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
keys[0].time = 0;
|
||||
keys[0].value = 1;
|
||||
keys[keys.Length - 1].time = 1;
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9e2b7a65f0a52974193ed497d145b0bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ad70ff8d98b257540b683737743828cb
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,256 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of the on-screen area where a clip is drawn
|
||||
/// </summary>
|
||||
public struct ClipBackgroundRegion
|
||||
{
|
||||
/// <summary>
|
||||
/// The rectangle where the background of the clip is drawn.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The rectangle is clipped to the screen. The rectangle does not include clip borders.
|
||||
/// </remarks>
|
||||
public Rect position { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start time of the region, relative to the clip.
|
||||
/// </summary>
|
||||
public double startTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the region, relative to the clip.
|
||||
/// </summary>
|
||||
public double endTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
/// <param name="_position"></param>
|
||||
/// <param name="_startTime"></param>
|
||||
/// <param name="_endTime"></param>
|
||||
public ClipBackgroundRegion(Rect _position, double _startTime, double _endTime)
|
||||
{
|
||||
position = _position;
|
||||
startTime = _startTime;
|
||||
endTime = _endTime;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is ClipBackgroundRegion))
|
||||
return false;
|
||||
|
||||
return Equals((ClipBackgroundRegion)obj);
|
||||
}
|
||||
|
||||
public bool Equals(ClipBackgroundRegion other)
|
||||
{
|
||||
return position.Equals(other.position) &&
|
||||
startTime == other.startTime &&
|
||||
endTime == other.endTime;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(
|
||||
position.GetHashCode(),
|
||||
startTime.GetHashCode(),
|
||||
endTime.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
public static bool operator==(ClipBackgroundRegion region1, ClipBackgroundRegion region2)
|
||||
{
|
||||
return region1.Equals(region2);
|
||||
}
|
||||
|
||||
public static bool operator!=(ClipBackgroundRegion region1, ClipBackgroundRegion region2)
|
||||
{
|
||||
return !region1.Equals(region2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The user-defined options for drawing a clip.
|
||||
/// </summary>
|
||||
public struct ClipDrawOptions
|
||||
{
|
||||
private IEnumerable<Texture2D> m_Icons;
|
||||
|
||||
/// <summary>
|
||||
/// Text that indicates if the clip should display an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the error text is not empty or null, then the clip displays a warning. The error text is used as the tooltip.
|
||||
/// </remarks>
|
||||
public string errorText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The tooltip to show for the clip.
|
||||
/// </summary>
|
||||
public string tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color drawn under the clip. By default, the color is the same as the track color.
|
||||
/// </summary>
|
||||
public Color highlightColor { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Icons to display on the clip.
|
||||
/// </summary>
|
||||
public IEnumerable<Texture2D> icons
|
||||
{
|
||||
get { return m_Icons ?? System.Linq.Enumerable.Empty<Texture2D>(); }
|
||||
set { m_Icons = value;}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is ClipDrawOptions))
|
||||
return false;
|
||||
|
||||
return Equals((ClipDrawOptions)obj);
|
||||
}
|
||||
|
||||
public bool Equals(ClipDrawOptions other)
|
||||
{
|
||||
return errorText == other.errorText &&
|
||||
tooltip == other.tooltip &&
|
||||
highlightColor == other.highlightColor &&
|
||||
System.Linq.Enumerable.SequenceEqual(icons, other.icons);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(
|
||||
errorText != null ? errorText.GetHashCode() : 0,
|
||||
tooltip != null ? tooltip.GetHashCode() : 0,
|
||||
highlightColor.GetHashCode(),
|
||||
icons != null ? icons.GetHashCode() : 0
|
||||
);
|
||||
}
|
||||
|
||||
public static bool operator==(ClipDrawOptions options1, ClipDrawOptions options2)
|
||||
{
|
||||
return options1.Equals(options2);
|
||||
}
|
||||
|
||||
public static bool operator!=(ClipDrawOptions options1, ClipDrawOptions options2)
|
||||
{
|
||||
return !options1.Equals(options2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Use this class to customize clip types in the TimelineEditor.
|
||||
/// </summary>
|
||||
public class ClipEditor
|
||||
{
|
||||
static readonly string k_NoPlayableAssetError = LocalizationDatabase.GetLocalizedString("This clip does not contain a valid playable asset");
|
||||
static readonly string k_ScriptLoadError = LocalizationDatabase.GetLocalizedString("The associated script can not be loaded");
|
||||
|
||||
internal readonly bool supportsSubTimelines;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public ClipEditor()
|
||||
{
|
||||
supportsSubTimelines = TypeUtility.HasOverrideMethod(GetType(), nameof(GetSubTimelines));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement this method to override the default options for drawing a clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip being drawn.</param>
|
||||
/// <returns>The options for drawing a clip.</returns>
|
||||
public virtual ClipDrawOptions GetClipOptions(TimelineClip clip)
|
||||
{
|
||||
return new ClipDrawOptions()
|
||||
{
|
||||
errorText = GetErrorText(clip),
|
||||
tooltip = string.Empty,
|
||||
highlightColor = GetDefaultHighlightColor(clip),
|
||||
icons = System.Linq.Enumerable.Empty<Texture2D>()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to draw a background for a clip .
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip being drawn.</param>
|
||||
/// <param name="region">The on-screen area where the clip is drawn.</param>
|
||||
public virtual void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a clip is created.
|
||||
/// </summary>
|
||||
/// <param name="clip">The newly created clip.</param>
|
||||
/// <param name="track">The track that the clip is assigned to.</param>
|
||||
/// <param name="clonedFrom">The source that the clip was copied from. This can be set to null if the clip is not a copy.</param>
|
||||
/// <remarks>
|
||||
/// The callback occurs before the clip is assigned to the track.
|
||||
/// </remarks>
|
||||
public virtual void OnCreate(TimelineClip clip, TrackAsset track, TimelineClip clonedFrom)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error text for the specified clip.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip being drawn.</param>
|
||||
/// <returns>Returns the error text to be displayed as the tool tip for the clip. If there is no error to be displayed, this method returns string.Empty.</returns>
|
||||
public string GetErrorText(TimelineClip clip)
|
||||
{
|
||||
if (clip == null || clip.asset == null)
|
||||
return k_NoPlayableAssetError;
|
||||
|
||||
var playableAsset = clip.asset as ScriptableObject;
|
||||
if (playableAsset == null || MonoScript.FromScriptableObject(playableAsset) == null)
|
||||
return k_ScriptLoadError;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The color drawn under the clip. By default, the color is the same as the track color.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip being drawn.</param>
|
||||
/// <returns>Returns the highlight color of the clip being drawn.</returns>
|
||||
public Color GetDefaultHighlightColor(TimelineClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return Color.white;
|
||||
|
||||
return TrackResourceCache.GetTrackColor(clip.parentTrack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a clip is changed by the Editor.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip that changed.</param>
|
||||
public virtual void OnClipChanged(TimelineClip clip)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sub-timelines for a specific clip. Implement this method if your clip supports playing nested timelines.
|
||||
/// </summary>
|
||||
/// <param name="clip">The clip with the ControlPlayableAsset.</param>
|
||||
/// <param name="director">The playable director driving the Timeline Clip. This may not be the same as TimelineEditor.inspectedDirector.</param>
|
||||
/// <param name="subTimelines">Specify the sub-timelines to control.</param>
|
||||
public virtual void GetSubTimelines(TimelineClip clip, PlayableDirector director, List<PlayableDirector> subTimelines)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2537ddddebaa455409dec422eb08fd7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,155 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class CustomTimelineEditorCache
|
||||
{
|
||||
static class SubClassCache<TEditorClass> where TEditorClass : class, new()
|
||||
{
|
||||
private static Type[] s_SubClasses = null;
|
||||
private static readonly TEditorClass s_DefaultInstance = new TEditorClass();
|
||||
private static readonly Dictionary<System.Type, TEditorClass> s_TypeMap = new Dictionary<Type, TEditorClass>();
|
||||
|
||||
public static TEditorClass DefaultInstance
|
||||
{
|
||||
get { return s_DefaultInstance; }
|
||||
}
|
||||
|
||||
static Type[] SubClasses
|
||||
{
|
||||
get
|
||||
{
|
||||
// order the subclass array by built-ins then user defined so built-in classes are chosen first
|
||||
return s_SubClasses ??
|
||||
(s_SubClasses = TypeCache.GetTypesDerivedFrom<TEditorClass>().OrderBy(t => t.Assembly == typeof(UnityEditor.Timeline.TimelineEditor).Assembly ? 1 : 0).ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static TEditorClass GetEditorForType(Type type)
|
||||
{
|
||||
TEditorClass editorClass = null;
|
||||
if (!s_TypeMap.TryGetValue(type, out editorClass) || editorClass == null)
|
||||
{
|
||||
Type editorClassType = null;
|
||||
Type searchType = type;
|
||||
while (searchType != null)
|
||||
{
|
||||
// search our way up the runtime class hierarchy so we get the best match
|
||||
editorClassType = GetExactEditorClassForType(searchType);
|
||||
if (editorClassType != null)
|
||||
break;
|
||||
searchType = searchType.BaseType;
|
||||
}
|
||||
|
||||
if (editorClassType == null)
|
||||
{
|
||||
editorClass = s_DefaultInstance;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
editorClass = (TEditorClass)Activator.CreateInstance(editorClassType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarningFormat("Could not create a Timeline editor class of type {0}: {1}", editorClassType, e.Message);
|
||||
editorClass = s_DefaultInstance;
|
||||
}
|
||||
}
|
||||
|
||||
s_TypeMap[type] = editorClass;
|
||||
}
|
||||
|
||||
return editorClass;
|
||||
}
|
||||
|
||||
private static Type GetExactEditorClassForType(Type type)
|
||||
{
|
||||
foreach (var subClass in SubClasses)
|
||||
{
|
||||
// first check for exact match
|
||||
var attr = (CustomTimelineEditorAttribute)Attribute.GetCustomAttribute(subClass, typeof(CustomTimelineEditorAttribute), false);
|
||||
if (attr != null && attr.classToEdit == type)
|
||||
{
|
||||
return subClass;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
s_TypeMap.Clear();
|
||||
s_SubClasses = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static TEditorClass GetEditorForType<TEditorClass, TRuntimeClass>(Type type) where TEditorClass : class, new()
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
if (!typeof(TRuntimeClass).IsAssignableFrom(type))
|
||||
throw new ArgumentException(type.FullName + " does not inherit from" + typeof(TRuntimeClass));
|
||||
|
||||
return SubClassCache<TEditorClass>.GetEditorForType(type);
|
||||
}
|
||||
|
||||
public static void ClearCache<TEditorClass>() where TEditorClass : class, new()
|
||||
{
|
||||
SubClassCache<TEditorClass>.Clear();
|
||||
}
|
||||
|
||||
public static ClipEditor GetClipEditor(TimelineClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
throw new ArgumentNullException(nameof(clip));
|
||||
|
||||
var type = typeof(IPlayableAsset);
|
||||
if (clip.asset != null)
|
||||
type = clip.asset.GetType();
|
||||
|
||||
if (!typeof(IPlayableAsset).IsAssignableFrom(type))
|
||||
return GetDefaultClipEditor();
|
||||
|
||||
return GetEditorForType<ClipEditor, IPlayableAsset>(type);
|
||||
}
|
||||
|
||||
public static ClipEditor GetDefaultClipEditor()
|
||||
{
|
||||
return SubClassCache<ClipEditor>.DefaultInstance;
|
||||
}
|
||||
|
||||
public static TrackEditor GetTrackEditor(TrackAsset track)
|
||||
{
|
||||
if (track == null)
|
||||
throw new ArgumentNullException(nameof(track));
|
||||
|
||||
return GetEditorForType<TrackEditor, TrackAsset>(track.GetType());
|
||||
}
|
||||
|
||||
public static TrackEditor GetDefaultTrackEditor()
|
||||
{
|
||||
return SubClassCache<TrackEditor>.DefaultInstance;
|
||||
}
|
||||
|
||||
public static MarkerEditor GetMarkerEditor(IMarker marker)
|
||||
{
|
||||
if (marker == null)
|
||||
throw new ArgumentNullException(nameof(marker));
|
||||
return GetEditorForType<MarkerEditor, IMarker>(marker.GetType());
|
||||
}
|
||||
|
||||
public static MarkerEditor GetDefaultMarkerEditor()
|
||||
{
|
||||
return SubClassCache<MarkerEditor>.DefaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fd6ede1d2f47ab146b2ec0a3969a37cc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,209 @@
|
|||
using UnityEngine;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// The flags that indicate the view status of a marker.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
public enum MarkerUIStates
|
||||
{
|
||||
/// <summary>
|
||||
/// No extra state specified.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The marker is selected.
|
||||
/// </summary>
|
||||
Selected = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The marker is in a collapsed state.
|
||||
/// </summary>
|
||||
Collapsed = 1 << 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The user-defined options for drawing a marker.
|
||||
/// </summary>
|
||||
public struct MarkerDrawOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The tooltip for the marker.
|
||||
/// </summary>
|
||||
public string tooltip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text that indicates if the marker should display an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the error text is not empty or null, then the marker displays a warning. The error text is used as the tooltip.
|
||||
/// </remarks>
|
||||
public string errorText { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is MarkerDrawOptions))
|
||||
return false;
|
||||
|
||||
return Equals((MarkerDrawOptions)obj);
|
||||
}
|
||||
|
||||
public bool Equals(MarkerDrawOptions other)
|
||||
{
|
||||
return errorText == other.errorText &&
|
||||
tooltip == other.tooltip;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(
|
||||
errorText != null ? errorText.GetHashCode() : 0,
|
||||
tooltip != null ? tooltip.GetHashCode() : 0
|
||||
);
|
||||
}
|
||||
|
||||
public static bool operator==(MarkerDrawOptions options1, MarkerDrawOptions options2)
|
||||
{
|
||||
return options1.Equals(options2);
|
||||
}
|
||||
|
||||
public static bool operator!=(MarkerDrawOptions options1, MarkerDrawOptions options2)
|
||||
{
|
||||
return !options1.Equals(options2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The description of the on-screen area where the marker is drawn.
|
||||
/// </summary>
|
||||
public struct MarkerOverlayRegion
|
||||
{
|
||||
/// <summary>
|
||||
/// The area where the marker is being drawn.
|
||||
/// </summary>
|
||||
public Rect markerRegion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// TThe area where the overlay is being drawn.
|
||||
/// </summary>
|
||||
public Rect timelineRegion { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The start time of the visible region of the window.
|
||||
/// </summary>
|
||||
public double startTime { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The end time of the visible region of the window.
|
||||
/// </summary>
|
||||
public double endTime { get; private set; }
|
||||
|
||||
/// <summary>Constructor</summary>
|
||||
public MarkerOverlayRegion(Rect _markerRegion, Rect _timelineRegion, double _startTime, double _endTime)
|
||||
{
|
||||
markerRegion = _markerRegion;
|
||||
timelineRegion = _timelineRegion;
|
||||
startTime = _startTime;
|
||||
endTime = _endTime;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is MarkerOverlayRegion))
|
||||
return false;
|
||||
|
||||
return Equals((MarkerOverlayRegion)obj);
|
||||
}
|
||||
|
||||
public bool Equals(MarkerOverlayRegion other)
|
||||
{
|
||||
return markerRegion == other.markerRegion &&
|
||||
timelineRegion == other.timelineRegion &&
|
||||
startTime == other.startTime &&
|
||||
endTime == other.endTime;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(
|
||||
markerRegion.GetHashCode(),
|
||||
timelineRegion.GetHashCode(),
|
||||
startTime.GetHashCode(),
|
||||
endTime.GetHashCode()
|
||||
);
|
||||
}
|
||||
|
||||
public static bool operator==(MarkerOverlayRegion region1, MarkerOverlayRegion region2)
|
||||
{
|
||||
return region1.Equals(region2);
|
||||
}
|
||||
|
||||
public static bool operator!=(MarkerOverlayRegion region1, MarkerOverlayRegion region2)
|
||||
{
|
||||
return !region1.Equals(region2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this class to customize marker types in the TimelineEditor.
|
||||
/// </summary>
|
||||
public class MarkerEditor
|
||||
{
|
||||
internal readonly bool supportsDrawOverlay;
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
public MarkerEditor()
|
||||
{
|
||||
supportsDrawOverlay = TypeUtility.HasOverrideMethod(GetType(), nameof(DrawOverlay));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implement this method to override the default options for drawing a marker.
|
||||
/// </summary>
|
||||
/// <param name="marker">The marker to draw.</param>
|
||||
/// <returns></returns>
|
||||
public virtual MarkerDrawOptions GetMarkerOptions(IMarker marker)
|
||||
{
|
||||
return new MarkerDrawOptions()
|
||||
{
|
||||
tooltip = string.Empty,
|
||||
errorText = string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a marker is created.
|
||||
/// </summary>
|
||||
/// <param name="marker">The marker that is created.</param>
|
||||
/// <param name="clonedFrom">TThe source that the marker was copied from. This can be set to null if the marker is not a copy.</param>
|
||||
/// <remarks>
|
||||
/// The callback occurs before the marker is assigned to the track.
|
||||
/// </remarks>
|
||||
public virtual void OnCreate(IMarker marker, IMarker clonedFrom)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws additional overlays for a marker.
|
||||
/// </summary>
|
||||
/// <param name="marker">The marker to draw.</param>
|
||||
/// <param name="uiState">The visual state of the marker.</param>
|
||||
/// <param name="region">The on-screen area where the marker is being drawn.</param>
|
||||
/// <remarks>
|
||||
/// Notes:
|
||||
/// * It is only called during TimelineWindow's Repaint step.
|
||||
/// * If there are multiple markers on top of each other, only the topmost marker receives the DrawOverlay call.
|
||||
/// </remarks>
|
||||
public virtual void DrawOverlay(IMarker marker, MarkerUIStates uiState, MarkerOverlayRegion region)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 99c5970046bb263469514e56eb6aa519
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,18 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(MarkerTrack))]
|
||||
class MarkerTrackEditor : TrackEditor
|
||||
{
|
||||
public static readonly float DefaultMarkerTrackHeight = 20;
|
||||
|
||||
public override TrackDrawOptions GetTrackOptions(TrackAsset track, Object binding)
|
||||
{
|
||||
var options = base.GetTrackOptions(track, binding);
|
||||
options.minimumHeight = DefaultMarkerTrackHeight;
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 844873d1afe1c3142ab922324950e1dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,284 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
/// <summary>
|
||||
/// The user-defined options for drawing a track."
|
||||
/// </summary>
|
||||
public struct TrackDrawOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Text that indicates if the track should display an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the error text is not empty or null, then the track displays a warning. The error text is used as the tooltip.
|
||||
/// </remarks>
|
||||
public string errorText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The highlight color of the track.
|
||||
/// </summary>
|
||||
public Color trackColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum height of the track.
|
||||
/// </summary>
|
||||
public float minimumHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon displayed on the track header.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If this value is null, then the default icon for the track is used.
|
||||
/// </remarks>
|
||||
public Texture2D icon { get; set; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is TrackDrawOptions))
|
||||
return false;
|
||||
|
||||
return Equals((TrackDrawOptions)obj);
|
||||
}
|
||||
|
||||
public bool Equals(TrackDrawOptions other)
|
||||
{
|
||||
return errorText == other.errorText &&
|
||||
trackColor == other.trackColor &&
|
||||
minimumHeight == other.minimumHeight &&
|
||||
icon == other.icon;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(
|
||||
errorText != null ? errorText.GetHashCode() : 0,
|
||||
trackColor.GetHashCode(),
|
||||
minimumHeight.GetHashCode(),
|
||||
icon != null ? icon.GetHashCode() : 0
|
||||
);
|
||||
}
|
||||
|
||||
public static bool operator==(TrackDrawOptions options1, TrackDrawOptions options2)
|
||||
{
|
||||
return options1.Equals(options2);
|
||||
}
|
||||
|
||||
public static bool operator!=(TrackDrawOptions options1, TrackDrawOptions options2)
|
||||
{
|
||||
return !options1.Equals(options2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The errors displayed for the track binding.
|
||||
/// </summary>
|
||||
public enum TrackBindingErrors
|
||||
{
|
||||
/// <summary>
|
||||
/// Select no errors.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The bound GameObject is disabled.
|
||||
/// </summary>
|
||||
BoundGameObjectDisabled = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// The bound GameObject does not have a valid component.
|
||||
/// </summary>
|
||||
NoValidComponent = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// The bound Object is a disabled Behaviour.
|
||||
/// </summary>
|
||||
BehaviourIsDisabled = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// The bound Object is not of the correct type.
|
||||
/// </summary>
|
||||
InvalidBinding = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The bound Object is part of a prefab, and not an instance.
|
||||
/// </summary>
|
||||
PrefabBound = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Select all errors.
|
||||
/// </summary>
|
||||
All = Int32.MaxValue
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this class to customize track types in the TimelineEditor.
|
||||
/// </summary>
|
||||
public class TrackEditor
|
||||
{
|
||||
static readonly string k_BoundGameObjectDisabled = LocalizationDatabase.GetLocalizedString("The bound GameObject is disabled.");
|
||||
static readonly string k_NoValidComponent = LocalizationDatabase.GetLocalizedString("Could not find appropriate component on this gameObject");
|
||||
static readonly string k_RequiredComponentIsDisabled = LocalizationDatabase.GetLocalizedString("The component is disabled");
|
||||
static readonly string k_InvalidBinding = LocalizationDatabase.GetLocalizedString("The bound object is not the correct type.");
|
||||
static readonly string k_PrefabBound = LocalizationDatabase.GetLocalizedString("The bound object is a Prefab");
|
||||
|
||||
readonly Dictionary<TrackAsset, System.Type> m_BindingCache = new Dictionary<TrackAsset, System.Type>();
|
||||
|
||||
/// <summary>
|
||||
/// The default height of a track.
|
||||
/// </summary>
|
||||
public static readonly float DefaultTrackHeight = 30.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The minimum unscaled height of a track.
|
||||
/// </summary>
|
||||
public static readonly float MinimumTrackHeight = 10.0f;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum height of a track.
|
||||
/// </summary>
|
||||
public static readonly float MaximumTrackHeight = 256.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Implement this method to override the default options for drawing a track.
|
||||
/// </summary>
|
||||
/// <param name="track">The track from which track options are retrieved.</param>
|
||||
/// <param name="binding">The binding for the track.</param>
|
||||
/// <returns>The options for drawing the track.</returns>
|
||||
public virtual TrackDrawOptions GetTrackOptions(TrackAsset track, UnityEngine.Object binding)
|
||||
{
|
||||
return new TrackDrawOptions()
|
||||
{
|
||||
errorText = GetErrorText(track, binding, TrackBindingErrors.All),
|
||||
minimumHeight = DefaultTrackHeight,
|
||||
trackColor = GetTrackColor(track),
|
||||
icon = null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the error text for the specified track.
|
||||
/// </summary>
|
||||
/// <param name="track">The track to retrieve options for.</param>
|
||||
/// <param name="boundObject">The binding for the track.</param>
|
||||
/// <param name="detectErrors">The errors to check for.</param>
|
||||
/// <returns>An error to be displayed on the track, or string.Empty if there is no error.</returns>
|
||||
public string GetErrorText(TrackAsset track, UnityEngine.Object boundObject, TrackBindingErrors detectErrors)
|
||||
{
|
||||
if (track == null || boundObject == null)
|
||||
return string.Empty;
|
||||
|
||||
var bindingType = GetBindingType(track);
|
||||
if (bindingType != null)
|
||||
{
|
||||
// bound to a prefab asset
|
||||
if (HasFlag(detectErrors, TrackBindingErrors.PrefabBound) && PrefabUtility.IsPartOfPrefabAsset(boundObject))
|
||||
{
|
||||
return k_PrefabBound;
|
||||
}
|
||||
|
||||
// If we are a component, allow for bound game objects (legacy)
|
||||
if (typeof(Component).IsAssignableFrom(bindingType))
|
||||
{
|
||||
var gameObject = boundObject as GameObject;
|
||||
var component = boundObject as Component;
|
||||
if (component != null)
|
||||
gameObject = component.gameObject;
|
||||
|
||||
// game object is bound with no component
|
||||
if (HasFlag(detectErrors, TrackBindingErrors.NoValidComponent) && gameObject != null && component == null)
|
||||
{
|
||||
component = gameObject.GetComponent(bindingType);
|
||||
if (component == null)
|
||||
{
|
||||
return k_NoValidComponent;
|
||||
}
|
||||
}
|
||||
|
||||
// attached gameObject is disables (ignores Activation Track)
|
||||
if (HasFlag(detectErrors, TrackBindingErrors.BoundGameObjectDisabled) && gameObject != null && !gameObject.activeInHierarchy)
|
||||
{
|
||||
return k_BoundGameObjectDisabled;
|
||||
}
|
||||
|
||||
// component is disabled
|
||||
var behaviour = component as Behaviour;
|
||||
if (HasFlag(detectErrors, TrackBindingErrors.BehaviourIsDisabled) && behaviour != null && !behaviour.enabled)
|
||||
{
|
||||
return k_RequiredComponentIsDisabled;
|
||||
}
|
||||
|
||||
// mismatched binding
|
||||
if (HasFlag(detectErrors, TrackBindingErrors.InvalidBinding) && component != null && !bindingType.IsAssignableFrom(component.GetType()))
|
||||
{
|
||||
return k_InvalidBinding;
|
||||
}
|
||||
}
|
||||
// Mismatched binding (non-component)
|
||||
else if (HasFlag(detectErrors, TrackBindingErrors.InvalidBinding) && !bindingType.IsAssignableFrom(boundObject.GetType()))
|
||||
{
|
||||
return k_InvalidBinding;
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color information of a track.
|
||||
/// </summary>
|
||||
/// <param name="track"></param>
|
||||
/// <returns>Returns the color for the specified track.</returns>
|
||||
public Color GetTrackColor(TrackAsset track)
|
||||
{
|
||||
return TrackResourceCache.GetTrackColor(track);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the binding type for a track.
|
||||
/// </summary>
|
||||
/// <param name="track">The track to retrieve the binding type from.</param>
|
||||
/// <returns>Returns the binding type for the specified track. Returns null if the track does not have binding.</returns>
|
||||
public System.Type GetBindingType(TrackAsset track)
|
||||
{
|
||||
if (track == null)
|
||||
return null;
|
||||
|
||||
System.Type result = null;
|
||||
if (m_BindingCache.TryGetValue(track, out result))
|
||||
return result;
|
||||
|
||||
result = track.outputs.Select(x => x.outputTargetType).FirstOrDefault();
|
||||
m_BindingCache[track] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when a track is created.
|
||||
/// </summary>
|
||||
/// <param name="track">The track that is created.</param>
|
||||
/// <param name="copiedFrom">The source that the track is copied from. This can be set to null if the track is not a copy.</param>
|
||||
public virtual void OnCreate(TrackAsset track, TrackAsset copiedFrom)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback for when a track is changed.
|
||||
/// </summary>
|
||||
/// <param name="track">The track that is changed.</param>
|
||||
public virtual void OnTrackChanged(TrackAsset track)
|
||||
{
|
||||
}
|
||||
|
||||
private static bool HasFlag(TrackBindingErrors errors, TrackBindingErrors flag)
|
||||
{
|
||||
return (errors & flag) != 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 35cb34351b19cf44ba78afbd58746610
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,288 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor.Timeline;
|
||||
|
||||
namespace UnityEngine.Timeline
|
||||
{
|
||||
[Serializable]
|
||||
class DirectorNamedColor : ScriptableObject
|
||||
{
|
||||
[SerializeField]
|
||||
public Color colorPlayhead;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorSelection;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorEndmarker;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorGroup;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorGroupTrackBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorAnimation;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorAnimationRecorded;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorAudio;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorAudioWaveform;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorActivation;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorDropTarget;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorClipFont;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInvalidClipOverlay;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackHeaderBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackDarken;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackBackgroundRecording;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInfiniteTrackBackgroundRecording;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackBackgroundSelected;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackFont;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorClipUnion;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTopOutline3;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorDurationLine;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorRange;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorSequenceBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTooltipBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInfiniteClipLine;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorDefaultTrackDrawer;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorDuration = new Color(0.66f, 0.66f, 0.66f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorRecordingClipOutline = new Color(1, 0, 0, 0.9f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorAnimEditorBinding = new Color(54.0f / 255.0f, 54.0f / 255.0f, 54.0f / 255.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTimelineBackground = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorLockTextBG = Color.red;
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInlineCurveVerticalLines = new Color(1.0f, 1.0f, 1.0f, 0.2f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInlineCurveOutOfRangeOverlay = new Color(0.0f, 0.0f, 0.0f, 0.5f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorInlineCurvesBackground;
|
||||
|
||||
[SerializeField]
|
||||
public Color markerDrawerBackgroundColor = new Color(0.4f, 0.4f, 0.4f , 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color markerHeaderDrawerBackgroundColor = new Color(0.5f, 0.5f, 0.5f , 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorControl = new Color(0.2313f, 0.6353f, 0.5843f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorSubSequenceBackground = new Color(0.1294118f, 0.1764706f, 0.1764706f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackSubSequenceBackground = new Color(0.1607843f, 0.2156863f, 0.2156863f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorTrackSubSequenceBackgroundSelected = new Color(0.0726923f, 0.252f, 0.252f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorSubSequenceOverlay = new Color(0.02f, 0.025f, 0.025f, 0.30f);
|
||||
|
||||
[SerializeField]
|
||||
public Color colorSubSequenceDurationLine = new Color(0.0f, 1.0f, 0.88f, 0.46f);
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBckg = new Color(0.5f, 0.5f, 0.5f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color clipSelectedBckg = new Color(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBorderColor = new Color(0.4f, 0.4f, 0.4f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color clipEaseBckgColor = new Color(0.4f, 0.4f, 0.4f, 1.0f);
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBlendIn;
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBlendInSelected;
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBlendOut;
|
||||
|
||||
[SerializeField]
|
||||
public Color clipBlendOutSelected;
|
||||
|
||||
public void SetDefault()
|
||||
{
|
||||
colorPlayhead = DirectorStyles.Instance.timeCursor.normal.textColor;
|
||||
colorSelection = DirectorStyles.Instance.selectedStyle.normal.textColor;
|
||||
colorEndmarker = DirectorStyles.Instance.endmarker.normal.textColor;
|
||||
|
||||
colorGroup = new Color(0.094f, 0.357f, 0.384f, 0.310f);
|
||||
colorGroupTrackBackground = new Color(0f, 0f, 0f, 1f);
|
||||
colorAnimation = new Color(0.3f, 0.39f, 0.46f, 1.0f);
|
||||
colorAnimationRecorded = new Color(colorAnimation.r * 0.75f, colorAnimation.g * 0.75f, colorAnimation.b * 0.75f, 1.0f);
|
||||
colorAudio = new Color(1f, 0.635f, 0f);
|
||||
colorAudioWaveform = new Color(0.129f, 0.164f, 0.254f);
|
||||
colorActivation = Color.green;
|
||||
|
||||
colorDropTarget = new Color(0.514f, 0.627f, 0.827f);
|
||||
colorClipFont = DirectorStyles.Instance.fontClip.normal.textColor;
|
||||
colorTrackBackground = new Color(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
colorTrackBackgroundSelected = new Color(1f, 1f, 1f, 0.33f);
|
||||
|
||||
colorInlineCurvesBackground = new Color(0.25f, 0.25f, 0.25f, 0.6f);
|
||||
|
||||
colorTrackFont = DirectorStyles.Instance.trackHeaderFont.normal.textColor;
|
||||
|
||||
colorClipUnion = new Color(0.72f, 0.72f, 0.72f, 0.8f);
|
||||
colorTopOutline3 = new Color(0.274f, 0.274f, 0.274f, 1.0f);
|
||||
|
||||
colorDurationLine = new Color(33.0f / 255.0f, 109.0f / 255.0f, 120.0f / 255.0f);
|
||||
|
||||
colorRange = new Color(0.733f, 0.733f, 0.733f, 0.70f);
|
||||
|
||||
colorSequenceBackground = new Color(0.16f, 0.16f, 0.16f, 1.0f);
|
||||
|
||||
colorTooltipBackground = new Color(29.0f / 255.0f, 32.0f / 255.0f, 33.0f / 255.0f);
|
||||
|
||||
colorInfiniteClipLine = new Color(72.0f / 255.0f, 78.0f / 255.0f, 82.0f / 255.0f);
|
||||
|
||||
colorTrackBackgroundRecording = new Color(1, 0, 0, 0.1f);
|
||||
|
||||
colorTrackDarken = new Color(0.0f, 0.0f, 0.0f, 0.4f);
|
||||
|
||||
colorTrackHeaderBackground = new Color(51.0f / 255.0f, 51.0f / 255.0f, 51.0f / 255.0f, 1.0f);
|
||||
|
||||
colorDefaultTrackDrawer = new Color(218.0f / 255.0f, 220.0f / 255.0f, 222.0f / 255.0f);
|
||||
|
||||
colorRecordingClipOutline = new Color(1, 0, 0, 0.9f);
|
||||
colorInlineCurveVerticalLines = new Color(1.0f, 1.0f, 1.0f, 0.2f);
|
||||
colorInlineCurveOutOfRangeOverlay = new Color(0.0f, 0.0f, 0.0f, 0.5f);
|
||||
}
|
||||
|
||||
public void ToText(string path)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (f.FieldType != typeof(Color))
|
||||
continue;
|
||||
|
||||
Color c = (Color)f.GetValue(this);
|
||||
builder.Append(f.Name + "," + c);
|
||||
builder.Append("\n");
|
||||
}
|
||||
File.WriteAllText(path, builder.ToString());
|
||||
}
|
||||
|
||||
public void FromText(string text)
|
||||
{
|
||||
// parse to a map
|
||||
string[] lines = text.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var map = new Dictionary<string, Color>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var pieces = line.Replace("RGBA(", "").Replace(")", "").Split(',');
|
||||
if (pieces.Length == 5)
|
||||
{
|
||||
string name = pieces[0].Trim();
|
||||
Color c = Color.black;
|
||||
bool b = ParseFloat(pieces[1], out c.r) &&
|
||||
ParseFloat(pieces[2], out c.g) &&
|
||||
ParseFloat(pieces[3], out c.b) &&
|
||||
ParseFloat(pieces[4], out c.a);
|
||||
|
||||
if (b)
|
||||
{
|
||||
map[name] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fields = typeof(DirectorNamedColor).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
||||
foreach (var f in fields)
|
||||
{
|
||||
if (f.FieldType != typeof(Color))
|
||||
continue;
|
||||
|
||||
Color c = Color.black;
|
||||
if (map.TryGetValue(f.Name, out c))
|
||||
{
|
||||
f.SetValue(this, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 938534 - Timeline window has white background when running on .NET 4.6 depending on the set system language
|
||||
// Make sure we're using an invariant culture so "0.35" is parsed as 0.35 and not 35
|
||||
static bool ParseFloat(string str, out float f)
|
||||
{
|
||||
return float.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out f);
|
||||
}
|
||||
|
||||
public static DirectorNamedColor CreateAndLoadFromText(string text)
|
||||
{
|
||||
DirectorNamedColor instance = CreateInstance<DirectorNamedColor>();
|
||||
instance.FromText(text);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7116e04a377b195458798657c617e324
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,362 @@
|
|||
using UnityEditor.Experimental;
|
||||
using UnityEditor.StyleSheets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class DirectorStyles
|
||||
{
|
||||
const string k_Elipsis = "…";
|
||||
const string k_ImagePath = "Packages/com.unity.timeline/Editor/StyleSheets/Images/Icons/{0}.png";
|
||||
public const string resourcesPath = "Packages/com.unity.timeline/Editor/StyleSheets/res/";
|
||||
|
||||
//Timeline resources
|
||||
public static readonly GUIContent referenceTrackLabel = TrTextContent("R", "This track references an external asset");
|
||||
public static readonly GUIContent recordingLabel = TrTextContent("Recording...");
|
||||
public static readonly GUIContent noTimelineAssetSelected = TrTextContent("To start creating a timeline, select a GameObject");
|
||||
public static readonly GUIContent createTimelineOnSelection = TrTextContent("To begin a new timeline with {0}, create {1}");
|
||||
public static readonly GUIContent noTimelinesInScene = TrTextContent("No timeline found in the scene");
|
||||
public static readonly GUIContent createNewTimelineText = TrTextContent("Create a new Timeline and Director Component for Game Object");
|
||||
public static readonly GUIContent previewContent = TrTextContent("Preview", "Enable/disable scene preview mode");
|
||||
public static readonly GUIContent mixOff = TrIconContent("TimelineEditModeMixOFF", "Mix Mode (1)");
|
||||
public static readonly GUIContent mixOn = TrIconContent("TimelineEditModeMixON", "Mix Mode (1)");
|
||||
public static readonly GUIContent rippleOff = TrIconContent("TimelineEditModeRippleOFF", "Ripple Mode (2)");
|
||||
public static readonly GUIContent rippleOn = TrIconContent("TimelineEditModeRippleON", "Ripple Mode (2)");
|
||||
public static readonly GUIContent replaceOff = TrIconContent("TimelineEditModeReplaceOFF", "Replace Mode (3)");
|
||||
public static readonly GUIContent replaceOn = TrIconContent("TimelineEditModeReplaceON", "Replace Mode (3)");
|
||||
public static readonly GUIContent showMarkersOn = TrIconContent("TimelineMarkerAreaButtonEnabled", "Show / Hide Timeline Markers");
|
||||
public static readonly GUIContent showMarkersOff = TrIconContent("TimelineMarkerAreaButtonDisabled", "Show / Hide Timeline Markers");
|
||||
public static readonly GUIContent showMarkersOnTimeline = TrTextContent("Show markers");
|
||||
public static readonly GUIContent timelineMarkerTrackHeader = TrTextContentWithIcon("Markers", string.Empty, "TimelineHeaderMarkerIcon");
|
||||
public static readonly GUIContent markerCollapseButton = TrTextContent(string.Empty, "Expand / Collapse Track Markers");
|
||||
public static readonly GUIContent signalTrackIcon = IconContent("TimelineSignal");
|
||||
|
||||
//Unity Default Resources
|
||||
public static readonly GUIContent playContent = EditorGUIUtility.TrIconContent("Animation.Play", "Play the timeline (Space)");
|
||||
public static readonly GUIContent gotoBeginingContent = EditorGUIUtility.TrIconContent("Animation.FirstKey", "Go to the beginning of the timeline (Shift+<)");
|
||||
public static readonly GUIContent gotoEndContent = EditorGUIUtility.TrIconContent("Animation.LastKey", "Go to the end of the timeline (Shift+>)");
|
||||
public static readonly GUIContent nextFrameContent = EditorGUIUtility.TrIconContent("Animation.NextKey", "Go to the next frame");
|
||||
public static readonly GUIContent previousFrameContent = EditorGUIUtility.TrIconContent("Animation.PrevKey", "Go to the previous frame");
|
||||
public static readonly GUIContent newContent = EditorGUIUtility.IconContent("CreateAddNew", "Add new tracks.");
|
||||
public static readonly GUIContent optionsCogIcon = EditorGUIUtility.TrIconContent("_Popup", "Options");
|
||||
public static readonly GUIContent animationTrackIcon = EditorGUIUtility.IconContent("AnimationClip Icon");
|
||||
public static readonly GUIContent audioTrackIcon = EditorGUIUtility.IconContent("AudioSource Icon");
|
||||
public static readonly GUIContent playableTrackIcon = EditorGUIUtility.IconContent("cs Script Icon");
|
||||
public static readonly GUIContent timelineSelectorArrow = EditorGUIUtility.TrIconContent("icon dropdown", "Timeline Selector");
|
||||
|
||||
public GUIContent playrangeContent;
|
||||
|
||||
public static readonly float kBaseIndent = 15.0f;
|
||||
public static readonly float kDurationGuiThickness = 5.0f;
|
||||
|
||||
// matches dark skin warning color.
|
||||
public static readonly Color kClipErrorColor = new Color(0.957f, 0.737f, 0.008f, 1f);
|
||||
|
||||
// TODO: Make skinnable? If we do, we should probably also make the associated cursors skinnable...
|
||||
public static readonly Color kMixToolColor = Color.white;
|
||||
public static readonly Color kRippleToolColor = new Color(255f / 255f, 210f / 255f, 51f / 255f);
|
||||
public static readonly Color kReplaceToolColor = new Color(165f / 255f, 30f / 255f, 30f / 255f);
|
||||
|
||||
public const string markerDefaultStyle = "MarkerItem";
|
||||
|
||||
public GUIStyle groupBackground;
|
||||
public GUIStyle displayBackground;
|
||||
public GUIStyle fontClip;
|
||||
public GUIStyle fontClipLoop;
|
||||
public GUIStyle trackHeaderFont;
|
||||
public GUIStyle trackGroupAddButton;
|
||||
public GUIStyle groupFont;
|
||||
public GUIStyle timeCursor;
|
||||
public GUIStyle endmarker;
|
||||
public GUIStyle tinyFont;
|
||||
public GUIStyle foldout;
|
||||
public GUIStyle mute;
|
||||
public GUIStyle locked;
|
||||
public GUIStyle autoKey;
|
||||
public GUIStyle playTimeRangeStart;
|
||||
public GUIStyle playTimeRangeEnd;
|
||||
public GUIStyle selectedStyle;
|
||||
public GUIStyle trackSwatchStyle;
|
||||
public GUIStyle connector;
|
||||
public GUIStyle keyframe;
|
||||
public GUIStyle warning;
|
||||
public GUIStyle extrapolationHold;
|
||||
public GUIStyle extrapolationLoop;
|
||||
public GUIStyle extrapolationPingPong;
|
||||
public GUIStyle extrapolationContinue;
|
||||
public GUIStyle collapseMarkers;
|
||||
public GUIStyle markerMultiOverlay;
|
||||
public GUIStyle bottomShadow;
|
||||
public GUIStyle trackOptions;
|
||||
public GUIStyle infiniteTrack;
|
||||
public GUIStyle clipOut;
|
||||
public GUIStyle clipIn;
|
||||
public GUIStyle curves;
|
||||
public GUIStyle lockedBG;
|
||||
public GUIStyle activation;
|
||||
public GUIStyle playrange;
|
||||
public GUIStyle lockButton;
|
||||
public GUIStyle avatarMaskOn;
|
||||
public GUIStyle avatarMaskOff;
|
||||
public GUIStyle markerWarning;
|
||||
public GUIStyle editModeBtn;
|
||||
public GUIStyle showMarkersBtn;
|
||||
public GUIStyle sequenceSwitcher;
|
||||
|
||||
static internal DirectorStyles s_Instance;
|
||||
|
||||
DirectorNamedColor m_DarkSkinColors;
|
||||
DirectorNamedColor m_LightSkinColors;
|
||||
DirectorNamedColor m_DefaultSkinColors;
|
||||
|
||||
const string k_DarkSkinPath = resourcesPath + "Timeline_DarkSkin.txt";
|
||||
const string k_LightSkinPath = resourcesPath + "Timeline_LightSkin.txt";
|
||||
|
||||
static readonly GUIContent s_TempContent = new GUIContent();
|
||||
|
||||
public static bool IsInitialized
|
||||
{
|
||||
get { return s_Instance != null; }
|
||||
}
|
||||
|
||||
public static DirectorStyles Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Instance == null)
|
||||
{
|
||||
s_Instance = new DirectorStyles();
|
||||
s_Instance.Initialize();
|
||||
}
|
||||
|
||||
return s_Instance;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ReloadStylesIfNeeded()
|
||||
{
|
||||
if (Instance.ShouldLoadStyles())
|
||||
{
|
||||
Instance.LoadStyles();
|
||||
if (!Instance.ShouldLoadStyles())
|
||||
Instance.Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
public DirectorNamedColor customSkin
|
||||
{
|
||||
get { return EditorGUIUtility.isProSkin ? m_DarkSkinColors : m_LightSkinColors; }
|
||||
internal set
|
||||
{
|
||||
if (EditorGUIUtility.isProSkin)
|
||||
m_DarkSkinColors = value;
|
||||
else
|
||||
m_LightSkinColors = value;
|
||||
}
|
||||
}
|
||||
|
||||
DirectorNamedColor LoadColorSkin(string path)
|
||||
{
|
||||
var asset = EditorGUIUtility.LoadRequired(path) as TextAsset;
|
||||
|
||||
if (asset != null && !string.IsNullOrEmpty(asset.text))
|
||||
{
|
||||
return DirectorNamedColor.CreateAndLoadFromText(asset.text);
|
||||
}
|
||||
|
||||
return m_DefaultSkinColors;
|
||||
}
|
||||
|
||||
static DirectorNamedColor CreateDefaultSkin()
|
||||
{
|
||||
var nc = ScriptableObject.CreateInstance<DirectorNamedColor>();
|
||||
nc.SetDefault();
|
||||
return nc;
|
||||
}
|
||||
|
||||
public void ExportSkinToFile()
|
||||
{
|
||||
if (customSkin == m_DarkSkinColors)
|
||||
customSkin.ToText(k_DarkSkinPath);
|
||||
|
||||
if (customSkin == m_LightSkinColors)
|
||||
customSkin.ToText(k_LightSkinPath);
|
||||
}
|
||||
|
||||
public void ReloadSkin()
|
||||
{
|
||||
if (customSkin == m_DarkSkinColors)
|
||||
{
|
||||
m_DarkSkinColors = LoadColorSkin(k_DarkSkinPath);
|
||||
}
|
||||
else if (customSkin == m_LightSkinColors)
|
||||
{
|
||||
m_LightSkinColors = LoadColorSkin(k_LightSkinPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
m_DefaultSkinColors = CreateDefaultSkin();
|
||||
m_DarkSkinColors = LoadColorSkin(k_DarkSkinPath);
|
||||
m_LightSkinColors = LoadColorSkin(k_LightSkinPath);
|
||||
|
||||
// add the built in colors (control track uses attribute)
|
||||
TrackResourceCache.ClearTrackColorCache();
|
||||
TrackResourceCache.SetTrackColor<AnimationTrack>(customSkin.colorAnimation);
|
||||
TrackResourceCache.SetTrackColor<PlayableTrack>(Color.white);
|
||||
TrackResourceCache.SetTrackColor<AudioTrack>(customSkin.colorAudio);
|
||||
TrackResourceCache.SetTrackColor<ActivationTrack>(customSkin.colorActivation);
|
||||
TrackResourceCache.SetTrackColor<GroupTrack>(customSkin.colorGroup);
|
||||
TrackResourceCache.SetTrackColor<ControlTrack>(customSkin.colorControl);
|
||||
|
||||
// add default icons
|
||||
TrackResourceCache.ClearTrackIconCache();
|
||||
TrackResourceCache.SetTrackIcon<AnimationTrack>(animationTrackIcon);
|
||||
TrackResourceCache.SetTrackIcon<AudioTrack>(audioTrackIcon);
|
||||
TrackResourceCache.SetTrackIcon<PlayableTrack>(playableTrackIcon);
|
||||
TrackResourceCache.SetTrackIcon<ActivationTrack>(new GUIContent(GetBackgroundImage(activation)));
|
||||
TrackResourceCache.SetTrackIcon<SignalTrack>(signalTrackIcon);
|
||||
}
|
||||
|
||||
DirectorStyles()
|
||||
{
|
||||
LoadStyles();
|
||||
}
|
||||
|
||||
bool ShouldLoadStyles()
|
||||
{
|
||||
return endmarker == null ||
|
||||
endmarker.name == GUISkin.error.name;
|
||||
}
|
||||
|
||||
void LoadStyles()
|
||||
{
|
||||
endmarker = GetGUIStyle("Icon-Endmarker");
|
||||
groupBackground = GetGUIStyle("groupBackground");
|
||||
displayBackground = GetGUIStyle("sequenceClip");
|
||||
fontClip = GetGUIStyle("Font-Clip");
|
||||
trackHeaderFont = GetGUIStyle("sequenceTrackHeaderFont");
|
||||
trackGroupAddButton = GetGUIStyle("sequenceTrackGroupAddButton");
|
||||
groupFont = GetGUIStyle("sequenceGroupFont");
|
||||
timeCursor = GetGUIStyle("Icon-TimeCursor");
|
||||
tinyFont = GetGUIStyle("tinyFont");
|
||||
foldout = GetGUIStyle("Icon-Foldout");
|
||||
mute = GetGUIStyle("Icon-Mute");
|
||||
locked = GetGUIStyle("Icon-Locked");
|
||||
autoKey = GetGUIStyle("Icon-AutoKey");
|
||||
playTimeRangeStart = GetGUIStyle("Icon-PlayAreaStart");
|
||||
playTimeRangeEnd = GetGUIStyle("Icon-PlayAreaEnd");
|
||||
selectedStyle = GetGUIStyle("Color-Selected");
|
||||
trackSwatchStyle = GetGUIStyle("Icon-TrackHeaderSwatch");
|
||||
connector = GetGUIStyle("Icon-Connector");
|
||||
keyframe = GetGUIStyle("Icon-Keyframe");
|
||||
warning = GetGUIStyle("Icon-Warning");
|
||||
extrapolationHold = GetGUIStyle("Icon-ExtrapolationHold");
|
||||
extrapolationLoop = GetGUIStyle("Icon-ExtrapolationLoop");
|
||||
extrapolationPingPong = GetGUIStyle("Icon-ExtrapolationPingPong");
|
||||
extrapolationContinue = GetGUIStyle("Icon-ExtrapolationContinue");
|
||||
bottomShadow = GetGUIStyle("Icon-Shadow");
|
||||
trackOptions = GetGUIStyle("Icon-TrackOptions");
|
||||
infiniteTrack = GetGUIStyle("Icon-InfiniteTrack");
|
||||
clipOut = GetGUIStyle("Icon-ClipOut");
|
||||
clipIn = GetGUIStyle("Icon-ClipIn");
|
||||
curves = GetGUIStyle("Icon-Curves");
|
||||
lockedBG = GetGUIStyle("Icon-LockedBG");
|
||||
activation = GetGUIStyle("Icon-Activation");
|
||||
playrange = GetGUIStyle("Icon-Playrange");
|
||||
lockButton = GetGUIStyle("IN LockButton");
|
||||
avatarMaskOn = GetGUIStyle("Icon-AvatarMaskOn");
|
||||
avatarMaskOff = GetGUIStyle("Icon-AvatarMaskOff");
|
||||
collapseMarkers = GetGUIStyle("TrackCollapseMarkerButton");
|
||||
markerMultiOverlay = GetGUIStyle("MarkerMultiOverlay");
|
||||
editModeBtn = GetGUIStyle("editModeBtn");
|
||||
showMarkersBtn = GetGUIStyle("showMarkerBtn");
|
||||
markerWarning = GetGUIStyle("markerWarningOverlay");
|
||||
sequenceSwitcher = GetGUIStyle("sequenceSwitcher");
|
||||
|
||||
playrangeContent = new GUIContent(GetBackgroundImage(playrange)) { tooltip = "Toggle play range markers." };
|
||||
|
||||
fontClipLoop = new GUIStyle(fontClip) { fontStyle = FontStyle.Bold };
|
||||
}
|
||||
|
||||
public static GUIStyle GetGUIStyle(string s)
|
||||
{
|
||||
return EditorStyles.FromUSS(s);
|
||||
}
|
||||
|
||||
public static GUIContent TrIconContent(string iconName, string tooltip = null)
|
||||
{
|
||||
return EditorGUIUtility.TrIconContent(iconName == null ? null : ResolveIcon(iconName), tooltip);
|
||||
}
|
||||
|
||||
public static GUIContent IconContent(string iconName)
|
||||
{
|
||||
return EditorGUIUtility.IconContent(iconName == null ? null : ResolveIcon(iconName));
|
||||
}
|
||||
|
||||
public static GUIContent TrTextContentWithIcon(string text, string tooltip, string iconName)
|
||||
{
|
||||
return EditorGUIUtility.TrTextContentWithIcon(text, tooltip, iconName == null ? null : ResolveIcon(iconName));
|
||||
}
|
||||
|
||||
public static GUIContent TrTextContent(string text, string tooltip = null)
|
||||
{
|
||||
return EditorGUIUtility.TrTextContent(text, tooltip);
|
||||
}
|
||||
|
||||
public static Texture2D LoadIcon(string iconName)
|
||||
{
|
||||
return EditorGUIUtility.LoadIconRequired(iconName == null ? null : ResolveIcon(iconName));
|
||||
}
|
||||
|
||||
static string ResolveIcon(string icon)
|
||||
{
|
||||
return string.Format(k_ImagePath, icon);
|
||||
}
|
||||
|
||||
public static string Elipsify(string label, Rect rect, GUIStyle style)
|
||||
{
|
||||
var ret = label;
|
||||
|
||||
if (label.Length == 0)
|
||||
return ret;
|
||||
|
||||
s_TempContent.text = label;
|
||||
float neededWidth = style.CalcSize(s_TempContent).x;
|
||||
|
||||
return Elipsify(label, rect.width, neededWidth);
|
||||
}
|
||||
|
||||
public static string Elipsify(string label, float destinationWidth, float neededWidth)
|
||||
{
|
||||
var ret = label;
|
||||
|
||||
if (label.Length == 0)
|
||||
return ret;
|
||||
|
||||
if (destinationWidth < neededWidth)
|
||||
{
|
||||
float averageWidthOfOneChar = neededWidth / label.Length;
|
||||
int floor = Mathf.Max((int)Mathf.Floor(destinationWidth / averageWidthOfOneChar), 0);
|
||||
|
||||
if (floor < k_Elipsis.Length)
|
||||
ret = string.Empty;
|
||||
else if (floor == k_Elipsis.Length)
|
||||
ret = k_Elipsis;
|
||||
else if (floor < label.Length)
|
||||
ret = label.Substring(0, floor - k_Elipsis.Length) + k_Elipsis;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static Texture2D GetBackgroundImage(GUIStyle style, StyleState state = StyleState.normal)
|
||||
{
|
||||
var blockName = GUIStyleExtensions.StyleNameToBlockName(style.name, false);
|
||||
var styleBlock = EditorResources.GetStyle(blockName, state);
|
||||
return styleBlock.GetTexture(StyleCatalogKeyword.backgroundImage);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d7b5b883d3aae8d479647d5ae6182974
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f601c0d674119f74db9c15166b3a58c4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,150 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimatedParameterExtensions
|
||||
{
|
||||
public static bool HasAnyAnimatableParameters(this ICurvesOwner curvesOwner)
|
||||
{
|
||||
return AnimatedParameterUtility.HasAnyAnimatableParameters(curvesOwner.asset);
|
||||
}
|
||||
|
||||
public static IEnumerable<SerializedProperty> GetAllAnimatableParameters(this ICurvesOwner curvesOwner)
|
||||
{
|
||||
return AnimatedParameterUtility.GetAllAnimatableParameters(curvesOwner.asset);
|
||||
}
|
||||
|
||||
public static bool IsParameterAnimatable(this ICurvesOwner curvesOwner, string parameterName)
|
||||
{
|
||||
return AnimatedParameterUtility.IsParameterAnimatable(curvesOwner.asset, parameterName);
|
||||
}
|
||||
|
||||
public static bool IsParameterAnimated(this ICurvesOwner curvesOwner, string parameterName)
|
||||
{
|
||||
return AnimatedParameterUtility.IsParameterAnimated(curvesOwner.asset, curvesOwner.curves, parameterName);
|
||||
}
|
||||
|
||||
public static EditorCurveBinding GetCurveBinding(this ICurvesOwner curvesOwner, string parameterName)
|
||||
{
|
||||
return AnimatedParameterUtility.GetCurveBinding(curvesOwner.asset, parameterName);
|
||||
}
|
||||
|
||||
public static string GetUniqueRecordedClipName(this ICurvesOwner curvesOwner)
|
||||
{
|
||||
return AnimationTrackRecorder.GetUniqueRecordedClipName(curvesOwner.assetOwner, curvesOwner.defaultCurvesName);
|
||||
}
|
||||
|
||||
public static AnimationCurve GetAnimatedParameter(this ICurvesOwner curvesOwner, string bindingName)
|
||||
{
|
||||
return AnimatedParameterUtility.GetAnimatedParameter(curvesOwner.asset, curvesOwner.curves, bindingName);
|
||||
}
|
||||
|
||||
public static bool AddAnimatedParameterValueAt(this ICurvesOwner curvesOwner, string parameterName, float value, float time)
|
||||
{
|
||||
if (!curvesOwner.IsParameterAnimatable(parameterName))
|
||||
return false;
|
||||
|
||||
if (curvesOwner.curves == null)
|
||||
curvesOwner.CreateCurves(curvesOwner.GetUniqueRecordedClipName());
|
||||
|
||||
var binding = curvesOwner.GetCurveBinding(parameterName);
|
||||
var curve = AnimationUtility.GetEditorCurve(curvesOwner.curves, binding) ?? new AnimationCurve();
|
||||
|
||||
var serializedObject = AnimatedParameterUtility.GetSerializedPlayableAsset(curvesOwner.asset);
|
||||
var property = serializedObject.FindProperty(parameterName);
|
||||
|
||||
bool isStepped = property.propertyType == SerializedPropertyType.Boolean ||
|
||||
property.propertyType == SerializedPropertyType.Integer ||
|
||||
property.propertyType == SerializedPropertyType.Enum;
|
||||
|
||||
CurveEditUtility.AddKeyFrameToCurve(curve, time, curvesOwner.curves.frameRate, value, isStepped);
|
||||
AnimationUtility.SetEditorCurve(curvesOwner.curves, binding, curve);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void SanitizeCurvesData(this ICurvesOwner curvesOwner)
|
||||
{
|
||||
var curves = curvesOwner.curves;
|
||||
if (curves == null)
|
||||
return;
|
||||
|
||||
// Remove any 0-length curves
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(curves))
|
||||
{
|
||||
var curve = AnimationUtility.GetEditorCurve(curves, binding);
|
||||
if (curve.length == 0)
|
||||
AnimationUtility.SetEditorCurve(curves, binding, null);
|
||||
}
|
||||
|
||||
// If no curves remain, delete the curves asset
|
||||
if (curves.empty)
|
||||
{
|
||||
var track = curvesOwner.targetTrack;
|
||||
var timeline = track != null ? track.timelineAsset : null;
|
||||
TimelineUndo.PushDestroyUndo(timeline, track, curves, "Delete Parameter Curves");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool AddAnimatedParameter(this ICurvesOwner curvesOwner, string parameterName)
|
||||
{
|
||||
var newBinding = new EditorCurveBinding();
|
||||
|
||||
SerializedProperty property;
|
||||
if (!InternalAddParameter(curvesOwner, parameterName, ref newBinding, out property))
|
||||
return false;
|
||||
|
||||
var duration = (float)curvesOwner.duration;
|
||||
CurveEditUtility.AddKey(curvesOwner.curves, newBinding, property, 0);
|
||||
CurveEditUtility.AddKey(curvesOwner.curves, newBinding, property, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RemoveAnimatedParameter(this ICurvesOwner curvesOwner, string parameterName)
|
||||
{
|
||||
if (!curvesOwner.IsParameterAnimated(parameterName) || curvesOwner.curves == null)
|
||||
return false;
|
||||
|
||||
var binding = curvesOwner.GetCurveBinding(parameterName);
|
||||
AnimationUtility.SetEditorCurve(curvesOwner.curves, binding, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set an animated parameter. Requires the field identifier 'position.x', but will add default curves to all fields
|
||||
public static bool SetAnimatedParameter(this ICurvesOwner curvesOwner, string parameterName, AnimationCurve curve)
|
||||
{
|
||||
// this will add a basic curve for all the related parameters
|
||||
if (!curvesOwner.IsParameterAnimated(parameterName) && !curvesOwner.AddAnimatedParameter(parameterName))
|
||||
return false;
|
||||
|
||||
var binding = curvesOwner.GetCurveBinding(parameterName);
|
||||
AnimationUtility.SetEditorCurve(curvesOwner.curves, binding, curve);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool InternalAddParameter([NotNull] ICurvesOwner curvesOwner, string parameterName, ref EditorCurveBinding binding, out SerializedProperty property)
|
||||
{
|
||||
property = null;
|
||||
|
||||
if (curvesOwner.IsParameterAnimated(parameterName))
|
||||
return false;
|
||||
|
||||
var serializedObject = AnimatedParameterUtility.GetSerializedPlayableAsset(curvesOwner.asset);
|
||||
if (serializedObject == null)
|
||||
return false;
|
||||
|
||||
property = serializedObject.FindProperty(parameterName);
|
||||
if (property == null || !AnimatedParameterUtility.IsTypeAnimatable(property.propertyType))
|
||||
return false;
|
||||
|
||||
if (curvesOwner.curves == null)
|
||||
curvesOwner.CreateCurves(curvesOwner.GetUniqueRecordedClipName());
|
||||
|
||||
binding = curvesOwner.GetCurveBinding(parameterName);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7d3aa106cfe752241997b3759bf80163
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,134 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationTrackExtensions
|
||||
{
|
||||
public static void ConvertToClipMode(this AnimationTrack track)
|
||||
{
|
||||
if (!track.CanConvertToClipMode())
|
||||
return;
|
||||
|
||||
TimelineUndo.PushUndo(track, "Convert To Clip");
|
||||
|
||||
if (!track.infiniteClip.empty)
|
||||
{
|
||||
var animClip = track.infiniteClip;
|
||||
TimelineUndo.PushUndo(animClip, "Convert To Clip");
|
||||
TimelineUndo.PushUndo(track, "Convert To Clip");
|
||||
var start = AnimationClipCurveCache.Instance.GetCurveInfo(animClip).keyTimes.FirstOrDefault();
|
||||
animClip.ShiftBySeconds(-start);
|
||||
|
||||
track.infiniteClip = null;
|
||||
var clip = track.CreateClip(animClip);
|
||||
|
||||
clip.start = start;
|
||||
clip.preExtrapolationMode = track.infiniteClipPreExtrapolation;
|
||||
clip.postExtrapolationMode = track.infiniteClipPostExtrapolation;
|
||||
clip.recordable = true;
|
||||
if (Mathf.Abs(animClip.length) < TimelineClip.kMinDuration)
|
||||
{
|
||||
clip.duration = 1;
|
||||
}
|
||||
|
||||
var animationAsset = clip.asset as AnimationPlayableAsset;
|
||||
if (animationAsset)
|
||||
{
|
||||
animationAsset.position = track.infiniteClipOffsetPosition;
|
||||
animationAsset.eulerAngles = track.infiniteClipOffsetEulerAngles;
|
||||
|
||||
// going to / from infinite mode should reset this. infinite mode
|
||||
animationAsset.removeStartOffset = track.infiniteClipRemoveOffset;
|
||||
animationAsset.applyFootIK = track.infiniteClipApplyFootIK;
|
||||
animationAsset.loop = track.infiniteClipLoop;
|
||||
|
||||
track.infiniteClipOffsetPosition = Vector3.zero;
|
||||
track.infiniteClipOffsetEulerAngles = Vector3.zero;
|
||||
}
|
||||
|
||||
track.CalculateExtrapolationTimes();
|
||||
}
|
||||
|
||||
track.infiniteClip = null;
|
||||
|
||||
EditorUtility.SetDirty(track);
|
||||
}
|
||||
|
||||
public static void ConvertFromClipMode(this AnimationTrack track, TimelineAsset timeline)
|
||||
{
|
||||
if (!track.CanConvertFromClipMode())
|
||||
return;
|
||||
|
||||
TimelineUndo.PushUndo(track, "Convert From Clip");
|
||||
|
||||
var clip = track.clips[0];
|
||||
var delta = (float)clip.start;
|
||||
track.infiniteClipTimeOffset = 0.0f;
|
||||
track.infiniteClipPreExtrapolation = clip.preExtrapolationMode;
|
||||
track.infiniteClipPostExtrapolation = clip.postExtrapolationMode;
|
||||
|
||||
var animAsset = clip.asset as AnimationPlayableAsset;
|
||||
if (animAsset)
|
||||
{
|
||||
track.infiniteClipOffsetPosition = animAsset.position;
|
||||
track.infiniteClipOffsetEulerAngles = animAsset.eulerAngles;
|
||||
track.infiniteClipRemoveOffset = animAsset.removeStartOffset;
|
||||
track.infiniteClipApplyFootIK = animAsset.applyFootIK;
|
||||
track.infiniteClipLoop = animAsset.loop;
|
||||
}
|
||||
|
||||
// clone it, it may not be in the same asset
|
||||
var animClip = clip.animationClip;
|
||||
|
||||
float scale = (float)clip.timeScale;
|
||||
if (!Mathf.Approximately(scale, 1.0f))
|
||||
{
|
||||
if (!Mathf.Approximately(scale, 0.0f))
|
||||
scale = 1.0f / scale;
|
||||
animClip.ScaleTime(scale);
|
||||
}
|
||||
|
||||
TimelineUndo.PushUndo(animClip, "Convert From Clip");
|
||||
animClip.ShiftBySeconds(delta);
|
||||
|
||||
// manually delete the clip
|
||||
var asset = clip.asset;
|
||||
clip.asset = null;
|
||||
|
||||
// Remove the clip, remove old assets
|
||||
ClipModifier.Delete(timeline, clip);
|
||||
TimelineUndo.PushDestroyUndo(null, track, asset, "Convert From Clip");
|
||||
|
||||
track.infiniteClip = animClip;
|
||||
|
||||
EditorUtility.SetDirty(track);
|
||||
}
|
||||
|
||||
public static bool CanConvertToClipMode(this AnimationTrack track)
|
||||
{
|
||||
if (track == null || track.inClipMode)
|
||||
return false;
|
||||
return (track.infiniteClip != null && !track.infiniteClip.empty);
|
||||
}
|
||||
|
||||
// Requirements to go from clip mode
|
||||
// - one clip, recordable, and animation clip belongs to the same asset as the track
|
||||
public static bool CanConvertFromClipMode(this AnimationTrack track)
|
||||
{
|
||||
if ((track == null) ||
|
||||
(!track.inClipMode) ||
|
||||
(track.clips.Length != 1) ||
|
||||
(track.clips[0].start < 0) ||
|
||||
(!track.clips[0].recordable))
|
||||
return false;
|
||||
|
||||
var asset = track.clips[0].asset as AnimationPlayableAsset;
|
||||
if (asset == null)
|
||||
return false;
|
||||
|
||||
return TimelineHelpers.HaveSameContainerAsset(track, asset.clip);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5a31542ccf4e8584ca4f60843e9d02d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,495 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
// Editor-only extension methods on track assets
|
||||
static class TrackExtensions
|
||||
{
|
||||
public static readonly double kMinOverlapTime = TimeUtility.kTimeEpsilon * 1000;
|
||||
|
||||
public static AnimationClip GetOrCreateClip(this AnimationTrack track)
|
||||
{
|
||||
if (track.infiniteClip == null && !track.inClipMode)
|
||||
track.CreateInfiniteClip(AnimationTrackRecorder.GetUniqueRecordedClipName(track, AnimationTrackRecorder.kRecordClipDefaultName));
|
||||
|
||||
return track.infiniteClip;
|
||||
}
|
||||
|
||||
public static TimelineClip CreateClip(this TrackAsset track, double time)
|
||||
{
|
||||
var attr = track.GetType().GetCustomAttributes(typeof(TrackClipTypeAttribute), true);
|
||||
|
||||
if (attr.Length == 0)
|
||||
return null;
|
||||
|
||||
if (TimelineWindow.instance.state == null)
|
||||
return null;
|
||||
|
||||
if (attr.Length == 1)
|
||||
{
|
||||
var clipClass = (TrackClipTypeAttribute)attr[0];
|
||||
|
||||
var clip = TimelineHelpers.CreateClipOnTrack(clipClass.inspectedType, track, TimelineWindow.instance.state);
|
||||
clip.start = time;
|
||||
return clip;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool Overlaps(TimelineClip blendOut, TimelineClip blendIn)
|
||||
{
|
||||
if (blendIn == blendOut)
|
||||
return false;
|
||||
|
||||
if (Math.Abs(blendIn.start - blendOut.start) < TimeUtility.kTimeEpsilon)
|
||||
{
|
||||
return blendIn.duration >= blendOut.duration;
|
||||
}
|
||||
|
||||
return blendIn.start >= blendOut.start && blendIn.start < blendOut.end;
|
||||
}
|
||||
|
||||
public static void ComputeBlendsFromOverlaps(this TrackAsset asset)
|
||||
{
|
||||
ComputeBlendsFromOverlaps(asset.clips);
|
||||
}
|
||||
|
||||
internal static void ComputeBlendsFromOverlaps(TimelineClip[] clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
clip.blendInDuration = -1;
|
||||
clip.blendOutDuration = -1;
|
||||
}
|
||||
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var blendIn = clip;
|
||||
var blendOut = clips
|
||||
.Where(c => Overlaps(c, blendIn))
|
||||
.OrderBy(c => c.start)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (blendOut != null)
|
||||
{
|
||||
UpdateClipIntersection(blendOut, blendIn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static void UpdateClipIntersection(TimelineClip blendOutClip, TimelineClip blendInClip)
|
||||
{
|
||||
if (!blendOutClip.SupportsBlending() || !blendInClip.SupportsBlending())
|
||||
return;
|
||||
|
||||
if (blendInClip.end < blendOutClip.end)
|
||||
return;
|
||||
|
||||
double duration = Math.Max(0, blendOutClip.start + blendOutClip.duration - blendInClip.start);
|
||||
duration = duration <= kMinOverlapTime ? 0 : duration;
|
||||
blendOutClip.blendOutDuration = duration;
|
||||
blendInClip.blendInDuration = duration;
|
||||
|
||||
var blendInMode = blendInClip.blendInCurveMode;
|
||||
var blendOutMode = blendOutClip.blendOutCurveMode;
|
||||
|
||||
if (blendInMode == TimelineClip.BlendCurveMode.Manual && blendOutMode == TimelineClip.BlendCurveMode.Auto)
|
||||
{
|
||||
blendOutClip.mixOutCurve = CurveEditUtility.CreateMatchingCurve(blendInClip.mixInCurve);
|
||||
}
|
||||
else if (blendInMode == TimelineClip.BlendCurveMode.Auto && blendOutMode == TimelineClip.BlendCurveMode.Manual)
|
||||
{
|
||||
blendInClip.mixInCurve = CurveEditUtility.CreateMatchingCurve(blendOutClip.mixOutCurve);
|
||||
}
|
||||
else if (blendInMode == TimelineClip.BlendCurveMode.Auto && blendOutMode == TimelineClip.BlendCurveMode.Auto)
|
||||
{
|
||||
blendInClip.mixInCurve = null; // resets to default curves
|
||||
blendOutClip.mixOutCurve = null;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RecursiveSubtrackClone(TrackAsset source, TrackAsset duplicate, IExposedPropertyTable sourceTable, IExposedPropertyTable destTable, PlayableAsset assetOwner)
|
||||
{
|
||||
var subtracks = source.GetChildTracks();
|
||||
foreach (var sub in subtracks)
|
||||
{
|
||||
var newSub = TimelineHelpers.Clone(duplicate, sub, sourceTable, destTable, assetOwner);
|
||||
duplicate.AddChild(newSub);
|
||||
RecursiveSubtrackClone(sub, newSub, sourceTable, destTable, assetOwner);
|
||||
|
||||
// Call the custom editor on Create
|
||||
var customEditor = CustomTimelineEditorCache.GetTrackEditor(newSub);
|
||||
try
|
||||
{
|
||||
customEditor.OnCreate(newSub, sub);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
|
||||
// registration has to happen AFTER recursion
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(newSub, assetOwner);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(newSub, "Duplicate");
|
||||
}
|
||||
}
|
||||
|
||||
internal static TrackAsset Duplicate(this TrackAsset track, IExposedPropertyTable sourceTable, IExposedPropertyTable destTable,
|
||||
TimelineAsset destinationTimeline = null)
|
||||
{
|
||||
if (track == null)
|
||||
return null;
|
||||
|
||||
// if the destination is us, clear to avoid bad parenting (case 919421)
|
||||
if (destinationTimeline == track.timelineAsset)
|
||||
destinationTimeline = null;
|
||||
|
||||
var timelineParent = track.parent as TimelineAsset;
|
||||
var trackParent = track.parent as TrackAsset;
|
||||
if (timelineParent == null && trackParent == null)
|
||||
{
|
||||
Debug.LogWarning("Cannot duplicate track because it is not parented to known type");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine who the final parent is. If we are pasting into another track, it's always the timeline.
|
||||
// Otherwise it's the original parent
|
||||
PlayableAsset finalParent = destinationTimeline != null ? destinationTimeline : track.parent;
|
||||
|
||||
// grab the list of tracks to generate a name from (923360) to get the list of names
|
||||
// no need to do this part recursively
|
||||
var finalTrackParent = finalParent as TrackAsset;
|
||||
var finalTimelineAsset = finalParent as TimelineAsset;
|
||||
var otherTracks = (finalTimelineAsset != null) ? finalTimelineAsset.trackObjects : finalTrackParent.subTracksObjects;
|
||||
|
||||
// Important to create the new objects before pushing the original undo, or redo breaks the
|
||||
// sequence
|
||||
var newTrack = TimelineHelpers.Clone(finalParent, track, sourceTable, destTable, finalParent);
|
||||
newTrack.name = TimelineCreateUtilities.GenerateUniqueActorName(otherTracks, newTrack.name);
|
||||
|
||||
RecursiveSubtrackClone(track, newTrack, sourceTable, destTable, finalParent);
|
||||
TimelineCreateUtilities.SaveAssetIntoObject(newTrack, finalParent);
|
||||
TimelineUndo.RegisterCreatedObjectUndo(newTrack, "Duplicate");
|
||||
TimelineUndo.PushUndo(finalParent, "Duplicate");
|
||||
|
||||
if (destinationTimeline != null) // other timeline
|
||||
destinationTimeline.AddTrackInternal(newTrack);
|
||||
else if (timelineParent != null) // this timeline, no parent
|
||||
ReparentTracks(new List<TrackAsset> { newTrack }, timelineParent, timelineParent.GetRootTracks().Last(), false);
|
||||
else // this timeline, with parent
|
||||
trackParent.AddChild(newTrack);
|
||||
|
||||
// Call the custom editor. this check prevents the call when copying to the clipboard
|
||||
if (destinationTimeline == null || destinationTimeline == TimelineEditor.inspectedAsset)
|
||||
{
|
||||
var customEditor = CustomTimelineEditorCache.GetTrackEditor(newTrack);
|
||||
try
|
||||
{
|
||||
customEditor.OnCreate(newTrack, track);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return newTrack;
|
||||
}
|
||||
|
||||
// Reparents a list of tracks to a new parent
|
||||
// the new parent cannot be null (has to be track asset or sequence)
|
||||
// the insertAfter can be null (will not reorder)
|
||||
internal static bool ReparentTracks(List<TrackAsset> tracksToMove, PlayableAsset targetParent,
|
||||
TrackAsset insertMarker = null, bool insertBefore = false)
|
||||
{
|
||||
var targetParentTrack = targetParent as TrackAsset;
|
||||
var targetSequenceTrack = targetParent as TimelineAsset;
|
||||
|
||||
if (tracksToMove == null || tracksToMove.Count == 0 || (targetParentTrack == null && targetSequenceTrack == null))
|
||||
return false;
|
||||
|
||||
// invalid parent type on a track
|
||||
if (targetParentTrack != null && tracksToMove.Any(x => !TimelineCreateUtilities.ValidateParentTrack(targetParentTrack, x.GetType())))
|
||||
return false;
|
||||
|
||||
// no valid tracks means this is simply a rearrangement
|
||||
var validTracks = tracksToMove.Where(x => x.parent != targetParent).ToList();
|
||||
if (insertMarker == null && !validTracks.Any())
|
||||
return false;
|
||||
|
||||
var parents = validTracks.Select(x => x.parent).Where(x => x != null).Distinct().ToList();
|
||||
|
||||
// push the current state of the tracks that will change
|
||||
foreach (var p in parents)
|
||||
{
|
||||
TimelineUndo.PushUndo(p, "Reparent");
|
||||
}
|
||||
foreach (var t in validTracks)
|
||||
{
|
||||
TimelineUndo.PushUndo(t, "Reparent");
|
||||
}
|
||||
TimelineUndo.PushUndo(targetParent, "Reparent");
|
||||
|
||||
// need to reparent tracks first, before moving them.
|
||||
foreach (var t in validTracks)
|
||||
{
|
||||
if (t.parent != targetParent)
|
||||
{
|
||||
TrackAsset toMoveParent = t.parent as TrackAsset;
|
||||
TimelineAsset toMoveTimeline = t.parent as TimelineAsset;
|
||||
if (toMoveTimeline != null)
|
||||
{
|
||||
toMoveTimeline.RemoveTrack(t);
|
||||
}
|
||||
else if (toMoveParent != null)
|
||||
{
|
||||
toMoveParent.RemoveSubTrack(t);
|
||||
}
|
||||
|
||||
if (targetParentTrack != null)
|
||||
{
|
||||
targetParentTrack.AddChild(t);
|
||||
targetParentTrack.SetCollapsed(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetSequenceTrack.AddTrackInternal(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (insertMarker != null)
|
||||
{
|
||||
// re-ordering track. This is using internal APIs, so invalidation of the tracks must be done manually to avoid
|
||||
// cache mismatches
|
||||
var children = targetParentTrack != null ? targetParentTrack.subTracksObjects : targetSequenceTrack.trackObjects;
|
||||
TimelineUtility.ReorderTracks(children, tracksToMove, insertMarker, insertBefore);
|
||||
if (targetParentTrack != null)
|
||||
targetParentTrack.Invalidate();
|
||||
if (insertMarker.timelineAsset != null)
|
||||
insertMarker.timelineAsset.Invalidate();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static IEnumerable<TrackAsset> FilterTracks(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
var nTracks = tracks.Count();
|
||||
// Duplicate is recursive. If should not have parent and child in the list
|
||||
var hash = new HashSet<TrackAsset>(tracks);
|
||||
var take = new Dictionary<TrackAsset, bool>(nTracks);
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
var parent = track.parent as TrackAsset;
|
||||
var foundParent = false;
|
||||
// go up the hierarchy
|
||||
while (parent != null && !foundParent)
|
||||
{
|
||||
if (hash.Contains(parent))
|
||||
{
|
||||
foundParent = true;
|
||||
}
|
||||
|
||||
parent = parent.parent as TrackAsset;
|
||||
}
|
||||
|
||||
take[track] = !foundParent;
|
||||
}
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (take[track])
|
||||
yield return track;
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsVisibleRecursive(this TrackAsset track)
|
||||
{
|
||||
var t = track;
|
||||
while ((t = t.parent as TrackAsset) != null)
|
||||
{
|
||||
if (t.GetCollapsed())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool GetCollapsed(this TrackAsset track)
|
||||
{
|
||||
return TimelineWindowViewPrefs.IsTrackCollapsed(track);
|
||||
}
|
||||
|
||||
internal static void SetCollapsed(this TrackAsset track, bool collapsed)
|
||||
{
|
||||
TimelineWindowViewPrefs.SetTrackCollapsed(track, collapsed);
|
||||
}
|
||||
|
||||
internal static bool GetShowMarkers(this TrackAsset track)
|
||||
{
|
||||
return TimelineWindowViewPrefs.IsShowMarkers(track);
|
||||
}
|
||||
|
||||
internal static void SetShowMarkers(this TrackAsset track, bool collapsed)
|
||||
{
|
||||
TimelineWindowViewPrefs.SetTrackShowMarkers(track, collapsed);
|
||||
}
|
||||
|
||||
internal static bool GetShowInlineCurves(this TrackAsset track)
|
||||
{
|
||||
return TimelineWindowViewPrefs.GetShowInlineCurves(track);
|
||||
}
|
||||
|
||||
internal static void SetShowInlineCurves(this TrackAsset track, bool inlineOn)
|
||||
{
|
||||
TimelineWindowViewPrefs.SetShowInlineCurves(track, inlineOn);
|
||||
}
|
||||
|
||||
internal static bool ShouldShowInfiniteClipEditor(this TrackAsset track)
|
||||
{
|
||||
var animationTrack = track as AnimationTrack;
|
||||
if (animationTrack != null)
|
||||
return animationTrack.ShouldShowInfiniteClipEditor();
|
||||
|
||||
return track.HasAnyAnimatableParameters();
|
||||
}
|
||||
|
||||
internal static bool ShouldShowInfiniteClipEditor(this AnimationTrack track)
|
||||
{
|
||||
return track != null && !track.inClipMode && track.infiniteClip != null;
|
||||
}
|
||||
|
||||
// Special method to remove a track that is in a broken state. i.e. the script won't load
|
||||
internal static bool RemoveBrokenTrack(PlayableAsset parent, ScriptableObject track)
|
||||
{
|
||||
var parentTrack = parent as TrackAsset;
|
||||
var parentTimeline = parent as TimelineAsset;
|
||||
|
||||
if (parentTrack == null && parentTimeline == null)
|
||||
throw new ArgumentException("parent is not a valid parent type", "parent");
|
||||
|
||||
// this object must be a Unity null, but not actually null;
|
||||
object trackAsObject = track;
|
||||
if (trackAsObject == null || track != null) // yes, this is correct
|
||||
throw new ArgumentException("track is not in a broken state");
|
||||
|
||||
// this belongs to a parent track
|
||||
if (parentTrack != null)
|
||||
{
|
||||
int index = parentTrack.subTracksObjects.FindIndex(t => t.GetInstanceID() == track.GetInstanceID());
|
||||
if (index >= 0)
|
||||
{
|
||||
TimelineUndo.PushUndo(parentTrack, "Remove Track");
|
||||
parentTrack.subTracksObjects.RemoveAt(index);
|
||||
parentTrack.Invalidate();
|
||||
Undo.DestroyObjectImmediate(track);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (parentTimeline != null)
|
||||
{
|
||||
int index = parentTimeline.trackObjects.FindIndex(t => t.GetInstanceID() == track.GetInstanceID());
|
||||
if (index >= 0)
|
||||
{
|
||||
TimelineUndo.PushUndo(parentTimeline, "Remove Track");
|
||||
parentTimeline.trackObjects.RemoveAt(index);
|
||||
parentTimeline.Invalidate();
|
||||
Undo.DestroyObjectImmediate(track);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the gap at the given time
|
||||
// return true if there is a gap, false if there is an intersection
|
||||
// endGap will be Infinity if the gap has no end
|
||||
internal static bool GetGapAtTime(this TrackAsset track, double time, out double startGap, out double endGap)
|
||||
{
|
||||
startGap = 0;
|
||||
endGap = Double.PositiveInfinity;
|
||||
|
||||
if (track == null || !track.GetClips().Any())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var discreteTime = new DiscreteTime(time);
|
||||
|
||||
track.SortClips();
|
||||
var sortedByStartTime = track.clips;
|
||||
for (int i = 0; i < sortedByStartTime.Length; i++)
|
||||
{
|
||||
var clip = sortedByStartTime[i];
|
||||
|
||||
// intersection
|
||||
if (discreteTime >= new DiscreteTime(clip.start) && discreteTime < new DiscreteTime(clip.end))
|
||||
{
|
||||
endGap = time;
|
||||
startGap = time;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (clip.end < time)
|
||||
{
|
||||
startGap = clip.end;
|
||||
}
|
||||
if (clip.start > time)
|
||||
{
|
||||
endGap = clip.start;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endGap - startGap < TimelineClip.kMinDuration)
|
||||
{
|
||||
startGap = time;
|
||||
endGap = time;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsCompatibleWithClip(this TrackAsset track, TimelineClip clip)
|
||||
{
|
||||
if (track == null || clip == null || clip.asset == null)
|
||||
return false;
|
||||
|
||||
return TypeUtility.GetPlayableAssetsHandledByTrack(track.GetType()).Contains(clip.asset.GetType());
|
||||
}
|
||||
|
||||
// Get a flattened list of all child tracks
|
||||
public static void GetFlattenedChildTracks(this TrackAsset asset, List<TrackAsset> list)
|
||||
{
|
||||
if (asset == null || list == null)
|
||||
return;
|
||||
|
||||
foreach (var track in asset.GetChildTracks())
|
||||
{
|
||||
list.Add(track);
|
||||
GetFlattenedChildTracks(track, list);
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<TrackAsset> GetFlattenedChildTracks(this TrackAsset asset)
|
||||
{
|
||||
if (asset == null || !asset.GetChildTracks().Any())
|
||||
return Enumerable.Empty<TrackAsset>();
|
||||
|
||||
var flattenedChildTracks = new List<TrackAsset>();
|
||||
GetFlattenedChildTracks(asset, flattenedChildTracks);
|
||||
return flattenedChildTracks;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5b24618beecc3bf41acadfcf2246d772
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3a5038547af7c7f46bd90a015862e0b3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,143 @@
|
|||
using System;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class ClipItem : IBlendable, ITrimmable
|
||||
{
|
||||
readonly TimelineClip m_Clip;
|
||||
|
||||
public TimelineClip clip
|
||||
{
|
||||
get { return m_Clip; }
|
||||
}
|
||||
|
||||
public ClipItem(TimelineClip clip)
|
||||
{
|
||||
m_Clip = clip;
|
||||
}
|
||||
|
||||
public TrackAsset parentTrack
|
||||
{
|
||||
get { return m_Clip.parentTrack; }
|
||||
set { m_Clip.parentTrack = value; }
|
||||
}
|
||||
|
||||
public double start
|
||||
{
|
||||
get { return m_Clip.start; }
|
||||
set { m_Clip.start = value; }
|
||||
}
|
||||
|
||||
public double end
|
||||
{
|
||||
get { return m_Clip.end; }
|
||||
}
|
||||
|
||||
public double duration
|
||||
{
|
||||
get { return m_Clip.duration; }
|
||||
}
|
||||
|
||||
public bool IsCompatibleWithTrack(TrackAsset track)
|
||||
{
|
||||
return track.IsCompatibleWithClip(m_Clip);
|
||||
}
|
||||
|
||||
public void PushUndo(string operation)
|
||||
{
|
||||
TimelineUndo.PushUndo(m_Clip.parentTrack, operation);
|
||||
}
|
||||
|
||||
public TimelineItemGUI gui
|
||||
{
|
||||
get { return ItemToItemGui.GetGuiForClip(m_Clip); }
|
||||
}
|
||||
|
||||
public bool supportsBlending
|
||||
{
|
||||
get { return m_Clip.SupportsBlending(); }
|
||||
}
|
||||
|
||||
public bool hasLeftBlend
|
||||
{
|
||||
get { return m_Clip.hasBlendIn; }
|
||||
}
|
||||
|
||||
public bool hasRightBlend
|
||||
{
|
||||
get { return m_Clip.hasBlendOut; }
|
||||
}
|
||||
|
||||
public double leftBlendDuration
|
||||
{
|
||||
get { return m_Clip.hasBlendIn ? m_Clip.blendInDuration : m_Clip.easeInDuration; }
|
||||
}
|
||||
|
||||
public double rightBlendDuration
|
||||
{
|
||||
get { return m_Clip.hasBlendOut ? m_Clip.blendOutDuration : m_Clip.easeOutDuration; }
|
||||
}
|
||||
|
||||
public void SetStart(double time)
|
||||
{
|
||||
ClipModifier.SetStart(m_Clip, time);
|
||||
}
|
||||
|
||||
public void SetEnd(double time, bool affectTimeScale)
|
||||
{
|
||||
ClipModifier.SetEnd(m_Clip, time, affectTimeScale);
|
||||
}
|
||||
|
||||
public void Delete()
|
||||
{
|
||||
EditorClipFactory.RemoveEditorClip(m_Clip);
|
||||
ClipModifier.Delete(m_Clip.parentTrack.timelineAsset, m_Clip);
|
||||
}
|
||||
|
||||
public void TrimStart(double time)
|
||||
{
|
||||
ClipModifier.TrimStart(m_Clip, time);
|
||||
}
|
||||
|
||||
public void TrimEnd(double time)
|
||||
{
|
||||
ClipModifier.TrimEnd(m_Clip, time);
|
||||
}
|
||||
|
||||
public ITimelineItem CloneTo(TrackAsset parent, double time)
|
||||
{
|
||||
return new ClipItem(TimelineHelpers.Clone(m_Clip, TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector, time, parent));
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return m_Clip.ToString();
|
||||
}
|
||||
|
||||
public bool Equals(ClipItem otherClip)
|
||||
{
|
||||
if (ReferenceEquals(null, otherClip)) return false;
|
||||
if (ReferenceEquals(this, otherClip)) return true;
|
||||
return Equals(m_Clip, otherClip.m_Clip);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (m_Clip != null ? m_Clip.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
public bool Equals(ITimelineItem other)
|
||||
{
|
||||
return Equals((object)other);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
var other = obj as ClipItem;
|
||||
return other != null && Equals(other);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d143f3edd0494bc4c98a421bd59564fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface ITimelineItem : IEquatable<ITimelineItem>
|
||||
{
|
||||
double start { get; set; }
|
||||
double end { get; }
|
||||
double duration { get; }
|
||||
|
||||
TrackAsset parentTrack { get; set; }
|
||||
bool IsCompatibleWithTrack(TrackAsset track);
|
||||
|
||||
void Delete();
|
||||
ITimelineItem CloneTo(TrackAsset parent, double time);
|
||||
void PushUndo(string operation);
|
||||
|
||||
TimelineItemGUI gui { get; }
|
||||
}
|
||||
|
||||
interface ITrimmable : ITimelineItem
|
||||
{
|
||||
void SetStart(double time);
|
||||
void SetEnd(double time, bool affectTimeScale);
|
||||
void TrimStart(double time);
|
||||
void TrimEnd(double time);
|
||||
}
|
||||
|
||||
interface IBlendable : ITimelineItem
|
||||
{
|
||||
bool supportsBlending { get; }
|
||||
bool hasLeftBlend { get; }
|
||||
bool hasRightBlend { get; }
|
||||
|
||||
double leftBlendDuration { get; }
|
||||
double rightBlendDuration { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2c87ec8c97244cd47945ec90a99abe35
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class ItemsGroup
|
||||
{
|
||||
readonly ITimelineItem[] m_Items;
|
||||
readonly ITimelineItem m_LeftMostItem;
|
||||
readonly ITimelineItem m_RightMostItem;
|
||||
|
||||
public ITimelineItem[] items
|
||||
{
|
||||
get { return m_Items; }
|
||||
}
|
||||
|
||||
public double start
|
||||
{
|
||||
get { return m_LeftMostItem.start; }
|
||||
set
|
||||
{
|
||||
var offset = value - m_LeftMostItem.start;
|
||||
|
||||
foreach (var clip in m_Items)
|
||||
clip.start += offset;
|
||||
}
|
||||
}
|
||||
|
||||
public double end
|
||||
{
|
||||
get { return m_RightMostItem.end; }
|
||||
}
|
||||
|
||||
public ITimelineItem leftMostItem
|
||||
{
|
||||
get { return m_LeftMostItem; }
|
||||
}
|
||||
|
||||
public ITimelineItem rightMostItem
|
||||
{
|
||||
get { return m_RightMostItem; }
|
||||
}
|
||||
|
||||
public ItemsGroup(IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
Debug.Assert(items != null && items.Any());
|
||||
|
||||
m_Items = items.ToArray();
|
||||
m_LeftMostItem = null;
|
||||
m_RightMostItem = null;
|
||||
|
||||
foreach (var item in m_Items)
|
||||
{
|
||||
if (m_LeftMostItem == null || item.start < m_LeftMostItem.start)
|
||||
m_LeftMostItem = item;
|
||||
|
||||
if (m_RightMostItem == null || item.end > m_RightMostItem.end)
|
||||
m_RightMostItem = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1ec4b8ec4b34f4344bac53c19288eaa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class ItemsPerTrack
|
||||
{
|
||||
public virtual TrackAsset targetTrack { get; }
|
||||
|
||||
public IEnumerable<ITimelineItem> items
|
||||
{
|
||||
get { return m_ItemsGroup.items; }
|
||||
}
|
||||
|
||||
public IEnumerable<TimelineClip> clips
|
||||
{
|
||||
get { return m_ItemsGroup.items.OfType<ClipItem>().Select(i => i.clip); }
|
||||
}
|
||||
|
||||
public IEnumerable<IMarker> markers
|
||||
{
|
||||
get { return m_ItemsGroup.items.OfType<MarkerItem>().Select(i => i.marker); }
|
||||
}
|
||||
|
||||
public ITimelineItem leftMostItem
|
||||
{
|
||||
get { return m_ItemsGroup.leftMostItem; }
|
||||
}
|
||||
|
||||
public ITimelineItem rightMostItem
|
||||
{
|
||||
get { return m_ItemsGroup.rightMostItem; }
|
||||
}
|
||||
|
||||
protected readonly ItemsGroup m_ItemsGroup;
|
||||
|
||||
public ItemsPerTrack(TrackAsset targetTrack, IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
this.targetTrack = targetTrack;
|
||||
m_ItemsGroup = new ItemsGroup(items);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue