com.wallstop-studios.unity-helpers 2.0.0-rc75.9 → 2.0.0-rc76.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Editor/CustomDrawers/IntDropdownDrawer.cs +46 -0
- package/Editor/CustomDrawers/IntDropdownDrawer.cs.meta +3 -0
- package/Editor/{StringInListeDrawer.cs → CustomDrawers/StringInListeDrawer.cs} +2 -2
- package/Editor/{WShowIfPropertyDrawer.cs → CustomDrawers/WShowIfPropertyDrawer.cs} +3 -3
- package/Editor/CustomDrawers.meta +3 -0
- package/Editor/Sprites/AnimationViewerWindow.cs +1521 -0
- package/Editor/Sprites/AnimationViewerWindow.cs.meta +3 -0
- package/Editor/Sprites/ProjectAnimationSettings.cs +50 -0
- package/Editor/Sprites/ProjectAnimationSettings.cs.meta +3 -0
- package/Editor/Sprites/ScriptableSpriteAtlas.cs +4 -2
- package/Editor/Sprites/ScriptableSpriteAtlasEditor.cs +1 -9
- package/Editor/Styles/AnimationViewer.uss +116 -0
- package/Editor/Styles/AnimationViewer.uss.meta +3 -0
- package/Editor/Styles/AnimationViewer.uxml +57 -0
- package/Editor/Styles/AnimationViewer.uxml.meta +3 -0
- package/Editor/Styles.meta +3 -0
- package/Editor/UI.meta +3 -0
- package/Runtime/Core/Attributes/IntDropdownAttribute.cs +14 -0
- package/Runtime/Core/Attributes/IntDropdownAttribute.cs.meta +3 -0
- package/Runtime/UI/LayeredImage.cs +193 -98
- package/Runtime/UI/MultiFileSelectorElement.cs +322 -0
- package/Runtime/UI/MultiFileSelectorElement.cs.meta +3 -0
- package/package.json +3 -1
- package/Editor/AnimatorControllerCopier.cs +0 -156
- package/Editor/AnimatorControllerCopier.cs.meta +0 -3
- /package/Editor/{StringInListeDrawer.cs.meta → CustomDrawers/StringInListeDrawer.cs.meta} +0 -0
- /package/Editor/{WShowIfPropertyDrawer.cs.meta → CustomDrawers/WShowIfPropertyDrawer.cs.meta} +0 -0
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
// ReSharper disable HeapView.CanAvoidClosure
|
|
2
|
+
namespace WallstopStudios.UnityHelpers.Editor.Sprites
|
|
3
|
+
{
|
|
4
|
+
#if UNITY_EDITOR
|
|
5
|
+
using System;
|
|
6
|
+
using System.Collections.Generic;
|
|
7
|
+
using System.IO;
|
|
8
|
+
using System.Linq;
|
|
9
|
+
using Core.Extension;
|
|
10
|
+
using Core.Helper;
|
|
11
|
+
using UnityEditor;
|
|
12
|
+
using UnityEditor.UIElements;
|
|
13
|
+
using UnityEngine;
|
|
14
|
+
using UnityEngine.UIElements;
|
|
15
|
+
using UI;
|
|
16
|
+
using Object = UnityEngine.Object;
|
|
17
|
+
|
|
18
|
+
public sealed class AnimationViewerWindow : EditorWindow
|
|
19
|
+
{
|
|
20
|
+
private const string PackageId = "com.wallstop-studios.unity-helpers";
|
|
21
|
+
private const float DragThresholdSqrMagnitude = 10f * 10f;
|
|
22
|
+
private const int InvalidPointerId = -1;
|
|
23
|
+
|
|
24
|
+
private sealed class EditorLayerData
|
|
25
|
+
{
|
|
26
|
+
public AnimationClip SourceClip { get; }
|
|
27
|
+
public List<Sprite> Sprites { get; }
|
|
28
|
+
public string ClipName => SourceClip != null ? SourceClip.name : "Unnamed Layer";
|
|
29
|
+
public float OriginalClipFps { get; }
|
|
30
|
+
public string BindingPath { get; }
|
|
31
|
+
|
|
32
|
+
public EditorLayerData(AnimationClip clip)
|
|
33
|
+
{
|
|
34
|
+
SourceClip = clip;
|
|
35
|
+
Sprites = clip.GetSpritesFromClip()?.ToList();
|
|
36
|
+
OriginalClipFps =
|
|
37
|
+
clip.frameRate > 0 ? clip.frameRate : AnimatedSpriteLayer.FrameRate;
|
|
38
|
+
|
|
39
|
+
BindingPath = string.Empty;
|
|
40
|
+
if (SourceClip != null)
|
|
41
|
+
{
|
|
42
|
+
foreach (
|
|
43
|
+
EditorCurveBinding binding in AnimationUtility.GetObjectReferenceCurveBindings(
|
|
44
|
+
SourceClip
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
{
|
|
48
|
+
if (
|
|
49
|
+
binding.type == typeof(SpriteRenderer)
|
|
50
|
+
&& binding.propertyName == "m_Sprite"
|
|
51
|
+
)
|
|
52
|
+
{
|
|
53
|
+
BindingPath = binding.path;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[MenuItem("Tools/Wallstop Studios/Unity Helpers/Sprite Animation Editor")]
|
|
62
|
+
public static void ShowWindow()
|
|
63
|
+
{
|
|
64
|
+
AnimationViewerWindow wnd = GetWindow<AnimationViewerWindow>();
|
|
65
|
+
wnd.titleContent = new GUIContent("2D Animation Viewer");
|
|
66
|
+
wnd.minSize = new Vector2(750, 500);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private VisualTreeAsset _visualTree;
|
|
70
|
+
private StyleSheet _styleSheet;
|
|
71
|
+
|
|
72
|
+
private ObjectField _addAnimationClipField;
|
|
73
|
+
private Button _browseAndAddButton;
|
|
74
|
+
private FloatField _fpsField;
|
|
75
|
+
private Button _applyFpsButton;
|
|
76
|
+
private Button _saveClipButton;
|
|
77
|
+
private VisualElement _loadedClipsContainer;
|
|
78
|
+
private VisualElement _previewPanelHost;
|
|
79
|
+
private LayeredImage _animationPreview;
|
|
80
|
+
private VisualElement _framesContainer;
|
|
81
|
+
private Label _fpsDebugLabel;
|
|
82
|
+
private Label _framesPanelTitle;
|
|
83
|
+
private MultiFileSelectorElement _fileSelector;
|
|
84
|
+
|
|
85
|
+
private readonly List<EditorLayerData> _loadedEditorLayers = new();
|
|
86
|
+
|
|
87
|
+
private EditorLayerData _activeEditorLayer;
|
|
88
|
+
private float _currentPreviewFps = AnimatedSpriteLayer.FrameRate;
|
|
89
|
+
|
|
90
|
+
private VisualElement _draggedFrameElement;
|
|
91
|
+
private int _draggedFrameOriginalDataIndex;
|
|
92
|
+
private VisualElement _frameDropPlaceholder;
|
|
93
|
+
|
|
94
|
+
private VisualElement _draggedLoadedClipElement;
|
|
95
|
+
private int _draggedLoadedClipOriginalIndex;
|
|
96
|
+
private VisualElement _loadedClipDropPlaceholder;
|
|
97
|
+
|
|
98
|
+
private bool _isClipDragPending;
|
|
99
|
+
private Vector3 _clipDragStartPosition;
|
|
100
|
+
private VisualElement _clipDragPendingElement;
|
|
101
|
+
private int _clipDragPendingOriginalIndex;
|
|
102
|
+
|
|
103
|
+
public void CreateGUI()
|
|
104
|
+
{
|
|
105
|
+
VisualElement root = rootVisualElement;
|
|
106
|
+
|
|
107
|
+
TryLoadStyleSheets();
|
|
108
|
+
|
|
109
|
+
if (_visualTree == null)
|
|
110
|
+
{
|
|
111
|
+
root.Add(new Label("Error: AnimationViewer.uxml not found."));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (_styleSheet != null)
|
|
115
|
+
{
|
|
116
|
+
root.styleSheets.Add(_styleSheet);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_visualTree.CloneTree(root);
|
|
120
|
+
|
|
121
|
+
_addAnimationClipField = root.Q<ObjectField>("addAnimationClipField");
|
|
122
|
+
_browseAndAddButton = root.Q<Button>("browseAndAddButton");
|
|
123
|
+
_fpsField = root.Q<FloatField>("fpsField");
|
|
124
|
+
_applyFpsButton = root.Q<Button>("applyFpsButton");
|
|
125
|
+
_saveClipButton = root.Q<Button>("saveClipButton");
|
|
126
|
+
_loadedClipsContainer = root.Q<VisualElement>("loadedClipsContainer");
|
|
127
|
+
_previewPanelHost = root.Q<VisualElement>("preview-panel");
|
|
128
|
+
_framesContainer = root.Q<VisualElement>("framesContainer");
|
|
129
|
+
_fpsDebugLabel = root.Q<Label>("fpsDebugLabel");
|
|
130
|
+
_framesPanelTitle = root.Q<Label>("framesPanelTitle");
|
|
131
|
+
|
|
132
|
+
_previewPanelHost.AddToClassList("animation-preview-container");
|
|
133
|
+
|
|
134
|
+
_fpsField.value = _currentPreviewFps;
|
|
135
|
+
_saveClipButton.SetEnabled(false);
|
|
136
|
+
|
|
137
|
+
_addAnimationClipField.RegisterValueChangedCallback(OnAddAnimationClipFieldChanged);
|
|
138
|
+
_browseAndAddButton.text = "Add Selected Clips from Project";
|
|
139
|
+
_browseAndAddButton.clicked -= OnBrowseAndAddClicked;
|
|
140
|
+
if (_browseAndAddButton != null)
|
|
141
|
+
{
|
|
142
|
+
_browseAndAddButton.text = "Browse Clips (Multi)...";
|
|
143
|
+
_browseAndAddButton.clicked -= OnAddSelectedClipsFromProjectClicked;
|
|
144
|
+
_browseAndAddButton.clicked += ToggleMultiFileSelector;
|
|
145
|
+
}
|
|
146
|
+
else
|
|
147
|
+
{
|
|
148
|
+
this.LogError(
|
|
149
|
+
$"'browseAndAddButton' not found in UXML. Multi-file browser cannot be triggered."
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
_applyFpsButton.clicked += OnApplyFpsToPreviewClicked;
|
|
153
|
+
_saveClipButton.clicked += OnSaveClipClicked;
|
|
154
|
+
|
|
155
|
+
_frameDropPlaceholder = new VisualElement();
|
|
156
|
+
_frameDropPlaceholder.AddToClassList("drop-placeholder");
|
|
157
|
+
_frameDropPlaceholder.style.height = 5;
|
|
158
|
+
_frameDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
159
|
+
|
|
160
|
+
_framesContainer.RegisterCallback<DragUpdatedEvent>(OnFramesContainerDragUpdated);
|
|
161
|
+
_framesContainer.RegisterCallback<DragPerformEvent>(OnFramesContainerDragPerform);
|
|
162
|
+
_framesContainer.RegisterCallback<DragLeaveEvent>(OnFramesContainerDragLeave);
|
|
163
|
+
|
|
164
|
+
_loadedClipDropPlaceholder = new VisualElement();
|
|
165
|
+
_loadedClipDropPlaceholder.AddToClassList("drop-placeholder");
|
|
166
|
+
_loadedClipDropPlaceholder.style.height = 5;
|
|
167
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
168
|
+
|
|
169
|
+
_loadedClipsContainer.RegisterCallback<DragUpdatedEvent>(
|
|
170
|
+
OnLoadedClipsContainerDragUpdated
|
|
171
|
+
);
|
|
172
|
+
_loadedClipsContainer.RegisterCallback<DragPerformEvent>(
|
|
173
|
+
OnLoadedClipsContainerDragPerform
|
|
174
|
+
);
|
|
175
|
+
_loadedClipsContainer.RegisterCallback<DragLeaveEvent>(OnLoadedClipsContainerDragLeave);
|
|
176
|
+
|
|
177
|
+
UpdateFramesPanelTitle();
|
|
178
|
+
RebuildLoadedClipsUI();
|
|
179
|
+
RecreatePreviewImage();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private void Update()
|
|
183
|
+
{
|
|
184
|
+
_animationPreview?.Update();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private void TryLoadStyleSheets()
|
|
188
|
+
{
|
|
189
|
+
string packageRoot = DirectoryHelper.FindPackageRootPath(
|
|
190
|
+
DirectoryHelper.GetCallerScriptDirectory()
|
|
191
|
+
);
|
|
192
|
+
if (!string.IsNullOrWhiteSpace(packageRoot))
|
|
193
|
+
{
|
|
194
|
+
if (
|
|
195
|
+
packageRoot.StartsWith("Packages", StringComparison.OrdinalIgnoreCase)
|
|
196
|
+
&& !packageRoot.Contains(PackageId, StringComparison.OrdinalIgnoreCase)
|
|
197
|
+
)
|
|
198
|
+
{
|
|
199
|
+
int helpersIndex = packageRoot.LastIndexOf(
|
|
200
|
+
"UnityHelpers",
|
|
201
|
+
StringComparison.Ordinal
|
|
202
|
+
);
|
|
203
|
+
if (0 <= helpersIndex)
|
|
204
|
+
{
|
|
205
|
+
packageRoot = packageRoot[..helpersIndex];
|
|
206
|
+
packageRoot += PackageId;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
char pathSeparator = Path.DirectorySeparatorChar;
|
|
211
|
+
string styleSheetPath =
|
|
212
|
+
$"{packageRoot}{pathSeparator}Editor{pathSeparator}Styles{pathSeparator}AnimationViewer.uss";
|
|
213
|
+
string unityRelativeStyleSheetPath = DirectoryHelper.AbsoluteToUnityRelativePath(
|
|
214
|
+
styleSheetPath
|
|
215
|
+
);
|
|
216
|
+
unityRelativeStyleSheetPath = unityRelativeStyleSheetPath.SanitizePath();
|
|
217
|
+
|
|
218
|
+
const string packageCache = "PackageCache/";
|
|
219
|
+
int packageCacheIndex;
|
|
220
|
+
if (!string.IsNullOrWhiteSpace(unityRelativeStyleSheetPath))
|
|
221
|
+
{
|
|
222
|
+
_styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
|
223
|
+
unityRelativeStyleSheetPath
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (_styleSheet == null && !string.IsNullOrWhiteSpace(unityRelativeStyleSheetPath))
|
|
228
|
+
{
|
|
229
|
+
packageCacheIndex = unityRelativeStyleSheetPath.IndexOf(
|
|
230
|
+
packageCache,
|
|
231
|
+
StringComparison.OrdinalIgnoreCase
|
|
232
|
+
);
|
|
233
|
+
if (0 <= packageCacheIndex)
|
|
234
|
+
{
|
|
235
|
+
unityRelativeStyleSheetPath = unityRelativeStyleSheetPath[
|
|
236
|
+
(packageCacheIndex + packageCache.Length)..
|
|
237
|
+
];
|
|
238
|
+
int forwardIndex = unityRelativeStyleSheetPath.IndexOf(
|
|
239
|
+
"/",
|
|
240
|
+
StringComparison.Ordinal
|
|
241
|
+
);
|
|
242
|
+
if (0 <= forwardIndex)
|
|
243
|
+
{
|
|
244
|
+
unityRelativeStyleSheetPath = unityRelativeStyleSheetPath.Substring(
|
|
245
|
+
forwardIndex
|
|
246
|
+
);
|
|
247
|
+
unityRelativeStyleSheetPath =
|
|
248
|
+
"Packages/" + PackageId + "/" + unityRelativeStyleSheetPath;
|
|
249
|
+
}
|
|
250
|
+
else
|
|
251
|
+
{
|
|
252
|
+
unityRelativeStyleSheetPath = "Packages/" + unityRelativeStyleSheetPath;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!string.IsNullOrWhiteSpace(unityRelativeStyleSheetPath))
|
|
257
|
+
{
|
|
258
|
+
_styleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(
|
|
259
|
+
unityRelativeStyleSheetPath
|
|
260
|
+
);
|
|
261
|
+
if (_styleSheet == null)
|
|
262
|
+
{
|
|
263
|
+
this.LogError(
|
|
264
|
+
$"Failed to load Animation Viewer style sheet (package root: '{packageRoot}'), relative path '{unityRelativeStyleSheetPath}'."
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
else
|
|
269
|
+
{
|
|
270
|
+
this.LogError(
|
|
271
|
+
$"Failed to convert absolute path '{styleSheetPath}' to Unity relative path."
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
string visualTreePath =
|
|
277
|
+
$"{packageRoot}{pathSeparator}Editor{pathSeparator}Styles{pathSeparator}AnimationViewer.uxml";
|
|
278
|
+
string unityRelativeVisualTreePath = DirectoryHelper.AbsoluteToUnityRelativePath(
|
|
279
|
+
visualTreePath
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
_visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
|
283
|
+
unityRelativeVisualTreePath
|
|
284
|
+
);
|
|
285
|
+
if (_visualTree == null)
|
|
286
|
+
{
|
|
287
|
+
packageCacheIndex = unityRelativeVisualTreePath.IndexOf(
|
|
288
|
+
packageCache,
|
|
289
|
+
StringComparison.OrdinalIgnoreCase
|
|
290
|
+
);
|
|
291
|
+
if (0 <= packageCacheIndex)
|
|
292
|
+
{
|
|
293
|
+
unityRelativeVisualTreePath = unityRelativeVisualTreePath[
|
|
294
|
+
(packageCacheIndex + packageCache.Length)..
|
|
295
|
+
];
|
|
296
|
+
int forwardIndex = unityRelativeVisualTreePath.IndexOf(
|
|
297
|
+
"/",
|
|
298
|
+
StringComparison.Ordinal
|
|
299
|
+
);
|
|
300
|
+
if (0 <= forwardIndex)
|
|
301
|
+
{
|
|
302
|
+
unityRelativeVisualTreePath = unityRelativeVisualTreePath.Substring(
|
|
303
|
+
forwardIndex
|
|
304
|
+
);
|
|
305
|
+
unityRelativeVisualTreePath =
|
|
306
|
+
"Packages/" + PackageId + "/" + unityRelativeVisualTreePath;
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
{
|
|
310
|
+
unityRelativeVisualTreePath = "Packages/" + unityRelativeVisualTreePath;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (!string.IsNullOrWhiteSpace(unityRelativeVisualTreePath))
|
|
315
|
+
{
|
|
316
|
+
_visualTree = AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(
|
|
317
|
+
unityRelativeVisualTreePath
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else
|
|
323
|
+
{
|
|
324
|
+
this.LogError(
|
|
325
|
+
$"Failed to find Animation Viewer style sheet (package root: '{packageRoot}')."
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
private void ToggleMultiFileSelector()
|
|
331
|
+
{
|
|
332
|
+
VisualElement root = rootVisualElement;
|
|
333
|
+
if (_fileSelector == null)
|
|
334
|
+
{
|
|
335
|
+
_fileSelector = new MultiFileSelectorElement(
|
|
336
|
+
ProjectAnimationSettings.Instance.lastAnimationPath,
|
|
337
|
+
new[] { ".anim" }
|
|
338
|
+
);
|
|
339
|
+
_fileSelector.OnFilesSelected += HandleFilesSelectedFromCustomBrowser;
|
|
340
|
+
_fileSelector.OnCancelled += HideMultiFileSelector;
|
|
341
|
+
root.Add(_fileSelector);
|
|
342
|
+
if (root.childCount > 1)
|
|
343
|
+
{
|
|
344
|
+
_fileSelector.PlaceInFront(root.Children().FirstOrDefault());
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
else if (_fileSelector.parent == null)
|
|
348
|
+
{
|
|
349
|
+
_fileSelector.ResetAndShow(ProjectAnimationSettings.Instance.lastAnimationPath);
|
|
350
|
+
root.Add(_fileSelector);
|
|
351
|
+
if (root.childCount > 1)
|
|
352
|
+
{
|
|
353
|
+
_fileSelector.PlaceInFront(root.Children().FirstOrDefault());
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
else
|
|
357
|
+
{
|
|
358
|
+
HideMultiFileSelector();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private void HideMultiFileSelector()
|
|
363
|
+
{
|
|
364
|
+
if (_fileSelector is { parent: not null })
|
|
365
|
+
{
|
|
366
|
+
_fileSelector.parent.Remove(_fileSelector);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private void HandleFilesSelectedFromCustomBrowser(List<string> selectedFullPaths)
|
|
371
|
+
{
|
|
372
|
+
HideMultiFileSelector();
|
|
373
|
+
|
|
374
|
+
if (selectedFullPaths == null || selectedFullPaths.Count == 0)
|
|
375
|
+
{
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
int clipsAddedCount = 0;
|
|
380
|
+
string lastValidDirectory = null;
|
|
381
|
+
|
|
382
|
+
foreach (string fullPath in selectedFullPaths)
|
|
383
|
+
{
|
|
384
|
+
string assetPath = fullPath.SanitizePath();
|
|
385
|
+
if (!assetPath.StartsWith("Assets", StringComparison.OrdinalIgnoreCase))
|
|
386
|
+
{
|
|
387
|
+
if (
|
|
388
|
+
assetPath.StartsWith(
|
|
389
|
+
Application.dataPath,
|
|
390
|
+
StringComparison.OrdinalIgnoreCase
|
|
391
|
+
)
|
|
392
|
+
)
|
|
393
|
+
{
|
|
394
|
+
assetPath = "Assets" + assetPath.Substring(Application.dataPath.Length);
|
|
395
|
+
}
|
|
396
|
+
else
|
|
397
|
+
{
|
|
398
|
+
this.LogWarn(
|
|
399
|
+
$"Selected file '{fullPath}' is outside the project's Assets folder or path is not project-relative. Skipping."
|
|
400
|
+
);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(assetPath);
|
|
406
|
+
if (clip != null)
|
|
407
|
+
{
|
|
408
|
+
if (_loadedEditorLayers.All(layer => layer.SourceClip != clip))
|
|
409
|
+
{
|
|
410
|
+
AddEditorLayer(clip);
|
|
411
|
+
clipsAddedCount++;
|
|
412
|
+
lastValidDirectory = Path.GetDirectoryName(assetPath);
|
|
413
|
+
}
|
|
414
|
+
else
|
|
415
|
+
{
|
|
416
|
+
this.LogWarn($"Clip '{clip.name}' already loaded. Skipping.");
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else
|
|
420
|
+
{
|
|
421
|
+
this.LogWarn($"Could not load AnimationClip from: {assetPath}");
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (clipsAddedCount > 0)
|
|
426
|
+
{
|
|
427
|
+
this.Log($"Added {clipsAddedCount} clip(s).");
|
|
428
|
+
if (!string.IsNullOrWhiteSpace(lastValidDirectory))
|
|
429
|
+
{
|
|
430
|
+
ProjectAnimationSettings.Instance.lastAnimationPath = lastValidDirectory;
|
|
431
|
+
ProjectAnimationSettings.Instance.Save();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private void OnAddAnimationClipFieldChanged(ChangeEvent<Object> evt)
|
|
437
|
+
{
|
|
438
|
+
AnimationClip clip = evt.newValue as AnimationClip;
|
|
439
|
+
if (clip != null)
|
|
440
|
+
{
|
|
441
|
+
AddEditorLayer(clip);
|
|
442
|
+
_addAnimationClipField.SetValueWithoutNotify(null);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private void OnAddSelectedClipsFromProjectClicked()
|
|
447
|
+
{
|
|
448
|
+
Object[] selectedObjects = Selection.GetFiltered(
|
|
449
|
+
typeof(AnimationClip),
|
|
450
|
+
SelectionMode.Assets
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
if (selectedObjects == null || selectedObjects.Length == 0)
|
|
454
|
+
{
|
|
455
|
+
EditorUtility.DisplayDialog(
|
|
456
|
+
"No Clips Selected",
|
|
457
|
+
"Please select one or more AnimationClip assets in the Project window first.",
|
|
458
|
+
"OK"
|
|
459
|
+
);
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
int clipsAddedCount = 0;
|
|
464
|
+
foreach (Object obj in selectedObjects)
|
|
465
|
+
{
|
|
466
|
+
if (obj is AnimationClip clip)
|
|
467
|
+
{
|
|
468
|
+
bool alreadyExists = _loadedEditorLayers.Any(layer => layer.SourceClip == clip);
|
|
469
|
+
if (!alreadyExists)
|
|
470
|
+
{
|
|
471
|
+
AddEditorLayer(clip);
|
|
472
|
+
clipsAddedCount++;
|
|
473
|
+
}
|
|
474
|
+
else
|
|
475
|
+
{
|
|
476
|
+
this.LogWarn($"Clip '{clip.name}' is already loaded. Skipping.");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (clipsAddedCount > 0)
|
|
482
|
+
{
|
|
483
|
+
this.Log($"Added {clipsAddedCount} new AnimationClip(s) to the viewer.");
|
|
484
|
+
}
|
|
485
|
+
else if (selectedObjects.Length > 0)
|
|
486
|
+
{
|
|
487
|
+
this.Log($"All selected AnimationClips were already loaded.");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private void OnBrowseAndAddClicked()
|
|
492
|
+
{
|
|
493
|
+
string path = EditorUtility.OpenFilePanelWithFilters(
|
|
494
|
+
"Select Animation Clip to Add",
|
|
495
|
+
ProjectAnimationSettings.Instance.lastAnimationPath,
|
|
496
|
+
new[] { "Animation Clip", "anim" }
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
if (!string.IsNullOrWhiteSpace(path))
|
|
500
|
+
{
|
|
501
|
+
if (path.StartsWith(Application.dataPath))
|
|
502
|
+
{
|
|
503
|
+
path = "Assets" + path.Substring(Application.dataPath.Length);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
ProjectAnimationSettings.Instance.lastAnimationPath = Path.GetDirectoryName(path);
|
|
507
|
+
ProjectAnimationSettings.Instance.Save();
|
|
508
|
+
|
|
509
|
+
AnimationClip clip = AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
|
|
510
|
+
if (clip != null)
|
|
511
|
+
{
|
|
512
|
+
AddEditorLayer(clip);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private void AddEditorLayer(AnimationClip clip)
|
|
518
|
+
{
|
|
519
|
+
if (clip == null || _loadedEditorLayers.Any(layer => layer.SourceClip == clip))
|
|
520
|
+
{
|
|
521
|
+
this.LogWarn($"Clip '{clip?.name}' is null or already loaded.");
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
EditorLayerData newEditorLayer = new(clip);
|
|
526
|
+
_loadedEditorLayers.Add(newEditorLayer);
|
|
527
|
+
|
|
528
|
+
if (_activeEditorLayer == null && newEditorLayer.Sprites.Count > 0)
|
|
529
|
+
{
|
|
530
|
+
SetActiveEditorLayer(newEditorLayer);
|
|
531
|
+
}
|
|
532
|
+
else if (_activeEditorLayer == null)
|
|
533
|
+
{
|
|
534
|
+
SetActiveEditorLayer(newEditorLayer);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
RebuildLoadedClipsUI();
|
|
538
|
+
RecreatePreviewImage();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
private void RemoveEditorLayer(EditorLayerData layerToRemove)
|
|
542
|
+
{
|
|
543
|
+
if (layerToRemove == null)
|
|
544
|
+
{
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
_loadedEditorLayers.Remove(layerToRemove);
|
|
549
|
+
|
|
550
|
+
if (_activeEditorLayer == layerToRemove)
|
|
551
|
+
{
|
|
552
|
+
SetActiveEditorLayer(_loadedEditorLayers.FirstOrDefault());
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
RebuildLoadedClipsUI();
|
|
556
|
+
RecreatePreviewImage();
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private void SetActiveEditorLayer(EditorLayerData layer)
|
|
560
|
+
{
|
|
561
|
+
_activeEditorLayer = layer;
|
|
562
|
+
_framesContainer.Clear();
|
|
563
|
+
_fpsDebugLabel.text = "Detected FPS Info (Active Clip):";
|
|
564
|
+
|
|
565
|
+
if (_activeEditorLayer != null)
|
|
566
|
+
{
|
|
567
|
+
UpdateFpsDebugLabelForActiveLayer();
|
|
568
|
+
_saveClipButton.SetEnabled(_activeEditorLayer.SourceClip != null);
|
|
569
|
+
}
|
|
570
|
+
else
|
|
571
|
+
{
|
|
572
|
+
_saveClipButton.SetEnabled(false);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
RebuildFramesListUI();
|
|
576
|
+
RebuildLoadedClipsUI();
|
|
577
|
+
UpdateFramesPanelTitle();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private void UpdateFramesPanelTitle()
|
|
581
|
+
{
|
|
582
|
+
_framesPanelTitle.text =
|
|
583
|
+
_activeEditorLayer != null
|
|
584
|
+
? $"Frames (Editing: {_activeEditorLayer.ClipName})"
|
|
585
|
+
: "Frames (No Active Clip Selected)";
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private void RebuildLoadedClipsUI()
|
|
589
|
+
{
|
|
590
|
+
_loadedClipsContainer.Clear();
|
|
591
|
+
if (_loadedClipDropPlaceholder.parent == _loadedClipsContainer)
|
|
592
|
+
{
|
|
593
|
+
_loadedClipsContainer.Remove(_loadedClipDropPlaceholder);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
for (int i = 0; i < _loadedEditorLayers.Count; i++)
|
|
597
|
+
{
|
|
598
|
+
EditorLayerData editorLayer = _loadedEditorLayers[i];
|
|
599
|
+
|
|
600
|
+
VisualElement itemElement = new();
|
|
601
|
+
itemElement.AddToClassList("loaded-clip-item");
|
|
602
|
+
if (editorLayer == _activeEditorLayer)
|
|
603
|
+
{
|
|
604
|
+
itemElement.AddToClassList("loaded-clip-item--active");
|
|
605
|
+
}
|
|
606
|
+
itemElement.userData = i;
|
|
607
|
+
|
|
608
|
+
Label label = new(editorLayer.ClipName);
|
|
609
|
+
itemElement.Add(label);
|
|
610
|
+
|
|
611
|
+
Button removeButton = new(() => RemoveEditorLayer(editorLayer)) { text = "X" };
|
|
612
|
+
itemElement.Add(removeButton);
|
|
613
|
+
|
|
614
|
+
itemElement.RegisterCallback<PointerDownEvent>(evt =>
|
|
615
|
+
{
|
|
616
|
+
if (evt.button == 0 && _draggedLoadedClipElement == null)
|
|
617
|
+
{
|
|
618
|
+
SetActiveEditorLayer(editorLayer);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
int currentIndex = i;
|
|
623
|
+
itemElement.RegisterCallback<PointerDownEvent>(evt =>
|
|
624
|
+
OnLoadedClipItemPointerDownSetup(evt, itemElement, currentIndex)
|
|
625
|
+
);
|
|
626
|
+
itemElement.RegisterCallback<PointerMoveEvent>(OnLoadedClipItemPointerMove);
|
|
627
|
+
itemElement.RegisterCallback<PointerUpEvent>(evt =>
|
|
628
|
+
OnLoadedClipItemPointerUpForClick(evt, itemElement, currentIndex)
|
|
629
|
+
);
|
|
630
|
+
|
|
631
|
+
_loadedClipsContainer.Add(itemElement);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
private void OnLoadedClipItemPointerDownSetup(
|
|
636
|
+
PointerDownEvent evt,
|
|
637
|
+
VisualElement clipElement,
|
|
638
|
+
int originalListIndex
|
|
639
|
+
)
|
|
640
|
+
{
|
|
641
|
+
if (evt.button != 0 || _draggedLoadedClipElement != null || _isClipDragPending)
|
|
642
|
+
{
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
_isClipDragPending = true;
|
|
647
|
+
_clipDragPendingElement = clipElement;
|
|
648
|
+
_clipDragPendingOriginalIndex = originalListIndex;
|
|
649
|
+
_clipDragStartPosition = evt.position;
|
|
650
|
+
|
|
651
|
+
clipElement.CapturePointer(evt.pointerId);
|
|
652
|
+
|
|
653
|
+
evt.StopPropagation();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private void OnLoadedClipItemPointerMove(PointerMoveEvent evt)
|
|
657
|
+
{
|
|
658
|
+
if (!_isClipDragPending)
|
|
659
|
+
{
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
float diffSqrMagnitude = (evt.position - _clipDragStartPosition).sqrMagnitude;
|
|
664
|
+
|
|
665
|
+
if (diffSqrMagnitude >= DragThresholdSqrMagnitude)
|
|
666
|
+
{
|
|
667
|
+
_draggedLoadedClipElement = _clipDragPendingElement;
|
|
668
|
+
_draggedLoadedClipOriginalIndex = _clipDragPendingOriginalIndex;
|
|
669
|
+
|
|
670
|
+
_draggedLoadedClipElement.AddToClassList("frame-item-dragged");
|
|
671
|
+
|
|
672
|
+
DragAndDrop.PrepareStartDrag();
|
|
673
|
+
DragAndDrop.SetGenericData(
|
|
674
|
+
"DraggedLoadedClipIndex",
|
|
675
|
+
_draggedLoadedClipOriginalIndex
|
|
676
|
+
);
|
|
677
|
+
Object dragContextObject =
|
|
678
|
+
_loadedEditorLayers[_draggedLoadedClipOriginalIndex]?.SourceClip
|
|
679
|
+
?? (Object)CreateInstance<ScriptableObject>();
|
|
680
|
+
DragAndDrop.objectReferences = new[] { dragContextObject };
|
|
681
|
+
DragAndDrop.StartDrag(
|
|
682
|
+
_loadedEditorLayers[_draggedLoadedClipOriginalIndex].ClipName
|
|
683
|
+
?? "Dragging Layer"
|
|
684
|
+
);
|
|
685
|
+
|
|
686
|
+
_isClipDragPending = false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
private void OnLoadedClipItemPointerUpForClick(
|
|
691
|
+
PointerUpEvent evt,
|
|
692
|
+
VisualElement clipElement,
|
|
693
|
+
int listIndex
|
|
694
|
+
)
|
|
695
|
+
{
|
|
696
|
+
if (evt.button != 0)
|
|
697
|
+
{
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (clipElement.HasPointerCapture(evt.pointerId))
|
|
702
|
+
{
|
|
703
|
+
clipElement.ReleasePointer(evt.pointerId);
|
|
704
|
+
}
|
|
705
|
+
if (_isClipDragPending)
|
|
706
|
+
{
|
|
707
|
+
if (listIndex >= 0 && listIndex < _loadedEditorLayers.Count)
|
|
708
|
+
{
|
|
709
|
+
SetActiveEditorLayer(_loadedEditorLayers[listIndex]);
|
|
710
|
+
}
|
|
711
|
+
_isClipDragPending = false;
|
|
712
|
+
_clipDragPendingElement = null;
|
|
713
|
+
evt.StopPropagation();
|
|
714
|
+
}
|
|
715
|
+
else if (_draggedLoadedClipElement == _clipDragPendingElement)
|
|
716
|
+
{
|
|
717
|
+
if (DragAndDrop.GetGenericData("DraggedLoadedClipIndex") != null)
|
|
718
|
+
{
|
|
719
|
+
CleanupLoadedClipDragState(evt.pointerId);
|
|
720
|
+
}
|
|
721
|
+
evt.StopPropagation();
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
_isClipDragPending = false;
|
|
725
|
+
_clipDragPendingElement = null;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
private void OnDraggedLoadedClipItemPointerUp(PointerUpEvent evt)
|
|
729
|
+
{
|
|
730
|
+
if (_draggedLoadedClipElement == null || evt.currentTarget != _draggedLoadedClipElement)
|
|
731
|
+
{
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (
|
|
736
|
+
DragAndDrop.GetGenericData("DraggedLoadedClipIndex") != null
|
|
737
|
+
|| _draggedLoadedClipElement != null
|
|
738
|
+
&& _draggedLoadedClipElement.HasPointerCapture(evt.pointerId)
|
|
739
|
+
)
|
|
740
|
+
{
|
|
741
|
+
CleanupLoadedClipDragState(evt.pointerId);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
evt.StopPropagation();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
private void OnLoadedClipsContainerDragUpdated(DragUpdatedEvent evt)
|
|
748
|
+
{
|
|
749
|
+
object draggedIndexData = DragAndDrop.GetGenericData("DraggedLoadedClipIndex");
|
|
750
|
+
if (draggedIndexData != null && _draggedLoadedClipElement != null)
|
|
751
|
+
{
|
|
752
|
+
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
|
|
753
|
+
float mouseY = evt.localMousePosition.y;
|
|
754
|
+
int newVisualIndex = -1;
|
|
755
|
+
|
|
756
|
+
if (_loadedClipDropPlaceholder.parent == _loadedClipsContainer)
|
|
757
|
+
{
|
|
758
|
+
_loadedClipsContainer.Remove(_loadedClipDropPlaceholder);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
for (int i = 0; i < _loadedClipsContainer.childCount; i++)
|
|
762
|
+
{
|
|
763
|
+
VisualElement child = _loadedClipsContainer[i];
|
|
764
|
+
if (child == _draggedLoadedClipElement)
|
|
765
|
+
{
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
float childMidY = child.layout.yMin + child.layout.height / 2f;
|
|
770
|
+
if (mouseY < childMidY)
|
|
771
|
+
{
|
|
772
|
+
newVisualIndex = i;
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (
|
|
777
|
+
newVisualIndex < 0
|
|
778
|
+
&& _loadedClipsContainer.childCount > 0
|
|
779
|
+
&& _draggedLoadedClipElement
|
|
780
|
+
!= _loadedClipsContainer.ElementAt(_loadedClipsContainer.childCount - 1)
|
|
781
|
+
)
|
|
782
|
+
{
|
|
783
|
+
newVisualIndex = _loadedClipsContainer.childCount;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
if (0 <= newVisualIndex)
|
|
787
|
+
{
|
|
788
|
+
_loadedClipsContainer.Insert(newVisualIndex, _loadedClipDropPlaceholder);
|
|
789
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Visible;
|
|
790
|
+
}
|
|
791
|
+
else if (_loadedClipsContainer.childCount == 0 && _draggedLoadedClipElement != null)
|
|
792
|
+
{
|
|
793
|
+
_loadedClipsContainer.Add(_loadedClipDropPlaceholder);
|
|
794
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Visible;
|
|
795
|
+
}
|
|
796
|
+
else
|
|
797
|
+
{
|
|
798
|
+
if (_loadedClipDropPlaceholder.parent == _loadedClipsContainer)
|
|
799
|
+
{
|
|
800
|
+
_loadedClipsContainer.Remove(_loadedClipDropPlaceholder);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
else
|
|
807
|
+
{
|
|
808
|
+
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
809
|
+
}
|
|
810
|
+
evt.StopPropagation();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private void OnLoadedClipsContainerDragPerform(DragPerformEvent evt)
|
|
814
|
+
{
|
|
815
|
+
object draggedIndexData = DragAndDrop.GetGenericData("DraggedLoadedClipIndex");
|
|
816
|
+
if (draggedIndexData != null && _draggedLoadedClipElement != null)
|
|
817
|
+
{
|
|
818
|
+
int originalListIndex = (int)draggedIndexData;
|
|
819
|
+
|
|
820
|
+
if (originalListIndex < 0 || originalListIndex >= _loadedEditorLayers.Count)
|
|
821
|
+
{
|
|
822
|
+
this.LogError(
|
|
823
|
+
$"DragPerform (LoadedClips): Stale or invalid dragged index. Aborting drop."
|
|
824
|
+
);
|
|
825
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
826
|
+
evt.StopPropagation();
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
EditorLayerData movedLayer = _loadedEditorLayers[originalListIndex];
|
|
831
|
+
_loadedEditorLayers.RemoveAt(originalListIndex);
|
|
832
|
+
|
|
833
|
+
int placeholderVisualIndex = _loadedClipsContainer.IndexOf(
|
|
834
|
+
_loadedClipDropPlaceholder
|
|
835
|
+
);
|
|
836
|
+
int targetListIndex;
|
|
837
|
+
|
|
838
|
+
if (0 <= placeholderVisualIndex)
|
|
839
|
+
{
|
|
840
|
+
int itemsBeforePlaceholder = -1;
|
|
841
|
+
for (int i = 0; i < placeholderVisualIndex; i++)
|
|
842
|
+
{
|
|
843
|
+
if (_loadedClipsContainer[i] != _loadedClipDropPlaceholder)
|
|
844
|
+
{
|
|
845
|
+
itemsBeforePlaceholder++;
|
|
846
|
+
}
|
|
847
|
+
else
|
|
848
|
+
{
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
targetListIndex = itemsBeforePlaceholder;
|
|
853
|
+
}
|
|
854
|
+
else
|
|
855
|
+
{
|
|
856
|
+
targetListIndex = _loadedEditorLayers.Count;
|
|
857
|
+
}
|
|
858
|
+
targetListIndex = Mathf.Clamp(targetListIndex, 0, _loadedEditorLayers.Count);
|
|
859
|
+
_loadedEditorLayers.Insert(targetListIndex, movedLayer);
|
|
860
|
+
|
|
861
|
+
DragAndDrop.AcceptDrag();
|
|
862
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
863
|
+
|
|
864
|
+
RebuildLoadedClipsUI();
|
|
865
|
+
RecreatePreviewImage();
|
|
866
|
+
}
|
|
867
|
+
else
|
|
868
|
+
{
|
|
869
|
+
if (DragAndDrop.GetGenericData("DraggedLoadedClipIndex") != null)
|
|
870
|
+
{
|
|
871
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
evt.StopPropagation();
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
private void OnLoadedClipsContainerDragLeave(DragLeaveEvent evt)
|
|
878
|
+
{
|
|
879
|
+
if (evt.target == _loadedClipsContainer)
|
|
880
|
+
{
|
|
881
|
+
if (_loadedClipDropPlaceholder.parent == _loadedClipsContainer)
|
|
882
|
+
{
|
|
883
|
+
_loadedClipsContainer.Remove(_loadedClipDropPlaceholder);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (_loadedClipDropPlaceholder != null)
|
|
887
|
+
{
|
|
888
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
private void CleanupLoadedClipDragState(int pointerIdToRelease)
|
|
894
|
+
{
|
|
895
|
+
if (_draggedLoadedClipElement != null)
|
|
896
|
+
{
|
|
897
|
+
if (
|
|
898
|
+
pointerIdToRelease != InvalidPointerId
|
|
899
|
+
&& _draggedLoadedClipElement.HasPointerCapture(pointerIdToRelease)
|
|
900
|
+
)
|
|
901
|
+
{
|
|
902
|
+
_draggedLoadedClipElement.ReleasePointer(pointerIdToRelease);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
_draggedLoadedClipElement.UnregisterCallback<PointerUpEvent>(
|
|
906
|
+
OnDraggedLoadedClipItemPointerUp
|
|
907
|
+
);
|
|
908
|
+
|
|
909
|
+
_draggedLoadedClipElement.RemoveFromClassList("frame-item-dragged");
|
|
910
|
+
_draggedLoadedClipElement = null;
|
|
911
|
+
}
|
|
912
|
+
_draggedLoadedClipOriginalIndex = -1;
|
|
913
|
+
|
|
914
|
+
_isClipDragPending = false;
|
|
915
|
+
if (_clipDragPendingElement != null)
|
|
916
|
+
{
|
|
917
|
+
if (
|
|
918
|
+
pointerIdToRelease != InvalidPointerId
|
|
919
|
+
&& _clipDragPendingElement.HasPointerCapture(pointerIdToRelease)
|
|
920
|
+
)
|
|
921
|
+
{
|
|
922
|
+
_clipDragPendingElement.ReleasePointer(pointerIdToRelease);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
_clipDragPendingElement = null;
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
if (_loadedClipDropPlaceholder != null)
|
|
929
|
+
{
|
|
930
|
+
if (_loadedClipDropPlaceholder.parent == _loadedClipsContainer)
|
|
931
|
+
{
|
|
932
|
+
_loadedClipsContainer.Remove(_loadedClipDropPlaceholder);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
_loadedClipDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
936
|
+
}
|
|
937
|
+
DragAndDrop.SetGenericData("DraggedLoadedClipIndex", null);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
private void RecreatePreviewImage()
|
|
941
|
+
{
|
|
942
|
+
if (_animationPreview != null)
|
|
943
|
+
{
|
|
944
|
+
if (_animationPreview.parent == _previewPanelHost)
|
|
945
|
+
{
|
|
946
|
+
_previewPanelHost.Remove(_animationPreview);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
_animationPreview = null;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (_previewPanelHost == null)
|
|
953
|
+
{
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
List<AnimatedSpriteLayer> animatedSpriteLayers = new();
|
|
958
|
+
if (_loadedEditorLayers.Count > 0)
|
|
959
|
+
{
|
|
960
|
+
foreach (EditorLayerData editorLayer in _loadedEditorLayers)
|
|
961
|
+
{
|
|
962
|
+
animatedSpriteLayers.Add(new AnimatedSpriteLayer(editorLayer.Sprites));
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
_animationPreview = new LayeredImage(
|
|
967
|
+
animatedSpriteLayers,
|
|
968
|
+
Color.clear,
|
|
969
|
+
_currentPreviewFps,
|
|
970
|
+
updatesSelf: false
|
|
971
|
+
)
|
|
972
|
+
{
|
|
973
|
+
name = "animationPreviewElement",
|
|
974
|
+
};
|
|
975
|
+
|
|
976
|
+
_previewPanelHost.Add(_animationPreview);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
private void OnFramesContainerDragUpdated(DragUpdatedEvent evt)
|
|
980
|
+
{
|
|
981
|
+
object draggedIndexData = DragAndDrop.GetGenericData("DraggedFrameDataIndex");
|
|
982
|
+
if (draggedIndexData != null && _draggedFrameElement != null)
|
|
983
|
+
{
|
|
984
|
+
DragAndDrop.visualMode = DragAndDropVisualMode.Move;
|
|
985
|
+
float mouseY = evt.localMousePosition.y;
|
|
986
|
+
int newVisualIndex = -1;
|
|
987
|
+
|
|
988
|
+
if (_frameDropPlaceholder.parent == _framesContainer)
|
|
989
|
+
{
|
|
990
|
+
_framesContainer.Remove(_frameDropPlaceholder);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
for (int i = 0; i < _framesContainer.childCount; i++)
|
|
994
|
+
{
|
|
995
|
+
VisualElement child = _framesContainer[i];
|
|
996
|
+
if (child == _draggedFrameElement)
|
|
997
|
+
{
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
float childMidY = child.layout.yMin + child.layout.height / 2f;
|
|
1002
|
+
if (mouseY < childMidY)
|
|
1003
|
+
{
|
|
1004
|
+
newVisualIndex = i;
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
if (
|
|
1009
|
+
newVisualIndex < 0
|
|
1010
|
+
&& _framesContainer.childCount > 0
|
|
1011
|
+
&& _draggedFrameElement
|
|
1012
|
+
!= _framesContainer.ElementAt(_framesContainer.childCount - 1)
|
|
1013
|
+
)
|
|
1014
|
+
{
|
|
1015
|
+
newVisualIndex = _framesContainer.childCount;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
if (0 <= newVisualIndex)
|
|
1019
|
+
{
|
|
1020
|
+
_framesContainer.Insert(newVisualIndex, _frameDropPlaceholder);
|
|
1021
|
+
_frameDropPlaceholder.style.visibility = Visibility.Visible;
|
|
1022
|
+
}
|
|
1023
|
+
else if (_framesContainer.childCount == 0 && _draggedFrameElement != null)
|
|
1024
|
+
{
|
|
1025
|
+
_framesContainer.Add(_frameDropPlaceholder);
|
|
1026
|
+
_frameDropPlaceholder.style.visibility = Visibility.Visible;
|
|
1027
|
+
}
|
|
1028
|
+
else
|
|
1029
|
+
{
|
|
1030
|
+
if (_frameDropPlaceholder.parent == _framesContainer)
|
|
1031
|
+
{
|
|
1032
|
+
_framesContainer.Remove(_frameDropPlaceholder);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
_frameDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
else
|
|
1039
|
+
{
|
|
1040
|
+
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected;
|
|
1041
|
+
}
|
|
1042
|
+
evt.StopPropagation();
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private void OnFramesContainerDragPerform(DragPerformEvent evt)
|
|
1046
|
+
{
|
|
1047
|
+
object draggedIndexData = DragAndDrop.GetGenericData("DraggedFrameDataIndex");
|
|
1048
|
+
if (
|
|
1049
|
+
draggedIndexData != null
|
|
1050
|
+
&& _draggedFrameElement != null
|
|
1051
|
+
&& _activeEditorLayer != null
|
|
1052
|
+
)
|
|
1053
|
+
{
|
|
1054
|
+
int originalDataIndex = (int)draggedIndexData;
|
|
1055
|
+
|
|
1056
|
+
if (originalDataIndex < 0 || originalDataIndex >= _activeEditorLayer.Sprites.Count)
|
|
1057
|
+
{
|
|
1058
|
+
this.LogError(
|
|
1059
|
+
$"DragPerform (Frames): Stale or invalid dragged index. Aborting drop."
|
|
1060
|
+
);
|
|
1061
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1062
|
+
evt.StopPropagation();
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
Sprite movedSprite = _activeEditorLayer.Sprites[originalDataIndex];
|
|
1067
|
+
_activeEditorLayer.Sprites.RemoveAt(originalDataIndex);
|
|
1068
|
+
|
|
1069
|
+
int placeholderVisualIndex = _framesContainer.IndexOf(_frameDropPlaceholder);
|
|
1070
|
+
int targetDataIndex;
|
|
1071
|
+
|
|
1072
|
+
if (0 <= placeholderVisualIndex)
|
|
1073
|
+
{
|
|
1074
|
+
int itemsBeforePlaceholder = 0;
|
|
1075
|
+
for (int i = 0; i < placeholderVisualIndex; i++)
|
|
1076
|
+
{
|
|
1077
|
+
if (
|
|
1078
|
+
_framesContainer[i] != _draggedFrameElement
|
|
1079
|
+
&& _framesContainer[i] != _frameDropPlaceholder
|
|
1080
|
+
)
|
|
1081
|
+
{
|
|
1082
|
+
itemsBeforePlaceholder++;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
targetDataIndex = itemsBeforePlaceholder;
|
|
1086
|
+
}
|
|
1087
|
+
else
|
|
1088
|
+
{
|
|
1089
|
+
targetDataIndex = _activeEditorLayer.Sprites.Count;
|
|
1090
|
+
}
|
|
1091
|
+
targetDataIndex = Mathf.Clamp(targetDataIndex, 0, _activeEditorLayer.Sprites.Count);
|
|
1092
|
+
_activeEditorLayer.Sprites.Insert(targetDataIndex, movedSprite);
|
|
1093
|
+
|
|
1094
|
+
DragAndDrop.AcceptDrag();
|
|
1095
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1096
|
+
|
|
1097
|
+
RebuildFramesListUI();
|
|
1098
|
+
RecreatePreviewImage();
|
|
1099
|
+
}
|
|
1100
|
+
else
|
|
1101
|
+
{
|
|
1102
|
+
if (DragAndDrop.GetGenericData("DraggedFrameDataIndex") != null)
|
|
1103
|
+
{
|
|
1104
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
evt.StopPropagation();
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
private void OnFramesContainerDragLeave(DragLeaveEvent evt)
|
|
1111
|
+
{
|
|
1112
|
+
if (evt.target == _framesContainer)
|
|
1113
|
+
{
|
|
1114
|
+
if (_frameDropPlaceholder.parent == _framesContainer)
|
|
1115
|
+
{
|
|
1116
|
+
_framesContainer.Remove(_frameDropPlaceholder);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
if (_frameDropPlaceholder != null)
|
|
1120
|
+
{
|
|
1121
|
+
_frameDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
private void CleanupFrameDragState(int pointerIdToRelease)
|
|
1129
|
+
{
|
|
1130
|
+
this.Log($"Cleaning up frame drag state with pointer {pointerIdToRelease}");
|
|
1131
|
+
if (_draggedFrameElement != null)
|
|
1132
|
+
{
|
|
1133
|
+
if (
|
|
1134
|
+
pointerIdToRelease != InvalidPointerId
|
|
1135
|
+
&& _draggedFrameElement.HasPointerCapture(pointerIdToRelease)
|
|
1136
|
+
)
|
|
1137
|
+
{
|
|
1138
|
+
_draggedFrameElement.ReleasePointer(pointerIdToRelease);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
_draggedFrameElement.UnregisterCallback<PointerUpEvent>(
|
|
1142
|
+
OnDraggedFrameItemPointerUp
|
|
1143
|
+
);
|
|
1144
|
+
_draggedFrameElement.RemoveFromClassList("frame-item-dragged");
|
|
1145
|
+
_draggedFrameElement = null;
|
|
1146
|
+
}
|
|
1147
|
+
_draggedFrameOriginalDataIndex = -1;
|
|
1148
|
+
|
|
1149
|
+
if (_frameDropPlaceholder != null)
|
|
1150
|
+
{
|
|
1151
|
+
if (_frameDropPlaceholder.parent == _framesContainer)
|
|
1152
|
+
{
|
|
1153
|
+
_framesContainer.Remove(_frameDropPlaceholder);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
_frameDropPlaceholder.style.visibility = Visibility.Hidden;
|
|
1157
|
+
}
|
|
1158
|
+
DragAndDrop.SetGenericData("DraggedFrameDataIndex", null);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
private void UpdateFpsDebugLabelForActiveLayer()
|
|
1162
|
+
{
|
|
1163
|
+
if (_activeEditorLayer == null)
|
|
1164
|
+
{
|
|
1165
|
+
_fpsDebugLabel.text = "Detected FPS Info: No active clip.";
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
_fpsDebugLabel.text =
|
|
1170
|
+
$"Active Clip FPS (Original): {FormatFps(_activeEditorLayer.OriginalClipFps)}fps. Preview uses global FPS.";
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
private static string FormatFps(float fps)
|
|
1174
|
+
{
|
|
1175
|
+
return fps.ToString("F1");
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
private void RebuildFramesListUI()
|
|
1179
|
+
{
|
|
1180
|
+
_framesContainer.Clear();
|
|
1181
|
+
|
|
1182
|
+
if (_frameDropPlaceholder != null && _frameDropPlaceholder.parent == _framesContainer)
|
|
1183
|
+
{
|
|
1184
|
+
_framesContainer.Remove(_frameDropPlaceholder);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
if (_activeEditorLayer?.Sprites == null)
|
|
1188
|
+
{
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
for (int i = 0; i < _activeEditorLayer.Sprites.Count; i++)
|
|
1193
|
+
{
|
|
1194
|
+
Sprite sprite = _activeEditorLayer.Sprites[i];
|
|
1195
|
+
|
|
1196
|
+
VisualElement frameElement = new();
|
|
1197
|
+
frameElement.AddToClassList("frame-item");
|
|
1198
|
+
|
|
1199
|
+
Image frameImage = new() { sprite = sprite, scaleMode = ScaleMode.ScaleToFit };
|
|
1200
|
+
frameImage.AddToClassList("frame-image");
|
|
1201
|
+
frameElement.Add(frameImage);
|
|
1202
|
+
|
|
1203
|
+
VisualElement frameInfo = new();
|
|
1204
|
+
frameInfo.AddToClassList("frame-info");
|
|
1205
|
+
frameInfo.Add(new Label($"Frame: {i + 1}"));
|
|
1206
|
+
frameInfo.Add(new Label($"Sprite: {(sprite != null ? sprite.name : "(None)")}"));
|
|
1207
|
+
frameElement.Add(frameInfo);
|
|
1208
|
+
|
|
1209
|
+
IntegerField indexField = new(null) { value = i + 1 };
|
|
1210
|
+
indexField.AddToClassList("frame-index-field");
|
|
1211
|
+
frameElement.Add(indexField);
|
|
1212
|
+
|
|
1213
|
+
VisualElement orderFieldContainer = new()
|
|
1214
|
+
{
|
|
1215
|
+
style =
|
|
1216
|
+
{
|
|
1217
|
+
flexDirection = FlexDirection.Row,
|
|
1218
|
+
alignItems = Align.Center,
|
|
1219
|
+
marginLeft = StyleKeyword.Auto,
|
|
1220
|
+
},
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
Label orderLabel = new("Order:") { style = { marginRight = 3 } };
|
|
1224
|
+
orderFieldContainer.Add(orderLabel);
|
|
1225
|
+
orderFieldContainer.Add(indexField);
|
|
1226
|
+
frameElement.Add(orderFieldContainer);
|
|
1227
|
+
|
|
1228
|
+
int currentDataIndex = i;
|
|
1229
|
+
frameElement.RegisterCallback<PointerDownEvent>(evt =>
|
|
1230
|
+
OnFrameItemPointerDown(evt, frameElement, currentDataIndex)
|
|
1231
|
+
);
|
|
1232
|
+
|
|
1233
|
+
indexField.userData = currentDataIndex;
|
|
1234
|
+
|
|
1235
|
+
indexField.RegisterCallback<FocusInEvent>(_ =>
|
|
1236
|
+
{
|
|
1237
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1238
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
indexField.RegisterCallback<FocusOutEvent>(_ =>
|
|
1242
|
+
OnFrameIndexFieldChanged(indexField)
|
|
1243
|
+
);
|
|
1244
|
+
indexField.RegisterCallback<KeyDownEvent>(evt =>
|
|
1245
|
+
{
|
|
1246
|
+
if (evt.keyCode is KeyCode.Return or KeyCode.KeypadEnter)
|
|
1247
|
+
{
|
|
1248
|
+
OnFrameIndexFieldChanged(indexField);
|
|
1249
|
+
indexField.Blur();
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
_framesContainer.Add(frameElement);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
private void OnFrameIndexFieldChanged(IntegerField field)
|
|
1258
|
+
{
|
|
1259
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1260
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
1261
|
+
if (_activeEditorLayer?.Sprites == null)
|
|
1262
|
+
{
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
int originalDataIndex = (int)field.userData;
|
|
1267
|
+
int newUiIndex = field.value;
|
|
1268
|
+
int newRequestedDataIndex = newUiIndex - 1;
|
|
1269
|
+
int newClampedDataIndex = Mathf.Clamp(
|
|
1270
|
+
newRequestedDataIndex,
|
|
1271
|
+
0,
|
|
1272
|
+
_activeEditorLayer.Sprites.Count - 1
|
|
1273
|
+
);
|
|
1274
|
+
|
|
1275
|
+
if (newClampedDataIndex != originalDataIndex)
|
|
1276
|
+
{
|
|
1277
|
+
if (originalDataIndex < 0 || originalDataIndex >= _activeEditorLayer.Sprites.Count)
|
|
1278
|
+
{
|
|
1279
|
+
this.LogWarn(
|
|
1280
|
+
$"Original index {originalDataIndex} out of bounds. Rebuilding UI to correct."
|
|
1281
|
+
);
|
|
1282
|
+
RebuildFramesListUI();
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
Sprite spriteToMove = _activeEditorLayer.Sprites[originalDataIndex];
|
|
1286
|
+
_activeEditorLayer.Sprites.RemoveAt(originalDataIndex);
|
|
1287
|
+
_activeEditorLayer.Sprites.Insert(newClampedDataIndex, spriteToMove);
|
|
1288
|
+
RebuildFramesListUI();
|
|
1289
|
+
RecreatePreviewImage();
|
|
1290
|
+
}
|
|
1291
|
+
else if (newUiIndex - 1 != newClampedDataIndex)
|
|
1292
|
+
{
|
|
1293
|
+
RebuildFramesListUI();
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
private void OnFrameItemPointerDown(
|
|
1298
|
+
PointerDownEvent evt,
|
|
1299
|
+
VisualElement frameElement,
|
|
1300
|
+
int originalDataIndex
|
|
1301
|
+
)
|
|
1302
|
+
{
|
|
1303
|
+
if (evt.button != 0 || _draggedFrameElement != null)
|
|
1304
|
+
{
|
|
1305
|
+
return;
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
if (
|
|
1309
|
+
_activeEditorLayer == null
|
|
1310
|
+
|| originalDataIndex < 0
|
|
1311
|
+
|| originalDataIndex >= _activeEditorLayer.Sprites.Count
|
|
1312
|
+
)
|
|
1313
|
+
{
|
|
1314
|
+
this.LogError(
|
|
1315
|
+
$"OnFrameItemPointerDown: Invalid originalDataIndex ({originalDataIndex}) or no active layer. Sprite count: {_activeEditorLayer?.Sprites?.Count ?? -1}"
|
|
1316
|
+
);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
_draggedFrameElement = frameElement;
|
|
1321
|
+
_draggedFrameOriginalDataIndex = originalDataIndex;
|
|
1322
|
+
|
|
1323
|
+
try
|
|
1324
|
+
{
|
|
1325
|
+
_draggedFrameElement.RegisterCallback<PointerUpEvent>(OnDraggedFrameItemPointerUp);
|
|
1326
|
+
_draggedFrameElement.AddToClassList("frame-item-dragged");
|
|
1327
|
+
|
|
1328
|
+
DragAndDrop.PrepareStartDrag();
|
|
1329
|
+
DragAndDrop.SetGenericData("DraggedFrameDataIndex", _draggedFrameOriginalDataIndex);
|
|
1330
|
+
|
|
1331
|
+
Object dragContextObject =
|
|
1332
|
+
_activeEditorLayer.SourceClip ?? (Object)CreateInstance<ScriptableObject>();
|
|
1333
|
+
if (dragContextObject == null)
|
|
1334
|
+
{
|
|
1335
|
+
this.LogError($"Failed to create dragContextObject for frame drag.");
|
|
1336
|
+
|
|
1337
|
+
_draggedFrameElement.ReleasePointer(evt.pointerId);
|
|
1338
|
+
_draggedFrameElement.UnregisterCallback<PointerUpEvent>(
|
|
1339
|
+
OnDraggedFrameItemPointerUp
|
|
1340
|
+
);
|
|
1341
|
+
_draggedFrameElement.RemoveFromClassList("frame-item-dragged");
|
|
1342
|
+
_draggedFrameElement = null;
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
DragAndDrop.objectReferences = new[] { dragContextObject };
|
|
1346
|
+
|
|
1347
|
+
Sprite spriteBeingDragged = _activeEditorLayer.Sprites[originalDataIndex];
|
|
1348
|
+
string dragTitle;
|
|
1349
|
+
if (spriteBeingDragged != null)
|
|
1350
|
+
{
|
|
1351
|
+
dragTitle = !string.IsNullOrWhiteSpace(spriteBeingDragged.name)
|
|
1352
|
+
? spriteBeingDragged.name
|
|
1353
|
+
: $"Unnamed Sprite Frame {originalDataIndex + 1}";
|
|
1354
|
+
}
|
|
1355
|
+
else
|
|
1356
|
+
{
|
|
1357
|
+
dragTitle = $"Empty Frame {originalDataIndex + 1}";
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (string.IsNullOrWhiteSpace(dragTitle))
|
|
1361
|
+
{
|
|
1362
|
+
dragTitle = "Dragging Frame";
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
DragAndDrop.StartDrag(dragTitle);
|
|
1366
|
+
}
|
|
1367
|
+
catch (Exception e)
|
|
1368
|
+
{
|
|
1369
|
+
this.LogError(
|
|
1370
|
+
$"Exception during OnFrameItemPointerDown before StartDrag: {e.Message}\n{e.StackTrace}"
|
|
1371
|
+
);
|
|
1372
|
+
|
|
1373
|
+
if (_draggedFrameElement != null)
|
|
1374
|
+
{
|
|
1375
|
+
if (_draggedFrameElement.HasPointerCapture(evt.pointerId))
|
|
1376
|
+
{
|
|
1377
|
+
_draggedFrameElement.ReleasePointer(evt.pointerId);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
_draggedFrameElement.UnregisterCallback<PointerUpEvent>(
|
|
1381
|
+
OnDraggedFrameItemPointerUp
|
|
1382
|
+
);
|
|
1383
|
+
_draggedFrameElement.RemoveFromClassList("frame-item-dragged");
|
|
1384
|
+
_draggedFrameElement = null;
|
|
1385
|
+
}
|
|
1386
|
+
_draggedFrameOriginalDataIndex = -1;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private void OnDraggedFrameItemPointerUp(PointerUpEvent evt)
|
|
1391
|
+
{
|
|
1392
|
+
if (_draggedFrameElement == null || evt.currentTarget != _draggedFrameElement)
|
|
1393
|
+
{
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
if (DragAndDrop.GetGenericData("DraggedFrameDataIndex") != null)
|
|
1398
|
+
{
|
|
1399
|
+
CleanupFrameDragState(evt.pointerId);
|
|
1400
|
+
}
|
|
1401
|
+
else if (
|
|
1402
|
+
_draggedFrameElement != null
|
|
1403
|
+
&& _draggedFrameElement.HasPointerCapture(evt.pointerId)
|
|
1404
|
+
)
|
|
1405
|
+
{
|
|
1406
|
+
_draggedFrameElement.ReleasePointer(evt.pointerId);
|
|
1407
|
+
_draggedFrameElement.UnregisterCallback<PointerUpEvent>(
|
|
1408
|
+
OnDraggedFrameItemPointerUp
|
|
1409
|
+
);
|
|
1410
|
+
_draggedFrameElement.RemoveFromClassList("frame-item-dragged");
|
|
1411
|
+
_draggedFrameElement = null;
|
|
1412
|
+
}
|
|
1413
|
+
evt.StopPropagation();
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
private void OnApplyFpsToPreviewClicked()
|
|
1417
|
+
{
|
|
1418
|
+
_currentPreviewFps = Mathf.Max(0.1f, _fpsField.value);
|
|
1419
|
+
_fpsField.SetValueWithoutNotify(_currentPreviewFps);
|
|
1420
|
+
if (_animationPreview != null)
|
|
1421
|
+
{
|
|
1422
|
+
_animationPreview.Fps = _currentPreviewFps;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
private void OnSaveClipClicked()
|
|
1427
|
+
{
|
|
1428
|
+
if (_activeEditorLayer == null || _activeEditorLayer.SourceClip == null)
|
|
1429
|
+
{
|
|
1430
|
+
this.LogError($"No active animation clip to save.");
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
AnimationClip clipToSave = _activeEditorLayer.SourceClip;
|
|
1435
|
+
string bindingPath = _activeEditorLayer.BindingPath;
|
|
1436
|
+
|
|
1437
|
+
EditorCurveBinding spriteBinding = default;
|
|
1438
|
+
bool bindingFound = false;
|
|
1439
|
+
EditorCurveBinding[] allBindings = AnimationUtility.GetObjectReferenceCurveBindings(
|
|
1440
|
+
clipToSave
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
foreach (EditorCurveBinding b in allBindings)
|
|
1444
|
+
{
|
|
1445
|
+
if (
|
|
1446
|
+
b.type == typeof(SpriteRenderer)
|
|
1447
|
+
&& b.propertyName == "m_Sprite"
|
|
1448
|
+
&& (string.IsNullOrWhiteSpace(bindingPath) || b.path == bindingPath)
|
|
1449
|
+
)
|
|
1450
|
+
{
|
|
1451
|
+
spriteBinding = b;
|
|
1452
|
+
bindingFound = true;
|
|
1453
|
+
break;
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
if (!bindingFound)
|
|
1458
|
+
{
|
|
1459
|
+
foreach (EditorCurveBinding b in allBindings)
|
|
1460
|
+
{
|
|
1461
|
+
if (b.type == typeof(SpriteRenderer) && b.propertyName == "m_Sprite")
|
|
1462
|
+
{
|
|
1463
|
+
spriteBinding = b;
|
|
1464
|
+
bindingFound = true;
|
|
1465
|
+
this.LogWarn(
|
|
1466
|
+
$"Saving to first available m_Sprite binding on '{clipToSave.name}' as specific path '{bindingPath}' was not found or empty."
|
|
1467
|
+
);
|
|
1468
|
+
break;
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
if (!bindingFound)
|
|
1474
|
+
{
|
|
1475
|
+
this.LogError(
|
|
1476
|
+
$"Cannot save '{clipToSave.name}': No SpriteRenderer m_Sprite binding found (Path Hint: '{bindingPath}'). Clip might be empty or not a sprite animation."
|
|
1477
|
+
);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
List<Sprite> spritesToSave = _activeEditorLayer.Sprites;
|
|
1482
|
+
ObjectReferenceKeyframe[] newKeyframes = new ObjectReferenceKeyframe[
|
|
1483
|
+
spritesToSave.Count
|
|
1484
|
+
];
|
|
1485
|
+
float timePerFrame = _currentPreviewFps > 0 ? 1.0f / _currentPreviewFps : 0f;
|
|
1486
|
+
|
|
1487
|
+
for (int i = 0; i < spritesToSave.Count; i++)
|
|
1488
|
+
{
|
|
1489
|
+
newKeyframes[i] = new ObjectReferenceKeyframe
|
|
1490
|
+
{
|
|
1491
|
+
time = i * timePerFrame,
|
|
1492
|
+
value = spritesToSave[i],
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
Undo.RecordObject(clipToSave, "Modify Animation Clip Frames");
|
|
1497
|
+
AnimationUtility.SetObjectReferenceCurve(clipToSave, spriteBinding, newKeyframes);
|
|
1498
|
+
clipToSave.frameRate = _currentPreviewFps;
|
|
1499
|
+
|
|
1500
|
+
EditorUtility.SetDirty(clipToSave);
|
|
1501
|
+
AssetDatabase.SaveAssets();
|
|
1502
|
+
|
|
1503
|
+
this.Log(
|
|
1504
|
+
$"Animation clip '{clipToSave.name}' saved with {spritesToSave.Count} frames at {_currentPreviewFps} FPS."
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
private void OnDisable()
|
|
1509
|
+
{
|
|
1510
|
+
CleanupFrameDragState(InvalidPointerId);
|
|
1511
|
+
CleanupLoadedClipDragState(InvalidPointerId);
|
|
1512
|
+
|
|
1513
|
+
if (_animationPreview != null && _animationPreview.parent == _previewPanelHost)
|
|
1514
|
+
{
|
|
1515
|
+
_previewPanelHost.Remove(_animationPreview);
|
|
1516
|
+
}
|
|
1517
|
+
_animationPreview = null;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
#endif
|
|
1521
|
+
}
|