Initial Commit
This commit is contained in:
parent
53eb92e9af
commit
270ab7d11f
15341 changed files with 700234 additions and 0 deletions
498
Library/PackageCache/com.unity.timeline@1.5.4/CHANGELOG.md
Normal file
498
Library/PackageCache/com.unity.timeline@1.5.4/CHANGELOG.md
Normal file
|
@ -0,0 +1,498 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [1.5.4] - 2021-03-10
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where the horizontal scrollbar could not be moved or resized.
|
||||
|
||||
## [1.5.3] - 2021-03-05
|
||||
|
||||
### Changed
|
||||
|
||||
- Disabled edition of Track Asset Inspector Script field as it could break Timeline Assets.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where the timeline header track would automatically open during a drag and drop operation. ([1305436](https://issuetracker.unity3d.com/product/unity/issues/guid/1305436))
|
||||
- Fixed a rare issue where some broken tracks could not be removed. ([1305388](https://issuetracker.unity3d.com/product/unity/issues/guid/1305388))
|
||||
- Fixed rare issue where the time field could not be edited after opening a timeline. ([1312198](https://issuetracker.unity3d.com/product/unity/issues/guid/1312198))
|
||||
- Fixed cosmetic issue where the duration marker was drawn over the scroll bar.
|
||||
- Fixed issue where times without a decimal separator (. or , depending on locale) would not be interpreted correctly by the time field. (1315605)
|
||||
- Fixed issue where a selection rectangle could not be made when started inside a track. ([1315840](https://issuetracker.unity3d.com/product/unity/issues/guid/1315840))
|
||||
- Performing Undo/Redo will not affect Timeline window selection when the window is locked. (Selecting sub-timelines can still be undone). ([1313515](https://issuetracker.unity3d.com/product/unity/issues/guid/1313515))
|
||||
- Fixed an issue where text would be clipped in the track header binding. ([1302401](https://issuetracker.unity3d.com/product/unity/issues/guid/1302401))
|
||||
- Fixed issue where clicking in the Timeline window while there is no active timeline would throw an exception.
|
||||
|
||||
## [1.5.2] - 2021-01-08
|
||||
|
||||
### Added
|
||||
|
||||
- During recording, there are new ways to key animated properties:
|
||||
- A new Inspector context menu has been added (`Key All Animated`) that sets a key to all currently animated properties.
|
||||
- It is possible to make a multi-selection of tracks to set a keyframe to all currently animated properties. If no track is selected, all recording tracks are keyed.
|
||||
- If properties are selected in the curve editor, only those properties are keyed.
|
||||
- `TimelineEditor.GetWindow` and `TimelineEditor.GetOrCreateWindow` to get the current Timeline window or create a Timeline window.
|
||||
- `TimelineEditorWindow.SetCurrentTimeline` to change which timeline asset is opened in the Timeline window.
|
||||
- `TimelineEditorWindow.lock` to lock or unlock the Timeline window.
|
||||
- `TrackExtensions.GetCollapsed`, `TrackExtensions.SetCollapsed`, `TrackExtensions.IsVisibleRecursive` to get and change the visibility state of a track.
|
||||
- `AnimationTrackExtensions.IsRecording`, `AnimationTrackExtensions.SetRecording`, `AnimationTrackExtensions.SupportsRecording` to get or change the recording state of an Animation track.
|
||||
- Added two methods in `TrackEditor` to control how an object is bound to a track: `IsBindingAssignableFrom` and `GetBindingFrom`.
|
||||
- Added Japanese translation.
|
||||
- The Timeline window will automatically rebuild the graph when a notifications's properties are changed.
|
||||
- The Timeline window will be automatically refreshed when a marker's properties are changed.
|
||||
- Added `TimelineEditor.GetInspectedTimeFromMasterTime` and `TimelineEditor.GetMasterTimeFromInspectedTime` to convert time from master to inspected timeline and vice versa when using sub-timelines.
|
||||
- Added API to improve how to get/set a `TimelineClip`'s parent track:
|
||||
- `TimelineClip.GetParentTrack` (replaces obsolete property getter)
|
||||
- `ItemsUtils.SetParentTrack` (extension method thar replaces obsolete property setter)
|
||||
- Added a new `Seconds` time display mode and renamed previous Seconds mode to Timecode.
|
||||
- `TimelinePreferences.timeFormat` field,
|
||||
- `UnityEditor.Timeline.TimeFormat` enum.
|
||||
- Added API for the user to clip to the track area:
|
||||
- API: Relevant member to `MarkerOverlayRegion`,
|
||||
- API: `MarkerOverlayRegion.trackRegion`,
|
||||
- API: `MarkerOverlayRegion` constructor.
|
||||
- Added _Gameplay sequence_ sample.
|
||||
- This sample demonstrates how Timeline can be used to create a small in-game moment, using built-in tracks.
|
||||
- Added _Customization_ sample.
|
||||
- This sample demonstrates how to create custom tracks, clips, markers and actions.
|
||||
|
||||
### Changed
|
||||
|
||||
- The binding field on a track header will change its background color when dragging a valid object on it.
|
||||
- Timeline marker track is now selectable.
|
||||
- `TimelineClip` property `parentTrack` is now obsolete.
|
||||
- `TimelinePreferences.timeUnitInFrames` is now obsolete.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed a bug affecting the conversion between seconds and frames in the inspector.
|
||||
- Fixed issue where `KeyAllAnimated` was available when right-clicking on markers and tracks that were not in record mode. (1270304)
|
||||
- Fixed issue where the mouse cursor would stay stuck to a resize icon when resizing the track header. ([1076031](https://issuetracker.unity3d.com/product/unity/issues/guid/1076031/))
|
||||
- Fixed case where an animation event at time 0 would not fire on a timeline loop. ([1184106](https://issuetracker.unity3d.com/product/unity/issues/guid/1184106))
|
||||
- Fixed issue where Timeline objects (ie. `TrackAsset`, `ControlTrack`, `SignalAsset`, etc.) would have incorrect links to the documentation pages. *Available starting from Unity 2021.1*. ([1082941](https://issuetracker.unity3d.com/product/unity/issues/guid/1082941))
|
||||
- Fixed multiple issues related to blends
|
||||
- Fix display of blends when clips have ease-in/ease-out ([1178066](https://issuetracker.unity3d.com/product/unity/issues/guid/1178066))
|
||||
- Fix clip disappearing when dragging it from left to right completely inside another clip.
|
||||
- Fix select and drag clip discarding foreground display rule of selected clip after releasing the drag.
|
||||
- Fix fully blended clips selection not available. ([1289912](https://issuetracker.unity3d.com/product/unity/issues/guid/1289912))
|
||||
- Fixed issue where the clip display would flicker when moving two clips that are completely overlapped. ([1085679](https://issuetracker.unity3d.com/product/unity/issues/guid/1085679))
|
||||
- The Timeline window will no longer revert to editing only the asset if the user uses the Timeline selector to pick a game object and switches focus. ([1291455](https://issuetracker.unity3d.com/product/unity/issues/guid/1291455))
|
||||
- Create button on timeline panel no longer defaults to an invalid path. ([1289923](https://issuetracker.unity3d.com/product/unity/issues/guid/1289923))
|
||||
- Fixed issue where Timeline's bindings field would loses names and bindings when selecting clips. ([1293941](https://issuetracker.unity3d.com/product/unity/issues/guid/1293941))
|
||||
- Make Timeline's duration result displayed in the Inspector, when switching from duration mode: Based On Clips to Fixed Length, closer to the actual duration. ([1156920](https://issuetracker.unity3d.com/product/unity/issues/guid/1156920))
|
||||
- Copy/Paste of clips in the Timeline Window will no longer paste clips at an invalid time in mix-mode. ([1289925](https://issuetracker.unity3d.com/product/unity/issues/guid/1289925))
|
||||
|
||||
## [1.4.5] - 2020-11-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue where changing a clip's extrapolation values would clear the current clip selection. ([936046](https://issuetracker.unity3d.com/product/unity/issues/guid/936046))
|
||||
- Fixed multiple issues related to the curves view:
|
||||
- Fixed curve removal not functioning with `PlayableAsset`s (clips & tracks curves). ([1231002](https://issuetracker.unity3d.com/product/unity/issues/guid/1231002))
|
||||
- Fixed inconsistent icon display on curves.
|
||||
- Fixed incorrect ordering of properties. Properties now have a object/type/property ordering.
|
||||
- Fixed unnecessary grouping of fields.
|
||||
- Changed context menu from `Remove Properties` to `Remove Curves` to better reflect the change in functionality between curves for GameObjects and curves for `PlayableAssets`.
|
||||
- Fixed behaviour where removing a single field in a `Position`, `Rotation` or `Scale` group would remove the entire group.
|
||||
- Fixed case where pausing in Playmode and switching the active director in editor could pause the director. ([1263707](https://issuetracker.unity3d.com/product/unity/issues/guid/1263707))
|
||||
- Material properties are now displayed by their shader name in the curves view when possible. ([1115961](https://issuetracker.unity3d.com/product/unity/issues/guid/1115961))
|
||||
- Fixed issue where a signal could be pasted on a track that doesn't support notifications. ([1283763](https://issuetracker.unity3d.com/product/unity/issues/guid/1283763))
|
||||
- Fixed issue where a clip could be paseted on an incompatible track. ([1283763](https://issuetracker.unity3d.com/product/unity/issues/guid/1283763))
|
||||
- Fixed errors when leaving prefab mode when a timeline is opened. ([1280331](https://issuetracker.unity3d.com/product/unity/issues/guid/1280331))
|
||||
- No preview will be shown when the PlayableDirector is disabled. ([1286198](https://issuetracker.unity3d.com/product/unity/issues/guid/1286198))
|
||||
- Fixed issue where an infinite clip's `Foot Ik` property was not visible in the Inspector when selecting its track. ([1279824](https://issuetracker.unity3d.com/product/unity/issues/guid/1279824))
|
||||
- Fixed issue where child particle systems were not controlled correctly when they are not subemitters. ([1212943](https://issuetracker.unity3d.com/product/unity/issues/guid/1212943))
|
||||
- Fixed inconsistent recording behaviour on audio tracks and `PlayableAssets`. Default values are changed when a value is not recorded, and the key added/updated when a value is already animated. ([1283453](https://issuetracker.unity3d.com/product/unity/issues/guid/1283453))
|
||||
- Fixed issue where the curves view for tracks and `PlayableAsset`s would not update when changed externally (such as from the Animation window).
|
||||
- Fixed `Add Key`/`Remove Key` context menus not being properly enabled in some cases when using tracks and `PlayableAsset`s.
|
||||
- Fixed simulation of subemitters when scrubbing a timeline. ([1142781](https://issuetracker.unity3d.com/product/unity/issues/guid/1142781))
|
||||
- Fixed choppy playback of particles with a large fixed time step. ([1262234](https://issuetracker.unity3d.com/product/unity/issues/guid/1262234))
|
||||
|
||||
## [1.4.4] - 2020-10-09
|
||||
|
||||
### Fixed
|
||||
- Disable drag and drop of Signal asset on Control Track. ([1222760](https://issuetracker.unity3d.com/product/unity/issues/guid/1222760/))
|
||||
- Fixed system locale causing issues when keying float values on custom clips. ([1190877](https://issuetracker.unity3d.com/product/unity/issues/guid/1190877/))
|
||||
- Fixed issue where recording to a clip would place keys on the frame. ([1274892](https://issuetracker.unity3d.com/product/unity/issues/guid/1274892/))
|
||||
- Fixed keyboard clip selection from locked tracks. ([1233612](https://issuetracker.unity3d.com/product/unity/issues/guid/1233612/))
|
||||
- Fixed issue where the Timeline window would stay locked even when no timeline asset is shown. ([1278598](https://issuetracker.unity3d.com/product/unity/issues/guid/1278598/))
|
||||
- Fixed issue where invoking `SelectLeft` or `SelectRight` shortcuts on a group track, the group would not collapse or expand. ([1279379](https://issuetracker.unity3d.com/product/unity/issues/guid/1279379/))
|
||||
- Fixed Blend Curve Editor from the clip's inspector that was not responding correctly to undo and redo commands. (978673)
|
||||
- Fixed issue where the `Frame All` action would not frame keys outside of clips when the curve display is collapsed. ([1273725](https://issuetracker.unity3d.com/product/unity/issues/guid/1273725/), #295)
|
||||
- Scrolling the horizontal scrollbar of the timeline to the right edge will no longer prevent the user from dragging left again. ([1127199](https://issuetracker.unity3d.com/product/unity/issues/guid/1127199/), #301)
|
||||
- Splitting a clip with an ease in or out value now ensures ease duration stays on correct side of split. ([1279350](https://issuetracker.unity3d.com/product/unity/issues/guid/1279350/))
|
||||
- Fixed delay when zooming in after reaching Timeline window's maximum and then zooming back. ([1214228](https://issuetracker.unity3d.com/product/unity/issues/guid/1214228/))
|
||||
- Prevent creation of presets with Group Tracks. ([1281056](https://issuetracker.unity3d.com/product/unity/issues/guid/1281056))
|
||||
- Fixed issue where markers placed on top of clips could not be selected. ([1284807](https://issuetracker.unity3d.com/product/unity/issues/guid/1284807), #314)
|
||||
- Fixed issue where multiple markers placed on top of each other could not be selected. ([1284801](https://issuetracker.unity3d.com/product/unity/issues/guid/1284801), #314)
|
||||
|
||||
## [1.4.3] - 2020-08-26
|
||||
|
||||
### Fixed
|
||||
- Fixed incorrect selection when clicking on a clip's blend. (1178052)
|
||||
- Fixed issue where an exception was thrown when drawing an Audio clip's waveform when that clip wasn't in the AssetDatabase. ([1268868](https://issuetracker.unity3d.com/product/unity/issues/guid/1268868/))
|
||||
- When choosing `Add Signal Emitter from Signal Asset`, closing the Object Selector window will not add an empty Signal Emitter. ([1261553](https://issuetracker.unity3d.com/product/unity/issues/guid/1261553/))
|
||||
- Fixed issue where an error would appear when editing keys in the Animation window if the Timeline window is opened. (1269829)
|
||||
- Fixed issue where the `Frame All` operation would continually increase the zoom value when only empty tracks are added to the timeline ([1273540](https://issuetracker.unity3d.com/product/unity/issues/guid/1273540/)).
|
||||
|
||||
## [1.4.2] - 2020-08-04
|
||||
|
||||
### Fixed
|
||||
- Fixed double-click not opening the AnimationWindow on clips with animated parameters. ([1262950](https://issuetracker.unity3d.com/product/unity/issues/guid/1262950/))
|
||||
- Fixed issue where the Timeline window would rebuild its Playable Graph every time an AnimationClip would be added, changed or deserialized. (1265314, [1267055](https://issuetracker.unity3d.com/product/unity/issues/guid/1267055/))
|
||||
|
||||
## [1.4.1] - 2020-07-15
|
||||
|
||||
### Fixed
|
||||
- Fixed `IndexOutOfRangeException` exception being thrown when editing inspector curves. ([1259902](https://issuetracker.unity3d.com/product/unity/issues/guid/1259902/))
|
||||
- Fixed `IndexOutOfRangeException` exception being thrown when the `New Signal` dialog replaces an existing signal. ([1241170](https://issuetracker.unity3d.com/product/unity/issues/guid/1241170/))
|
||||
- Fixed signal state being reset on paused timelines. ([1257208](https://issuetracker.unity3d.com/product/unity/issues/guid/1257208/))
|
||||
- Fixed nested custom types not updating animation values in the inspector. ([1239893](https://issuetracker.unity3d.com/product/unity/issues/guid/1239893/))
|
||||
- Fixed `AnimationTrack`s SceneOffset mode incorrectly overriding root transform on tracks without root transform in editor. ([1237704](https://issuetracker.unity3d.com/product/unity/issues/guid/1237704/))
|
||||
- The `DisplayName` attribute is now supported when used with `TrackAsset`s. ([1253397](https://issuetracker.unity3d.com/product/unity/issues/guid/1253397/))
|
||||
- Fixed `NullReference` exception being thrown when clicking on the `Scene Preview` checkbox if the Timeline window was closed. (1261543)
|
||||
|
||||
## [1.4.0] - 2020-06-26
|
||||
|
||||
### Added
|
||||
- Added `ClipCaps.AutoScale` to automatically change the speed multiplier value when the clip is trimmed in the Timeline window.
|
||||
- Added a `DeleteClip` method in `TrackAsset`.
|
||||
- Added dependency on Animation, Audio, Director and Particle System modules. ([1229825](https://issuetracker.unity3d.com/product/unity/issues/guid/1229825/))
|
||||
- Added an option in `TimelineAsset.EditorSettings` to disable scene preview.
|
||||
- Added base classes to define custom actions:
|
||||
- `TimelineAction`
|
||||
- `TrackAction`
|
||||
- `ClipAction`
|
||||
- `MarkerAction`
|
||||
- Added the following attributes that can be used with action classes:
|
||||
- `ApplyDefaultUndo` to automatically manage undo operations.
|
||||
- `ActiveInMode` to control in which Timeline mode the action is valid.
|
||||
- `MenuEntry` to add the action to the context menu.
|
||||
- `TimelineShortcut` can be added to a static method to invoke the action with a shortcut.
|
||||
- `Invoker` to invoke actions using Timeline's selection or context.
|
||||
- `MenuOrder` contains menu priority values, to be used with `MenuEntry`.
|
||||
- `TimelineModes` to specify in which mode an action is valid, to be used with `MenuEntry`.
|
||||
- `ActionContext` to provide a context to invoke `TimelineAction`s.
|
||||
- `ActionValidity` to specify is an action is valid for a given context.
|
||||
- `UndoExtension` to manage undo operations with common Timeline types.
|
||||
|
||||
### Changed
|
||||
- Improved performance with ControlTracks in preview mode for cases where multiple Control Tracks are assigned to the same PlayableDirector.
|
||||
- Improved layout and appearance of track header buttons.
|
||||
- Reduced icons' file size without any quality loss.
|
||||
- A track's binding will be duplicated when pasting or duplicating a track.
|
||||
- When creating a new timeline asset, the "Timeline" suffix will not be added to the file name twice.
|
||||
- `ClipCaps.All` now includes the new `Autoscale` feature. To get the previous `ClipCaps.All` behaviour on clips, use
|
||||
```
|
||||
ClipCaps.Looping | ClipCaps.Extrapolation | ClipCaps.ClipIn | ClipCaps.SpeedMultiplier | ClipCaps.Blending
|
||||
```
|
||||
- Inline curve selection is now synced with the clip's selection.
|
||||
- Selecting a curve view property will also select the corresponding curve view.
|
||||
- Clicking and holding the `Command` or `Control` key on a curve view will deselect it if it was already selected.
|
||||
- Improved Timeline window UI performance.
|
||||
|
||||
### Fixed
|
||||
- Selecting clips from locked tracks is not allowed anymore when using the playhead's context menu.
|
||||
- Inserting gaps in locked tracks is not allowed anymore.
|
||||
- When adding an Activation track, the viewport is adjusted to show the new Activation clip.
|
||||
- Fixed issue where trimming AnimationClips would also change the speed multiplier.
|
||||
|
||||
## [1.3.4] - 2020-06-09
|
||||
|
||||
### Fixed
|
||||
- Fix a Control Track bug that caused the first frame of an animation to evaluated incorrectly when scrubbing forwards and backwards. (1253485)
|
||||
- Fixed memory leak where the most recently played timeline would not get unloaded. ([1214752](https://issuetracker.unity3d.com/product/unity/issues/guid/1214752/) and 1253974)
|
||||
|
||||
## [1.3.3] - 2020-05-29
|
||||
|
||||
### Fixed
|
||||
- Fixed regression where animation tracks were writing root motion when the animation clip did not contain root transform values ([1249355](https://issuetracker.unity3d.com/product/unity/issues/guid/1249355/))
|
||||
|
||||
## [1.3.2] - 2020-04-02
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where the clip Inspector's curve preview would close when clicking on the curve. ([1228127](https://issuetracker.unity3d.com/product/unity/issues/guid/1228127/))
|
||||
- Fixed issue where the curves view was not synced between Animation and Timeline windows. ([1213937](https://issuetracker.unity3d.com/issues/animation-window-curves-are-not-updated-immediately-when-changing-them-in-timeline-window))
|
||||
- Fixed issue where play range didn't loop when range ends on the final frame. ([1215926](https://issuetracker.unity3d.com/issues/timeline-play-range-doesnt-loop-when-play-range-ends-on-the-final-frame))
|
||||
- Fixed issue where displaying an array in the curves view generated errors. ([1178251](https://issuetracker.unity3d.com/product/unity/issues/guid/1178251/))
|
||||
|
||||
## [1.3.1] - 2020-03-13
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where the curves view would flicker when editing multiple keys. ([1217326](https://issuetracker.unity3d.com/product/unity/issues/guid/1217326/))
|
||||
- Fixed issue where adding a keyframe in the curves view at the end of a clip would not place the keyframe at the correct position. ([1221337](https://issuetracker.unity3d.com/product/unity/issues/guid/1221337/))
|
||||
|
||||
## [1.3.0] - 2020-02-26
|
||||
|
||||
### Added
|
||||
- Inline Curve Properties can be removed.
|
||||
- Tracks can be individually resized.
|
||||
|
||||
### Changed
|
||||
- Creating a new Timeline will no longer automatically add an Animation Track and an Animator to the target GameObject.
|
||||
- Ease-in and ease-out values for clips are no longer restricted to 50% of the clip's duration.
|
||||
- The resize handle for inline curves has been moved to the track header area.
|
||||
- Reduced the minimum width of the track header area.
|
||||
- Trimming the left edge of a clip while pressing the Shift key will change the Speed Multiplier value.
|
||||
|
||||
### Fixed
|
||||
- Fixed humanoid characters going to default pose during initial root motion recording. (1174752)
|
||||
- Fixed Override Tracks not masking RootTransform when an AvatarMask without the Root Node is applied. ([1190600](https://issuetracker.unity3d.com/product/unity/issues/guid/1190600/))
|
||||
- Fixed preview of Avatar Masks on base level Animation Tracks. ([1190600](https://issuetracker.unity3d.com/product/unity/issues/guid/1190600/))
|
||||
|
||||
## [1.2.13] - 2020-02-24
|
||||
|
||||
### Fixed
|
||||
- Fixed Performance issue where Control Tracks would resimulate during the tail of a non-looping particle clip. ([1216702](https://issuetracker.unity3d.com/product/unity/issues/guid/1216702/))
|
||||
- Fixed adjacent recording clips highlighting the wrong clip. ([1210312](https://issuetracker.unity3d.com/product/unity/issues/guid/1210312/))
|
||||
- Fixed timescale drawing to only draw visible lines which avoids a hang with very large clips. ([1213189](https://issuetracker.unity3d.com/product/unity/issues/guid/1213189/))
|
||||
- Fixed `SignalReceiver.ChangeSignalAtIndex` incorrectly throwing exception when multiple entries are set to null. ([1210877](https://issuetracker.unity3d.com/product/unity/issues/guid/1210877/))
|
||||
- Fixed a memory leak with Animation Clips in Edit mode.
|
||||
- Fixed issue where changes to a Signal Receiver component in a prefab were reverted. ([1210883](https://issuetracker.unity3d.com/product/unity/issues/guid/1210883/))
|
||||
- Fixed avatar mask reassignment not causing immediate re-evaluation. ([1219326](https://issuetracker.unity3d.com/product/unity/issues/guid/1219326/))
|
||||
- Fixed issues related to recursive control tracks. (1178423)
|
||||
- Fixed issue where using the `HideInMenu` attribute in combination with a class inheriting from `Marker` would not hide the marker from the Timeline context menus. ([1221054](https://issuetracker.unity3d.com/product/unity/issues/guid/1221054/))
|
||||
|
||||
## [1.2.12] - 2020-02-21
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where the curves view would change its framing when moving a clip. ([1217353](https://issuetracker.unity3d.com/product/unity/issues/guid/1217353/))
|
||||
|
||||
## [1.2.11] - 2020-01-22
|
||||
|
||||
### Fixed
|
||||
- Fixed Control Track inspector dropdown not opening. ([1208943](https://issuetracker.unity3d.com/product/unity/issues/guid/1208943/))
|
||||
- Fixed issue where applying the Match content command on subtimeline clip with a newly created subtimeline with no duration makes the clip disappear. ([1203662](https://issuetracker.unity3d.com/product/unity/issues/guid/1203662/))
|
||||
- Fixed issue where the opened timeline is changed to another timeline when switching focus from Unity to a different application. ([1087348](https://issuetracker.unity3d.com/product/unity/issues/guid/1087348/))
|
||||
- Fixed issue where the keys in the inline curves view were incorrectly positioned ([1205835](https://issuetracker.unity3d.com/product/unity/issues/guid/1205835/))
|
||||
|
||||
### Changed
|
||||
- ControlPlayableAsset.searchHierarchy (a.k.a. Control Children) now defaults to false.
|
||||
|
||||
## [1.2.10] - 2019-12-08
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where object selectors on tracks did not show bound objects. (1202853)
|
||||
- Fixing inspector blend graph display for animation clips. (1201474)
|
||||
- Fixed Timeline Window lock state when restarting Unity and no timeline are selected. ([1201405](https://issuetracker.unity3d.com/product/unity/issues/guid/1201405/))
|
||||
|
||||
## [1.2.9] - 2019-12-06
|
||||
|
||||
### Fixed
|
||||
- Added missing high-resolution icons for Personal Skin.
|
||||
|
||||
## [1.2.8] - 2019-11-21
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where recording couldn't be turned on for override tracks. (1199389)
|
||||
- Fixed overlay bug when panning. (1198348)
|
||||
- Fixed Foot IK being applied in Editor when option is disabled. ([1197426](https://issuetracker.unity3d.com/product/unity/issues/guid/1197426/))
|
||||
- Fixed issue where the Animation Track's inline curves were not properly aligned when panning the timeline. (1198364)
|
||||
|
||||
## [1.2.7] - 2019-11-15
|
||||
|
||||
### Fixed
|
||||
- Fixed inline curves to display PlayableBehaviour array properties. (1178251)
|
||||
- Fixed clip selection from playhead. (1187495)
|
||||
- Fixed recorded clips dirtying the scene on copy/paste. (1181492)
|
||||
|
||||
## [1.2.6] - 2019-10-25
|
||||
|
||||
### Added
|
||||
- Added Timeline manual.
|
||||
|
||||
## [1.2.5] - 2019-10-16
|
||||
|
||||
### Changed
|
||||
- Added tooltips that were missing for Timeline selector and settings buttons. ([1152790](https://issuetracker.unity3d.com/product/unity/issues/guid/1152790/))
|
||||
- Removed Undo menu entry that was added when clicking on the Inline curves button. ([1187402](https://issuetracker.unity3d.com/product/unity/issues/guid/1187402/))
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where recording couldn't be turned off when an object is deactivated. (1187174)
|
||||
- Timelines listed in the Timeline selector will now be sorted alphabetically. (1190514)
|
||||
- Fixed Insert Frames options from Trackhead context menu not applying to markers. (1187895)
|
||||
- Fixed incorrect display when a large number of nested group tracks was added to a Timeline. (1157367)
|
||||
|
||||
## [1.2.4] - 2019-10-03
|
||||
|
||||
### Changed
|
||||
- Properties in the Inline Curve editor will now be listed in the same order as the Animation window. (1184058)
|
||||
- Updated the appearance of the Timeline window to conform to the [editor's UX redesign](https://blogs.unity3d.com/2019/08/29/evolving-the-unity-editor-ux/)
|
||||
- Improved the appearance of clip blends.
|
||||
|
||||
### Fixed
|
||||
- Adding a PlayableDirector with no Playable Asset will no longer trigger a repaint of the Timeline Window on each frame. ([1172707](https://issuetracker.unity3d.com/product/unity/issues/guid/1172707/))
|
||||
- Fixed issue where a clip's blend selection border was not drawn correctly when there was a previous clip. (1178173)
|
||||
- Fixed issue where Animation Events were fired twice when the Playable Director Wrap mode is set to Loop. ([1173281](https://issuetracker.unity3d.com/product/unity/issues/guid/1173281/))
|
||||
- Fixed issue where double-clicking on a Timeline Asset would not open it in the Timeline window. ([1182159](https://issuetracker.unity3d.com/product/unity/issues/guid/1182159))
|
||||
- Fixed issue where the paste shortcut would not work when copying and pasting between two different timelines. (1184967)
|
||||
- Fixed audio stutter when going into playmode. ([1167289](https://issuetracker.unity3d.com/product/unity/issues/guid/1167289/))
|
||||
- Fixed PreviousFrame and NextFrame controls in subtimelines with large offsets. (1175320)
|
||||
- Fixed issue where exceptions were thrown when resetting a Signal Receiver component. ([1158227](https://issuetracker.unity3d.com/product/unity/issues/guid/1158227/))
|
||||
- Increased font size of clip labels (1179642)
|
||||
|
||||
## [1.2.3] - 2019-10-03
|
||||
|
||||
### Fixed
|
||||
- Removed unnecessary directories from the package.
|
||||
|
||||
## [1.2.2] - 2019-08-20
|
||||
|
||||
### Fixed
|
||||
- Fixed issue where fields for custom clips were not responding to Add Key commands. (1174416)
|
||||
- Fixed issue where a different track's bound GameObject is highlighted when clicking a track's bound GameObject box. (1141836)
|
||||
- Fixed issue where a clip locks to the playhead's position when moving it. (1157280)
|
||||
|
||||
## [1.2.1] - 2019-08-01
|
||||
|
||||
### Fixed
|
||||
- Fixed appearance of a selected clip's border.
|
||||
- Fixed non-transform properties from AnimationClips not being correctly put into preview mode when the avatar root does not contain the animator component. ([1162334](https://issuetracker.unity3d.com/product/unity/issues/guid/1162334/))
|
||||
- Fixed an issue where the context menu for inline curves keys would not open on MacOS. ([1158584](https://issuetracker.unity3d.com/product/unity/issues/guid/1158584/))
|
||||
- Fixed recording state being incorrect after toggling preview mode (1146551)
|
||||
- Fixed copying clips without ExposedReferences causing the scene to dirty (1144469)
|
||||
|
||||
## [1.2.0] - 2019-07-16
|
||||
*Compatible with Unity 2019.3*
|
||||
|
||||
### Added
|
||||
- Added ILayerable interface. Implementing this interface on a custom track will enable support for multiple layers, similar to the AnimationTracks override tracks.
|
||||
- Added "Pan" autoscrolling option in the Timeline window.
|
||||
- Enabled rectangle tool for inline curves.
|
||||
|
||||
### Changed
|
||||
- Scrolling horizontally with the mouse wheel or trackpad now pans the timeline view horizontally, instead of zooming.
|
||||
- Scrolling vertically with the mouse wheel or trackpad on the track headers or on the vertical scroll bar now pans the timeline view vertically, instead of zooming.
|
||||
|
||||
### Fixed
|
||||
- Fixed an issue causing info text to overlap when displaying multiple lines (1150863).
|
||||
- Fixed duration mode not reverting from "Fixed Length" to "Based On Clips" properly. (1154034)
|
||||
- Fixed playrange markers being drawn over horizontal scrollbar (1156023)
|
||||
- Fixed an issue where a hotkey does not autofit all when Marker is present (1158704)
|
||||
- Fixed an issue where an exception was thrown when overwriting a Signal Asset through the Signal Emitter inspector. (1152202)
|
||||
- Fixed Control Tracks not updating instances when source prefab change. (case 1158592)
|
||||
- An exception will be thrown when calling TrackAsset.CreateMarker() with a marker that implements INotification if the track does not support notifications. (1150248)
|
||||
- Fixed preview mode being reenabled when warnings change on tracks. (case 1151381)
|
||||
- Fixed minimum clip duration to be frame aligned. (case 1156602)
|
||||
- Fixed playhead being moved when applying undo while recording.(case 1154802)
|
||||
- Fixed warnings about localEulerAnglesRaw when using RectTransform. (case 1151100)
|
||||
- Fixed precision error on the duration of infinite tracks. (case 1156723)
|
||||
- Fixed issue where two GatherProperties call were made when switching between two PlayableDirectors. (1159036)
|
||||
- Fixed issue where inspectors for clips, tracks and markers would get incorrectly displayed when no Timeline Window is opened. (1158242, 1158283)
|
||||
- Fixed issue with clip connectors that were incorrectly drawn when the timeline was panned or zoomed. (1141960)
|
||||
- Fixed issue where evaluating a Playable Graph inside a Notification Receiver would cause an infinite recursion. ([1149930](https://issuetracker.unity3d.com/product/unity/issues/guid/1149930/))
|
||||
- Fixed Trim and Move operations to ensure playable duration is updated upon completion. ([1151894](https://issuetracker.unity3d.com/product/unity/issues/guid/1151894/))
|
||||
- Fixed options menu icon that was blurry on high-dpi screens. (1154623)
|
||||
- Track binding field is now larger. (1153446)
|
||||
- Fixed issue where an empty Timeline window would create new objects on each repaint. (1142894)
|
||||
- Fixed an issue causing info text to overlap when displaying multiple lines (when trimming + time scaling, for example). (1150863)
|
||||
- Fixed duration mode not reverting from "Fixed Length" to "Based On Clips" properly. ([1154034](https://issuetracker.unity3d.com/product/unity/issues/guid/1154034/))
|
||||
- Prevented the PlayableGraph from being created twice when playing a timeline in play mode with the Timeline window opened. (1147247)
|
||||
- Fixed issue where an exception was thrown when clicking on a SignalEmitter with the Timeline window in asset mode. (1146261)
|
||||
- A timeline will now be played correctly when building a player with Mono and Managed Stripping Level set higher than Low. ([1133182](https://issuetracker.unity3d.com/product/unity/issues/guid/1133182/))
|
||||
- The Signal Asset creation dialog will no longer throw exceptions when canceled on macOS. ([1141959](https://issuetracker.unity3d.com/product/unity/issues/guid/1141959/))
|
||||
- Fixed issue where the Emit Signal property on a Signal Emitter would not get saved correctly. ([1148709](https://issuetracker.unity3d.com/product/unity/issues/guid/1148709/))
|
||||
- Fixed issue where a Signal Emitter placed at the start of a timeline would be fired twice. ([1149653](https://issuetracker.unity3d.com/product/unity/issues/guid/1149653/))
|
||||
- Fixed record button state not updating when offset modes are changed. ([1142747](https://issuetracker.unity3d.com/product/unity/issues/guid/1142747/))
|
||||
- Cleared invalid assets from the Timeline Clipboard when going into or out of PlayMode. (1144473)
|
||||
- Copying a Control Clip during play mode no longer throws exceptions. (1141581)
|
||||
- Going to Play Mode while inspecting a Track Asset will no longer throw exceptions. (1141958)
|
||||
- Resizing Timeline's window no longer affects the zoom value. ([1147150](https://issuetracker.unity3d.com/product/unity/issues/guid/1147150/))
|
||||
- Snap relaxing now responds to Command on Mac, instead of Control. (1149144)
|
||||
- Clips will no longer randomly disappear when showing or hiding inline curves. (1141661)
|
||||
- The global/local time referential button will no longer be shown for a top-level timeline. (1080872)
|
||||
- Playhead will not be drawn above the bottom scrollbar anymore. (1134016)
|
||||
- Fixed moving a marker on an Infinite Track will keep the track in infinite mode (1141190)
|
||||
- Fixed zooming in/out will keep the padding at the beginning of the timeline (1030689)
|
||||
- Fixed marker UI is the same color and size on infinite track (1139370)
|
||||
- Fixed Disable the possibility to add Markers to tracks of a Timeline that is ReadOnly (1134463)
|
||||
- Fixed wrong context menu being shown when right-clicking a marker (1133592)
|
||||
- Fixed creation of override track to work with multiselection (1133592)
|
||||
|
||||
## [1.1.0] - 2019-02-14
|
||||
*Compatible with Unity 2019.2*
|
||||
### Added
|
||||
- ClipEditor, TrackEditor and MarkerEditor classes users can derive from to control visual appearance of custom timeline clips, tracks and markers using the CustomTimelineEditor attribute.
|
||||
- ClipEditor.GetSubTimelines to allow user created clips that support sub-timelines in editor
|
||||
- TimelineEditor.selectedClip and TimelineEditor.selectedClips to set and retrieve the currently selected timeline clips
|
||||
- IPropertyCollector.AddFromName override that takes a component.
|
||||
- Warning icons to SignalEmitters when they do not reference an asset
|
||||
- Ability to mute/unmute a Group Track.
|
||||
- Mute/Unmute only selected track command added for tracks with multiple layers.
|
||||
- Animate-able Properties on Tracks and Clips can now be edited through inline curves.
|
||||
- Added loop override on AnimationTrack clips (1140766)
|
||||
- ReadOnly/Source Control Lock support for Timeline Scene
|
||||
|
||||
### Changed
|
||||
- Control Track display to show a particle system icon when particle systems are being controlled
|
||||
- Animate-able Properties for clips are no longer edited using by "recording"; they are edited through the inline curves just like tracks.
|
||||
- AudioTrack properties can now be animated through inline curves.
|
||||
- Changed Marker show/hide to be undoable. Hide will also unselect markers. (1124661)
|
||||
- Changed SignalReceivers show their enabled state in the inspector. (1131163)
|
||||
- Changed Track Context Menu to show "Add Signal Emitter" at the top of the list of Marker commands. (1131166)
|
||||
- Moved "Add Signal Emitter" and "Add Signal Emitter From Asset" commands out of their sub-menu. (1131166)
|
||||
|
||||
### Fixed
|
||||
- Fixed markers being drawn outside their pane. (1124381)
|
||||
- Fixed non-public tracks not being recognized by the Timeline Editor. (1122803)
|
||||
- Fixed keyboard shortcuts for _Frame All_ (default: A) and _Frame Selected_ (default: F) to also apply horizontally ([1126623](https://issuetracker.unity3d.com/product/unity/issues/guid/1126623/))
|
||||
- Fixed recording getting disabled when selecting a different GameObject while the Timeline Window is not locked. (1123119)
|
||||
- Fixed time sync between Animation and Timeline windows when clips have non-default timescale or clip-in values. ([930909](https://issuetracker.unity3d.com/product/unity/issues/guid/930909/))
|
||||
- Fixed animation window link not releasing when deleting the timeline asset. (1127425)
|
||||
- Fixed an exception being raised when selecting both a Track marker and a Timeline marker at the same time. ([1113006](https://issuetracker.unity3d.com/product/unity/issues/guid/1113006/))
|
||||
- Fixed the header marker area will so it no longer opens its context menu if it's hidden. (1124351)
|
||||
- Fixed Signal emitters to show the Signals list when created on override tracks. (1102913)
|
||||
- Fixed a crash on IL2CPP platforms when the VideoPlayer component is not used. (1129572)
|
||||
- Fixed Timeline Duration changes in editor not being undoable. (1109279)
|
||||
- Fixed _Match Offsets_ commands causing improper animation defaults to be applied. (911678)
|
||||
- Fixed Timeline Inspectors leaving _EditorGUI.showMixedValue_ in the wrong state. ([1123895](https://issuetracker.unity3d.com/product/unity/issues/guid/1123895/))
|
||||
- Fixed issue where performing undo after moving items on multiple tracks would not undo some items. (1131071)
|
||||
- Fixed cog icon in the Signal Receiver inspector being blurry. (1130320)
|
||||
- Fixed Timeline marker track hamburger icon not being centered vertically. (1131112)
|
||||
- Fixed detection of signal receivers when track is in a group. (1131811)
|
||||
- Fixed exception being thrown when deleting Signal entries. (1131065)
|
||||
- Fixed Markers blocking against Clips when moving both Clips and Markers in Ripple mode. (1102594)
|
||||
- Fixed NullReferenceException being thrown when muting an empty marker track. (1131106)
|
||||
- Fixed SignalEmitter Inspector losing the Receiver UI when it is locked and another object is selected. (1116041)
|
||||
- Fixed Marker and Clip appearing to be allowed to move to another track in Ripple mode. (1131123)
|
||||
- Fixed issue where the Signal Emitter inspector did not show the Signal Receiver UI when placed on the timeline marker track. (1131811)
|
||||
- Fixed Replace mode not drawing clips when moved together with a Marker. (1132605)
|
||||
- Fixed inline curves to retain their state when performing undo/redo or keying from the inspector. ([1125443](https://issuetracker.unity3d.com/product/unity/issues/guid/1125443))
|
||||
- Fixed an issue preventing Timeline from entering preview mode when an Audio Track is present an a full assembly reload is performed. (1132243)
|
||||
- Fixed an issue where the Marker context menu would show a superfluous line at the bottom. (1132662)
|
||||
- Fixed an issue preventing Timeline asset to be removed from a locked Timeline Window when a new scene is loaded. (1135073)
|
||||
- Fixed EaseIn/Out shortcut for clips
|
||||
|
||||
## [1.0.0] - 2019-01-28
|
||||
*Compatible with Unity 2019.1*
|
||||
### Added
|
||||
- This is the first release of Timeline, as a Package
|
||||
- Added API calls to access all AnimationClips used by Timeline.
|
||||
- Added support in the runtime API to Animate Properties used by template-style PlayableBehaviours used as Mixers.
|
||||
- Added Markers. Markers are abstract types that represent a single point in time.
|
||||
- Added Signal Emitters and Signal Assets. Signal Emitters are markers that send a notification, indicated by a SignalAsset, to a GameObject indicating an event has occurred during playback of the Timeline.
|
||||
- Added Signal Receiver Components. Signal Receivers are MonoBehaviour that listen for Signals from Timeline and respond by invoking UnityEvents.
|
||||
- Added Signal Tracks. Signal Tracks are Timeline Tracks that are used only for Signal Emitters.
|
||||
|
||||
### Fixed
|
||||
- Signal Receiver will no longer throw exceptions when its inspector is locked ([1114526](https://issuetracker.unity3d.com/product/unity/issues/guid/1114526/))
|
||||
- Context menu operations will now be applied on all selected tracks (1089820)
|
||||
- Clip edit mode clutch keys will not get stuck when holding multiple keys at the same time (1097216)
|
||||
- Marker inspector will be disabled when the marker is collapsed (1102860)
|
||||
- Clip inspector will no longer throw exceptions when changing values when the inspector is locked (1115984)
|
||||
- Fixed appearance of muted tracks (1018643)
|
||||
- Fixed multiple issues where clips and markers were selectable when located under the time ruler and the marker header track ([1117925](https://issuetracker.unity3d.com/product/unity/issues/guid/1117925/), 1102598)
|
||||
- A marker aligned with the edge of a clip is now easier to select (1102591)
|
||||
- Changed behaviour of the Timeline Window to apply modifications immediately during Playmode ([922846](https://issuetracker.unity3d.com/product/unity/issues/guid/922846/), 1111908)
|
||||
- PlayableDirector.played event is now called after entering or exiting Playmode ([1088918](https://issuetracker.unity3d.com/product/unity/issues/guid/1088918/))
|
||||
- Undoing a paste track operation in a group will no longer corrupt the timeline (1116052)
|
||||
- The correct context menu will now be displayed on the marker header track (1120857)
|
||||
- Fixed an issue where a circular reference warning appeared in the Control Clip inspector even if there was no circular reference (1116520)
|
||||
- Fixed preview mode when animation clips with root curves are used (case 1116297, case 1116007)
|
||||
- Added option to disable foot IK on animation playable assets (case 1115652)
|
||||
- Fixed unevaluated animation tracks causing default pose (case 1109118)
|
||||
- Fixed drawing of Group Tracks when header is off-screen (case 876340)
|
||||
- Fixed drag and drop of objects inside a group being inserted outside (case 1011381, case 1014774)
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4a0757ee0236f39489520769ae710288
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5486badfefef3eb47b45d071d232469d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace DocCodeExamples
|
||||
{
|
||||
class ActionExamples_HideAPI
|
||||
{
|
||||
#region declare-sampleClipAction
|
||||
|
||||
[MenuEntry("Custom Actions/Sample clip Action")]
|
||||
public class SampleClipAction : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clip)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> items)
|
||||
{
|
||||
Debug.Log("Test Action");
|
||||
return true;
|
||||
}
|
||||
|
||||
[TimelineShortcut("SampleClipAction", KeyCode.K)]
|
||||
public static void HandleShortCut(ShortcutArguments args)
|
||||
{
|
||||
Invoker.InvokeWithSelectedClips<SampleClipAction>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-sampleMarkerAction
|
||||
|
||||
[MenuEntry("Custom Actions/Sample marker Action")]
|
||||
public class SampleMarkerAction : MarkerAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<IMarker> markers)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<IMarker> items)
|
||||
{
|
||||
Debug.Log("Test Action");
|
||||
return true;
|
||||
}
|
||||
|
||||
[TimelineShortcut("SampleMarkerAction", KeyCode.L)]
|
||||
public static void HandleShortCut(ShortcutArguments args)
|
||||
{
|
||||
Invoker.InvokeWithSelectedMarkers<SampleMarkerAction>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-sampleTrackAction
|
||||
|
||||
[MenuEntry("Custom Actions/Sample track Action")]
|
||||
public class SampleTrackAction : TrackAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
Debug.Log("Test Action");
|
||||
return true;
|
||||
}
|
||||
|
||||
[TimelineShortcut("SampleTrackAction", KeyCode.H)]
|
||||
public static void HandleShortCut(ShortcutArguments args)
|
||||
{
|
||||
Invoker.InvokeWithSelectedTracks<SampleTrackAction>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-sampleTimelineAction
|
||||
|
||||
[MenuEntry("Custom Actions/Sample Timeline Action")]
|
||||
public class SampleTimelineAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext context)
|
||||
{
|
||||
Debug.Log("Test Action");
|
||||
return true;
|
||||
}
|
||||
|
||||
[TimelineShortcut("SampleTimelineAction", KeyCode.Q)]
|
||||
public static void HandleShortCut(ShortcutArguments args)
|
||||
{
|
||||
Invoker.InvokeWithSelected<SampleTimelineAction>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 617e6579183b7cc488aae748fe3f88bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "DocCodeExamples",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:f06555f75b070af458a003d92f9efb00",
|
||||
"GUID:02f771204943f4a40949438e873e3eff"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9550f6b9c1ac87345a996c43f204fb30
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DocCodeExamples
|
||||
{
|
||||
class MarkerEditorExamples
|
||||
{
|
||||
void MarkerRegionExample(MarkerOverlayRegion region)
|
||||
{
|
||||
#region declare-trackRegion
|
||||
|
||||
GUI.BeginClip(region.trackRegion, -region.trackRegion.min, Vector2.zero, false);
|
||||
EditorGUI.DrawRect(region.markerRegion, Color.blue);
|
||||
GUI.EndClip();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ebaa79c245e369449a3565c4b9c3e703
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,132 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace DocCodeExamples
|
||||
{
|
||||
class TimelineAttributesExamples_HideAPI
|
||||
{
|
||||
#region declare-sampleTrackBindingAttr
|
||||
|
||||
[TrackBindingType(typeof(Light), TrackBindingFlags.AllowCreateComponent)]
|
||||
public class LightTrack : TrackAsset {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-menuEntryAttribute
|
||||
|
||||
[MenuEntry("Simple Menu Action")]
|
||||
class SimpleMenuAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext actionContext)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Menu Action with priority", 9999)]
|
||||
class MenuActionWithPriority : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext actionContext)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("My Menu/Menu Action inside submenu")]
|
||||
class MenuActionInsideSubMenu : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext actionContext)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-timelineShortcutAttr
|
||||
|
||||
public class ShortcutAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext _)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext _)
|
||||
{
|
||||
Debug.Log("Action executed.");
|
||||
return true;
|
||||
}
|
||||
|
||||
[TimelineShortcut("Test Action", KeyCode.K, ShortcutModifiers.Shift | ShortcutModifiers.Alt)]
|
||||
public static void HandleShortCut(ShortcutArguments args)
|
||||
{
|
||||
Invoker.InvokeWithSelected<ShortcutAction>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-applyDefaultUndoAttr
|
||||
|
||||
[ApplyDefaultUndo]
|
||||
public class SetNameToTypeAction : TrackAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> items)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> items)
|
||||
{
|
||||
foreach (TrackAsset track in items)
|
||||
track.name = track.GetType().Name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-customStyleMarkerAttr
|
||||
|
||||
[CustomStyle("MyStyle")]
|
||||
public class MyMarker : UnityEngine.Timeline.Marker {}
|
||||
|
||||
#endregion
|
||||
|
||||
#region declare-customTimelineEditorAttr
|
||||
|
||||
[CustomTimelineEditor(typeof(MyCustomClip))]
|
||||
class MyCustomClipEditor : ClipEditor {}
|
||||
|
||||
#endregion
|
||||
|
||||
class MyCustomClip : PlayableAsset
|
||||
{
|
||||
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
|
||||
{
|
||||
return Playable.Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5724bf63aa724f5586275b3153a02a01
|
||||
timeCreated: 1600894640
|
|
@ -0,0 +1,16 @@
|
|||
using UnityEditor.Timeline;
|
||||
|
||||
namespace DocCodeExamples
|
||||
{
|
||||
class TimelineEditorExamples_HideAPI
|
||||
{
|
||||
void RefreshReasonExample()
|
||||
{
|
||||
#region declare-refreshReason
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified | RefreshReason.SceneNeedsUpdate);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6cd9f16fc29247c8af731c6b3b0a990f
|
||||
timeCreated: 1600965115
|
|
@ -0,0 +1,17 @@
|
|||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace DocCodeExamples
|
||||
{
|
||||
class TrackAssetExamples_HideAPI
|
||||
{
|
||||
#region declare-trackAssetExample
|
||||
|
||||
[TrackColor(1, 0, 0)]
|
||||
[TrackBindingType(typeof(Animator))]
|
||||
[TrackClipType(typeof(AnimationClip))]
|
||||
public class CustomAnimationTrack : TrackAsset {}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9f0489deaf04d0fbcbe45fd50a018f6
|
||||
timeCreated: 1600894170
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9b4f21acd14fdd445b37b76f6587539e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8620e97e7e9859049934889a52248435
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,63 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Action context to be used by actions.
|
||||
/// </summary>
|
||||
/// <seealso cref="Invoker"/>
|
||||
/// <seealso cref="TimelineAction"/>
|
||||
public struct ActionContext
|
||||
{
|
||||
IEnumerable<TrackAsset> m_Tracks;
|
||||
IEnumerable<TimelineClip> m_Clips;
|
||||
IEnumerable<IMarker> m_Markers;
|
||||
|
||||
/// <summary>
|
||||
/// The Timeline asset that is currently opened in the Timeline window.
|
||||
/// </summary>
|
||||
public TimelineAsset timeline;
|
||||
|
||||
/// <summary>
|
||||
/// The PlayableDirector that is used to play the current Timeline asset.
|
||||
/// </summary>
|
||||
public PlayableDirector director;
|
||||
|
||||
/// <summary>
|
||||
/// Time based on the position of the cursor on the timeline (in seconds).
|
||||
/// null if the time is not available (in case of a shortcut for example).
|
||||
/// </summary>
|
||||
public double? invocationTime;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks that will be used by the actions.
|
||||
/// </summary>
|
||||
public IEnumerable<TrackAsset> tracks
|
||||
{
|
||||
get => m_Tracks ?? Enumerable.Empty<TrackAsset>();
|
||||
set => m_Tracks = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clips that will be used by the actions.
|
||||
/// </summary>
|
||||
public IEnumerable<TimelineClip> clips
|
||||
{
|
||||
get => m_Clips ?? Enumerable.Empty<TimelineClip>();
|
||||
set => m_Clips = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Markers that will be used by the actions.
|
||||
/// </summary>
|
||||
public IEnumerable<IMarker> markers
|
||||
{
|
||||
get => m_Markers ?? Enumerable.Empty<IMarker>();
|
||||
set => m_Markers = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e79d50dea5c9c74d9297cce6cd6d053
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,306 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
static class ActionManager
|
||||
{
|
||||
static bool s_ShowActionTriggeredByShortcut = false;
|
||||
|
||||
public static readonly IReadOnlyList<TimelineAction> TimelineActions = InstantiateClassesOfType<TimelineAction>();
|
||||
public static readonly IReadOnlyList<ClipAction> ClipActions = InstantiateClassesOfType<ClipAction>();
|
||||
public static readonly IReadOnlyList<TrackAction> TrackActions = InstantiateClassesOfType<TrackAction>();
|
||||
public static readonly IReadOnlyList<MarkerAction> MarkerActions = InstantiateClassesOfType<MarkerAction>();
|
||||
|
||||
public static readonly IReadOnlyList<TimelineAction> TimelineActionsWithShortcuts = ActionsWithShortCuts(TimelineActions);
|
||||
public static readonly IReadOnlyList<ClipAction> ClipActionsWithShortcuts = ActionsWithShortCuts(ClipActions);
|
||||
public static readonly IReadOnlyList<TrackAction> TrackActionsWithShortcuts = ActionsWithShortCuts(TrackActions);
|
||||
public static readonly IReadOnlyList<MarkerAction> MarkerActionsWithShortcuts = ActionsWithShortCuts(MarkerActions);
|
||||
|
||||
public static readonly HashSet<Type> ActionsWithAutoUndo = TypesWithAttribute<ApplyDefaultUndoAttribute>();
|
||||
|
||||
public static TU GetCachedAction<T, TU>(this IReadOnlyList<TU> list) where T : TU
|
||||
{
|
||||
return list.FirstOrDefault(x => x.GetType() == typeof(T));
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(IReadOnlyList<TimelineAction> actions, Vector2? mousePos, List<MenuActionItem> menuItems)
|
||||
{
|
||||
var globalContext = TimelineEditor.CurrentContext(mousePos);
|
||||
foreach (var action in actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildMenu(action, globalContext, menuItems);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(IReadOnlyList<TrackAction> actions, List<MenuActionItem> menuItems)
|
||||
{
|
||||
var tracks = SelectionManager.SelectedTracks();
|
||||
if (!tracks.Any())
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildMenu(action, tracks, menuItems);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(IReadOnlyList<ClipAction> actions, List<MenuActionItem> menuItems)
|
||||
{
|
||||
var clips = SelectionManager.SelectedClips();
|
||||
bool any = clips.Any();
|
||||
if (!clips.Any())
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (action is EditSubTimeline editSubTimelineAction)
|
||||
editSubTimelineAction.AddMenuItem(menuItems);
|
||||
else if (any)
|
||||
BuildMenu(action, clips, menuItems);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetMenuEntries(IReadOnlyList<MarkerAction> actions, List<MenuActionItem> menuItems)
|
||||
{
|
||||
var markers = SelectionManager.SelectedMarkers();
|
||||
if (!markers.Any())
|
||||
return;
|
||||
|
||||
foreach (var action in actions)
|
||||
{
|
||||
try
|
||||
{
|
||||
BuildMenu(action, markers, menuItems);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void BuildMenu(TimelineAction action, ActionContext context, List<MenuActionItem> menuItems)
|
||||
{
|
||||
BuildMenu(action, action.Validate(context), () => ExecuteTimelineAction(action, context), menuItems);
|
||||
}
|
||||
|
||||
static void BuildMenu(TrackAction action, IEnumerable<TrackAsset> tracks, List<MenuActionItem> menuItems)
|
||||
{
|
||||
BuildMenu(action, action.Validate(tracks), () => ExecuteTrackAction(action, tracks), menuItems);
|
||||
}
|
||||
|
||||
static void BuildMenu(ClipAction action, IEnumerable<TimelineClip> clips, List<MenuActionItem> menuItems)
|
||||
{
|
||||
BuildMenu(action, action.Validate(clips), () => ExecuteClipAction(action, clips), menuItems);
|
||||
}
|
||||
|
||||
static void BuildMenu(MarkerAction action, IEnumerable<IMarker> markers, List<MenuActionItem> menuItems)
|
||||
{
|
||||
BuildMenu(action, action.Validate(markers), () => ExecuteMarkerAction(action, markers), menuItems);
|
||||
}
|
||||
|
||||
static void BuildMenu(IAction action, ActionValidity validity, GenericMenu.MenuFunction executeFunction, List<MenuActionItem> menuItems)
|
||||
{
|
||||
var menuAttribute = action.GetType().GetCustomAttribute<MenuEntryAttribute>(false);
|
||||
if (menuAttribute == null)
|
||||
return;
|
||||
|
||||
if (validity == ActionValidity.NotApplicable)
|
||||
return;
|
||||
|
||||
var menuActionItem = new MenuActionItem
|
||||
{
|
||||
state = validity,
|
||||
entryName = action.GetMenuEntryName(),
|
||||
priority = menuAttribute.priority,
|
||||
category = menuAttribute.subMenuPath,
|
||||
isActiveInMode = action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode),
|
||||
shortCut = action.GetShortcut(),
|
||||
callback = executeFunction,
|
||||
isChecked = action.IsChecked()
|
||||
};
|
||||
menuItems.Add(menuActionItem);
|
||||
}
|
||||
|
||||
internal 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 == ActionValidity.NotApplicable)
|
||||
continue;
|
||||
|
||||
var priority = s.priority;
|
||||
if (lastPriority != int.MinValue && priority / MenuPriority.separatorAt > lastPriority / MenuPriority.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 == ActionValidity.Valid && s.isActiveInMode)
|
||||
menu.AddItem(new GUIContent(entry), s.isChecked, s.callback);
|
||||
else
|
||||
menu.AddDisabledItem(new GUIContent(entry), s.isChecked);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HandleShortcut(Event evt)
|
||||
{
|
||||
if (EditorGUI.IsEditingTextField())
|
||||
return false;
|
||||
|
||||
return HandleShortcut(evt, TimelineActionsWithShortcuts, (x) => ExecuteTimelineAction(x, TimelineEditor.CurrentContext())) ||
|
||||
HandleShortcut(evt, ClipActionsWithShortcuts, (x => ExecuteClipAction(x, SelectionManager.SelectedClips()))) ||
|
||||
HandleShortcut(evt, TrackActionsWithShortcuts, (x => ExecuteTrackAction(x, SelectionManager.SelectedTracks()))) ||
|
||||
HandleShortcut(evt, MarkerActionsWithShortcuts, (x => ExecuteMarkerAction(x, SelectionManager.SelectedMarkers())));
|
||||
}
|
||||
|
||||
public static bool HandleShortcut<T>(Event evt, IReadOnlyList<T> actions, Func<T, bool> invoke) where T : class, IAction
|
||||
{
|
||||
for (int i = 0; i < actions.Count; i++)
|
||||
{
|
||||
var action = actions[i];
|
||||
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 (!action.IsActionActiveInMode(TimelineWindow.instance.currentMode.mode))
|
||||
continue;
|
||||
|
||||
if (invoke(action))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ExecuteTimelineAction(TimelineAction timelineAction, ActionContext context)
|
||||
{
|
||||
if (timelineAction.Validate(context) == ActionValidity.Valid)
|
||||
{
|
||||
if (timelineAction.HasAutoUndo())
|
||||
UndoExtensions.RegisterContext(context, timelineAction.GetUndoName());
|
||||
return timelineAction.Execute(context);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ExecuteTrackAction(TrackAction trackAction, IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks != null && tracks.Any() && trackAction.Validate(tracks) == ActionValidity.Valid)
|
||||
{
|
||||
if (trackAction.HasAutoUndo())
|
||||
UndoExtensions.RegisterTracks(tracks, trackAction.GetUndoName());
|
||||
return trackAction.Execute(tracks);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ExecuteClipAction(ClipAction clipAction, IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips != null && clips.Any() && clipAction.Validate(clips) == ActionValidity.Valid)
|
||||
{
|
||||
if (clipAction.HasAutoUndo())
|
||||
UndoExtensions.RegisterClips(clips, clipAction.GetUndoName());
|
||||
return clipAction.Execute(clips);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool ExecuteMarkerAction(MarkerAction markerAction, IEnumerable<IMarker> markers)
|
||||
{
|
||||
if (markers != null && markers.Any() && markerAction.Validate(markers) == ActionValidity.Valid)
|
||||
{
|
||||
if (markerAction.HasAutoUndo())
|
||||
UndoExtensions.RegisterMarkers(markers, markerAction.GetUndoName());
|
||||
return markerAction.Execute(markers);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static List<T> InstantiateClassesOfType<T>() where T : class
|
||||
{
|
||||
var typeCollection = TypeCache.GetTypesDerivedFrom(typeof(T));
|
||||
var list = new List<T>(typeCollection.Count);
|
||||
for (int i = 0; i < typeCollection.Count; i++)
|
||||
{
|
||||
if (typeCollection[i].IsAbstract || typeCollection[i].IsGenericType)
|
||||
continue;
|
||||
|
||||
if (typeCollection[i].GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Debug.LogWarning($"{typeCollection[i].FullName} requires a default constructor to be automatically instantiated by Timeline");
|
||||
continue;
|
||||
}
|
||||
|
||||
list.Add((T)Activator.CreateInstance(typeCollection[i]));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static List<T> ActionsWithShortCuts<T>(IReadOnlyList<T> list)
|
||||
{
|
||||
return list.Where(x => x.GetType().GetCustomAttributes(typeof(ShortcutAttribute), true).Length > 0).ToList();
|
||||
}
|
||||
|
||||
static HashSet<System.Type> TypesWithAttribute<T>() where T : Attribute
|
||||
{
|
||||
var hashSet = new HashSet<System.Type>();
|
||||
var typeCollection = TypeCache.GetTypesWithAttribute(typeof(T));
|
||||
for (int i = 0; i < typeCollection.Count; i++)
|
||||
{
|
||||
hashSet.Add(typeCollection[i]);
|
||||
}
|
||||
|
||||
return hashSet;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b185b14d428805c46aceb7c663825b2d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a clip action.
|
||||
/// Inherit from this class to make an action that would react on selected clips after a menu click and/or a key shortcut.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Simple Clip Action example (with context menu and shortcut support).
|
||||
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleClipAction" title="SampleClipAction"/>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
|
||||
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
|
||||
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
|
||||
/// </remarks>
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
public abstract class ClipAction : IAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the action based on clips.
|
||||
/// </summary>
|
||||
/// <param name="clips">clips that the action will act on.</param>
|
||||
/// <returns>Returns true if the action has been correctly executed, false otherwise.</returns>
|
||||
public abstract bool Execute(IEnumerable<TimelineClip> clips);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the validity of an Action for a given set of clips.
|
||||
/// </summary>
|
||||
/// <param name="clips">The clips that the action will act on.</param>
|
||||
/// <returns>The validity of the set of clips.</returns>
|
||||
public abstract ActionValidity Validate(IEnumerable<TimelineClip> clips);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fa130a47ff26ad84f867961fc32334af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,392 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Edit in Animation Window", MenuPriority.ClipEditActionSection.editInAnimationWindow), UsedImplicitly]
|
||||
class EditClipInAnimationWindow : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!GetEditableClip(clips, out _, out _))
|
||||
return ActionValidity.NotApplicable;
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
TimelineClip clip;
|
||||
AnimationClip clipToEdit;
|
||||
if (!GetEditableClip(clips, out clip, out clipToEdit))
|
||||
return false;
|
||||
|
||||
GameObject gameObject = null;
|
||||
if (TimelineEditor.inspectedDirector != null)
|
||||
gameObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack());
|
||||
|
||||
var timeController = TimelineAnimationUtilities.CreateTimeController(clip);
|
||||
TimelineAnimationUtilities.EditAnimationClipWithTimeController(
|
||||
clipToEdit, timeController, clip.animationClip != null ? gameObject : null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetEditableClip(IEnumerable<TimelineClip> clips, out TimelineClip clip, out AnimationClip animClip)
|
||||
{
|
||||
clip = null;
|
||||
animClip = null;
|
||||
|
||||
if (clips.Count() != 1)
|
||||
return false;
|
||||
|
||||
clip = clips.FirstOrDefault();
|
||||
if (clip == null)
|
||||
return false;
|
||||
|
||||
if (clip.animationClip != null)
|
||||
animClip = clip.animationClip;
|
||||
else if (clip.curves != null && !clip.curves.empty)
|
||||
animClip = clip.curves;
|
||||
|
||||
return animClip != null;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Edit Sub-Timeline", MenuPriority.ClipEditActionSection.editSubTimeline), UsedImplicitly]
|
||||
class EditSubTimeline : ClipAction
|
||||
{
|
||||
private static readonly string MultiItemPrefix = "Edit Sub-Timelines/";
|
||||
private static readonly string SingleItemPrefix = "Edit ";
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips == null || clips.Count() != 1 || TimelineEditor.inspectedDirector == null)
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
var clip = clips.First();
|
||||
var directors = TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector);
|
||||
return directors.Any(x => x != null) ? ActionValidity.Valid : ActionValidity.NotApplicable;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (Validate(clips) != ActionValidity.Valid) return false;
|
||||
|
||||
var clip = clips.First();
|
||||
|
||||
var directors = TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector);
|
||||
ExecuteInternal(directors, 0, clip);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ExecuteInternal(IList<PlayableDirector> directors, int directorIndex, TimelineClip clip)
|
||||
{
|
||||
SelectionManager.Clear();
|
||||
TimelineWindow.instance.SetCurrentTimeline(directors[directorIndex], clip);
|
||||
}
|
||||
|
||||
internal void AddMenuItem(List<MenuActionItem> menuItems)
|
||||
{
|
||||
var clips = TimelineEditor.selectedClips;
|
||||
if (clips == null || clips.Length != 1)
|
||||
return;
|
||||
|
||||
var mode = TimelineWindow.instance.currentMode.mode;
|
||||
MenuEntryAttribute menuAttribute = GetType().GetCustomAttributes(typeof(MenuEntryAttribute), false).OfType<MenuEntryAttribute>().FirstOrDefault();
|
||||
var menuItem = new MenuActionItem()
|
||||
{
|
||||
category = menuAttribute.subMenuPath ?? string.Empty,
|
||||
entryName = menuAttribute.name,
|
||||
isActiveInMode = this.IsActionActiveInMode(mode),
|
||||
priority = menuAttribute.priority,
|
||||
state = Validate(clips),
|
||||
callback = null
|
||||
};
|
||||
|
||||
var subDirectors = TimelineUtility.GetSubTimelines(clips[0], TimelineEditor.inspectedDirector);
|
||||
if (subDirectors.Count == 1)
|
||||
{
|
||||
menuItem.entryName = SingleItemPrefix + DisplayNameHelper.GetDisplayName(subDirectors[0]);
|
||||
menuItem.callback = () =>
|
||||
{
|
||||
Execute(clips);
|
||||
};
|
||||
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(subDirectors, index, clips[0]);
|
||||
};
|
||||
menuItems.Add(menuItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim Start", MenuPriority.ClipActionSection.trimStart)]
|
||||
[Shortcut(Shortcuts.Clip.trimStart), UsedImplicitly]
|
||||
class TrimStart : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.TrimStart(clips, TimelineEditor.inspectedSequenceTime);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim End", MenuPriority.ClipActionSection.trimEnd), UsedImplicitly]
|
||||
[Shortcut(Shortcuts.Clip.trimEnd)]
|
||||
class TrimEnd : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.TrimEnd(clips, TimelineEditor.inspectedSequenceTime);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Clip.split)]
|
||||
[MenuEntry("Editing/Split", MenuPriority.ClipActionSection.split), UsedImplicitly]
|
||||
class Split : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return clips.All(x => TimelineEditor.inspectedSequenceTime <= x.start || TimelineEditor.inspectedSequenceTime >= x.start + x.duration) ? ActionValidity.Invalid : ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool success = ClipModifier.Split(clips, TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedDirector);
|
||||
if (success)
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Complete Last Loop", MenuPriority.ClipActionSection.completeLastLoop), UsedImplicitly]
|
||||
class CompleteLastLoop : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.CompleteLastLoop(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Trim Last Loop", MenuPriority.ClipActionSection.trimLastLoop), UsedImplicitly]
|
||||
class TrimLastLoop : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.TrimLastLoop(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Match Duration", MenuPriority.ClipActionSection.matchDuration), UsedImplicitly]
|
||||
class MatchDuration : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.MatchDuration(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Double Speed", MenuPriority.ClipActionSection.doubleSpeed), UsedImplicitly]
|
||||
class DoubleSpeed : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.DoubleSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Half Speed", MenuPriority.ClipActionSection.halfSpeed), UsedImplicitly]
|
||||
class HalfSpeed : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.HalfSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset Duration", MenuPriority.ClipActionSection.resetDuration), UsedImplicitly]
|
||||
class ResetDuration : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration);
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.ResetEditing(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset Speed", MenuPriority.ClipActionSection.resetSpeed), UsedImplicitly]
|
||||
class ResetSpeed : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.ResetSpeed(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Editing/Reset All", MenuPriority.ClipActionSection.resetAll), UsedImplicitly]
|
||||
class ResetAll : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
bool canDisplay = clips.Any(TimelineHelpers.HasUsableAssetDuration) || clips.All(x => x.SupportsSpeedMultiplier());
|
||||
|
||||
return canDisplay ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
var speedResult = ClipModifier.ResetSpeed(clips);
|
||||
var editResult = ClipModifier.ResetEditing(clips);
|
||||
return speedResult || editResult;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Tile", MenuPriority.ClipActionSection.tile), UsedImplicitly]
|
||||
class Tile : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return clips.Count() > 1 ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
return ClipModifier.Tile(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Find Source Asset", MenuPriority.ClipActionSection.findSourceAsset), UsedImplicitly]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FindSourceAsset : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips.Count() > 1)
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
if (GetUnderlyingAsset(clips.First()) == null)
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
EditorGUIUtility.PingObject(GetUnderlyingAsset(clips.First()));
|
||||
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(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 (TimelineEditor.inspectedDirector == null && field.isSceneReference)
|
||||
continue;
|
||||
var obj = field.Find(asset, TimelineEditor.inspectedDirector);
|
||||
if (obj != null)
|
||||
return obj;
|
||||
}
|
||||
|
||||
return GetExternalPlayableAsset(clip);
|
||||
}
|
||||
}
|
||||
|
||||
class CopyClipsToClipboard : ClipAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips) => ActionValidity.Valid;
|
||||
public override bool Execute(IEnumerable<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,123 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// interface indicating an Action class
|
||||
interface IAction {}
|
||||
|
||||
/// extension methods for IActions
|
||||
static class ActionExtensions
|
||||
{
|
||||
const string kActionPostFix = "Action";
|
||||
|
||||
public static string GetUndoName(this IAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
var attr = action.GetType().GetCustomAttribute<ApplyDefaultUndoAttribute>(false);
|
||||
if (attr != null && !string.IsNullOrWhiteSpace(attr.UndoTitle))
|
||||
return attr.UndoTitle;
|
||||
|
||||
return action.GetDisplayName();
|
||||
}
|
||||
|
||||
public static string GetMenuEntryName(this IAction action)
|
||||
{
|
||||
var menuAction = action as IMenuName;
|
||||
if (menuAction != null && !string.IsNullOrWhiteSpace(menuAction.menuName))
|
||||
return menuAction.menuName;
|
||||
|
||||
var attr = action.GetType().GetCustomAttribute<MenuEntryAttribute>(false);
|
||||
if (attr != null && !string.IsNullOrWhiteSpace(attr.name))
|
||||
return attr.name;
|
||||
|
||||
return action.GetDisplayName();
|
||||
}
|
||||
|
||||
public static string GetDisplayName(this IAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
var attr = action.GetType().GetCustomAttribute<DisplayNameAttribute>(false);
|
||||
if (attr != null && !string.IsNullOrEmpty(attr.DisplayName))
|
||||
return attr.DisplayName;
|
||||
|
||||
var name = action.GetType().Name;
|
||||
if (name.EndsWith(kActionPostFix))
|
||||
return ObjectNames.NicifyVariableName(name.Substring(0, name.Length - kActionPostFix.Length));
|
||||
|
||||
return ObjectNames.NicifyVariableName(name);
|
||||
}
|
||||
|
||||
public static bool HasAutoUndo(this IAction action)
|
||||
{
|
||||
return action != null && ActionManager.ActionsWithAutoUndo.Contains(action.GetType());
|
||||
}
|
||||
|
||||
public static bool IsChecked(this IAction action)
|
||||
{
|
||||
return (action is IMenuChecked menuAction) && menuAction.isChecked;
|
||||
}
|
||||
|
||||
public static bool IsActionActiveInMode(this IAction action, TimelineModes mode)
|
||||
{
|
||||
var attr = action.GetType().GetCustomAttribute<ActiveInModeAttribute>(true);
|
||||
return attr != null && (attr.modes & mode) != 0;
|
||||
}
|
||||
|
||||
public static string GetShortcut(this IAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
var shortcutAttribute = GetShortcutAttributeForAction(action);
|
||||
var shortCut = shortcutAttribute == null ? string.Empty : shortcutAttribute.GetMenuShortcut();
|
||||
if (string.IsNullOrWhiteSpace(shortCut))
|
||||
{
|
||||
//Check if there is a static method with attribute
|
||||
var customShortcutMethod = action.GetType().GetMethods().FirstOrDefault(m => m.GetCustomAttribute<TimelineShortcutAttribute>(true) != null);
|
||||
if (customShortcutMethod != null)
|
||||
{
|
||||
var shortcutId = customShortcutMethod.GetCustomAttribute<TimelineShortcutAttribute>(true).identifier;
|
||||
var shortcut = ShortcutIntegration.instance.directory.FindShortcutEntry(shortcutId);
|
||||
if (shortcut != null && shortcut.combinations.Any())
|
||||
shortCut = KeyCombination.SequenceToMenuString(shortcut.combinations);
|
||||
}
|
||||
}
|
||||
|
||||
return shortCut;
|
||||
}
|
||||
|
||||
static ShortcutAttribute GetShortcutAttributeForAction(this IAction action)
|
||||
{
|
||||
if (action == null)
|
||||
throw new ArgumentNullException(nameof(action));
|
||||
|
||||
var shortcutAttributes = action.GetType()
|
||||
.GetCustomAttributes(typeof(ShortcutAttribute), true)
|
||||
.Cast<ShortcutAttribute>();
|
||||
|
||||
foreach (var shortcutAttribute in shortcutAttributes)
|
||||
{
|
||||
if (shortcutAttribute is ShortcutPlatformOverrideAttribute shortcutOverride)
|
||||
{
|
||||
if (shortcutOverride.MatchesCurrentPlatform())
|
||||
return shortcutOverride;
|
||||
}
|
||||
else
|
||||
{
|
||||
return shortcutAttribute;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de11c48878ba1b44b8b641af54dba8c1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface IMenuChecked
|
||||
{
|
||||
bool isChecked { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0939c83f40ef46e584340aa87b3cadfe
|
||||
timeCreated: 1591130283
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
interface IMenuName
|
||||
{
|
||||
string menuName { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 512ac45650d443f3be59379c2ecbf068
|
||||
timeCreated: 1591130274
|
|
@ -0,0 +1,101 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Class containing methods to invoke actions.
|
||||
/// </summary>
|
||||
public static class Invoker
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute a given action with a context parameter.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <param name="context">Context for the action.</param>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool Invoke<T>(this ActionContext context) where T : TimelineAction
|
||||
{
|
||||
var action = ActionManager.TimelineActions.GetCachedAction<T, TimelineAction>();
|
||||
return ActionManager.ExecuteTimelineAction(action, context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given action with tracks
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <param name="tracks">Tracks that the action will act on.</param>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool Invoke<T>(this IEnumerable<TrackAsset> tracks) where T : TrackAction
|
||||
{
|
||||
var action = ActionManager.TrackActions.GetCachedAction<T, TrackAction>();
|
||||
return ActionManager.ExecuteTrackAction(action, tracks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given action with clips
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <param name="clips">Clips that the action will act on.</param>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool Invoke<T>(this IEnumerable<TimelineClip> clips) where T : ClipAction
|
||||
{
|
||||
var action = ActionManager.ClipActions.GetCachedAction<T, ClipAction>();
|
||||
return ActionManager.ExecuteClipAction(action, clips);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given action with markers
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <param name="markers">Markers that the action will act on.</param>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool Invoke<T>(this IEnumerable<IMarker> markers) where T : MarkerAction
|
||||
{
|
||||
var action = ActionManager.MarkerActions.GetCachedAction<T, MarkerAction>();
|
||||
return ActionManager.ExecuteMarkerAction(action, markers);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given timeline action with the selected clips, tracks and markers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool InvokeWithSelected<T>() where T : TimelineAction
|
||||
{
|
||||
return Invoke<T>(TimelineEditor.CurrentContext());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given clip action with the selected clips.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool InvokeWithSelectedClips<T>() where T : ClipAction
|
||||
{
|
||||
return Invoke<T>(SelectionManager.SelectedClips());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given track action with the selected tracks.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool InvokeWithSelectedTracks<T>() where T : TrackAction
|
||||
{
|
||||
return Invoke<T>(SelectionManager.SelectedTracks());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute a given marker action with the selected markers.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Action type to execute.</typeparam>
|
||||
/// <returns>True if the action has been executed, false otherwise.</returns>
|
||||
public static bool InvokeWithSelectedMarkers<T>() where T : MarkerAction
|
||||
{
|
||||
return Invoke<T>(SelectionManager.SelectedMarkers());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0c379d496c819c14b8b5377d7830a59a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a marker action.
|
||||
/// Inherit from this class to make an action that would react on selected markers after a menu click and/or a key shortcut.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Simple track Action example (with context menu and shortcut support).
|
||||
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleMarkerAction" title="SampleMarkerAction"/>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
|
||||
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
|
||||
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
|
||||
/// </remarks>
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
public abstract class MarkerAction : IAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the action.
|
||||
/// </summary>
|
||||
/// <param name="markers">Markers that will be used for the action. </param>
|
||||
/// <returns>true if the action has been executed. false otherwise</returns>
|
||||
public abstract bool Execute(IEnumerable<IMarker> markers);
|
||||
/// <summary>
|
||||
/// Defines the validity of an Action for a given set of markers.
|
||||
/// </summary>
|
||||
/// <param name="markers">Markers that will be used for the action. </param>
|
||||
/// <returns>The validity of the set of markers.</returns>
|
||||
public abstract ActionValidity Validate(IEnumerable<IMarker> markers);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 03d19e545aea1b446b1c56530a4438ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,20 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[UsedImplicitly]
|
||||
class CopyMarkersToClipboard : MarkerAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<IMarker> markers) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(IEnumerable<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,36 @@
|
|||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the validity of an action for a given data set.
|
||||
/// </summary>
|
||||
public enum ActionValidity
|
||||
{
|
||||
/// <summary>
|
||||
/// Action is valid in the provided context.
|
||||
/// If the action is linked to a menu item, the menu item will be visible.
|
||||
/// </summary>
|
||||
Valid,
|
||||
/// <summary>
|
||||
/// Action is not applicable in the current context.
|
||||
/// If the action is linked to a menu item, the menu item will not be shown.
|
||||
/// </summary>
|
||||
NotApplicable,
|
||||
/// <summary>
|
||||
/// Action is not valid in the current context.
|
||||
/// If the action is linked to a menu item, the menu item will be shown but grayed out.
|
||||
/// </summary>
|
||||
Invalid
|
||||
}
|
||||
|
||||
struct MenuActionItem
|
||||
{
|
||||
public string category;
|
||||
public string entryName;
|
||||
public string shortCut;
|
||||
public int priority;
|
||||
public bool isActiveInMode;
|
||||
public ActionValidity state;
|
||||
public bool isChecked;
|
||||
public GenericMenu.MenuFunction callback;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5882d0e4313310143acb11d1a66c597f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,381 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class SequencerContextMenu
|
||||
{
|
||||
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 ShowNewTracksContextMenu(ICollection<TrackAsset> tracks, WindowState state)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
List<MenuActionItem> items = new List<MenuActionItem>(100);
|
||||
BuildNewTracksContextMenu(items, tracks, state);
|
||||
ActionManager.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);
|
||||
ActionManager.BuildMenu(menu, items);
|
||||
menu.DropDown(rect);
|
||||
}
|
||||
|
||||
public static void ShowTrackContextMenu(Vector2? mousePosition)
|
||||
{
|
||||
var items = new List<MenuActionItem>();
|
||||
var menu = new GenericMenu();
|
||||
BuildTrackContextMenu(items, mousePosition);
|
||||
ActionManager.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void ShowItemContextMenu(Vector2 mousePosition)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
var items = new List<MenuActionItem>();
|
||||
BuildItemContextMenu(items, mousePosition);
|
||||
ActionManager.BuildMenu(menu, items);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
|
||||
public static void BuildItemContextMenu(List<MenuActionItem> items, Vector2 mousePosition)
|
||||
{
|
||||
ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
|
||||
ActionManager.GetMenuEntries(ActionManager.ClipActions, items);
|
||||
ActionManager.GetMenuEntries(ActionManager.MarkerActions, items);
|
||||
|
||||
var clips = TimelineEditor.selectedClips;
|
||||
if (clips.Length > 0)
|
||||
AddMarkerMenuCommands(items, clips.Select(c => c.GetParentTrack()).Distinct().ToList(), TimelineHelpers.GetCandidateTime(mousePosition));
|
||||
}
|
||||
|
||||
public 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 = ActionValidity.Valid;
|
||||
if (state.editSequence.isReadOnly)
|
||||
menuState = ActionValidity.Invalid;
|
||||
if (parentTracks.Any() && parentTracks.Any(t => t != null && t.lockedInHierarchy))
|
||||
menuState = ActionValidity.Invalid;
|
||||
|
||||
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,
|
||||
isActiveInMode = true,
|
||||
priority = MenuPriority.AddItem.addGroup,
|
||||
state = menuState,
|
||||
isChecked = false,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
var allTypes = TypeUtility.AllTrackTypes().Where(x => x != typeof(GroupTrack) && !TypeUtility.IsHiddenInMenu(x)).ToList();
|
||||
|
||||
int builtInPriority = MenuPriority.AddItem.addTrack;
|
||||
int customPriority = MenuPriority.AddItem.addCustomTrack;
|
||||
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)),
|
||||
isActiveInMode = true,
|
||||
priority = TypeUtility.IsBuiltIn(trackType) ? builtInPriority++ : customPriority++,
|
||||
state = menuState,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void BuildTrackContextMenu(List<MenuActionItem> items, Vector2? mousePosition)
|
||||
{
|
||||
var tracks = SelectionManager.SelectedTracks().ToArray();
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
ActionManager.GetMenuEntries(ActionManager.TimelineActions, mousePosition, items);
|
||||
ActionManager.GetMenuEntries(ActionManager.TrackActions, 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(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 = MenuPriority.AddTrackMenu.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,
|
||||
isActiveInMode = true,
|
||||
priority = priority++,
|
||||
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
|
||||
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 = MenuPriority.AddItem.addClip;
|
||||
int customPriority = MenuPriority.AddItem.addCustomClip;
|
||||
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)),
|
||||
isActiveInMode = true,
|
||||
priority = TypeUtility.IsBuiltIn(assetType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
|
||||
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,
|
||||
isActiveInMode = true,
|
||||
priority = TypeUtility.IsBuiltIn(assetItemType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
|
||||
callback = command
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static void AddMarkerMenuCommands(List<MenuActionItem> menu, IEnumerable<Type> markerTypes, Action<Type, Object> addMarkerCommand, bool enabled)
|
||||
{
|
||||
int builtInPriority = MenuPriority.AddItem.addMarker;
|
||||
int customPriority = MenuPriority.AddItem.addCustomMarker;
|
||||
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)),
|
||||
isActiveInMode = true,
|
||||
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
|
||||
callback = () => addMarkerCommand(markerItemType, null)
|
||||
}
|
||||
);
|
||||
|
||||
foreach (var objectReference in TypeUtility.ObjectReferencesForType(markerType))
|
||||
{
|
||||
var isSceneReference = objectReference.isSceneReference;
|
||||
GenericMenu.MenuFunction menuCallback = () =>
|
||||
{
|
||||
Type assetDataType = objectReference.type;
|
||||
ObjectSelector.get.titleContent = EditorGUIUtility.TrTextContent(string.Format(Styles.typeSelectorTemplate, TypeUtility.GetDisplayName(assetDataType)));
|
||||
ObjectSelector.get.Show(null, assetDataType, null, isSceneReference, null, obj =>
|
||||
{
|
||||
if (obj != null)
|
||||
addMarkerCommand(markerItemType, obj);
|
||||
}, null);
|
||||
};
|
||||
|
||||
menu.Add(
|
||||
new MenuActionItem
|
||||
{
|
||||
category = TimelineHelpers.GetItemCategoryName(markerItemType),
|
||||
entryName = string.Format(Styles.addItemFromAssetTemplate, TypeUtility.GetDisplayName(markerType), TypeUtility.GetDisplayName(objectReference.type)),
|
||||
isActiveInMode = true,
|
||||
priority = TypeUtility.IsBuiltIn(markerType) ? builtInPriority++ : customPriority++,
|
||||
state = enabled ? ActionValidity.Valid : ActionValidity.Invalid,
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de86b4ed8106fd84a8bc2f5d69798d53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,34 @@
|
|||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a timeline action.
|
||||
/// Inherit from this class to make an action on a timeline after a menu click and/or a key shortcut.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
|
||||
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
|
||||
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
|
||||
/// <seealso cref="ActiveInModeAttribute"/>
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// Simple Timeline Action example (with context menu and shortcut support).
|
||||
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleTimelineAction" title="SampleTimelineAction"/>
|
||||
/// </example>
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
public abstract class TimelineAction : IAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the action.
|
||||
/// </summary>
|
||||
/// <param name="context">Context for the action.</param>
|
||||
/// <returns>true if the action has been executed. false otherwise</returns>
|
||||
public abstract bool Execute(ActionContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the validity of an Action based on the context.
|
||||
/// </summary>
|
||||
/// <param name="context">Context for the action.</param>
|
||||
/// <returns>Visual state of the menu for the action.</returns>
|
||||
public abstract ActionValidity Validate(ActionContext context);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 38eaad816666dfb428c3f123a09413c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,952 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Copy", MenuPriority.TimelineActionSection.copy)]
|
||||
[Shortcut("Main Menu/Edit/Copy", EventCommandNames.Copy)]
|
||||
class CopyAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
if (SelectionManager.Count() == 0)
|
||||
return ActionValidity.NotApplicable;
|
||||
if (context.tracks.ContainsTimelineMarkerTrack(context.timeline))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext context)
|
||||
{
|
||||
TimelineEditor.clipboard.Clear();
|
||||
|
||||
var clips = context.clips;
|
||||
if (clips.Any())
|
||||
{
|
||||
clips.Invoke<CopyClipsToClipboard>();
|
||||
}
|
||||
var markers = context.markers;
|
||||
if (markers.Any())
|
||||
{
|
||||
markers.Invoke<CopyMarkersToClipboard>();
|
||||
}
|
||||
var tracks = context.tracks;
|
||||
if (tracks.Any())
|
||||
{
|
||||
CopyTracksToClipboard.Do(tracks.ToArray());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Paste", MenuPriority.TimelineActionSection.paste)]
|
||||
[Shortcut("Main Menu/Edit/Paste", EventCommandNames.Paste)]
|
||||
class PasteAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
return CanPaste(context.invocationTime) ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext context)
|
||||
{
|
||||
if (!CanPaste(context.invocationTime))
|
||||
return false;
|
||||
|
||||
PasteItems(context.invocationTime);
|
||||
PasteTracks();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CanPaste(double? invocationTime)
|
||||
{
|
||||
var copiedItems = TimelineEditor.clipboard.GetCopiedItems().ToList();
|
||||
|
||||
if (!copiedItems.Any())
|
||||
return TimelineEditor.clipboard.GetTracks().Any();
|
||||
|
||||
return CanPasteItems(copiedItems, invocationTime);
|
||||
}
|
||||
|
||||
static bool CanPasteItems(ICollection<ItemsPerTrack> itemsGroups, double? invocationTime)
|
||||
{
|
||||
var hasItemsCopiedFromMultipleTracks = itemsGroups.Count > 1;
|
||||
var allItemsCopiedFromCurrentAsset = itemsGroups.All(x => x.targetTrack.timelineAsset == TimelineEditor.inspectedAsset);
|
||||
var hasUsedShortcut = invocationTime == 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 == TimelineEditor.inspectedAsset;
|
||||
if (hasItemsCopiedFromMultipleTracks || (hasUsedShortcut && !isSelectedTrackInCurrentAsset))
|
||||
return false;
|
||||
}
|
||||
|
||||
// pasting to items to their source track, if items from multiple tracks are selected
|
||||
// and no track is in the selection items will each be pasted to their respective track.
|
||||
if (targetTrack == null || itemsGroups.All(x => x.targetTrack == targetTrack))
|
||||
return !anySourceLocked;
|
||||
|
||||
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(double? invocationTime)
|
||||
{
|
||||
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 (invocationTime.HasValue)
|
||||
target = GetPickedTrack();
|
||||
if (target == null)
|
||||
target = FindSuitableParentForSingleTrackPasteWithoutMouse(itemsGroup);
|
||||
|
||||
var candidateTime = invocationTime ?? TimelineHelpers.GetCandidateTime(null, target);
|
||||
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(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 = invocationTime ?? TimelineHelpers.GetCandidateTime(null, copiedItems.Select(c => c.targetTrack).ToArray());
|
||||
newItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(TimelineEditor.clipboard.exposedPropertyTable, TimelineEditor.inspectedDirector, copiedItems, time, "Paste Items").ToList();
|
||||
}
|
||||
|
||||
TimelineHelpers.FrameItems(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()
|
||||
{
|
||||
if (PickerUtils.pickedElements == null)
|
||||
return null;
|
||||
|
||||
var rowGUI = PickerUtils.pickedElements.OfType<IRowGUI>().FirstOrDefault();
|
||||
if (rowGUI != null)
|
||||
return rowGUI.asset;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void PasteTracks()
|
||||
{
|
||||
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);
|
||||
if (track.binding != null)
|
||||
{
|
||||
BindingUtility.Bind(TimelineEditor.inspectedDirector, newTrack, track.binding);
|
||||
}
|
||||
|
||||
SelectionManager.Add(newTrack);
|
||||
foreach (var childTrack in newTrack.GetFlattenedChildTracks())
|
||||
{
|
||||
SelectionManager.Add(childTrack);
|
||||
}
|
||||
|
||||
if (track.parent != null && track.parent.timelineAsset == TimelineEditor.inspectedAsset)
|
||||
{
|
||||
TrackExtensions.ReparentTracks(new List<TrackAsset> { newTrack }, track.parent, track.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Duplicate", MenuPriority.TimelineActionSection.duplicate)]
|
||||
[Shortcut("Main Menu/Edit/Duplicate", EventCommandNames.Duplicate)]
|
||||
class DuplicateAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
IEnumerable<TrackAsset> tracks = context.tracks.RemoveTimelineMarkerTrackFromList(context.timeline);
|
||||
return context.clips.Any() || tracks.Any() || context.markers.Any() ? ActionValidity.Valid : ActionValidity.NotApplicable;
|
||||
}
|
||||
|
||||
public bool Execute(Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
|
||||
{
|
||||
return Execute(TimelineEditor.CurrentContext(), gapBetweenItems);
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext context)
|
||||
{
|
||||
return Execute(context, (item1, item2) => ItemsUtils.TimeGapBetweenItems(item1, item2));
|
||||
}
|
||||
|
||||
internal bool Execute(ActionContext context, Func<ITimelineItem, ITimelineItem, double> gapBetweenItems)
|
||||
{
|
||||
List<ITimelineItem> items = new List<ITimelineItem>();
|
||||
items.AddRange(context.clips.Select(p => p.ToItem()));
|
||||
items.AddRange(context.markers.Select(p => p.ToItem()));
|
||||
List<ItemsPerTrack> selectedItems = items.ToItemsPerTrack().ToList();
|
||||
if (selectedItems.Any())
|
||||
{
|
||||
var requestedTime = CalculateDuplicateTime(selectedItems, gapBetweenItems);
|
||||
var duplicatedItems = TimelineHelpers.DuplicateItemsUsingCurrentEditMode(TimelineEditor.inspectedDirector, TimelineEditor.inspectedDirector, selectedItems, requestedTime, "Duplicate Items");
|
||||
|
||||
TimelineHelpers.FrameItems(duplicatedItems);
|
||||
SelectionManager.RemoveTimelineSelection();
|
||||
foreach (var item in duplicatedItems)
|
||||
SelectionManager.Add(item);
|
||||
}
|
||||
|
||||
var tracks = context.tracks.ToArray();
|
||||
if (tracks.Length > 0)
|
||||
tracks.Invoke<DuplicateTracks>();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
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", MenuPriority.TimelineActionSection.delete)]
|
||||
[Shortcut("Main Menu/Edit/Delete", EventCommandNames.Delete)]
|
||||
[ShortcutPlatformOverride(RuntimePlatform.OSXEditor, KeyCode.Backspace, ShortcutModifiers.Action)]
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
class DeleteAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
return CanDelete(context) ? ActionValidity.Valid : ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
static bool CanDelete(ActionContext context)
|
||||
{
|
||||
if (TimelineWindow.instance.state.editSequence.isReadOnly)
|
||||
return false;
|
||||
|
||||
if (context.tracks.ContainsTimelineMarkerTrack(context.timeline))
|
||||
return false;
|
||||
|
||||
// All() returns true when empty
|
||||
return context.tracks.All(x => !x.lockedInHierarchy) &&
|
||||
context.clips.All(x => x.GetParentTrack() == null || !x.GetParentTrack().lockedInHierarchy) &&
|
||||
context.markers.All(x => x.parent == null || !x.parent.lockedInHierarchy);
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext context)
|
||||
{
|
||||
if (!CanDelete(context))
|
||||
return false;
|
||||
|
||||
var selectedItems = context.clips.Select(p => p.ToItem()).ToList();
|
||||
selectedItems.AddRange(context.markers.Select(p => p.ToItem()));
|
||||
DeleteItems(selectedItems);
|
||||
|
||||
if (context.tracks.Any() && SelectionManager.GetCurrentInlineEditorCurve() == null)
|
||||
context.tracks.Invoke<DeleteTracks>();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
return selectedItems.Any() || context.tracks.Any();
|
||||
}
|
||||
|
||||
internal static void DeleteItems(IEnumerable<ITimelineItem> items)
|
||||
{
|
||||
var tracks = items.GroupBy(c => c.parentTrack);
|
||||
|
||||
foreach (var track in tracks)
|
||||
TimelineUndo.PushUndo(track.Key, L10n.Tr("Delete Items"));
|
||||
|
||||
TimelineAnimationUtilities.UnlinkAnimationWindowFromClips(items.OfType<ClipItem>().Select(i => i.clip));
|
||||
|
||||
EditMode.PrepareItemsDelete(ItemsUtils.ToItemsPerTrack(items));
|
||||
EditModeUtils.Delete(items);
|
||||
|
||||
SelectionManager.RemoveAllClips();
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Match Content", MenuPriority.TimelineActionSection.matchContent)]
|
||||
[Shortcut(Shortcuts.Timeline.matchContent)]
|
||||
class MatchContent : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext actionContext)
|
||||
{
|
||||
var clips = actionContext.clips;
|
||||
|
||||
if (!clips.Any())
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
return clips.Any(TimelineHelpers.HasUsableAssetDuration)
|
||||
? ActionValidity.Valid
|
||||
: ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
var clips = actionContext.clips;
|
||||
return clips.Any() && ClipModifier.MatchContent(clips);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.play)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PlayTimelineAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
var currentState = TimelineEditor.state.playing;
|
||||
TimelineEditor.state.SetPlaying(!currentState);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectAllAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
// otherwise select all tracks.
|
||||
SelectionManager.Clear();
|
||||
TimelineWindow.instance.allTracks.ForEach(x => SelectionManager.Add(x.track));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.previousFrame)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PreviousFrameAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (TimelineEditor.inspectedAsset == null)
|
||||
return false;
|
||||
var inspectedFrame = TimeUtility.ToFrames(TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
inspectedFrame = Mathf.Max(0, inspectedFrame - 1);
|
||||
TimelineEditor.inspectedSequenceTime = TimeUtility.FromFrames(inspectedFrame, TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.nextFrame)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class NextFrameAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (TimelineEditor.inspectedAsset == null)
|
||||
return false;
|
||||
var inspectedFrame = TimeUtility.ToFrames(TimelineEditor.inspectedSequenceTime, TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
inspectedFrame++;
|
||||
TimelineEditor.inspectedSequenceTime = TimeUtility.FromFrames(inspectedFrame, TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.frameAll)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FrameAllAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context)
|
||||
{
|
||||
if (context.timeline != null && !context.timeline.flattenedTracks.Any())
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
if (FrameSelectedAction.ShouldHandleInlineCurve(inlineCurveEditor))
|
||||
{
|
||||
FrameSelectedAction.FrameInlineCurves(inlineCurveEditor, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TimelineWindow.instance.state.IsCurrentEditingASequencerTextField())
|
||||
return false;
|
||||
|
||||
var visibleTracks = TimelineWindow.instance.treeView.visibleTracks.ToList();
|
||||
|
||||
if (TimelineEditor.inspectedAsset != null && TimelineEditor.inspectedAsset.markerTrack != null)
|
||||
visibleTracks.Add(TimelineEditor.inspectedAsset.markerTrack);
|
||||
|
||||
if (visibleTracks.Count == 0)
|
||||
return false;
|
||||
|
||||
var startTime = float.MaxValue;
|
||||
var endTime = float.MinValue;
|
||||
|
||||
foreach (var t in visibleTracks)
|
||||
{
|
||||
if (t == null)
|
||||
continue;
|
||||
|
||||
// time range based on track's curves and clips.
|
||||
double trackStart, trackEnd, trackDuration;
|
||||
t.GetSequenceTime(out trackStart, out trackDuration);
|
||||
trackEnd = trackStart + trackDuration;
|
||||
|
||||
// take track's markers into account
|
||||
double itemsStart, itemsEnd;
|
||||
ItemsUtils.GetItemRange(t, out itemsStart, out itemsEnd);
|
||||
|
||||
startTime = Mathf.Min(startTime, (float)trackStart, (float)itemsStart);
|
||||
endTime = Mathf.Max(endTime, (float)(trackEnd), (float)itemsEnd);
|
||||
}
|
||||
|
||||
FrameSelectedAction.FrameRange(startTime, endTime);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class FrameSelectedAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public static void FrameRange(float startTime, float endTime)
|
||||
{
|
||||
if (startTime > endTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var halfDuration = endTime - Math.Max(0.0f, startTime);
|
||||
|
||||
if (halfDuration > 0.0f)
|
||||
{
|
||||
TimelineEditor.visibleTimeRange = new Vector2(Mathf.Max(0.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 = TimelineEditor.visibleTimeRange.y - TimelineEditor.visibleTimeRange.x;
|
||||
TimelineEditor.visibleTimeRange = new Vector2(startTime - currentRange / 2, startTime + currentRange / 2);
|
||||
}
|
||||
|
||||
TimelineZoomManipulator.InvalidateWheelZoom();
|
||||
TimelineEditor.Refresh(RefreshReason.SceneNeedsUpdate);
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
var inlineCurveEditor = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
if (ShouldHandleInlineCurve(inlineCurveEditor))
|
||||
{
|
||||
FrameInlineCurves(inlineCurveEditor, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TimelineWindow.instance.state.IsCurrentEditingASequencerTextField())
|
||||
return false;
|
||||
|
||||
if (SelectionManager.Count() == 0)
|
||||
{
|
||||
actionContext.Invoke<FrameAllAction>();
|
||||
return true;
|
||||
}
|
||||
|
||||
var startTime = float.MaxValue;
|
||||
var endTime = float.MinValue;
|
||||
|
||||
var clips = actionContext.clips.Select(ItemToItemGui.GetGuiForClip);
|
||||
var markers = actionContext.markers;
|
||||
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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool ShouldHandleInlineCurve(IClipCurveEditorOwner curveEditorOwner)
|
||||
{
|
||||
return curveEditorOwner?.clipCurveEditor != null &&
|
||||
curveEditorOwner.inlineCurvesSelected &&
|
||||
curveEditorOwner.owner != null &&
|
||||
curveEditorOwner.owner.GetShowInlineCurves();
|
||||
}
|
||||
|
||||
public static void FrameInlineCurves(IClipCurveEditorOwner curveEditorOwner, 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);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.previousKey)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class PrevKeyAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (TimelineEditor.inspectedAsset == null)
|
||||
return false;
|
||||
var keyTraverser = new Utilities.KeyTraverser(TimelineEditor.inspectedAsset, 0.01f / TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
var time = keyTraverser.GetPrevKey((float)TimelineEditor.inspectedSequenceTime, TimelineWindow.instance.state.dirtyStamp);
|
||||
if (time != TimelineEditor.inspectedSequenceTime)
|
||||
{
|
||||
TimelineEditor.inspectedSequenceTime = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.nextKey)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class NextKeyAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (TimelineEditor.inspectedAsset == null)
|
||||
return false;
|
||||
var keyTraverser = new Utilities.KeyTraverser(TimelineEditor.inspectedAsset, 0.01f / TimelineEditor.inspectedAsset.editorSettings.fps);
|
||||
var time = keyTraverser.GetNextKey((float)TimelineEditor.inspectedSequenceTime, TimelineWindow.instance.state.dirtyStamp);
|
||||
if (time != TimelineEditor.inspectedSequenceTime)
|
||||
{
|
||||
TimelineEditor.inspectedSequenceTime = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.goToStart)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class GotoStartAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
TimelineEditor.inspectedSequenceTime = 0.0f;
|
||||
TimelineWindow.instance.state.EnsurePlayHeadIsVisible();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.goToEnd)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class GotoEndAction : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
TimelineEditor.inspectedSequenceTime = TimelineWindow.instance.state.editSequence.duration;
|
||||
TimelineWindow.instance.state.EnsurePlayHeadIsVisible();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.zoomIn)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ZoomIn : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
TimelineZoomManipulator.Instance.DoZoom(1.15f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.zoomOut)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ZoomOut : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
TimelineZoomManipulator.Instance.DoZoom(0.85f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.collapseGroup)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class CollapseGroup : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.CollapseGroup(actionContext.tracks);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.unCollapseGroup)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class UnCollapseGroup : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.UnCollapseGroup(actionContext.tracks);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectLeftItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectLeftClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
// Switches to track header if no left track exists
|
||||
return KeyboardNavigation.SelectLeftItem();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectRightItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectRightClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectRightItem();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectUpItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectUpClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpItem();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectUpTrack)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectUpTrack : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpTrack();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectDownItem)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectDownClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectDownItem();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.selectDownTrack)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class SelectDownTrack : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (!KeyboardNavigation.ClipAreaActive() && !KeyboardNavigation.TrackHeadActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem();
|
||||
else
|
||||
return KeyboardNavigation.SelectDownTrack();
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectLeft)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectLeftClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectLeftItem(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectRight)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectRightClip : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectRightItem(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectUp)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectUpTrack : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectUpTrack(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.multiSelectDown)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class MultiselectDownTrack : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
return KeyboardNavigation.SelectDownTrack(true);
|
||||
}
|
||||
}
|
||||
|
||||
[Shortcut(Shortcuts.Timeline.toggleClipTrackArea)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ToggleClipTrackArea : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext context) => ActionValidity.Valid;
|
||||
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
if (KeyboardNavigation.TrackHeadActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem(actionContext.tracks);
|
||||
|
||||
if (!KeyboardNavigation.ClipAreaActive())
|
||||
return KeyboardNavigation.FocusFirstVisibleItem();
|
||||
|
||||
var item = KeyboardNavigation.GetVisibleSelectedItems().LastOrDefault();
|
||||
if (item != null)
|
||||
SelectionManager.SelectOnly(item.parentTrack);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Key All Animated", MenuPriority.TimelineActionSection.keyAllAnimated)]
|
||||
[Shortcut(Shortcuts.Timeline.keyAllAnimated)]
|
||||
class KeyAllAnimated : TimelineAction
|
||||
{
|
||||
public override ActionValidity Validate(ActionContext actionContext)
|
||||
{
|
||||
return CanExecute(TimelineEditor.state, actionContext)
|
||||
? ActionValidity.Valid
|
||||
: ActionValidity.NotApplicable;
|
||||
}
|
||||
|
||||
public override bool Execute(ActionContext actionContext)
|
||||
{
|
||||
WindowState state = TimelineEditor.state;
|
||||
PlayableDirector director = TimelineEditor.inspectedDirector;
|
||||
|
||||
if (!CanExecute(state, actionContext) || director == null)
|
||||
return false;
|
||||
|
||||
IEnumerable<TrackAsset> keyableTracks = GetKeyableTracks(state, actionContext);
|
||||
|
||||
var curveSelected = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
if (curveSelected != null)
|
||||
{
|
||||
var sel = curveSelected.clipCurveEditor.GetSelectedProperties().ToList();
|
||||
var go = (director.GetGenericBinding(curveSelected.owner) as Component).gameObject;
|
||||
if (sel.Count > 0)
|
||||
{
|
||||
TimelineRecording.KeyProperties(go, state, sel);
|
||||
}
|
||||
else
|
||||
{
|
||||
var binding = director.GetGenericBinding(curveSelected.owner) as Component;
|
||||
TimelineRecording.KeyAllProperties(binding, state);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var track in keyableTracks)
|
||||
{
|
||||
var binding = director.GetGenericBinding(track) as Component;
|
||||
TimelineRecording.KeyAllProperties(binding, state);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static IEnumerable<TrackAsset> GetKeyableTracks(WindowState state, ActionContext context)
|
||||
{
|
||||
if (!context.clips.Any() && !context.tracks.Any()) //no selection -> animate all recorded tracks
|
||||
return state.editSequence.asset.flattenedTracks.Where(state.IsArmedForRecord);
|
||||
|
||||
List<TrackAsset> parentTracks = context.tracks.ToList();
|
||||
parentTracks.AddRange(context.clips.Select(clip => clip.GetParentTrack()).Distinct());
|
||||
|
||||
if (!parentTracks.All(state.IsArmedForRecord))
|
||||
return Enumerable.Empty<TrackAsset>();
|
||||
|
||||
return parentTracks;
|
||||
}
|
||||
|
||||
static bool CanExecute(WindowState state, ActionContext context)
|
||||
{
|
||||
if (context.markers.Any())
|
||||
return false;
|
||||
|
||||
if (context.tracks.ContainsTimelineMarkerTrack(state.editSequence.asset))
|
||||
return false;
|
||||
|
||||
IClipCurveEditorOwner curveSelected = SelectionManager.GetCurrentInlineEditorCurve();
|
||||
// Can't have an inline curve selected and have multiple tracks also.
|
||||
if (curveSelected != null)
|
||||
{
|
||||
return state.IsArmedForRecord(curveSelected.owner);
|
||||
}
|
||||
|
||||
return GetKeyableTracks(state, context).Any();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b1c789407b55e3a4c9cc86135a714e33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,36 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for a track action.
|
||||
/// Inherit from this class to make an action that would react on selected tracks after a menu click and/or a key shortcut.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Simple track Action example (with context menu and shortcut support).
|
||||
/// <code source="../../DocCodeExamples/ActionExamples.cs" region="declare-sampleTrackAction" title="SampleTrackAction"/>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// To add an action as a menu item in the Timeline context menu, add <see cref="MenuEntryAttribute"/> on the action class.
|
||||
/// To make an action to react to a shortcut, use the Shortcut Manager API with <see cref="TimelineShortcutAttribute"/>.
|
||||
/// <seealso cref="UnityEditor.ShortcutManagement.ShortcutAttribute"/>
|
||||
/// </remarks>
|
||||
[ActiveInMode(TimelineModes.Default)]
|
||||
public abstract class TrackAction : IAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Execute the action.
|
||||
/// </summary>
|
||||
/// <param name="tracks">Tracks that will be used for the action. </param>
|
||||
/// <returns>true if the action has been executed. false otherwise</returns>
|
||||
public abstract bool Execute(IEnumerable<TrackAsset> tracks);
|
||||
|
||||
/// <summary>
|
||||
/// Defines the validity of an Action for a given set of tracks.
|
||||
/// </summary>
|
||||
/// <param name="tracks">tracks that the action would act on.</param>
|
||||
/// <returns>The validity of the set of tracks.</returns>
|
||||
public abstract ActionValidity Validate(IEnumerable<TrackAsset> tracks);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bcac94f4ceb0c8049bf47480708ca7e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,457 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Edit in Animation Window", MenuPriority.TrackActionSection.editInAnimationWindow)]
|
||||
class EditTrackInAnimationWindow : TrackAction
|
||||
{
|
||||
public static bool Do(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;
|
||||
|
||||
GameObject gameObject = null;
|
||||
if (TimelineEditor.inspectedDirector != null)
|
||||
gameObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track);
|
||||
|
||||
var timeController = TimelineAnimationUtilities.CreateTimeController(CreateTimeControlClipData(track));
|
||||
TimelineAnimationUtilities.EditAnimationClipWithTimeController(clipToEdit, timeController, gameObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
var firstTrack = tracks.First();
|
||||
if (firstTrack is AnimationTrack)
|
||||
{
|
||||
var animTrack = firstTrack as AnimationTrack;
|
||||
if (animTrack.CanConvertToClipMode())
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
else if (firstTrack.hasCurves)
|
||||
{
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
return Do(tracks.First());
|
||||
}
|
||||
|
||||
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", MenuPriority.TrackActionSection.lockSelected)]
|
||||
class LockSelectedTrack : TrackAction, IMenuName
|
||||
{
|
||||
public static readonly string LockSelectedTrackOnlyText = L10n.Tr("Lock selected track only");
|
||||
public static readonly string UnlockSelectedTrackOnlyText = L10n.Tr("Unlock selected track only");
|
||||
|
||||
public string menuName { get; private set; }
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
UpdateMenuName(tracks);
|
||||
if (tracks.Any(track => TimelineUtility.IsLockedFromGroup(track) || track is GroupTrack || !track.subTracksObjects.Any()))
|
||||
return ActionValidity.NotApplicable;
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||||
Lock(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnlockedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
menuName = tracks.All(t => t.locked) ? UnlockSelectedTrackOnlyText : LockSelectedTrackOnlyText;
|
||||
}
|
||||
|
||||
public static void Lock(TrackAsset[] tracks, bool shouldlock)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks.Where(t => !TimelineUtility.IsLockedFromGroup(t)))
|
||||
{
|
||||
TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks"));
|
||||
track.locked = shouldlock;
|
||||
}
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Lock", MenuPriority.TrackActionSection.lockTrack)]
|
||||
[Shortcut(Shortcuts.Timeline.toggleLock)]
|
||||
class LockTrack : TrackAction, IMenuName
|
||||
{
|
||||
static readonly string k_LockText = L10n.Tr("Lock");
|
||||
static readonly string k_UnlockText = L10n.Tr("Unlock");
|
||||
|
||||
public string menuName { get; private set; }
|
||||
|
||||
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
menuName = tracks.Any(x => !x.locked) ? k_LockText : k_UnlockText;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.locked);
|
||||
SetLockState(tracks, hasUnlockedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
UpdateMenuName(tracks);
|
||||
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
|
||||
|
||||
if (!tracks.Any())
|
||||
return ActionValidity.NotApplicable;
|
||||
if (tracks.Any(TimelineUtility.IsLockedFromGroup))
|
||||
return ActionValidity.Invalid;
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public static void SetLockState(IEnumerable<TrackAsset> tracks, bool shouldLock)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (TimelineUtility.IsLockedFromGroup(track))
|
||||
continue;
|
||||
|
||||
if (track as GroupTrack == null)
|
||||
SetLockState(track.GetChildTracks().ToArray(), shouldLock);
|
||||
|
||||
TimelineUndo.PushUndo(track, L10n.Tr("Lock Tracks"));
|
||||
track.locked = shouldLock;
|
||||
}
|
||||
|
||||
// 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);
|
||||
track.UnarmForRecord();
|
||||
foreach (var child in flattenedChildTracks)
|
||||
{
|
||||
SelectionManager.Remove(child);
|
||||
child.UnarmForRecord();
|
||||
foreach (var clip in child.GetClips())
|
||||
SelectionManager.Remove(clip);
|
||||
}
|
||||
}
|
||||
|
||||
// no need to rebuild, just repaint (including inspectors)
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
[MenuEntry("Show Markers", MenuPriority.TrackActionSection.showHideMarkers)]
|
||||
[ActiveInMode(TimelineModes.Default | TimelineModes.ReadOnly)]
|
||||
class ShowHideMarkers : TrackAction, IMenuChecked
|
||||
{
|
||||
public bool isChecked { get; private set; }
|
||||
|
||||
void UpdateCheckedStatus(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
isChecked = tracks.All(x => x.GetShowMarkers());
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
UpdateCheckedStatus(tracks);
|
||||
if (tracks.Any(x => x is GroupTrack) || tracks.Any(t => t.GetMarkerCount() == 0))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
{
|
||||
return ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any()) return false;
|
||||
|
||||
var hasUnlockedTracks = tracks.Any(x => !x.GetShowMarkers());
|
||||
ShowHide(tracks, hasUnlockedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ShowHide(IEnumerable<TrackAsset> tracks, bool shouldLock)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
track.SetShowTrackMarkers(shouldLock);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.WindowNeedsRedraw);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Mute selected track only", MenuPriority.TrackActionSection.muteSelected), UsedImplicitly]
|
||||
class MuteSelectedTrack : TrackAction, IMenuName
|
||||
{
|
||||
public static readonly string MuteSelectedText = L10n.Tr("Mute selected track only");
|
||||
public static readonly string UnmuteSelectedText = L10n.Tr("Unmute selected track only");
|
||||
|
||||
public string menuName { get; private set; }
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
UpdateMenuName(tracks);
|
||||
if (tracks.Any(track => TimelineUtility.IsParentMuted(track) || track is GroupTrack || !track.subTracksObjects.Any()))
|
||||
return ActionValidity.NotApplicable;
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return false;
|
||||
|
||||
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||||
Mute(tracks.Where(p => !(p is GroupTrack)).ToArray(), hasUnmutedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
menuName = tracks.All(t => t.muted) ? UnmuteSelectedText : MuteSelectedText;
|
||||
}
|
||||
|
||||
public static void Mute(TrackAsset[] tracks, bool shouldMute)
|
||||
{
|
||||
if (tracks.Length == 0)
|
||||
return;
|
||||
|
||||
foreach (var track in tracks.Where(t => !TimelineUtility.IsParentMuted(t)))
|
||||
{
|
||||
TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks"));
|
||||
track.muted = shouldMute;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Mute", MenuPriority.TrackActionSection.mute)]
|
||||
[Shortcut(Shortcuts.Timeline.toggleMute)]
|
||||
class MuteTrack : TrackAction, IMenuName
|
||||
{
|
||||
static readonly string k_MuteText = L10n.Tr("Mute");
|
||||
static readonly string k_UnMuteText = L10n.Tr("Unmute");
|
||||
|
||||
public string menuName { get; private set; }
|
||||
|
||||
void UpdateMenuName(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
menuName = tracks.Any(x => !x.muted) ? k_MuteText : k_UnMuteText;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (!tracks.Any() || tracks.Any(TimelineUtility.IsParentMuted))
|
||||
return false;
|
||||
|
||||
var hasUnmutedTracks = tracks.Any(x => !x.muted);
|
||||
Mute(tracks, hasUnmutedTracks);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
UpdateMenuName(tracks);
|
||||
if (tracks.Any(TimelineUtility.IsLockedFromGroup))
|
||||
return ActionValidity.Invalid;
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public static void Mute(IEnumerable<TrackAsset> tracks, bool shouldMute)
|
||||
{
|
||||
if (!tracks.Any())
|
||||
return;
|
||||
|
||||
foreach (var track in tracks)
|
||||
{
|
||||
if (track as GroupTrack == null)
|
||||
Mute(track.GetChildTracks().ToArray(), shouldMute);
|
||||
TimelineUndo.PushUndo(track, L10n.Tr("Mute Tracks"));
|
||||
track.muted = shouldMute;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteTracks : TrackAction
|
||||
{
|
||||
public static void Do(TimelineAsset timeline, TrackAsset track)
|
||||
{
|
||||
SelectionManager.Remove(track);
|
||||
TrackModifier.DeleteTrack(timeline, track);
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
|
||||
|
||||
// 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
|
||||
TimelineEditor.state.previewMode = false;
|
||||
|
||||
TimelineAnimationUtilities.UnlinkAnimationWindowFromTracks(tracks);
|
||||
|
||||
foreach (var track in tracks)
|
||||
Do(TimelineEditor.inspectedAsset, track);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class CopyTracksToClipboard : TrackAction
|
||||
{
|
||||
public static bool Do(TrackAsset[] tracks)
|
||||
{
|
||||
var action = new CopyTracksToClipboard();
|
||||
return action.Execute(tracks);
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
|
||||
TimelineEditor.clipboard.CopyTracks(tracks);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class DuplicateTracks : TrackAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks) => ActionValidity.Valid;
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
tracks = tracks.RemoveTimelineMarkerTrackFromList(TimelineEditor.inspectedAsset);
|
||||
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);
|
||||
}
|
||||
|
||||
if (TimelineEditor.inspectedDirector != null)
|
||||
{
|
||||
var binding = TimelineEditor.inspectedDirector.GetGenericBinding(track);
|
||||
if (binding != null)
|
||||
{
|
||||
TimelineUndo.PushUndo(TimelineEditor.inspectedDirector, L10n.Tr("Duplicate"));
|
||||
TimelineEditor.inspectedDirector.SetGenericBinding(newTrack, binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Remove Invalid Markers", MenuPriority.TrackActionSection.removeInvalidMarkers), UsedImplicitly]
|
||||
class RemoveInvalidMarkersAction : TrackAction
|
||||
{
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(target => target != null && target.GetMarkerCount() != target.GetMarkersRaw().Count()))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<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 = L10n.Tr("Active");
|
||||
|
||||
static readonly string k_ErrorParentString = L10n.Tr("The bound GameObject is a parent of the PlayableDirector.");
|
||||
static readonly string k_ErrorString = L10n.Tr("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 = L10n.TextContent("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,101 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[ApplyDefaultUndo("Match Offsets")]
|
||||
[MenuEntry("Match Offsets To Previous Clip", MenuPriority.CustomClipActionSection.matchPrevious), UsedImplicitly]
|
||||
class MatchOffsetsPreviousAction : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (clips == null || !clips.Any())
|
||||
return false;
|
||||
AnimationOffsetMenu.MatchClipsToPrevious(clips.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.GetParentTrack() != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.GetParentTrack().clips.Any(x => x.start < clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.GetParentTrack()) != null;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (clips.Any(c => IsValidClip(c, director)))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[ApplyDefaultUndo("Match Offsets")]
|
||||
[MenuEntry("Match Offsets To Next Clip", MenuPriority.CustomClipActionSection.matchNext), UsedImplicitly]
|
||||
class MatchOffsetsNextAction : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
AnimationOffsetMenu.MatchClipsToNext(clips.Where(x => IsValidClip(x, TimelineEditor.inspectedDirector)).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsValidClip(TimelineClip clip, PlayableDirector director)
|
||||
{
|
||||
return clip != null &&
|
||||
clip.GetParentTrack() != null &&
|
||||
(clip.asset as AnimationPlayableAsset) != null &&
|
||||
clip.GetParentTrack().clips.Any(x => x.start > clip.start) &&
|
||||
TimelineUtility.GetSceneGameObject(director, clip.GetParentTrack()) != null;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
var director = TimelineEditor.inspectedDirector;
|
||||
if (TimelineEditor.inspectedDirector == null)
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (clips.Any(c => IsValidClip(c, director)))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[ApplyDefaultUndo]
|
||||
[MenuEntry("Reset Offsets", MenuPriority.CustomClipActionSection.resetOffset), UsedImplicitly]
|
||||
class ResetOffsets : ClipAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
AnimationOffsetMenu.ResetClipOffsets(clips.Where(TimelineAnimationUtilities.IsAnimationClip).ToArray());
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
|
||||
{
|
||||
if (!clips.All(TimelineAnimationUtilities.IsAnimationClip))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bf22284ca28e7ef4490033b61e9b52cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,427 @@
|
|||
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)
|
||||
{
|
||||
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,34 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationClipExtensions
|
||||
{
|
||||
public static UInt64 ClipVersion(this AnimationClip clip)
|
||||
{
|
||||
if (clip == null)
|
||||
return 0;
|
||||
|
||||
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
|
||||
var version = (UInt32)info.version;
|
||||
var count = (UInt32)info.curves.Length;
|
||||
var result = (UInt64)version;
|
||||
result |= ((UInt64)count) << 32;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static CurveChangeType GetChangeType(this AnimationClip clip, ref UInt64 curveVersion)
|
||||
{
|
||||
var version = clip.ClipVersion();
|
||||
var changeType = CurveChangeType.None;
|
||||
if ((curveVersion >> 32) != (version >> 32))
|
||||
changeType = CurveChangeType.CurveAddedOrRemoved;
|
||||
else if (curveVersion != version)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
|
||||
curveVersion = version;
|
||||
return changeType;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c3b87750f9f94e8c918b71ec7d369bd4
|
||||
timeCreated: 1602859563
|
|
@ -0,0 +1,73 @@
|
|||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
static class AnimationOffsetMenu
|
||||
{
|
||||
public static string MatchFieldsPrefix = L10n.Tr("Match Offsets Fields/");
|
||||
|
||||
static bool EnforcePreviewMode()
|
||||
{
|
||||
TimelineEditor.state.previewMode = true; // try and set the preview mode
|
||||
if (!TimelineEditor.state.previewMode)
|
||||
{
|
||||
Debug.LogError("Match clips cannot be completed because preview mode cannot be enabed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void MatchClipsToPrevious(TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode())
|
||||
return;
|
||||
|
||||
clips = clips.OrderBy(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack());
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineAnimationUtilities.MatchPrevious(clip, sceneObject.transform, TimelineEditor.inspectedDirector);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
internal static void MatchClipsToNext(TimelineClip[] clips)
|
||||
{
|
||||
if (!EnforcePreviewMode())
|
||||
return;
|
||||
|
||||
clips = clips.OrderByDescending(x => x.start).ToArray();
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var sceneObject = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, clip.GetParentTrack());
|
||||
if (sceneObject != null)
|
||||
{
|
||||
TimelineAnimationUtilities.MatchNext(clip, sceneObject.transform, TimelineEditor.inspectedDirector);
|
||||
}
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
|
||||
public static void ResetClipOffsets(TimelineClip[] clips)
|
||||
{
|
||||
foreach (var clip in clips)
|
||||
{
|
||||
var asset = clip.asset as AnimationPlayableAsset;
|
||||
if (asset != null)
|
||||
asset.ResetOffsets();
|
||||
}
|
||||
|
||||
InspectorWindow.RepaintAllInspectors();
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 = L10n.Tr("No animation clip assigned");
|
||||
public static readonly string k_LegacyClipError = L10n.Tr("Legacy animation clips are not supported");
|
||||
static readonly string k_MotionCurveError = L10n.Tr("You are using motion curves without applyRootMotion enabled on the Animator. The root transform will not be animated");
|
||||
static readonly string k_RootCurveError = L10n.Tr("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.GetParentTrack() 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor.Timeline.Actions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[MenuEntry("Add Override Track", MenuPriority.CustomTrackActionSection.addOverrideTrack), UsedImplicitly]
|
||||
class AddOverrideTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
TimelineHelpers.CreateTrack(typeof(AnimationTrack), animTrack, "Override " + animTrack.GetChildTracks().Count());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => t.isSubTrack || !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Clip Track", MenuPriority.CustomTrackActionSection.convertToClipMode), UsedImplicitly]
|
||||
class ConvertToClipModeAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertToClipMode();
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertToClipMode()))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Convert To Infinite Clip", MenuPriority.CustomTrackActionSection.convertFromClipMode), UsedImplicitly]
|
||||
class ConvertFromClipTrackAction : TrackAction
|
||||
{
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
animTrack.ConvertFromClipMode(TimelineEditor.inspectedAsset);
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsAddedOrRemoved);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
return ActionValidity.Invalid;
|
||||
|
||||
if (tracks.OfType<AnimationTrack>().All(a => a.CanConvertFromClipMode()))
|
||||
return ActionValidity.Valid;
|
||||
|
||||
return ActionValidity.NotApplicable;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TrackOffsetBaseAction : TrackAction
|
||||
{
|
||||
public abstract TrackOffset trackOffset { get; }
|
||||
|
||||
public override ActionValidity Validate(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
if (tracks.Any(t => !t.GetType().IsAssignableFrom(typeof(AnimationTrack))))
|
||||
return ActionValidity.NotApplicable;
|
||||
|
||||
if (tracks.Any(t => t.lockedInHierarchy))
|
||||
{
|
||||
return ActionValidity.Invalid;
|
||||
}
|
||||
|
||||
return ActionValidity.Valid;
|
||||
}
|
||||
|
||||
public override bool Execute(IEnumerable<TrackAsset> tracks)
|
||||
{
|
||||
foreach (var animTrack in tracks.OfType<AnimationTrack>())
|
||||
{
|
||||
animTrack.UnarmForRecord();
|
||||
animTrack.trackOffset = trackOffset;
|
||||
}
|
||||
|
||||
TimelineEditor.Refresh(RefreshReason.ContentsModified);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Transform Offsets", MenuPriority.CustomTrackActionSection.applyTrackOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
class ApplyTransformOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplyTransformOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Apply Scene Offsets", MenuPriority.CustomTrackActionSection.applySceneOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
class ApplySceneOffsetAction : TrackOffsetBaseAction
|
||||
{
|
||||
public override TrackOffset trackOffset
|
||||
{
|
||||
get { return TrackOffset.ApplySceneOffsets; }
|
||||
}
|
||||
}
|
||||
|
||||
[MenuEntry("Track Offsets/Auto (Deprecated)", MenuPriority.CustomTrackActionSection.applyAutoOffset), UsedImplicitly]
|
||||
[ApplyDefaultUndo]
|
||||
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,179 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
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 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 HashSet<EditorCurveBinding>(AnimationPreviewUtilities.EditorCurveBindingComparer.Instance);
|
||||
foreach (int s in m_Selection)
|
||||
{
|
||||
var item = (CurveTreeViewNode)m_TreeView.FindItem(s);
|
||||
if (item != null && item.bindings != null)
|
||||
bindings.UnionWith(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,220 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using L10n = UnityEditor.Timeline.L10n;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class BindingTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
struct BindingGroup : IEquatable<BindingGroup>, IComparable<BindingGroup>
|
||||
{
|
||||
public readonly string GroupName;
|
||||
public readonly string Path;
|
||||
public readonly Type Type;
|
||||
|
||||
public BindingGroup(string path, string groupName, Type type)
|
||||
{
|
||||
Path = path;
|
||||
GroupName = groupName;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public string groupDisplayName => string.IsNullOrEmpty(Path) ? GroupName : string.Format($"{Path} : {GroupName}");
|
||||
public bool Equals(BindingGroup other) => GroupName == other.GroupName && Type == other.Type && Path == other.Path;
|
||||
public int CompareTo(BindingGroup other) => GetHashCode() - other.GetHashCode();
|
||||
public override bool Equals(object obj) => obj is BindingGroup other && Equals(other);
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashUtility.CombineHash(GroupName != null ? GroupName.GetHashCode() : 0, Type != null ? Type.GetHashCode() : 0, Path != null ? Path.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
static readonly string s_DefaultValue = L10n.Tr("{0} (Default Value)");
|
||||
|
||||
public const int RootID = int.MinValue;
|
||||
public const int GroupID = -1;
|
||||
|
||||
private readonly AnimationClip m_Clip;
|
||||
private readonly 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);
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
if (m_Clip == null)
|
||||
return;
|
||||
|
||||
var bindings = AnimationUtility.GetCurveBindings(m_Clip)
|
||||
.Union(AnimationUtility.GetObjectReferenceCurveBindings(m_Clip))
|
||||
.ToArray();
|
||||
|
||||
// a sorted linear list of nodes
|
||||
var results = bindings.GroupBy(GetBindingGroup, p => p, CreateTuple)
|
||||
.OrderBy(t => t.Item1.Path)
|
||||
.ThenBy(NamePrioritySort)
|
||||
// this makes component ordering match the animation window
|
||||
.ThenBy(t => t.Item1.Type.ToString())
|
||||
.ThenBy(t => t.Item1.GroupName).ToArray();
|
||||
|
||||
m_RootItem = new CurveTreeViewNode(RootID, null, "root", null)
|
||||
{
|
||||
children = new List<TreeViewItem>(1)
|
||||
};
|
||||
|
||||
if (results.Any())
|
||||
{
|
||||
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 key = r.Item1;
|
||||
var nodeBindings = r.Item2;
|
||||
|
||||
FillMissingTransformCurves(nodeBindings);
|
||||
if (nodeBindings.Count == 1)
|
||||
groupingNode.children.Add(CreateLeafNode(nodeBindings[0], groupingNode, PropertyName(nodeBindings[0], true)));
|
||||
else if (nodeBindings.Count > 1)
|
||||
{
|
||||
var childBindings = nodeBindings.OrderBy(BindingSort).ToArray();
|
||||
var parent = new CurveTreeViewNode(key.GetHashCode(), groupingNode, key.groupDisplayName, childBindings) {children = new List<TreeViewItem>()};
|
||||
groupingNode.children.Add(parent);
|
||||
foreach (var b in childBindings)
|
||||
parent.children.Add(CreateLeafNode(b, parent, PropertyName(b, false)));
|
||||
}
|
||||
}
|
||||
SetupRootNodeSettings();
|
||||
}
|
||||
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public void UpdateData()
|
||||
{
|
||||
m_TreeView.ReloadData();
|
||||
}
|
||||
|
||||
string GroupName(EditorCurveBinding binding)
|
||||
{
|
||||
var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
|
||||
return CleanUpArrayBinding(AnimationWindowUtility.NicifyPropertyGroupName(binding.type, propertyName), true);
|
||||
}
|
||||
|
||||
static string CleanUpArrayBinding(string propertyName, bool isGroup)
|
||||
{
|
||||
const string arrayIndicator = ".Array.data[";
|
||||
const string arrayDisplay = ".data[";
|
||||
|
||||
var arrayIndex = propertyName.LastIndexOf(arrayIndicator, StringComparison.Ordinal);
|
||||
if (arrayIndex == -1)
|
||||
return propertyName;
|
||||
if (isGroup)
|
||||
propertyName = propertyName.Substring(0, arrayIndex);
|
||||
return propertyName.Replace(arrayIndicator, arrayDisplay);
|
||||
}
|
||||
|
||||
string PropertyName(EditorCurveBinding binding, bool prependPathName)
|
||||
{
|
||||
var propertyName = m_CurveDataSource.ModifyPropertyDisplayName(binding.path, binding.propertyName);
|
||||
propertyName = CleanUpArrayBinding(AnimationWindowUtility.GetPropertyDisplayName(propertyName), false);
|
||||
if (binding.isPhantom)
|
||||
propertyName = string.Format(s_DefaultValue, propertyName);
|
||||
if (prependPathName && !string.IsNullOrEmpty(binding.path))
|
||||
propertyName = $"{binding.path} : {propertyName}";
|
||||
return propertyName;
|
||||
}
|
||||
|
||||
BindingGroup GetBindingGroup(EditorCurveBinding binding)
|
||||
{
|
||||
return new BindingGroup(binding.path ?? string.Empty, GroupName(binding), binding.type);
|
||||
}
|
||||
|
||||
static CurveTreeViewNode CreateLeafNode(EditorCurveBinding binding, TreeViewItem parent, string displayName)
|
||||
{
|
||||
return new CurveTreeViewNode(binding.GetHashCode(), parent, displayName, new[] { binding }, AnimationWindowUtility.ForceGrouping(binding));
|
||||
}
|
||||
|
||||
static void FillMissingTransformCurves(List<EditorCurveBinding> bindings)
|
||||
{
|
||||
if (!AnimationWindowUtility.IsActualTransformCurve(bindings[0]) || bindings.Count >= 3)
|
||||
return;
|
||||
|
||||
var binding = bindings[0];
|
||||
var prefixPropertyName = binding.propertyName.Split('.').First();
|
||||
|
||||
binding.isPhantom = true;
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".x")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".x";
|
||||
bindings.Insert(0, binding);
|
||||
}
|
||||
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".y")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".y";
|
||||
bindings.Insert(1, binding);
|
||||
}
|
||||
|
||||
if (!bindings.Any(p => p.propertyName.EndsWith(".z")))
|
||||
{
|
||||
binding.propertyName = prefixPropertyName + ".z";
|
||||
bindings.Insert(2, binding);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure vectors and colors are sorted correctly in their subgroups
|
||||
static int BindingSort(EditorCurveBinding b)
|
||||
{
|
||||
return AnimationWindowUtility.GetComponentIndex(b.propertyName);
|
||||
}
|
||||
|
||||
static int NamePrioritySort(ValueTuple<BindingGroup, List<EditorCurveBinding>> group)
|
||||
{
|
||||
if (group.Item1.Type != typeof(Transform))
|
||||
return 0;
|
||||
|
||||
switch (group.Item1.GroupName)
|
||||
{
|
||||
case "Position": return Int32.MinValue;
|
||||
case "Rotation": return Int32.MinValue + 1;
|
||||
case "Scale": return Int32.MinValue + 2;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static ValueTuple<BindingGroup, List<EditorCurveBinding>> CreateTuple(BindingGroup key, IEnumerable<EditorCurveBinding> items)
|
||||
{
|
||||
return new ValueTuple<BindingGroup, List<EditorCurveBinding>>()
|
||||
{
|
||||
Item1 = key,
|
||||
Item2 = items.ToList()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9c2177aaf0fde92439246adc2dc0bfa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,113 @@
|
|||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
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);
|
||||
static readonly Color s_PhantomPropertyLabelColor = new Color(0.0f, 0.8f, 0.8f, 1f);
|
||||
static readonly Texture2D s_DefaultScriptTexture = EditorGUIUtility.LoadIcon("cs Script Icon");
|
||||
static readonly Texture2D s_TrackDefault = EditorGUIUtility.LoadIcon("UnityEngine/ScriptableObject Icon");
|
||||
|
||||
public BindingTreeViewGUI(TreeViewController treeView)
|
||||
: base(treeView, true)
|
||||
{
|
||||
k_IconWidth = 13.0f;
|
||||
iconOverlayGUI += OnItemIconOverlay;
|
||||
}
|
||||
|
||||
public override void OnRowGUI(Rect rowRect, TreeViewItem node, int row, bool selected, bool focused)
|
||||
{
|
||||
Color originalColor = GUI.color;
|
||||
bool leafNode = node.parent != null && node.parent.id != BindingTreeViewDataSource.RootID && node.parent.id != BindingTreeViewDataSource.GroupID;
|
||||
GUI.color = Color.white;
|
||||
if (leafNode)
|
||||
{
|
||||
CurveTreeViewNode curveNode = node as CurveTreeViewNode;
|
||||
if (curveNode != null && curveNode.bindings.Any() && curveNode.bindings.First().isPhantom)
|
||||
GUI.color = s_PhantomPropertyLabelColor;
|
||||
else
|
||||
GUI.color = 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;
|
||||
}
|
||||
|
||||
static 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;
|
||||
|
||||
var type = node.iconType;
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
// track type icon
|
||||
if (typeof(TrackAsset).IsAssignableFrom(type))
|
||||
{
|
||||
var icon = TrackResourceCache.GetTrackIconForType(type);
|
||||
return icon == s_TrackDefault ? s_DefaultScriptTexture : icon;
|
||||
}
|
||||
|
||||
// custom clip icons always use the script texture
|
||||
if (typeof(PlayableAsset).IsAssignableFrom(type))
|
||||
return s_DefaultScriptTexture;
|
||||
|
||||
// this will return null for MonoBehaviours without a custom icon.
|
||||
// use the scripting icon instead
|
||||
return AssetPreview.GetMiniTypeThumbnail(type) ?? s_DefaultScriptTexture;
|
||||
}
|
||||
|
||||
static void OnItemIconOverlay(TreeViewItem item, Rect rect)
|
||||
{
|
||||
var curveNodeItem = item as CurveTreeViewNode;
|
||||
if (curveNodeItem != null && curveNodeItem.iconOverlay != null)
|
||||
GUI.Label(rect, curveNodeItem.iconOverlay);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c09dc5cd0a70cf40856b7d406106ee1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,367 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.Timeline;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor
|
||||
{
|
||||
class ClipCurveEditor
|
||||
{
|
||||
static readonly GUIContent s_RemoveCurveContent = new GUIContent(L10n.Tr("Remove Curve"));
|
||||
static readonly GUIContent s_RemoveCurvesContent = new GUIContent(L10n.Tr("Remove Curves"));
|
||||
|
||||
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;
|
||||
|
||||
UInt64 m_LastClipVersion = UInt64.MaxValue;
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
// called when curves are edited
|
||||
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;
|
||||
|
||||
// something changed, manage the undo properly.
|
||||
m_DataSource.ApplyCurveChanges(curvesToUpdate);
|
||||
m_LastClipVersion = m_DataSource.GetClipVersion();
|
||||
}
|
||||
|
||||
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));
|
||||
if (m_BindingHierarchy.treeViewController != null)
|
||||
m_BindingHierarchy.treeViewController.contextClickItemCallback = ContextClickItemCallback;
|
||||
GUILayout.EndScrollView();
|
||||
GUILayout.EndArea();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
void ContextClickItemCallback(int obj)
|
||||
{
|
||||
GenerateContextMenu(obj);
|
||||
}
|
||||
|
||||
void GenerateContextMenu(int obj = -1)
|
||||
{
|
||||
if (Event.current.type != EventType.ContextClick)
|
||||
return;
|
||||
|
||||
var selectedCurves = GetSelectedProperties().ToArray();
|
||||
if (selectedCurves.Length > 0)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
var content = selectedCurves.Length == 1 ? s_RemoveCurveContent : s_RemoveCurvesContent;
|
||||
menu.AddItem(content,
|
||||
false,
|
||||
() => RemoveCurves(selectedCurves)
|
||||
);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EditorCurveBinding> GetSelectedProperties(bool useForcedGroups = false)
|
||||
{
|
||||
var bindings = new HashSet<EditorCurveBinding>();
|
||||
var bindingTree = m_BindingHierarchy.treeViewController.data as BindingTreeViewDataSource;
|
||||
foreach (var selectedId in m_BindingHierarchy.treeViewController.GetSelection())
|
||||
{
|
||||
var node = bindingTree.FindItem(selectedId) as CurveTreeViewNode;
|
||||
if (node == null)
|
||||
continue;
|
||||
|
||||
var curveNodeParent = node.parent as CurveTreeViewNode;
|
||||
if (useForcedGroups && node.forceGroup && curveNodeParent != null)
|
||||
bindings.UnionWith(curveNodeParent.bindings);
|
||||
else
|
||||
bindings.UnionWith(node.bindings);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_DataSource.RemoveCurves(bindings);
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
TimelineWindow.instance.state.CalculateRowRects();
|
||||
m_LastClipVersion = m_DataSource.GetClipVersion();
|
||||
}
|
||||
|
||||
class CurveEditorState : ICurveEditorState
|
||||
{
|
||||
public TimeArea.TimeFormat timeFormat { get; set; }
|
||||
public Vector2 timeRange => new Vector2(0, 1);
|
||||
public bool rippleTime => false;
|
||||
}
|
||||
|
||||
void UpdateCurveEditorIfNeeded(WindowState state)
|
||||
{
|
||||
if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null))
|
||||
return;
|
||||
|
||||
// check if the curves have changed externally
|
||||
var curveChange = m_DataSource.UpdateExternalChanges(ref m_LastClipVersion);
|
||||
if (curveChange == CurveChangeType.None)
|
||||
return;
|
||||
|
||||
if (curveChange == CurveChangeType.CurveAddedOrRemoved)
|
||||
m_BindingHierarchy.RefreshTree();
|
||||
else // curve modified
|
||||
m_BindingHierarchy.RefreshCurves();
|
||||
|
||||
m_CurveEditor.InvalidateSelectionBounds();
|
||||
|
||||
m_CurveEditor.state = new CurveEditorState() {timeFormat = state.timeFormat.ToTimeAreaFormat()};
|
||||
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,439 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
enum CurveChangeType
|
||||
{
|
||||
None,
|
||||
CurveModified,
|
||||
CurveAddedOrRemoved
|
||||
}
|
||||
|
||||
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 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; }
|
||||
|
||||
// Applies changes from the visual curve in the curve wrapper back to the animation clips
|
||||
public virtual void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(animationClip, "Edit Clip Curve");
|
||||
foreach (CurveWrapper c in updatedCurves)
|
||||
{
|
||||
if (c.curve.length > 0)
|
||||
AnimationUtility.SetEditorCurve(animationClip, c.binding, c.curve);
|
||||
else
|
||||
RemoveCurves(new[] {c.binding});
|
||||
c.changed = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>The clip version is a value that will change when a curve gets updated.
|
||||
/// it's used to detect when an animation clip has been changed externally </summary>
|
||||
/// <returns>A versioning value indicating the state of the curve. If the curve is updated externally this value will change. </returns>
|
||||
public virtual UInt64 GetClipVersion()
|
||||
{
|
||||
return animationClip.ClipVersion();
|
||||
}
|
||||
|
||||
/// <summary>Call this method to check if the underlying clip has changed</summary>
|
||||
/// <param name="curveVersion">A versioning value. This will be updated to the latest version</param>
|
||||
/// <returns>A value indicating how the clip has changed</returns>
|
||||
public virtual CurveChangeType UpdateExternalChanges(ref UInt64 curveVersion)
|
||||
{
|
||||
return animationClip.GetChangeType(ref curveVersion);
|
||||
}
|
||||
|
||||
public virtual string ModifyPropertyDisplayName(string path, string propertyName) => propertyName;
|
||||
|
||||
public virtual void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(animationClip, "Remove Curve(s)");
|
||||
foreach (var binding in bindings)
|
||||
{
|
||||
if (binding.isPPtrCurve)
|
||||
AnimationUtility.SetObjectReferenceCurve(animationClip, binding, null);
|
||||
else
|
||||
AnimationUtility.SetEditorCurve(animationClip, binding, null);
|
||||
}
|
||||
}
|
||||
|
||||
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(IEnumerable<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());
|
||||
}
|
||||
|
||||
public override string ModifyPropertyDisplayName(string path, string propertyName)
|
||||
{
|
||||
if (!AnimatedPropertyUtility.IsMaterialProperty(propertyName))
|
||||
return propertyName;
|
||||
|
||||
var track = m_ClipGUI.clip.GetParentTrack();
|
||||
if (track == null)
|
||||
return propertyName;
|
||||
|
||||
var gameObjectBinding = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track);
|
||||
if (gameObjectBinding == null)
|
||||
return propertyName;
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
var transform = gameObjectBinding.transform.Find(path);
|
||||
if (transform == null)
|
||||
return propertyName;
|
||||
gameObjectBinding = transform.gameObject;
|
||||
}
|
||||
|
||||
return AnimatedPropertyUtility.RemapMaterialName(gameObjectBinding, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
class ClipParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Clip Properties");
|
||||
|
||||
readonly TimelineClipGUI m_ClipGUI;
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
|
||||
private int m_ClipDirtyVersion;
|
||||
|
||||
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 UInt64 GetClipVersion()
|
||||
{
|
||||
return sourceAnimationClip.ClipVersion();
|
||||
}
|
||||
|
||||
public override CurveChangeType UpdateExternalChanges(ref ulong curveVersion)
|
||||
{
|
||||
if (m_ClipGUI == null || m_ClipGUI.clip == null)
|
||||
return CurveChangeType.None;
|
||||
|
||||
var changeType = sourceAnimationClip.GetChangeType(ref curveVersion);
|
||||
if (changeType != CurveChangeType.None)
|
||||
{
|
||||
m_CurvesProxy.ApplyExternalChangesToProxy();
|
||||
}
|
||||
else if (m_ClipDirtyVersion != m_ClipGUI.clip.DirtyIndex)
|
||||
{
|
||||
m_CurvesProxy.UpdateProxyCurves();
|
||||
if (changeType == CurveChangeType.None)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
}
|
||||
m_ClipDirtyVersion = m_ClipGUI.clip.DirtyIndex;
|
||||
return changeType;
|
||||
}
|
||||
|
||||
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 RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_CurvesProxy.RemoveCurves(bindings);
|
||||
}
|
||||
|
||||
public override void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
protected override int CreateHashCode()
|
||||
{
|
||||
return base.CreateHashCode().CombineHash(m_ClipGUI.clip.GetHashCode());
|
||||
}
|
||||
|
||||
private AnimationClip sourceAnimationClip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ClipGUI == null || m_ClipGUI.clip == null || m_ClipGUI.clip.curves == null)
|
||||
return null;
|
||||
return m_ClipGUI.clip.curves;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
}
|
||||
|
||||
public override string ModifyPropertyDisplayName(string path, string propertyName)
|
||||
{
|
||||
if (m_AnimationTrack == null || !AnimatedPropertyUtility.IsMaterialProperty(propertyName))
|
||||
return propertyName;
|
||||
|
||||
var binding = m_AnimationTrack.GetBinding(TimelineEditor.inspectedDirector);
|
||||
if (binding == null)
|
||||
return propertyName;
|
||||
|
||||
var target = binding.transform;
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
target = target.Find(path);
|
||||
|
||||
if (target == null)
|
||||
return propertyName;
|
||||
|
||||
return AnimatedPropertyUtility.RemapMaterialName(target.gameObject, propertyName);
|
||||
}
|
||||
}
|
||||
|
||||
class TrackParametersCurveDataSource : CurveDataSource
|
||||
{
|
||||
static readonly string k_GroupingName = L10n.Tr("Track Properties");
|
||||
|
||||
readonly CurvesProxy m_CurvesProxy;
|
||||
private int m_TrackDirtyVersion;
|
||||
|
||||
public TrackParametersCurveDataSource(IRowGUI trackGui) : base(trackGui)
|
||||
{
|
||||
m_CurvesProxy = new CurvesProxy(trackGui.asset);
|
||||
}
|
||||
|
||||
public override AnimationClip animationClip
|
||||
{
|
||||
get { return m_CurvesProxy.curves; }
|
||||
}
|
||||
|
||||
public override UInt64 GetClipVersion()
|
||||
{
|
||||
return sourceAnimationClip.ClipVersion();
|
||||
}
|
||||
|
||||
public override CurveChangeType UpdateExternalChanges(ref ulong curveVersion)
|
||||
{
|
||||
if (m_CurvesProxy.targetTrack == null)
|
||||
return CurveChangeType.None;
|
||||
|
||||
var changeType = sourceAnimationClip.GetChangeType(ref curveVersion);
|
||||
if (changeType != CurveChangeType.None)
|
||||
{
|
||||
m_CurvesProxy.ApplyExternalChangesToProxy();
|
||||
}
|
||||
// track property has changed externally, update the curve proxies
|
||||
else if (m_TrackDirtyVersion != m_CurvesProxy.targetTrack.DirtyIndex)
|
||||
{
|
||||
if (changeType == CurveChangeType.None)
|
||||
changeType = CurveChangeType.CurveModified;
|
||||
m_CurvesProxy.UpdateProxyCurves();
|
||||
}
|
||||
m_TrackDirtyVersion = m_CurvesProxy.targetTrack.DirtyIndex;
|
||||
return changeType;
|
||||
}
|
||||
|
||||
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 RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
m_CurvesProxy.RemoveCurves(bindings);
|
||||
}
|
||||
|
||||
public override void ApplyCurveChanges(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
m_CurvesProxy.UpdateCurves(updatedCurves);
|
||||
}
|
||||
|
||||
protected override void ConfigureCurveWrapper(CurveWrapper wrapper)
|
||||
{
|
||||
m_CurvesProxy.ConfigureCurveWrapper(wrapper);
|
||||
}
|
||||
|
||||
private AnimationClip sourceAnimationClip
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CurvesProxy.targetTrack == null || m_CurvesProxy.targetTrack.curves == null)
|
||||
return null;
|
||||
return m_CurvesProxy.targetTrack.curves;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 87a1ae9719ec25d44a4dbec20ec0f892
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,39 @@
|
|||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
class CurveTreeViewNode : TreeViewItem
|
||||
{
|
||||
public bool forceGroup { get; }
|
||||
public System.Type iconType { get; }
|
||||
public GUIContent iconOverlay { get; }
|
||||
|
||||
EditorCurveBinding[] m_Bindings;
|
||||
|
||||
public EditorCurveBinding[] bindings
|
||||
{
|
||||
get { return m_Bindings; }
|
||||
}
|
||||
|
||||
public CurveTreeViewNode(int id, TreeViewItem parent, string displayName, EditorCurveBinding[] bindings, bool _forceGroup = false)
|
||||
: base(id, parent != null ? parent.depth + 1 : -1, parent, displayName)
|
||||
{
|
||||
m_Bindings = bindings;
|
||||
forceGroup = _forceGroup;
|
||||
|
||||
|
||||
// capture the preview icon type. If all subbindings are the same type, use that. Otherwise use null as a default
|
||||
iconType = null;
|
||||
if (parent != null && parent.depth >= 0 && bindings != null && bindings.Length > 0 && bindings.All(b => b.type == bindings[0].type))
|
||||
{
|
||||
iconType = bindings[0].type;
|
||||
|
||||
// for components put the component type in a tooltip
|
||||
if (iconType != null && typeof(Component).IsAssignableFrom(iconType))
|
||||
iconOverlay = new GUIContent(string.Empty, ObjectNames.NicifyVariableName(iconType.Name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1ec45eaf72174ca189827d7896952167
|
||||
timeCreated: 1599683561
|
|
@ -0,0 +1,358 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0 || !m_OriginalOwner.hasCurves)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, L10n.Tr("Remove Clip Curve"));
|
||||
foreach (var binding in bindings)
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
RebuildProxyCurves();
|
||||
}
|
||||
|
||||
public void UpdateCurves(IEnumerable<CurveWrapper> updatedCurves)
|
||||
{
|
||||
if (m_ProxyIsRebuilding > 0)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.asset, L10n.Tr("Edit Clip Curve"));
|
||||
if (m_OriginalOwner.curves != null)
|
||||
Undo.RegisterCompleteObjectUndo(m_OriginalOwner.curves, L10n.Tr("Edit Clip Curve"));
|
||||
|
||||
var requireRebuild = false;
|
||||
foreach (var curve in updatedCurves)
|
||||
{
|
||||
requireRebuild |= curve.curve.length == 0;
|
||||
UpdateCurve(curve.binding, curve.curve);
|
||||
}
|
||||
|
||||
if (requireRebuild)
|
||||
m_OriginalOwner.SanitizeCurvesData();
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
}
|
||||
|
||||
public void ApplyExternalChangesToProxy()
|
||||
{
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_OriginalOwner.curves == null)
|
||||
return;
|
||||
|
||||
var curveInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_OriginalOwner.curves);
|
||||
for (int i = 0; i < curveInfo.bindings.Length; i++)
|
||||
{
|
||||
if (curveInfo.curves[i] != null && curveInfo.curves.Length != 0)
|
||||
{
|
||||
if (m_PropertiesMap.TryGetValue(curveInfo.bindings[i], out var prop) && AnimatedParameterUtility.IsParameterAnimatable(prop))
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, curveInfo.bindings[i], curveInfo.curves[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
ApplyConstraints(binding, curve);
|
||||
|
||||
if (curve.length == 0)
|
||||
{
|
||||
HandleAllKeysDeleted(binding);
|
||||
return;
|
||||
}
|
||||
|
||||
// there is no curve in the animation clip, this is a proxy curve
|
||||
if (IsConstantCurve(binding, curve))
|
||||
HandleConstantCurveValueChanged(binding, curve);
|
||||
else
|
||||
HandleCurveUpdated(binding, curve);
|
||||
}
|
||||
|
||||
bool IsConstantCurve(EditorCurveBinding binding, AnimationCurve curve)
|
||||
{
|
||||
if (curve.length != 1)
|
||||
return false;
|
||||
return m_OriginalOwner.curves == null || AnimationUtility.GetEditorCurve(m_OriginalOwner.curves, binding) == null;
|
||||
}
|
||||
|
||||
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);
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, updatedCurve);
|
||||
}
|
||||
|
||||
void HandleConstantCurveValueChanged(EditorCurveBinding binding, AnimationCurve updatedCurve)
|
||||
{
|
||||
var prop = m_PropertiesMap[binding];
|
||||
if (prop == null)
|
||||
return;
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(prop.serializedObject.targetObject, L10n.Tr("Edit Clip Curve"));
|
||||
prop.serializedObject.UpdateIfRequiredOrScript();
|
||||
CurveEditUtility.SetFromKeyValue(prop, updatedCurve.keys[0].value);
|
||||
prop.serializedObject.ApplyModifiedProperties();
|
||||
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, updatedCurve);
|
||||
}
|
||||
|
||||
void HandleAllKeysDeleted(EditorCurveBinding binding)
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
// Remove curve from original asset
|
||||
AnimationUtility.SetEditorCurve(m_OriginalOwner.curves, binding, null);
|
||||
SetProxyCurve(m_PropertiesMap[binding], binding);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
var parameters = m_OriginalOwner.GetAllAnimatableParameters().ToArray();
|
||||
foreach (var param in parameters)
|
||||
CreateProxyCurve(param, m_ProxyCurves, m_OriginalOwner.asset, param.propertyPath);
|
||||
|
||||
AnimationClipCurveCache.Instance.GetCurveInfo(m_ProxyCurves).dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// updates the just the proxied values. This can be called when the asset changes, so the proxy values are properly updated
|
||||
public void UpdateProxyCurves()
|
||||
{
|
||||
if (!m_IsAnimatable || m_ProxyCurves == null || m_ProxyCurves.empty)
|
||||
return;
|
||||
|
||||
AnimatedParameterUtility.UpdateSerializedPlayableAsset(m_OriginalOwner.asset);
|
||||
var parameters = m_OriginalOwner.GetAllAnimatableParameters().ToArray();
|
||||
using (new RebuildGuard(this))
|
||||
{
|
||||
if (m_OriginalOwner.hasCurves)
|
||||
{
|
||||
var bindingInfo = AnimationClipCurveCache.Instance.GetCurveInfo(m_OriginalOwner.curves);
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var binding = AnimatedParameterUtility.GetCurveBinding(m_OriginalOwner.asset, param.propertyPath);
|
||||
if (!bindingInfo.bindings.Contains(binding, AnimationPreviewUtilities.EditorCurveBindingComparer.Instance))
|
||||
SetProxyCurve(param, AnimatedParameterUtility.GetCurveBinding(m_OriginalOwner.asset, param.propertyPath));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
SetProxyCurve(param, AnimatedParameterUtility.GetCurveBinding(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
|
||||
{
|
||||
SetProxyCurve(prop, binding);
|
||||
}
|
||||
|
||||
m_PropertiesMap[binding] = prop;
|
||||
}
|
||||
|
||||
void SetProxyCurve(SerializedProperty prop, EditorCurveBinding binding)
|
||||
{
|
||||
var curve = new AnimationCurve();
|
||||
CurveEditUtility.AddKeyFrameToCurve(
|
||||
curve, 0.0f, m_ProxyCurves.frameRate, CurveEditUtility.GetKeyValue(prop),
|
||||
prop.propertyType == SerializedPropertyType.Boolean);
|
||||
AnimationUtility.SetEditorCurve(m_ProxyCurves, binding, curve);
|
||||
}
|
||||
|
||||
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.GetParentTrack().clips)
|
||||
{
|
||||
if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start))
|
||||
previousClip = c;
|
||||
}
|
||||
return previousClip;
|
||||
}
|
||||
|
||||
public static TimelineClip GetNextClip(TimelineClip clip)
|
||||
{
|
||||
return clip.GetParentTrack().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.GetParentTrack() 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.GetParentTrack() 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.GetParentTrack() 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(TimelineClip clip)
|
||||
{
|
||||
var animationWindow = EditorWindow.GetWindow<AnimationWindow>();
|
||||
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>();
|
||||
timeController.Init(animationWindow.state, clip);
|
||||
return timeController;
|
||||
}
|
||||
|
||||
public static TimelineWindowTimeControl CreateTimeController(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,26 @@
|
|||
using System;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Define the activeness of an action depending on its timeline mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="TimelineModes"/>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class ActiveInModeAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Modes that will be used for activeness of an action.
|
||||
/// </summary>
|
||||
public TimelineModes modes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines in which mode the action will be active.
|
||||
/// </summary>
|
||||
/// <param name="timelineModes">Modes that will define activeness of the action.</param>
|
||||
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,51 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this attribute to add a menu item to a context menu.
|
||||
/// Used to indicate path and priority that are auto added to the menu
|
||||
/// (examples can be found on <see href="https://docs.unity3d.com/ScriptReference/MenuItem.html"/>).
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code source="../../DocCodeExamples/TimelineAttributesExamples.cs" region="declare-menuEntryAttribute" title="menuEntryAttr"/>
|
||||
/// </example>
|
||||
/// <remarks>
|
||||
/// Unlike Menu item, MenuEntryAttribute doesn't handle shortcuts in the menu name. See <see cref="TimelineShortcutAttribute"/>.
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class MenuEntryAttribute : Attribute
|
||||
{
|
||||
internal readonly int priority;
|
||||
internal readonly string name;
|
||||
internal readonly string subMenuPath;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for Menu Entry Attribute to define information about the menu item for an action.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the menu. If there is a "/" in the path, it will create one (or multiple) submenu items.</param>
|
||||
/// <param name="priority">Priority to decide where the menu will be positioned in the menu.
|
||||
/// The lower the priority, the higher the menu item will be in the context menu.
|
||||
/// </param>
|
||||
/// <seealso cref="MenuPriority"/>
|
||||
public MenuEntryAttribute(string path = default, int priority = MenuPriority.defaultPriority)
|
||||
{
|
||||
path = path ?? string.Empty;
|
||||
path = L10n.Tr(path);
|
||||
this.priority = priority;
|
||||
|
||||
var 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,24 @@
|
|||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Timeline.Actions
|
||||
{
|
||||
/// <summary>
|
||||
/// Use this attribute to make an action work with the shortcut system.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// TimelineShortcutAttribute needs to be added to a static method.
|
||||
/// <code source="../../DocCodeExamples/TimelineAttributesExamples.cs" region="declare-timelineShortcutAttr" title="TimelineShortcutAttr"/>
|
||||
/// </example>
|
||||
public class TimelineShortcutAttribute : ShortcutManagement.ShortcutAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// TimelineShortcutAttribute Constructor
|
||||
/// </summary>
|
||||
/// <param name="id">Id to register the shortcut. It will automatically be prefix by 'Timeline/' in order to be in the 'Timeline' section of the shortcut manager.</param>
|
||||
/// <param name="defaultKeyCode">Optional key code for default binding.</param>
|
||||
/// <param name="defaultShortcutModifiers">Optional shortcut modifiers for default binding.</param>
|
||||
public TimelineShortcutAttribute(string id, KeyCode defaultKeyCode, ShortcutModifiers defaultShortcutModifiers = ShortcutModifiers.None)
|
||||
: base("Timeline/" + id, typeof(TimelineWindow), defaultKeyCode, defaultShortcutModifiers) {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bbfc068399dbc814d96ebf991a1e5764
|
||||
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,84 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Timeline;
|
||||
#if !UNITY_2020_2_OR_NEWER
|
||||
using L10n = UnityEditor.Timeline.L10n;
|
||||
#endif
|
||||
|
||||
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") +
|
||||
L10n.Tr("Calculated from:\n") +
|
||||
k_Indent + L10n.Tr("Clip: {1}\n") +
|
||||
k_Indent + L10n.Tr("Track: {2}");
|
||||
|
||||
public static string audioSourceContribution = k_Indent + L10n.Tr("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.GetParentTrack() == null)
|
||||
return;
|
||||
|
||||
var clipVolume = volumeProp.floatValue;
|
||||
var trackVolume = new SerializedObject(clip.GetParentTrack()).FindProperty("m_TrackProperties.volume").floatValue;
|
||||
var binding = TimelineEditor.inspectedDirector.GetGenericBinding(clip.GetParentTrack()) 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,104 @@
|
|||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Timeline;
|
||||
|
||||
namespace UnityEditor.Timeline
|
||||
{
|
||||
[CustomTimelineEditor(typeof(AudioPlayableAsset)), UsedImplicitly]
|
||||
class AudioPlayableAssetEditor : ClipEditor
|
||||
{
|
||||
readonly string k_NoClipAssignedError = L10n.Tr("No audio clip assigned");
|
||||
readonly Dictionary<TimelineClip, WaveformPreview> m_PersistentPreviews = new Dictionary<TimelineClip, WaveformPreview>();
|
||||
ColorSpace m_ColorSpace = ColorSpace.Uninitialized;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 = GetOrCreateWaveformPreview(clip, audioClip, quantizedRect, region.startTime, region.endTime);
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
DrawWaveformPreview(preview, quantizedRect);
|
||||
}
|
||||
|
||||
public WaveformPreview GetOrCreateWaveformPreview(TimelineClip clip, AudioClip audioClip, Rect rect, double startTime, double endTime)
|
||||
{
|
||||
if (QualitySettings.activeColorSpace != m_ColorSpace)
|
||||
{
|
||||
m_ColorSpace = QualitySettings.activeColorSpace;
|
||||
m_PersistentPreviews.Clear();
|
||||
}
|
||||
|
||||
bool previewExists = m_PersistentPreviews.TryGetValue(clip, out WaveformPreview preview);
|
||||
bool audioClipHasChanged = preview != null && audioClip != preview.presentedObject;
|
||||
if (!previewExists || audioClipHasChanged)
|
||||
{
|
||||
if (AssetDatabase.Contains(audioClip))
|
||||
preview = CreateWaveformPreview(audioClip, rect);
|
||||
m_PersistentPreviews[clip] = preview;
|
||||
}
|
||||
|
||||
if (preview == null)
|
||||
return null;
|
||||
|
||||
preview.looping = clip.SupportsLooping();
|
||||
preview.SetTimeInfo(startTime, endTime - startTime);
|
||||
preview.OptimizeForSize(rect.size);
|
||||
return preview;
|
||||
}
|
||||
|
||||
public static void DrawWaveformPreview(WaveformPreview preview, Rect rect)
|
||||
{
|
||||
if (preview != null)
|
||||
{
|
||||
preview.ApplyModifications();
|
||||
preview.Render(rect);
|
||||
}
|
||||
}
|
||||
|
||||
static WaveformPreview CreateWaveformPreview(AudioClip audioClip, Rect quantizedRect)
|
||||
{
|
||||
WaveformPreview preview = 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);
|
||||
return preview;
|
||||
}
|
||||
|
||||
static Color GammaCorrect(Color color)
|
||||
{
|
||||
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.gamma : color;
|
||||
}
|
||||
}
|
||||
}
|
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