A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using UnityEngine;
4using UnityEngine.U2D.Animation;
5
6namespace UnityEditor.U2D.Animation.SpriteLibraryEditor
7{
8 internal class CategoryData
9 {
10 public readonly string name;
11 public readonly bool fromMain;
12 public readonly bool isOverride;
13
14 public CategoryData(string name, bool fromMain, bool isOverride)
15 {
16 this.name = name;
17 this.fromMain = fromMain;
18 this.isOverride = isOverride;
19 }
20 }
21
22 internal class LabelData
23 {
24 public readonly string name;
25 public readonly Sprite sprite;
26 public readonly bool fromMain;
27 public readonly bool spriteOverride;
28 public readonly bool categoryFromMain;
29
30 public LabelData(string name, Sprite sprite, bool fromMain, bool spriteOverride, bool categoryFromMain)
31 {
32 this.name = name;
33 this.sprite = sprite;
34 this.fromMain = fromMain;
35 this.spriteOverride = spriteOverride;
36 this.categoryFromMain = categoryFromMain;
37 }
38 }
39
40 internal class SpriteLibraryEditorModel : ScriptableObject
41 {
42 [SerializeField]
43 ActionType m_LastActionType;
44
45 public ActionType lastActionType
46 {
47 get => m_LastActionType;
48 set => m_LastActionType = value;
49 }
50
51 public SpriteLibraryAsset selectedAsset { get; private set; }
52 public bool hasSelectedCategories => m_SelectedCategoryIndices != null && m_SelectedCategoryIndices.Count > 0;
53 public bool hasSelectedLabels => m_SelectedLabelIndices != null && m_SelectedLabelIndices.Count > 0;
54 public bool isModified => m_CurrentVersion != m_SavedVersion;
55 public bool isSaving { get; private set; }
56
57 [SerializeField]
58 List<int> m_SelectedCategoryIndices = new();
59 [SerializeField]
60 List<int> m_SelectedLabelIndices = new();
61
62 [SerializeField]
63 List<SpriteLibCategoryOverride> m_CurrentLibrary;
64
65 [SerializeField]
66 uint m_CurrentVersion;
67 uint m_SavedVersion;
68
69 string m_SelectedAssetPath;
70
71 [SerializeField]
72 string m_PrimaryLibraryGUID;
73
74 public List<string> GetSelectedCategories()
75 {
76 var selection = new List<string>();
77 if (m_CurrentLibrary == null)
78 return selection;
79
80 foreach (var selectedCategoryIndex in m_SelectedCategoryIndices)
81 selection.Add(m_CurrentLibrary[selectedCategoryIndex].name);
82
83 return selection;
84 }
85
86 public void SelectCategories(IList<string> newSelection)
87 {
88 if (newSelection == null || newSelection.Count == 0)
89 {
90 m_SelectedCategoryIndices = new List<int>();
91 return;
92 }
93
94 if (m_CurrentLibrary == null)
95 return;
96
97 m_SelectedCategoryIndices = new List<int>(newSelection.Count);
98 for (var selection = 0; selection < newSelection.Count; selection++)
99 {
100 var index = -1;
101 for (var i = 0; i < m_CurrentLibrary.Count; i++)
102 {
103 if (m_CurrentLibrary[i].name == newSelection[selection])
104 {
105 index = i;
106 break;
107 }
108 }
109
110 if (index != -1)
111 m_SelectedCategoryIndices.Add(index);
112 }
113 }
114
115 public List<string> GetSelectedLabels()
116 {
117 var selectedCategory = GetSelectedCategory();
118 if (selectedCategory == null)
119 return new List<string>();
120
121 var labels = selectedCategory.overrideEntries;
122 var selectedLabels = new List<string>(m_SelectedLabelIndices.Count);
123 for (var i = 0; i < m_SelectedLabelIndices.Count; i++)
124 selectedLabels.Add(labels[m_SelectedLabelIndices[i]].name);
125
126 return selectedLabels;
127 }
128
129 public void SelectLabels(IList<string> labels)
130 {
131 if (labels == null || labels.Count == 0)
132 {
133 m_SelectedLabelIndices = new List<int>();
134 return;
135 }
136
137 var category = GetSelectedCategory();
138 if (category == null)
139 return;
140
141 m_SelectedLabelIndices = new List<int>(labels.Count);
142 for (var i = 0; i < labels.Count; i++)
143 m_SelectedLabelIndices.Add(category.overrideEntries.FindIndex(label => label.name == labels[i]));
144 }
145
146 public List<CategoryData> GetAllCategories()
147 {
148 var allCategories = new List<CategoryData>();
149 if (m_CurrentLibrary == null)
150 return allCategories;
151
152 for (var i = 0; i < m_CurrentLibrary.Count; i++)
153 allCategories.Add(CreateCategoryData(m_CurrentLibrary[i]));
154
155 return allCategories;
156 }
157
158 public List<CategoryData> GetFilteredCategories(string filterString, SearchType searchType, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
159 {
160 var categories = new List<CategoryData>();
161 if (m_CurrentLibrary != null)
162 {
163 var filter = searchType is SearchType.CategoryAndLabel or SearchType.Category && !string.IsNullOrEmpty(filterString);
164 foreach (var spriteLibCategoryOverride in m_CurrentLibrary)
165 {
166 if (!filter || spriteLibCategoryOverride.name.Contains(filterString, comparison))
167 categories.Add(CreateCategoryData(spriteLibCategoryOverride));
168 }
169 }
170
171 return categories;
172 }
173
174 public List<LabelData> GetAllLabels()
175 {
176 var category = GetSelectedCategory();
177 if (category == null)
178 return new List<LabelData>();
179
180 var categoryLabels = category.overrideEntries;
181 var labels = new List<LabelData>(category.entryOverrideCount);
182 for (var i = 0; i < categoryLabels.Count; i++)
183 labels.Add(CreateLabelData(category, categoryLabels[i]));
184
185 return labels;
186 }
187
188 public List<LabelData> GetFilteredLabels(string filterString, SearchType searchType, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
189 {
190 var category = GetSelectedCategory();
191 if (category == null)
192 return new List<LabelData>();
193
194 var labels = new List<LabelData>();
195 var filter = searchType is SearchType.CategoryAndLabel or SearchType.Label && !string.IsNullOrEmpty(filterString);
196 foreach (var categoryEntryOverride in category.overrideEntries)
197 {
198 if (!filter || categoryEntryOverride.name.Contains(filterString, comparison))
199 labels.Add(CreateLabelData(category, categoryEntryOverride));
200 }
201
202 return labels;
203 }
204
205 public void BeginUndo(ActionType actionType, string actionName)
206 {
207 lastActionType = actionType;
208
209 Undo.IncrementCurrentGroup();
210 Undo.RegisterCompleteObjectUndo(this, actionName);
211
212 if (IsActionModifyingAssets(actionType))
213 m_CurrentVersion++;
214 }
215
216 public void EndUndo()
217 {
218 Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
219
220 lastActionType = ActionType.None;
221 }
222
223 public static bool IsActionModifyingAssets(ActionType actionType)
224 {
225 var isNotModifying = actionType is
226 ActionType.None or
227 ActionType.SelectCategory or
228 ActionType.SelectLabels;
229
230 return !isNotModifying;
231 }
232
233 public void CreateNewLabel(string labelName)
234 {
235 var category = GetSelectedCategory();
236 var newLabel = new SpriteCategoryEntryOverride
237 {
238 name = labelName,
239 sprite = null,
240 spriteOverride = null,
241 fromMain = false,
242 };
243 category.overrideEntries.Add(newLabel);
244 category.RenameDuplicateOverrideEntries();
245 category.UpdateOverrideCount();
246 }
247
248 public void SetLabelSprite(string labelName, Sprite newSprite)
249 {
250 var category = GetSelectedCategory();
251 var label = GetLabel(category, labelName);
252 if (label != null)
253 {
254 label.spriteOverride = newSprite;
255 if (!label.fromMain)
256 label.sprite = newSprite;
257 }
258
259 category.UpdateOverrideCount();
260 }
261
262 public void DeleteSelectedLabels()
263 {
264 var category = GetSelectedCategory();
265 if (category == null)
266 return;
267
268 var labelsToRemove = new List<SpriteCategoryEntryOverride>();
269 for (var i = 0; i < m_SelectedLabelIndices.Count; i++)
270 {
271 var labelToRemove = category.overrideEntries[m_SelectedLabelIndices[i]];
272 if (!labelToRemove.fromMain)
273 labelsToRemove.Add(labelToRemove);
274 }
275
276 foreach (var label in labelsToRemove)
277 category.overrideEntries.Remove(label);
278
279 category.UpdateOverrideCount();
280 }
281
282 public void RenameSelectedLabel(string newName)
283 {
284 var category = GetSelectedCategory();
285 var label = GetSelectedLabel();
286 label.name = newName;
287 category.RenameDuplicateOverrideEntries();
288 }
289
290 public void AddLabelsToCategory(string categoryName, IEnumerable<Sprite> sprites, bool replaceConflicting)
291 {
292 var category = GetCategory(categoryName);
293 var conflictingLabels = new List<Sprite>();
294 var newLabels = new List<Sprite>();
295 foreach (var sprite in sprites)
296 {
297 if (GetLabel(category, sprite.name) != null)
298 conflictingLabels.Add(sprite);
299 else
300 newLabels.Add(sprite);
301 }
302
303 foreach (var newLabel in newLabels)
304 category.overrideEntries.Add(new SpriteCategoryEntryOverride
305 {
306 name = newLabel.name,
307 sprite = newLabel,
308 spriteOverride = newLabel,
309 fromMain = false
310 });
311
312 if (replaceConflicting)
313 {
314 foreach (var conflictingLabel in conflictingLabels)
315 {
316 var label = GetLabel(category, conflictingLabel.name);
317 label.spriteOverride = conflictingLabel;
318 if (!label.fromMain)
319 label.sprite = conflictingLabel;
320 }
321 }
322 else
323 {
324 foreach (var conflictingLabel in conflictingLabels)
325 {
326 category.overrideEntries.Add(new SpriteCategoryEntryOverride
327 {
328 name = conflictingLabel.name,
329 sprite = conflictingLabel,
330 spriteOverride = conflictingLabel,
331 fromMain = false
332 });
333 }
334 }
335
336 category.RenameDuplicateOverrideEntries();
337 category.UpdateOverrideCount();
338 }
339
340 public void DeleteSelectedCategories()
341 {
342 if (m_CurrentLibrary == null || m_SelectedCategoryIndices == null || m_SelectedCategoryIndices.Count == 0)
343 return;
344
345 var categoriesToRemove = new List<SpriteLibCategoryOverride>(m_SelectedCategoryIndices.Count);
346 foreach (var selectedCategoryIndex in m_SelectedCategoryIndices)
347 categoriesToRemove.Add(m_CurrentLibrary[selectedCategoryIndex]);
348
349 foreach (var categoryToRemove in categoriesToRemove)
350 m_CurrentLibrary.Remove(categoryToRemove);
351 }
352
353 public void ReorderCategories(IList<string> reorderedCategories)
354 {
355 var categories = new List<SpriteLibCategoryOverride>(reorderedCategories.Count);
356 for (var i = 0; i < reorderedCategories.Count; i++)
357 {
358 var reorderedCategory = GetCategory(reorderedCategories[i]);
359 categories.Add(reorderedCategory);
360 }
361
362 m_CurrentLibrary = categories;
363 }
364
365 public void RenameSelectedCategory(string newName)
366 {
367 var category = GetSelectedCategory();
368 category.name = newName;
369 RenameDuplicatedCategories();
370 }
371
372 public void SelectAsset(SpriteLibraryAsset asset)
373 {
374 ClearUndo();
375
376 m_SavedVersion = m_CurrentVersion;
377
378 selectedAsset = asset;
379
380 m_CurrentLibrary = new List<SpriteLibCategoryOverride>();
381 m_PrimaryLibraryGUID = null;
382
383 m_SelectedAssetPath = selectedAsset != null ? AssetDatabase.GetAssetPath(selectedAsset) : null;
384 m_SelectedCategoryIndices = new List<int>();
385 m_SelectedLabelIndices = new List<int>();
386
387 var sourceAsset = selectedAsset != null ? SpriteLibrarySourceAssetImporter.LoadSpriteLibrarySourceAsset(m_SelectedAssetPath) : null;
388 if (sourceAsset != null)
389 {
390 m_CurrentLibrary = new List<SpriteLibCategoryOverride>(sourceAsset.library);
391
392 // Update hashes to make sure name hashes are correct.
393 foreach (var categoryOverride in m_CurrentLibrary)
394 categoryOverride.UpdateHash();
395
396 m_PrimaryLibraryGUID = sourceAsset.primaryLibraryGUID;
397
398 var mainLibrary = GetMainLibrary();
399 if (mainLibrary)
400 SetMainLibrary(mainLibrary);
401 }
402 }
403
404 public void ReorderLabels(IList<string> reorderedLabels)
405 {
406 var category = GetSelectedCategory();
407 var labels = new List<SpriteCategoryEntryOverride>(reorderedLabels.Count);
408 for (var i = 0; i < reorderedLabels.Count; i++)
409 labels.Add(GetLabel(category, reorderedLabels[i]));
410
411 for (var i = 0; i < labels.Count; i++)
412 {
413 var label = labels[i];
414 if (label.fromMain)
415 continue;
416
417 var index = category.overrideEntries.IndexOf(label);
418
419 (category.overrideEntries[i], category.overrideEntries[index]) = (category.overrideEntries[index], category.overrideEntries[i]);
420 }
421
422 category.overrideEntries = labels;
423 }
424
425 public void CreateNewCategory(string categoryName, IList<Sprite> sprites)
426 {
427 var newLabelCount = sprites?.Count ?? 0;
428 var newLabels = new List<SpriteCategoryEntryOverride>(newLabelCount);
429 if (sprites != null)
430 {
431 for (var i = 0; i < newLabelCount; i++)
432 newLabels.Add(new SpriteCategoryEntryOverride
433 {
434 name = sprites[i].name,
435 fromMain = false,
436 sprite = sprites[i],
437 spriteOverride = sprites[i]
438 });
439 }
440
441 var category = new SpriteLibCategoryOverride
442 {
443 name = categoryName,
444 fromMain = false,
445 entryOverrideCount = newLabelCount,
446 overrideEntries = newLabels
447 };
448
449 m_CurrentLibrary.Add(category);
450 RenameDuplicatedCategories();
451 }
452
453 public void SaveLibrary(string path)
454 {
455 if (m_CurrentLibrary == null)
456 return;
457
458 Debug.Assert(!string.IsNullOrEmpty(path), "Asset path cannot be empty.");
459
460 m_SavedVersion = m_CurrentVersion;
461 isSaving = true;
462 var assetToSave = CreateInstance<SpriteLibrarySourceAsset>();
463 assetToSave.SetLibrary(m_CurrentLibrary);
464 assetToSave.SetPrimaryLibraryGUID(m_PrimaryLibraryGUID);
465 SpriteLibrarySourceAssetImporter.SaveSpriteLibrarySourceAsset(assetToSave, path);
466 AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
467 isSaving = false;
468 }
469
470 public SpriteLibraryAsset GetMainLibrary()
471 {
472 if (string.IsNullOrEmpty(m_PrimaryLibraryGUID))
473 return null;
474 var assetPath = AssetDatabase.GUIDToAssetPath(m_PrimaryLibraryGUID);
475 var asset = AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>(assetPath);
476 return asset;
477 }
478
479 public void SetMainLibrary(SpriteLibraryAsset newMainLibrary)
480 {
481 m_PrimaryLibraryGUID = newMainLibrary != null ? AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(newMainLibrary)) : null;
482 var newCategories = newMainLibrary != null ? newMainLibrary.categories : new List<SpriteLibCategory>();
483
484 var existingCategories = new List<SpriteLibCategoryOverride>(m_CurrentLibrary);
485 m_CurrentLibrary.Clear();
486
487 // populate new primary
488 foreach (var newCategory in newCategories)
489 {
490 var labels = new List<SpriteCategoryEntryOverride>();
491 SpriteLibCategoryOverride existingCategory = null;
492 for (var i = 0; i < existingCategories.Count; i++)
493 {
494 var category = existingCategories[i];
495 if (category.name == newCategory.name)
496 {
497 existingCategory = category;
498 existingCategory.fromMain = true;
499 existingCategories.RemoveAt(i);
500 break;
501 }
502 }
503
504 var newEntries = newCategory.categoryList;
505 foreach (var newEntry in newEntries)
506 {
507 var sprite = newEntry.sprite;
508
509 labels.Add(new SpriteCategoryEntryOverride
510 {
511 name = newEntry.name,
512 sprite = sprite,
513 spriteOverride = sprite,
514 fromMain = true
515 });
516 }
517
518 var overrideCount = 0;
519 if (existingCategory != null)
520 {
521 foreach (var existingLabel in existingCategory.overrideEntries)
522 {
523 var foundLabel = false;
524 foreach (var newLabel in labels)
525 {
526 if (existingLabel.name == newLabel.name)
527 {
528 if (newLabel.spriteOverride != existingLabel.spriteOverride)
529 {
530 newLabel.spriteOverride = existingLabel.spriteOverride;
531 overrideCount++;
532 }
533
534 foundLabel = true;
535 break;
536 }
537 }
538
539 if (!foundLabel)
540 {
541 overrideCount++;
542 labels.Add(new SpriteCategoryEntryOverride
543 {
544 name = existingLabel.name,
545 sprite = existingLabel.sprite,
546 spriteOverride = existingLabel.spriteOverride,
547 fromMain = false
548 });
549 }
550 }
551 }
552
553 m_CurrentLibrary.Add(new SpriteLibCategoryOverride
554 {
555 name = newCategory.name,
556 overrideEntries = labels,
557 fromMain = true,
558 entryOverrideCount = overrideCount
559 });
560 }
561
562 foreach (var existingCategory in existingCategories)
563 {
564 var keepCategory = false;
565 if (existingCategory.fromMain)
566 {
567 for (var i = existingCategory.overrideEntries.Count; i-- > 0;)
568 {
569 var entry = existingCategory.overrideEntries[i];
570 if (!entry.fromMain || entry.sprite != entry.spriteOverride)
571 {
572 entry.fromMain = false;
573 entry.sprite = entry.spriteOverride;
574 keepCategory = true;
575 }
576 else
577 existingCategory.overrideEntries.RemoveAt(i);
578 }
579 }
580
581 if (!existingCategory.fromMain || keepCategory)
582 {
583 existingCategory.fromMain = false;
584 existingCategory.entryOverrideCount = 0;
585 m_CurrentLibrary.Add(existingCategory);
586 }
587 }
588 }
589
590 public void RevertLabels(IList<string> labels)
591 {
592 var category = GetSelectedCategory();
593 foreach (var labelName in labels)
594 {
595 var label = GetLabel(category, labelName);
596 if (label.fromMain && label.sprite != label.spriteOverride)
597 {
598 label.spriteOverride = label.sprite;
599 }
600 else if (category.fromMain && !label.fromMain)
601 {
602 category.overrideEntries.Remove(label);
603 }
604 }
605
606 category.UpdateOverrideCount();
607 }
608
609 public void SetAssetPath(string assetPath)
610 {
611 m_SelectedAssetPath = assetPath;
612 }
613
614 SpriteLibCategoryOverride GetSelectedCategory()
615 {
616 if (m_CurrentLibrary == null || m_CurrentLibrary.Count == 0 || !hasSelectedCategories)
617 return null;
618
619 var selectedCategoryIndex = m_SelectedCategoryIndices[0];
620 if (selectedCategoryIndex < 0 || selectedCategoryIndex >= m_CurrentLibrary.Count)
621 return null;
622
623 return m_CurrentLibrary[selectedCategoryIndex];
624 }
625
626 SpriteLibCategoryOverride GetCategory(string categoryName)
627 {
628 if (m_CurrentLibrary == null || m_CurrentLibrary.Count == 0)
629 return null;
630
631 foreach (var category in m_CurrentLibrary)
632 {
633 if (category.name == categoryName)
634 return category;
635 }
636
637 return null;
638 }
639
640 SpriteCategoryEntryOverride GetSelectedLabel()
641 {
642 var selectedCategory = GetSelectedCategory();
643 if (selectedCategory == null || !hasSelectedLabels)
644 return null;
645
646 var selectedLabelIndex = m_SelectedLabelIndices[0];
647 if (selectedLabelIndex < 0 || selectedLabelIndex >= selectedCategory.overrideEntries.Count)
648 return null;
649
650 return selectedCategory.overrideEntries[selectedLabelIndex];
651 }
652
653 static SpriteCategoryEntryOverride GetLabel(SpriteLibCategoryOverride category, string labelName)
654 {
655 if (category == null)
656 return null;
657
658 foreach (var spriteCategoryEntryOverride in category.overrideEntries)
659 {
660 if (spriteCategoryEntryOverride.name == labelName)
661 return spriteCategoryEntryOverride;
662 }
663
664 return null;
665 }
666
667 public List<LabelData> GetLabels(string categoryName)
668 {
669 if (m_CurrentLibrary != null && m_CurrentLibrary != null)
670 {
671 foreach (var spriteLibCategoryOverride in m_CurrentLibrary)
672 {
673 if (spriteLibCategoryOverride.name == categoryName)
674 {
675 var labelsCache = spriteLibCategoryOverride.overrideEntries;
676 var labels = new List<LabelData>(labelsCache.Count);
677 foreach (var categoryEntryOverride in labelsCache)
678 labels.Add(CreateLabelData(spriteLibCategoryOverride, categoryEntryOverride));
679 return labels;
680 }
681 }
682 }
683
684 return new List<LabelData>();
685 }
686
687 public bool CompareLabels(IList<string> labelsToCompare)
688 {
689 if (labelsToCompare == null)
690 return false;
691
692 var category = GetSelectedCategory();
693 if (category == null || category.entryOverrideCount != labelsToCompare.Count)
694 return false;
695
696 for (var i = 0; i < labelsToCompare.Count; i++)
697 {
698 if (category.overrideEntries[i].name != labelsToCompare[i])
699 return false;
700 }
701
702 return true;
703 }
704
705 public bool CompareCategories(IList<string> categoriesToCompare)
706 {
707 if (categoriesToCompare == null)
708 return false;
709
710 if (m_CurrentLibrary.Count != categoriesToCompare.Count)
711 return false;
712
713 for (var i = 0; i < categoriesToCompare.Count; i++)
714 {
715 if (m_CurrentLibrary[i].name != categoriesToCompare[i])
716 return false;
717 }
718
719 return true;
720 }
721
722 void RenameDuplicatedCategories()
723 {
724 if (m_CurrentLibrary != null)
725 SpriteLibraryAsset.RenameDuplicate(m_CurrentLibrary, (_, _) => { });
726 }
727
728 void ClearUndo() => Undo.ClearUndo(this);
729
730 public void Destroy()
731 {
732 ClearUndo();
733 DestroyImmediate(this);
734 }
735
736 static CategoryData CreateCategoryData(SpriteLibCategoryOverride category)
737 {
738 return new CategoryData(
739 name: category.name,
740 fromMain: category.fromMain,
741 isOverride: category.fromMain && category.entryOverrideCount > 0);
742 }
743
744 static LabelData CreateLabelData(SpriteLibCategoryOverride category, SpriteCategoryEntryOverride label)
745 {
746 return new LabelData(
747 name: label.name,
748 sprite: label.spriteOverride,
749 fromMain: label.fromMain,
750 spriteOverride: label.sprite != label.spriteOverride,
751 categoryFromMain: category.fromMain);
752 }
753 }
754}