SysUndoRedoSelection.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using System.Linq;
  2. using System.Runtime.InteropServices;
  3. using Leopotam.Ecs;
  4. using UnityEditor;
  5. using UnityEngine;
  6. using static Asset_Cleaner.AufCtx;
  7. namespace Asset_Cleaner {
  8. class CleanupPrevArg { }
  9. class UndoEvt { }
  10. class RedoEvt { }
  11. class SysUndoRedoSelection : IEcsRunSystem, IEcsInitSystem, IEcsDestroySystem {
  12. EcsFilter<UndoEvt> UndoEvt = default;
  13. EcsFilter<RedoEvt> RedoEvt = default;
  14. bool _preventHistoryInsert;
  15. bool _preventSelectionSet;
  16. public void Init() {
  17. Undo.undoRedoPerformed += OnUndoRedoPerformed;
  18. Selection.selectionChanged += OnSelectionChanged;
  19. Globals<UndoRedoState>.Value = new UndoRedoState();
  20. if (Globals<PersistentUndoRedoState>.Value.History.Count > 0)
  21. _preventHistoryInsert = true;
  22. OnSelectionChanged(); //init selection
  23. }
  24. public void Destroy() {
  25. Undo.undoRedoPerformed -= OnUndoRedoPerformed;
  26. Selection.selectionChanged -= OnSelectionChanged;
  27. Globals<UndoRedoState>.Value = default;
  28. }
  29. public void Run() {
  30. MouseInput();
  31. if (UndoEvt.IsEmpty() && RedoEvt.IsEmpty()) return;
  32. Counters(undo: !UndoEvt.IsEmpty(), redo: !RedoEvt.IsEmpty(), false);
  33. _preventHistoryInsert = true;
  34. if (!_preventSelectionSet) {
  35. (var history, var id) = Globals<PersistentUndoRedoState>.Value;
  36. SelectionEntry entry = history[id];
  37. if (entry.Valid())
  38. Selection.objects = entry.IsGuids
  39. ? entry.Guids.Select(AssetDatabase.GUIDToAssetPath).Select(AssetDatabase.LoadAssetAtPath<Object>).Where(obj => obj).ToArray()
  40. : entry.SceneObjects;
  41. }
  42. _preventSelectionSet = false;
  43. UndoEvt.AllDestroy();
  44. RedoEvt.AllDestroy();
  45. }
  46. static void Counters(bool undo, bool redo, bool insertToHistory) {
  47. var state = Globals<PersistentUndoRedoState>.Value;
  48. World.NewEntityWith(out RequestRepaintEvt _);
  49. const int MinId = 0;
  50. if (insertToHistory) {
  51. var entry = new SelectionEntry();
  52. var count = state.History.Count - 1 - state.Id;
  53. if (count > 0)
  54. state.History.RemoveRange(state.Id + 1, count);
  55. state.History.Add(entry);
  56. state.Id = MaxId();
  57. if (Selection.assetGUIDs.Length > 0) {
  58. entry.IsGuids = true;
  59. entry.Guids = Selection.assetGUIDs;
  60. }
  61. else {
  62. entry.SceneObjects = Selection.objects;
  63. }
  64. }
  65. if (undo) {
  66. // loop to skip invalid
  67. while (true) {
  68. state.Id -= 1;
  69. if (state.Id < MinId) break;
  70. if (state.History[state.Id].Valid()) break;
  71. }
  72. }
  73. if (redo) {
  74. // loop to skip invalid
  75. while (true) {
  76. state.Id += 1;
  77. if (state.Id > MaxId()) break;
  78. if (state.History[state.Id].Valid()) break;
  79. }
  80. }
  81. state.Id = Mathf.Clamp(state.Id, MinId, MaxId());
  82. var undoRedoState = Globals<UndoRedoState>.Value;
  83. undoRedoState.UndoEnabled = state.Id != MinId;
  84. undoRedoState.RedoEnabled = state.Id != MaxId();
  85. int MaxId() => Mathf.Max(0, state.History.Count - 1);
  86. }
  87. void OnSelectionChanged() {
  88. World.NewEntityWith(out RequestRepaintEvt _);
  89. if (Globals<Config>.Value.Locked) return;
  90. Counters(undo: false, redo: false, insertToHistory: !_preventHistoryInsert);
  91. _preventHistoryInsert = false;
  92. World.NewEntityWith(out SelectionChanged comp);
  93. World.NewEntityWith(out CleanupPrevArg _);
  94. var go = Selection.activeGameObject;
  95. if (go && go.scene.IsValid()) {
  96. comp.From = FindModeEnum.Scene;
  97. comp.Target = go;
  98. comp.Scene = go.scene;
  99. }
  100. else {
  101. var guids = Selection.assetGUIDs;
  102. // comp.Guids = Selection.assetGUIDs;
  103. bool any = guids != null && guids.Length > 0;
  104. if (any) {
  105. comp.From = FindModeEnum.File;
  106. var path = AssetDatabase.GUIDToAssetPath(guids[0]);
  107. comp.Target = AssetDatabase.LoadAssetAtPath<Object>(path);
  108. }
  109. else {
  110. comp.From = FindModeEnum.None;
  111. comp.Target = null;
  112. }
  113. }
  114. }
  115. // prevents selection history flooding
  116. void OnUndoRedoPerformed() {
  117. // below is a hackish way to catch Undo/Redo from editor
  118. if (!Undo.GetCurrentGroupName().Equals("Selection Change")) return;
  119. var evt = Event.current;
  120. if (evt == null) return;
  121. if (evt.rawType != EventType.KeyDown) return;
  122. switch (evt.keyCode) {
  123. case KeyCode.Z:
  124. World.NewEntityWith(out UndoEvt _);
  125. _preventSelectionSet = true; // prevent manual Selection set
  126. break;
  127. case KeyCode.Y:
  128. World.NewEntityWith(out RedoEvt _);
  129. _preventSelectionSet = true;
  130. break;
  131. }
  132. }
  133. void MouseInput() {
  134. if (_nextClick > EditorApplication.timeSinceStartup) return;
  135. var any = false;
  136. if (Pressed(0x5)) {
  137. World.NewEntityWith(out UndoEvt _);
  138. any = true;
  139. }
  140. if (Pressed(0x6)) {
  141. World.NewEntityWith(out RedoEvt _);
  142. any = true;
  143. }
  144. if (any)
  145. _nextClick = EditorApplication.timeSinceStartup + 0.25;
  146. }
  147. #if UNITY_EDITOR_WIN
  148. [DllImport("USER32.dll")]
  149. static extern short GetKeyState(int keycode);
  150. #else
  151. static short GetKeyState(int keycode) => 0;
  152. #endif
  153. double _nextClick;
  154. // 5 back, 6 fw
  155. static bool Pressed(int keyCode) => (GetKeyState(keyCode) & 0x100) != 0;
  156. }
  157. }