VR Development Framework
v 1.0.0
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Events Pages
AVR_HookableWizard.cs
1 using System.Collections;
2 using System.Collections.Generic;
3 using UnityEngine;
4 using UnityEditor;
5 using System.Linq;
6 
7 namespace AVR.UEditor.Core {
8  /// Short documentation on HookableWizard:
9  /// A Hookable Wizard is a simple wizard to change porperties on a certain object or component. A typical example: Adding Modules to a VR Controller (such as movement provider etc.)
10  /// The wizard itself is merely an empty editorwindow with a title and a "Update" button. The actual contents/options are inserted via "WizardHooks".
11  /// Take the example given above. The wizard to add controller modules by itself is empty. But each package adds individual hooks by declaring them. (arc-vr-motion would add the option to add a MovementProvider module).
12  /// By keeping the wizards and hooks separate from one another, it allows us to add wizard-options on a per-package basis.
13  ///
14  /// Creating a new wizard is as simple as this:
15  //# public class MyWizard : AVR_HookableWizard<MyWizard> {}
16  /// Keep in mind that the class needs to *self-referencing* to work. (T needs to be the class of the wizard itself)
17  ///
18  /// Now, to display thw wizard one must only call the following:
19  //# MyWizard.ShowWindow(referenceObject, "My Wizard");
20  ///
21  /// As for the Hooks. Declaring a new hook (option) for your wizard consits only of declaring the class. An example for a simple hook:
22  //# public class MyHook : AVR_WizardHook<MyWizard> { }
23  ///
24  /// Just by exisitng, this hook now automatically appears in "MyWizard". Note that *how* this hook functions is not defined. Thus, it will simply appear as a label stating "Unnamed Module".
25  /// If you are looking for a simple hook, namely: a toggle field that will add or remove a given prefab with a certain component as a child to our target object, there is a preexisitng class:
26  //# public class MyHook : AVR_WizardHook_SimpleToggle<MyWizard, MyComponent> {
27  //# protected override string moduleName => "My Module";
28  //# protected override string prefabPathSettingsToken => "/some/settings/token";
29  //# }
30  /// This hook will add a toggle-field with the label "My Module" to the MyWizard. If the toggle is turned on and the targetObject has no MyComponent on itself or its children, it will
31  /// instantiate a prefab found under the path designated by the prefabPathSettingsToken (in AVR_Settings).
32  /// If the toggle is off, all gameobjects with a "MyComponent" will be destroyed.
33  ///
34  /// Implementing your own custom hooks is easy. There are only three methods to implement:
35  /// - void on_create_wizard(GameObject targetObject) will be called when a wizard that contains this hook is created with the given target gameobject
36  /// - void embed_GUI() contains the GUI of the given hook. If the hook should consist of a simple boolean toggle, you would write something along the lines of EditorGUILayout.Toggle(...) in here.
37  /// - void on_submit(GameObject targetObject) is called when the "Update" button on the wizard is called. This is where you ought to save your changes, instantiate prefabs or anything else.
38 
39  public abstract class AVR_HookableWizard<T> : EditorWindow where T : AVR_HookableWizard<T>
40  {
41  private static List<AVR_WizardHook<T>> registered_hooks = new List<AVR_WizardHook<T>>();
42 
43  private static GameObject targetObject;
44 
45  public static void ShowWindow(GameObject _targetObject, string windowName = "Wizard")
46  {
47  registered_hooks.Clear();
48  targetObject = _targetObject;
49 
50  // Get all classes that inherit from ModuleWizard_Hook
51  List<System.Type> hook_classes = System.AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly => assembly.GetTypes()).Where(type => type.IsSubclassOf(typeof(AVR_WizardHook<T>))).ToList();
52 
53  foreach (System.Type hook in hook_classes) {
54  // Don't register abstract hooks.
55  if(hook.IsAbstract) continue;
56 
57  AVR_WizardHook<T> o = (AVR_WizardHook<T>)System.Activator.CreateInstance(hook);
58 
59  registered_hooks.Add(o);
60  o.on_create_wizard(targetObject);
61  }
62 
63  registered_hooks.Sort((a, b) => a.category.CompareTo(b.category));
64 
65  EditorWindow.GetWindow(typeof(T), true, windowName);
66  }
67 
68  void OnGUI()
69  {
70  EditorGUILayout.BeginVertical();
71 
72  foreach(AVR_WizardHook<T> o in registered_hooks) {
73  EditorGUILayout.BeginVertical();
74 
75  o.embed_GUI();
76 
77  EditorGUILayout.EndVertical();
78  EditorGUILayout.Space();
79  }
80 
81  EditorGUILayout.EndVertical();
82 
83  if(GUILayout.Button("Update")) {
84  foreach (AVR_WizardHook<T> o in registered_hooks) {
85  o.on_submit(targetObject);
86  }
87  this.Close();
88  }
89 
90  }
91  }
92 
93 
94 
95  public abstract class AVR_WizardHook<Wiz> where Wiz : AVR_HookableWizard<Wiz>
96  {
97  // Flags can be used for WizardHooks to communicate with one another. Example: dependecies on InputManager
98  protected static Dictionary<string, bool> flags = new Dictionary<string, bool>();
99 
100  public virtual int category
101  {
102  get { return 9; }
103  }
104 
105  public virtual void on_create_wizard(GameObject targetObject)
106  {
107 
108  }
109 
110  public virtual void embed_GUI()
111  {
112  EditorGUILayout.LabelField("Unnamed Module");
113  }
114 
115  public virtual void on_submit(GameObject targetObject)
116  {
117  PrefabUtility.UnpackPrefabInstance(targetObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
118  }
119 
120  protected virtual void safeDestroyImmediate(GameObject toDestroy, GameObject wizardTarget) {
121  try {
122  GameObject.DestroyImmediate(toDestroy);
123  }
124  catch(System.InvalidOperationException) {
125  PrefabUtility.UnpackPrefabInstance(wizardTarget, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction);
126  GameObject.DestroyImmediate(toDestroy);
127  }
128  }
129  }
130 
131  public abstract class AVR_WizardHook_SimpleToggle<Wiz, Mod> : AVR_WizardHook<Wiz> where Wiz : AVR_HookableWizard<Wiz> where Mod : MonoBehaviour
132  {
133  protected abstract string moduleName { get; }
134 
135  protected abstract string prefabPathSettingsToken { get; }
136 
137  protected virtual string[] dependencies => new string[] {};
138 
139  protected Mod[] _module;
140 
141  public bool module;
142 
143  public override void on_create_wizard(GameObject targetObject)
144  {
145  _module = targetObject.GetComponentsInChildren<Mod>();
146  module = _module.Length > 0;
147  flags[moduleName] = module;
148  }
149 
150  public override void embed_GUI()
151  {
152  module = EditorGUILayout.BeginToggleGroup(moduleName, module);
153  // Nothing to see here
154  EditorGUILayout.EndToggleGroup();
155 
156  // See if there are any missing dependencies
157  string[] missing_d = dependencies.Where((s) => !flags[s]).ToArray();
158  // If yes, display error
159  if(module && missing_d.Length > 0) {
160  EditorGUILayout.HelpBox(moduleName + " needs " + string.Join(", ", missing_d), MessageType.Error);
161  }
162 
163  // Set flag for this module
164  flags[moduleName] = module;
165  }
166 
167  public override void on_submit(GameObject targetObject)
168  {
169  if (module && _module.Length < 1)
170  {
171  AVR_EditorUtility.InstantiatePrefabAsChild(targetObject.transform, prefabPathSettingsToken);
172  }
173  else if (!module && _module.Length > 0)
174  {
175  foreach (Mod c in _module) safeDestroyImmediate(c.gameObject, targetObject);
176  }
177  }
178  }
179 
180  public abstract class AVR_WizardHook_SimpleFilteredToggle<Wiz, Mod> : AVR_WizardHook_SimpleToggle<Wiz, Mod> where Wiz : AVR_HookableWizard<Wiz> where Mod : MonoBehaviour
181  {
182  protected abstract System.Func<Mod, bool> filter { get; }
183 
184  public override void on_create_wizard(GameObject targetObject)
185  {
186  _module = targetObject.GetComponentsInChildren<Mod>().Where(filter).ToArray();
187  module = _module.Length > 0;
188  flags[moduleName] = module;
189  }
190  }
191 
192  public abstract class AVR_WizardHook_DropdownChoiceToggle<Wiz, Mod> : AVR_WizardHook<Wiz> where Wiz : AVR_HookableWizard<Wiz> where Mod : MonoBehaviour
193  {
194  protected abstract string moduleName { get; }
195 
196  protected abstract DDChoice[] options { get; }
197 
198  protected virtual string[] dependencies => new string[] { };
199 
200  protected struct DDChoice {
201  public DDChoice(string choiceName, string prefabPathSettingsToken, System.Func<Mod, bool> filter) {
202  this.choiceName = choiceName;
203  this.prefabPathSettingsToken = prefabPathSettingsToken;
204  this.filter = filter;
205  }
206 
207  public string choiceName;
208  public string prefabPathSettingsToken;
209  public System.Func<Mod, bool> filter;
210  }
211 
212  protected Mod[] _module;
213 
214  protected DDChoice _selected;
215  protected DDChoice _prevselected;
216 
217  private string [] moduleTypeList;
218 
219  public bool module;
220 
221  public override void on_create_wizard(GameObject targetObject)
222  {
223  _module = targetObject.GetComponentsInChildren<Mod>();
224  moduleTypeList = options.Select(t => t.choiceName).ToArray();
225 
226  module = false;
227 
228  foreach(var entry in options) {
229  if(_module.Any(entry.filter)) {
230  _prevselected = _selected = entry;
231  module = true;
232  break;
233  }
234  }
235 
236  flags[moduleName] = module;
237  }
238 
239  public override void embed_GUI()
240  {
241  module = EditorGUILayout.BeginToggleGroup(moduleName, module);
242 
243  int index = Mathf.Max(0, System.Array.FindIndex(moduleTypeList, w => w == _selected.choiceName));
244  index = EditorGUILayout.Popup(index, moduleTypeList);
245  _selected = options[index];
246 
247  EditorGUILayout.EndToggleGroup();
248 
249  // See if there are any missing dependencies
250  string[] missing_d = dependencies.Where((s) => !flags[s]).ToArray();
251  // If yes, display error
252  if (module && missing_d.Length > 0)
253  {
254  EditorGUILayout.HelpBox(moduleName + " needs " + string.Join(", ", missing_d), MessageType.Error);
255  }
256 
257  // Set flag for this module
258  flags[moduleName] = module;
259  }
260 
261  public override void on_submit(GameObject targetObject)
262  {
263  if (module && _module.Length < 1)
264  {
265  AVR_EditorUtility.InstantiatePrefabAsChild(targetObject.transform, _selected.prefabPathSettingsToken);
266  }
267  else if (module && _module.Length > 0)
268  {
269  Mod first = _module[0];
270  if (!_selected.filter.Invoke(first)) {
271  safeDestroyImmediate(first.gameObject, targetObject);
272  AVR_EditorUtility.InstantiatePrefabAsChild(targetObject.transform, _selected.prefabPathSettingsToken);
273  }
274  }
275  else if (!module && _module.Length > 0)
276  {
277  foreach (Mod c in _module) safeDestroyImmediate(c.gameObject, targetObject);
278  }
279  }
280  }
281 }
virtual void on_submit(GameObject targetObject)
virtual void on_create_wizard(GameObject targetObject)
static void ShowWindow(GameObject _targetObject, string windowName="Wizard")
DDChoice(string choiceName, string prefabPathSettingsToken, System.Func< Mod, bool > filter)
override void on_create_wizard(GameObject targetObject)
virtual void safeDestroyImmediate(GameObject toDestroy, GameObject wizardTarget)