AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
MRTKEditor.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using System;
5 using System.Collections.Generic;
6 using System.Reflection;
7 using UnityEngine;
8 #if UNITY_EDITOR
9 using UnityEditor;
10 #endif
11 
12 namespace HoloToolkit.Unity
13 {
14 #if UNITY_EDITOR
15  public class MRTKEditor : Editor
28  {
29  #region static vars
30  const string mrtkShowEditorKey = "_Show_MRTK_Editors";
31 
32  // Toggles custom editors on / off
33  public static bool ShowCustomEditors
34  {
35  get
36  {
37  return GetEditorPref(mrtkShowEditorKey, true);
38  }
39  private set
40  {
41  SetEditorPref(mrtkShowEditorKey, value);
42  }
43  }
44  public static bool CustomEditorActive { get; private set; }
45  public static GameObject lastTarget;
46 
47  // Styles
48  private static GUIStyle toggleButtonOffStyle = null;
49  private static GUIStyle toggleButtonOnStyle = null;
50  private static GUIStyle sectionStyle = null;
51  private static GUIStyle toolTipStyle = null;
52  private static GUIStyle inProgressStyle = null;
53 
54  // Colors
55  protected readonly static Color defaultColor = new Color(1f, 1f, 1f);
56  protected readonly static Color disabledColor = new Color(0.6f, 0.6f, 0.6f);
57  protected readonly static Color borderedColor = new Color(0.8f, 0.8f, 0.8f);
58  protected readonly static Color warningColor = new Color(1f, 0.85f, 0.6f);
59  protected readonly static Color errorColor = new Color(1f, 0.55f, 0.5f);
60  protected readonly static Color successColor = new Color(0.8f, 1f, 0.75f);
61  protected readonly static Color objectColor = new Color(0.85f, 0.9f, 1f);
62  protected readonly static Color helpBoxColor = new Color(0.70f, 0.75f, 0.80f, 0.5f);
63  protected readonly static Color sectionColor = new Color(0.85f, 0.9f, 1f);
64  protected readonly static Color darkColor = new Color(0.1f, 0.1f, 0.1f);
65  protected readonly static Color objectColorEmpty = new Color(0.75f, 0.8f, 0.9f);
66  protected readonly static Color profileColor = new Color(0.88f, 0.7f, .97f);
67  protected readonly static Color handleColorSquare = new Color(0.0f, 0.9f, 1f);
68  protected readonly static Color handleColorCircle = new Color(1f, 0.5f, 1f);
69  protected readonly static Color handleColorSphere = new Color(1f, 0.5f, 1f);
70  protected readonly static Color handleColorAxis = new Color(0.0f, 1f, 0.2f);
71  protected readonly static Color handleColorRotation = new Color(0.0f, 1f, 0.2f);
72  protected readonly static Color handleColorTangent = new Color(0.1f, 0.8f, 0.5f, 0.7f);
73 
74  public const float DottedLineScreenSpace = 4.65f;
75 
76  // Toggles visible tooltips
77  private static bool showHelp = false;
78  // Stores the show / hide values of displayed sections by target name + section name
79  private static Dictionary<string, bool> displayedSections = new Dictionary<string, bool>();
80  private static int indentOnSectionStart = 0;
81 
82  private static BindingFlags defaultBindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
83 
84  private bool recordingUndo = false;
85 
86  #endregion
87 
88  public override void OnInspectorGUI()
89  {
90  // Set this to true so we can track which kind of headers to draw
91  CustomEditorActive = true;
92 
93  try
94  {
95  CreateStyles();
96  DrawInspectorHeader();
97  Undo.RecordObject(target, target.name);
98 
99  if (ShowCustomEditors)
100  {
101  BeginInspectorStyle();
102  DrawCustomEditor();
103  DrawCustomFooter();
104  EndInspectorStyle();
105  }
106  else
107  {
108  base.DrawDefaultInspector();
109  }
110 
111  SaveChanges();
112  } catch (Exception e)
113  {
114  DrawError(e.ToString());
115  }
116 
117  CustomEditorActive = false;
118  }
119 
120  public void OnSceneGUI()
121  {
122  recordingUndo = false;
123  DrawCustomSceneGUI();
124  }
125 
126  protected virtual void BeginInspectorStyle()
127  {
128  // Empty by default
129  }
130 
131  protected virtual void EndInspectorStyle()
132  {
133  // Empty by default
134  }
135 
136  protected virtual bool DisplayHeader { get { return true; } }
137 
141  private void DrawInspectorHeader()
142  {
143  if (!DisplayHeader)
144  {
145  return;
146  }
147 
148  EditorGUILayout.Space();
149  GUILayout.BeginHorizontal();
150  if (GUILayout.Button(ShowCustomEditors ? "Toggle Custom Editors (ON)" : "Toggle Custom Editors (OFF)", ShowCustomEditors ? toggleButtonOnStyle : toggleButtonOffStyle))
151  {
152  ShowCustomEditors = !ShowCustomEditors;
153  }
154  if (ShowCustomEditors)
155  {
156  if (GUILayout.Button(showHelp ? "Toggle Help (ON)" : "Toggle Help (OFF)", showHelp ? toggleButtonOnStyle : toggleButtonOffStyle))
157  {
158  showHelp = !showHelp;
159  }
160  if (GUILayout.Button("Expand Sections", toggleButtonOffStyle))
161  {
162  Type targetType = target.GetType();
163  foreach (MemberInfo member in targetType.GetMembers(defaultBindingFlags))
164  {
165  if (member.IsDefined(typeof(HeaderAttribute), true))
166  {
167  HeaderAttribute h = member.GetCustomAttributes(typeof(HeaderAttribute), true)[0] as HeaderAttribute;
168  string lookupName = targetType.Name + h.header;
169  if (!displayedSections.ContainsKey(lookupName))
170  {
171  displayedSections.Add(lookupName, true);
172  }
173  else
174  {
175  displayedSections[lookupName] = true;
176  }
177  }
178  }
179  }
180  if (GUILayout.Button("Collapse Sections", toggleButtonOffStyle))
181  {
182  Type targetType = target.GetType();
183  foreach (MemberInfo member in targetType.GetMembers(defaultBindingFlags))
184  {
185  if (member.IsDefined(typeof(HeaderAttribute), true))
186  {
187  HeaderAttribute h = member.GetCustomAttributes(typeof(HeaderAttribute), true)[0] as HeaderAttribute;
188  string lookupName = targetType.Name + h.header;
189  if (!displayedSections.ContainsKey(lookupName))
190  {
191  displayedSections.Add(lookupName, false);
192  }
193  else
194  {
195  displayedSections[lookupName] = false;
196  }
197  }
198  }
199  }
200  }
201  GUILayout.EndHorizontal();
202 
203  if (ShowCustomEditors)
204  {
205  GUI.color = defaultColor;
206 
207  GUILayout.BeginVertical();
208  GUILayout.BeginHorizontal();
209  Type targetType = target.GetType();
210  foreach (DocLinkAttribute attribute in targetType.GetCustomAttributes(typeof(DocLinkAttribute), true))
211  {
212  string description = attribute.Description;
213  if (string.IsNullOrEmpty(description))
214  {
215  description = "Click for documentation about " + targetType.Name;
216  }
217 
218  if (GUILayout.Button(description, EditorStyles.toolbarButton))
219  {
220  Application.OpenURL(attribute.DocURL);
221  }
222  }
223  GUILayout.EndHorizontal();
224  GUILayout.BeginHorizontal();
225  foreach (TutorialAttribute attribute in targetType.GetCustomAttributes(typeof(TutorialAttribute), true))
226  {
227  string description = attribute.Description;
228  if (string.IsNullOrEmpty(description))
229  {
230  description = "Click for a tutorial on " + targetType.Name;
231  }
232 
233  if (GUILayout.Button(description, EditorStyles.toolbarButton))
234  {
235  Application.OpenURL(attribute.TutorialURL);
236  }
237  }
238  GUILayout.EndHorizontal();
239  GUILayout.BeginHorizontal();
240  List<Type> missingTypes = new List<Type>();
241  foreach (UseWithAttribute attribute in targetType.GetCustomAttributes(typeof(UseWithAttribute), true))
242  {
243  Component targetGo = (Component)target;
244  if (targetGo == null)
245  {
246  break;
247  }
248 
249  foreach (Type type in attribute.UseWithTypes)
250  {
251  Component c = targetGo.GetComponent(type);
252  if (c == null)
253  {
254  missingTypes.Add(type);
255  }
256  }
257  }
258  if (missingTypes.Count > 0)
259  {
260  string warningMessage = "This class is designed to be accompanied by scripts of (or inheriting from) types: \n";
261  for (int i = 0; i < missingTypes.Count; i++)
262  {
263  warningMessage += " - " + missingTypes[i].FullName;
264  if (i < missingTypes.Count - 1)
265  warningMessage += "\n";
266  }
267  warningMessage += "\nIt may not function correctly without them.";
268  DrawWarning(warningMessage);
269  }
270  GUILayout.EndHorizontal();
271  GUILayout.EndVertical();
272  }
273  EditorGUILayout.Space();
274  }
275 
279  protected void DrawCustomEditor()
280  {
281  EditorGUILayout.BeginVertical();
282 
283  Type targetType = target.GetType();
284  // Get all the members of this type, public and private
285  List<MemberInfo> members = new List<MemberInfo>(targetType.GetMembers(defaultBindingFlags));
286 
287  // Start drawing the editor
288  int currentIndentLevel = 0;
289  bool insideSectionBlock = false;
290  bool drawCurrentSection = true;
291 
292  foreach (MemberInfo member in members)
293  {
294  try
295  {
296  switch (member.MemberType)
297  {
298  default:
299  // We only want to draw fields, properties and arrays
300  continue;
301 
302  case MemberTypes.Field:
303  case MemberTypes.Property:
304  break;
305  }
306 
307  // First get header and indent settings
308  if (member.IsDefined(typeof(HeaderAttribute), true))
309  {
310  HeaderAttribute header = member.GetCustomAttributes(typeof(HeaderAttribute), true)[0] as HeaderAttribute;
311  if (insideSectionBlock)
312  {
313  DrawSectionEnd();
314  }
315 
316  insideSectionBlock = true;
317  drawCurrentSection = DrawSectionStart(target.GetType().Name, header.header);
318  }
319 
320  // Then do basic show / hide based on ShowIfAttribute
321  if ((insideSectionBlock && !drawCurrentSection) || !ShouldDrawMember(member, targetType, target))
322  {
323  continue;
324  }
325 
326  // Handle drawing stuff (indent, help)
327  if (showHelp)
328  {
329  DrawToolTip(member);
330  }
331 
332  if (member.IsDefined(typeof(SetIndentAttribute), true))
333  {
334  SetIndentAttribute indent = member.GetCustomAttributes(typeof(SetIndentAttribute), true)[0] as SetIndentAttribute;
335  currentIndentLevel += indent.Indent;
336  EditorGUI.indentLevel = currentIndentLevel;
337  }
338 
339  if (member.IsDefined(typeof(FeatureInProgressAttribute), true))
340  {
341  GUI.color = helpBoxColor;
342  EditorGUILayout.LabelField("(This field or property is marked as 'In Progress' and may have no effect.)", inProgressStyle);
343  GUI.color = Color.Lerp(disabledColor, Color.clear, 0.5f);
344  }
345  else
346  {
347  GUI.color = defaultColor;
348  }
349 
350  // Now get down to drawing the thing
351  // Get an array ready for our override attributes
352  object[] drawOverrideAttributes = null;
353  if (member.MemberType == MemberTypes.Field)
354  {
355  FieldInfo field = targetType.GetField(member.Name, defaultBindingFlags);
356  if (!field.IsPrivate || field.IsDefined(typeof(SerializeField), true))
357  {
358  // If it's a profile field, take care of that first
359  if (IsSubclassOf(field.FieldType, typeof(ProfileBase)))
360  {
361  UnityEngine.Object profile = (UnityEngine.Object)field.GetValue(target);
362  profile = DrawProfileField(target, profile, field.FieldType);
363  field.SetValue(target, profile);
364  }
365  else
366  {
367  drawOverrideAttributes = field.GetCustomAttributes(typeof(DrawOverrideAttribute), true);
368  // If we fine overrides, draw using those
369  if (drawOverrideAttributes.Length > 0)
370  {
371  if (drawOverrideAttributes.Length > 1)
372  DrawWarning("You should only use one DrawOverride attribute per member. Drawing " + drawOverrideAttributes[0].GetType().Name + " only.");
373 
374  (drawOverrideAttributes[0] as DrawOverrideAttribute).DrawEditor(target, field, serializedObject.FindProperty(field.Name));
375  }
376  else
377  {
378  // Otherwise just draw the default editor
379  DrawSerializedField(serializedObject, field.Name);
380  }
381  }
382  }
383  }
384  else // Property
385  {
386  // We have to draw properties manually
387  PropertyInfo prop = targetType.GetProperty(member.Name, defaultBindingFlags);
388  drawOverrideAttributes = prop.GetCustomAttributes(typeof(DrawOverrideAttribute), true);
389  // If it's a profile field, take care of that first
390  if (IsSubclassOf(prop.PropertyType, typeof(ProfileBase)))
391  {
392  UnityEngine.Object profile = (UnityEngine.Object)prop.GetValue(target, null);
393  profile = DrawProfileField(target, profile, prop.PropertyType);
394  prop.SetValue(target, profile, null);
395  }
396  // If we find overrides, draw using those
397  else if (drawOverrideAttributes.Length > 0)
398  {
399  if (drawOverrideAttributes.Length > 1)
400  {
401  DrawWarning("You should only use one DrawOverride attribute per member. Drawing " + drawOverrideAttributes[0].GetType().Name + " only.");
402  }
403 
404  (drawOverrideAttributes[0] as DrawOverrideAttribute).DrawEditor(target, prop);
405  }
406  }
407  }
408  catch (Exception e)
409  {
410  DrawWarning("There was a problem drawing the member " + member.Name + ":");
411  DrawError(System.Environment.NewLine + e.ToString());
412  }
413  }
414 
415  if (insideSectionBlock)
416  {
417  DrawSectionEnd();
418  }
419 
420  EditorGUILayout.EndVertical();
421  }
422 
426  protected virtual void DrawCustomSceneGUI()
427  {
428 
429  }
430 
435  protected virtual void DrawCustomFooter()
436  {
437  //...
438  }
439 
443  protected void SaveChanges()
444  {
445  serializedObject.ApplyModifiedProperties();
446  EditorUtility.SetDirty(target);
447  }
448 
449  #region drawing
450 
458  private bool ShouldDrawMember(MemberInfo member, Type targetType, object target)
459  {
460  object[] hideAttributes = member.GetCustomAttributes(typeof(HideInInspector), true);
461  if (hideAttributes != null && hideAttributes.Length > 0)
462  return false;
463 
464  bool shouldBeVisible = true;
465 
466  switch (member.MemberType)
467  {
468  case MemberTypes.Field:
469  // Fields are visible by default unless they're hidden by a ShowIfAttribute
470  foreach (ShowIfAttribute attribute in member.GetCustomAttributes(typeof(ShowIfAttribute), true))
471  {
472  if (!attribute.ShouldShow(target))
473  {
474  shouldBeVisible = false;
475  break;
476  }
477  }
478  break;
479 
480  case MemberTypes.Property:
481  // Property types require at least one Attribute to be visible
482  if (member.GetCustomAttributes(typeof(Attribute), true).Length == 0)
483  {
484  shouldBeVisible = false;
485  }
486  else
487  {
488  // Even if they have an editor attribute, they can still be hidden by a ShowIfAttribute
489  foreach (ShowIfAttribute attribute in member.GetCustomAttributes(typeof(ShowIfAttribute), true))
490  {
491  if (!attribute.ShouldShow(target))
492  {
493  shouldBeVisible = false;
494  break;
495  }
496  }
497  }
498  break;
499 
500  default:
501  break;
502  }
503  return shouldBeVisible;
504  }
505 
511  protected void DrawSerializedField(SerializedObject serializedObject, string propertyPath)
512  {
513  SerializedProperty prop = serializedObject.FindProperty(propertyPath);
514  if (prop != null)
515  {
516  EditorGUILayout.PropertyField(prop, true);
517  }
518  }
519 
528  public static bool DrawSectionStart(string targetName, string headerName, bool toUpper = true, bool drawInitially = true)
529  {
530  string lookupName = targetName + headerName;
531  if (!displayedSections.ContainsKey(lookupName))
532  displayedSections.Add(lookupName, drawInitially);
533 
534  bool drawSection = displayedSections[lookupName];
535  EditorGUILayout.Space();
536  Color tColor = GUI.color;
537  GUI.color = sectionColor;
538 
539  if (toUpper)
540  {
541  headerName = headerName.ToUpper();
542  }
543 
544  drawSection = EditorGUILayout.Foldout(drawSection, headerName, true, sectionStyle);
545  displayedSections[lookupName] = drawSection;
546  EditorGUILayout.BeginVertical();
547  GUI.color = tColor;
548 
549  indentOnSectionStart = EditorGUI.indentLevel;
550  EditorGUI.indentLevel = 0;// indentOnSectionStart + 1;
551 
552  return drawSection;
553  }
554 
558  public static void DrawSectionEnd()
559  {
560  EditorGUILayout.EndVertical();
561  EditorGUI.indentLevel = indentOnSectionStart;
562  }
563 
568  public static void DrawToolTip(MemberInfo member)
569  {
570  if (member.IsDefined(typeof(TooltipAttribute), true))
571  {
572  TooltipAttribute tooltip = member.GetCustomAttributes(typeof(TooltipAttribute), true)[0] as TooltipAttribute;
573  Color prevColor = GUI.color;
574  GUI.color = helpBoxColor;
575  EditorGUI.indentLevel--;
576  EditorGUILayout.LabelField(tooltip.tooltip, toolTipStyle);
577  EditorGUI.indentLevel++;
578  GUI.color = prevColor;
579  }
580  }
581 
582  public static void DrawWarning(string warning)
583  {
584  Color prevColor = GUI.color;
585 
586  GUI.color = warningColor;
587  EditorGUILayout.BeginVertical(EditorStyles.textArea);
588  EditorGUILayout.LabelField(warning, EditorStyles.wordWrappedMiniLabel);
589  EditorGUILayout.EndVertical();
590 
591  GUI.color = prevColor;
592  }
593 
594  public static void DrawError(string error)
595  {
596  Color prevColor = GUI.color;
597 
598  GUI.color = errorColor;
599  EditorGUILayout.BeginVertical(EditorStyles.textArea);
600  EditorGUILayout.LabelField(error, EditorStyles.wordWrappedMiniLabel);
601  EditorGUILayout.EndVertical();
602 
603  GUI.color = prevColor;
604  }
605 
606  public static void DrawDivider()
607  {
608  GUIStyle styleHR = new GUIStyle(GUI.skin.box);
609  styleHR.stretchWidth = true;
610  styleHR.fixedHeight = 2;
611  GUILayout.Box("", styleHR);
612  }
613 
614  private void CreateStyles()
615  {
616  if (toggleButtonOffStyle == null)
617  {
618  toggleButtonOffStyle = "ToolbarButton";
619  toggleButtonOffStyle.fontSize = 9;
620  toggleButtonOnStyle = new GUIStyle(toggleButtonOffStyle);
621  toggleButtonOnStyle.normal.background = toggleButtonOnStyle.active.background;
622 
623  sectionStyle = new GUIStyle(EditorStyles.foldout);
624  sectionStyle.fontStyle = FontStyle.Bold;
625 
626  toolTipStyle = new GUIStyle(EditorStyles.wordWrappedMiniLabel);
627  toolTipStyle.fontStyle = FontStyle.Normal;
628  toolTipStyle.alignment = TextAnchor.LowerLeft;
629 
630  inProgressStyle = new GUIStyle(EditorStyles.wordWrappedMiniLabel);
631  inProgressStyle.fontStyle = FontStyle.Italic;
632  inProgressStyle.alignment = TextAnchor.LowerLeft;
633  }
634  }
635 
636  #endregion
637 
638  #region profiles
639 
651  private static UnityEngine.Object DrawProfileField(UnityEngine.Object target, UnityEngine.Object profile, Type profileType)
652  {
653  Color prevColor = GUI.color;
654  GUI.color = profileColor;
655  EditorGUILayout.BeginVertical(EditorStyles.helpBox);
656  GUI.color = Color.Lerp(Color.white, Color.gray, 0.25f);
657  EditorGUILayout.LabelField("Select a " + profileType.Name + " or create a new profile", EditorStyles.miniBoldLabel);
658  UnityEngine.Object newProfile = profile;
659  EditorGUILayout.BeginHorizontal();
660  newProfile = EditorGUILayout.ObjectField(profile, profileType, false);
661  // is this an abstract class?
662  if (profileType.IsAbstract)
663  {
664  EditorGUILayout.BeginVertical();
665  List<Type> types = GetDerivedTypes(profileType, Assembly.GetAssembly(profileType));
666 
667  EditorGUILayout.BeginHorizontal();
668  foreach (Type derivedType in types)
669  {
670  if (GUILayout.Button("Create " + derivedType.Name))
671  {
672  profile = CreateProfile(derivedType);
673  }
674  }
675  if (GUILayout.Button("What's a profile?"))
676  {
677  LaunchProfileHelp();
678  }
679  EditorGUILayout.EndHorizontal();
680 
681  EditorGUILayout.EndVertical();
682  }
683  else
684  {
685  EditorGUILayout.BeginHorizontal();
686  if (GUILayout.Button("Create Profile"))
687  {
688  profile = CreateProfile(profileType);
689  }
690  if (GUILayout.Button("What's a profile?"))
691  {
692  LaunchProfileHelp();
693  }
694  EditorGUILayout.EndHorizontal();
695  }
696  EditorGUILayout.EndHorizontal();
697 
698  if (profile == null)
699  {
700  DrawError("You must choose a profile.");
701  }
702  else
703  {
704  EditorGUI.indentLevel++;
705  // Draw the editor for this profile
706  // Set it to false initially
707  if (DrawSectionStart(target.GetType().Name, profile.name + " (Click to edit)", false, false))
708  {
709  // Draw the profile inspector
710  Editor inspector = Editor.CreateEditor(profile);
711  ProfileInspector profileInspector = (ProfileInspector)inspector;
712  if (profileInspector != null)
713  {
714  profileInspector.targetComponent = target as Component;
715  }
716  inspector.OnInspectorGUI();
717  }
718 
719  DrawSectionEnd();
720  EditorGUI.indentLevel--;
721  }
722 
723  EditorGUILayout.EndVertical();
724 
725  GUI.color = prevColor;
726  return newProfile;
727  }
728 
732  private static void LaunchProfileHelp()
733  {
734  EditorUtility.DisplayDialog(
735  "Profiles Help",
736  "Profiles are assets that contain a set of common settings like colors or sound files."
737  + "\n\nThose settings can be shared and used by any objects that keep a reference to the profile."
738  + "\n\nThey make changing the style of a set of objects quicker and easier, and they reduce memory usage."
739  + "\n\nA purple icon indicates that you're looking at a profile asset."
740  + "\n\nFor more information please see the documentation at:"
741  + "\n https://github.com/Microsoft/MRDesignLabs_Unity/"
742  , "OK");
743  }
744 
750  private static UnityEngine.Object CreateProfile(Type profileType)
751  {
752  UnityEngine.Object asset = ScriptableObject.CreateInstance(profileType);
753  if (asset != null)
754  {
755  AssetDatabase.CreateAsset(asset, "Assets/New" + profileType.Name + ".asset");
756  AssetDatabase.SaveAssets();
757  }
758  else
759  {
760  Debug.LogError("Couldn't create profile of type " + profileType.Name);
761  }
762  return asset;
763  }
764 
765  private static List<Type> GetDerivedTypes(Type baseType, Assembly assembly)
766  {
767  Type[] types = assembly.GetTypes();
768  List<Type> derivedTypes = new List<Type>();
769 
770  for (int i = 0, count = types.Length; i < count; i++)
771  {
772  Type type = types[i];
773  if (IsSubclassOf(type, baseType))
774  {
775  derivedTypes.Add(type);
776  }
777  }
778 
779  return derivedTypes;
780  }
781 
782  private static bool IsSubclassOf(Type type, Type baseType)
783  {
784  if (type == null || baseType == null || type == baseType)
785  return false;
786 
787  if (baseType.IsGenericType == false)
788  {
789  if (type.IsGenericType == false)
790  {
791  return type.IsSubclassOf(baseType);
792  }
793  }
794  else
795  {
796  baseType = baseType.GetGenericTypeDefinition();
797  }
798 
799  type = type.BaseType;
800  Type objectType = typeof(object);
801 
802  while (type != objectType && type != null)
803  {
804  Type curentType = type.IsGenericType ?
805  type.GetGenericTypeDefinition() : type;
806  if (curentType == baseType)
807  {
808  return true;
809  }
810 
811  type = type.BaseType;
812  }
813 
814  return false;
815  }
816  #endregion
817 
818  #region handles
819  // These are a set of handles for OnSceneGUI. They automatically register Undo for the target object and they have a consistent look.
820 
821  protected float AxisMoveHandle(Vector3 origin, Vector3 direction, float distance, float handleSize = 0.2f, bool autoSize = true)
822  {
823  Vector3 position = origin + (direction.normalized * distance);
824 
825  Handles.color = handleColorAxis;
826  if (autoSize)
827  {
828  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
829  }
830 
831  Handles.DrawDottedLine(origin, position, DottedLineScreenSpace);
832  Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(direction), handleSize * 2, EventType.Repaint);
833  Vector3 newPosition = Handles.FreeMoveHandle(position, Quaternion.identity, handleSize, Vector3.zero, Handles.CircleHandleCap);
834  if (recordingUndo)
835  return distance;
836 
837  float newDistance = Vector3.Distance(origin, newPosition);
838 
839  if (distance != newDistance)
840  {
841  recordingUndo = true;
842  Undo.RegisterCompleteObjectUndo(target, target.name);
843  distance = newDistance;
844  }
845  return distance;
846  }
847 
848  protected Vector3 CircleMoveHandle(Vector3 position, float handleSize = 0.2f, bool autoSize = true, float xScale = 1f, float yScale = 1f, float zScale = 1f)
849  {
850  Handles.color = handleColorCircle;
851  if (autoSize)
852  {
853  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
854  }
855 
856  Vector3 newPosition = Handles.FreeMoveHandle(position, Quaternion.identity, handleSize, Vector3.zero, Handles.CircleHandleCap);
857  if (recordingUndo)
858  return position;
859 
860  if (position != newPosition)
861  {
862  recordingUndo = true;
863  Undo.RegisterCompleteObjectUndo(target, target.name);
864 
865  position.x = Mathf.Lerp(position.x, newPosition.x, Mathf.Clamp01(xScale));
866  position.y = Mathf.Lerp(position.z, newPosition.y, Mathf.Clamp01(yScale));
867  position.z = Mathf.Lerp(position.y, newPosition.z, Mathf.Clamp01(zScale));
868  }
869  return position;
870  }
871 
872  protected Vector3 SquareMoveHandle(Vector3 position, float handleSize = 0.2f, bool autoSize = true, float xScale = 1f, float yScale = 1f, float zScale = 1f)
873  {
874  Handles.color = handleColorSquare;
875  if (autoSize)
876  {
877  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
878  }
879 
880  // Multiply square handle to match other types
881  Vector3 newPosition = Handles.FreeMoveHandle(position, Quaternion.identity, handleSize * 0.8f, Vector3.zero, Handles.RectangleHandleCap);
882  if (recordingUndo)
883  return position;
884 
885  if (position != newPosition)
886  {
887  recordingUndo = true;
888  Undo.RegisterCompleteObjectUndo(target, target.name);
889 
890  position.x = Mathf.Lerp(position.x, newPosition.x, Mathf.Clamp01(xScale));
891  position.y = Mathf.Lerp(position.z, newPosition.y, Mathf.Clamp01(yScale));
892  position.z = Mathf.Lerp(position.y, newPosition.z, Mathf.Clamp01(zScale));
893  }
894  return position;
895  }
896 
897  protected Vector3 SphereMoveHandle(Vector3 position, float handleSize = 0.2f, bool autoSize = true, float xScale = 1f, float yScale = 1f, float zScale = 1f)
898  {
899  Handles.color = handleColorSphere;
900  if (autoSize)
901  {
902  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
903  }
904 
905  // Multiply sphere handle size to match other types
906  Vector3 newPosition = Handles.FreeMoveHandle(position, Quaternion.identity, handleSize * 2, Vector3.zero, Handles.SphereHandleCap);
907  if (recordingUndo)
908  return position;
909 
910  if (position != newPosition)
911  {
912  recordingUndo = true;
913  Undo.RegisterCompleteObjectUndo(target, target.name);
914 
915  position.x = Mathf.Lerp(position.x, newPosition.x, Mathf.Clamp01(xScale));
916  position.y = Mathf.Lerp(position.z, newPosition.y, Mathf.Clamp01(yScale));
917  position.z = Mathf.Lerp(position.y, newPosition.z, Mathf.Clamp01(zScale));
918  }
919  return position;
920  }
921 
922  protected Vector3 VectorHandle(Vector3 origin, Vector3 vector, bool normalize = true, float handleLength = 1f, bool clamp = true, float handleSize = 0.1f, bool autoSize = true)
923  {
924  Handles.color = handleColorTangent;
925  if (autoSize)
926  {
927  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(origin) * handleSize, 0.75f);
928  }
929 
930  Vector3 handlePosition = origin + (vector * handleLength);
931  float distanceToOrigin = Vector3.Distance(origin, handlePosition) / handleLength;
932 
933  if (normalize)
934  {
935  vector.Normalize();
936  }
937  else
938  {
939  // If the handle isn't normalized, brighten based on distance to origin
940  Handles.color = Color.Lerp(Color.gray, handleColorTangent, distanceToOrigin * 0.85f);
941  if (clamp)
942  {
943  // To indicate that we're at the clamped limit, make the handle 'pop' slightly larger
944  if (distanceToOrigin >= 0.98f)
945  {
946  Handles.color = Color.Lerp(handleColorTangent, Color.white, 0.5f);
947  handleSize *= 1.5f;
948  }
949  }
950  }
951 
952  // Draw a line from origin to origin + direction
953  Handles.DrawLine(origin, handlePosition);
954 
955  Quaternion rotation = Quaternion.identity;
956  if (vector != Vector3.zero)
957  {
958  rotation = Quaternion.LookRotation(vector);
959  }
960 
961  Vector3 newPosition = Handles.FreeMoveHandle(handlePosition, rotation, handleSize, Vector3.zero, Handles.DotHandleCap);
962  if (recordingUndo)
963  {
964  return vector;
965  }
966 
967  if (handlePosition != newPosition)
968  {
969  recordingUndo = true;
970  Undo.RegisterCompleteObjectUndo(target, target.name);
971  vector = (newPosition - origin).normalized;
972 
973  // If we normalize, we're done
974  // Otherwise, multiply the vector by the distance between origin and target
975  if (!normalize)
976  {
977  distanceToOrigin = Vector3.Distance(origin, newPosition) / handleLength;
978  if (clamp)
979  {
980  distanceToOrigin = Mathf.Clamp01(distanceToOrigin);
981  }
982  vector *= distanceToOrigin;
983  }
984  }
985  return vector;
986  }
987 
988  protected Quaternion RotationHandle(Vector3 position, Quaternion rotation, float handleSize = 0.2f, bool autoSize = true)
989  {
990  Handles.color = handleColorRotation;
991  if (autoSize)
992  {
993  handleSize = Mathf.Lerp(handleSize, HandleUtility.GetHandleSize(position) * handleSize, 0.75f);
994  }
995 
996  // Make rotation handles larger so they can overlay movement handles
997  Quaternion newRotation = Handles.FreeRotateHandle(rotation, position, handleSize * 2);
998  if (recordingUndo)
999  {
1000  return newRotation;
1001  }
1002 
1003  Handles.color = Handles.zAxisColor;
1004  Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(newRotation * Vector3.forward), handleSize * 2, EventType.Repaint);
1005  Handles.color = Handles.xAxisColor;
1006  Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(newRotation * Vector3.right), handleSize * 2, EventType.Repaint);
1007  Handles.color = Handles.yAxisColor;
1008  Handles.ArrowHandleCap(0, position, Quaternion.LookRotation(newRotation * Vector3.up), handleSize * 2, EventType.Repaint);
1009 
1010  if (rotation != newRotation)
1011  {
1012  recordingUndo = true;
1013  Undo.RegisterCompleteObjectUndo(target, target.name);
1014 
1015  rotation = newRotation;
1016  }
1017  return rotation;
1018  }
1019  #endregion
1020 
1021  #region editor prefs
1022 
1023  private static void SetEditorPref(string key, bool value)
1024  {
1025  EditorPrefs.SetBool(Application.productName + key, value);
1026  }
1027 
1028  private static bool GetEditorPref(string key, bool defaultValue)
1029  {
1030  if (EditorPrefs.HasKey(Application.productName + key))
1031  {
1032  return EditorPrefs.GetBool(Application.productName + key);
1033  }
1034 
1035  EditorPrefs.SetBool(Application.productName + key, defaultValue);
1036  return defaultValue;
1037  }
1038 
1039  #endregion
1040  }
1041 #endif
1042 }