#if ODIN_INSPECTOR using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector.Editor; using Sirenix.Utilities; using Sirenix.Utilities.Editor; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; public class OdinWatchWindow : OdinEditorWindow { [SerializeField] private List _properties = new List(); private static OdinWatchWindow _instance; private bool _repaintSheduled; [SerializeField] private float _labelWidth = 200; private bool _showSettings; [MenuItem("Tools/Odin Watch Window")] public static void ShowMenu() { _instance = GetWindow(); _instance.Show(); } [InitializeOnLoadMethod] private static void OnPropertyContextMenu() { EditorApplication.contextualPropertyMenu += (menu, property) => { property = property.Copy(); menu.AddItem(new GUIContent("Watch"), false, () => { ShowMenu(); PropertyTree tree = Sirenix.OdinInspector.Editor.PropertyTree.Create(new SerializedObject(property.serializedObject.targetObject)); TreeValuesHolder holder = _instance._properties.FirstOrDefault(o => o.Tree.WeakTargets[0] == property.serializedObject.targetObject); if (holder == null) { holder = new TreeValuesHolder(tree); _instance._properties.Add(holder); tree.OnPropertyValueChanged += TreeOnOnPropertyValueChanged; } holder.ValuePaths.Add(property.propertyPath); }); }; } private void PlayModeStateChanged(PlayModeStateChange obj) { foreach (TreeValuesHolder holder in _properties) { holder.CheckRefresh(); } } private void OnInspectorUpdate() { Repaint(); } public static void AddWatch(InspectorProperty property) { ShowMenu(); PropertyTree tree = Sirenix.OdinInspector.Editor.PropertyTree.Create(property.Tree.WeakTargets, new SerializedObject(property.Tree.UnitySerializedObject.targetObject)); TreeValuesHolder holder = _instance._properties.FirstOrDefault(o => o.Tree.WeakTargets[0] == property.Tree.WeakTargets[0]); if (holder == null) { holder = new TreeValuesHolder(tree); _instance._properties.Add(holder); tree.OnPropertyValueChanged += TreeOnOnPropertyValueChanged; } holder.ValuePaths.Add(property.Path); } private static void TreeOnOnPropertyValueChanged(InspectorProperty property, int selectionindex) { if (!_instance._repaintSheduled) _instance.Repaint(); _instance._repaintSheduled = true; } private void OnDisable() { string json = EditorJsonUtility.ToJson(this); EditorPrefs.SetString("OWW_props", json); } protected override void OnEnable() { _labelWidth = EditorPrefs.GetFloat("OWW_labelWidth", 200); string json = EditorPrefs.GetString("OWW_props", ""); EditorJsonUtility.FromJsonOverwrite(json, this); EditorApplication.playModeStateChanged -= PlayModeStateChanged; EditorApplication.playModeStateChanged += PlayModeStateChanged; wantsMouseMove = true; for (int i = 0; i < _properties.Count; i++) { TreeValuesHolder holder = _properties[i]; if (!holder.CheckRefresh()) { _properties.RemoveAt(i--); } } } protected override void OnGUI() { _repaintSheduled = false; GUILayout.BeginHorizontal(); if (GUILayout.Button("Clear")) { _properties.Clear(); } Rect settingsRect = GUILayoutUtility.GetRect(24, 24, GUILayout.ExpandWidth(false)).AlignLeft(20).AlignCenterY(20); if (SirenixEditorGUI.IconButton(settingsRect, _showSettings ? EditorIcons.SettingsCog.Inactive : EditorIcons.SettingsCog.Active, "Settings")) { _showSettings = !_showSettings; } GUILayout.EndHorizontal(); if (_showSettings) { GUILayout.BeginHorizontal(); GUILayout.Space(40); GUI.changed = false; Rect rect = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight, GUILayout.ExpandWidth(true)); _labelWidth = GUI.HorizontalSlider(rect, _labelWidth, rect.xMin, rect.xMax); if (GUI.changed) EditorPrefs.SetFloat("OWW_labelWidth", _labelWidth); EditorGUILayout.LabelField("Label Width", GUILayout.Width(70)); GUILayout.EndHorizontal(); } GUILayout.Space(5); bool first = true; if (_properties.Count == 0) { EditorGUILayout.LabelField("Right-click any property in an Inspector and select 'Watch' to make it show up here.", SirenixGUIStyles.MultiLineCenteredLabel); } GUIHelper.PushLabelWidth(_labelWidth - 30); for (int i = 0; i < _properties.Count; i++) { TreeValuesHolder holder = _properties[i]; holder.CheckRefresh(); if (!first) GUILayout.Space(5); first = false; Rect titleRect = SirenixEditorGUI.BeginBox(" " + holder.Tree.TargetType.Name); titleRect = titleRect.AlignTop(21); if (holder.ParentObject != null) { Rect alignRight = titleRect.AlignRight(200).AlignCenterY(16).AlignLeft(180); GUIHelper.PushGUIEnabled(false); SirenixEditorFields.UnityObjectField(alignRight, holder.ParentObject, typeof(GameObject), true); GUIHelper.PopGUIEnabled(); } if (SirenixEditorGUI.IconButton(titleRect.AlignRight(20).AlignCenterY(18), EditorIcons.X)) { _properties.RemoveAt(i--); } Rect titleDragDropRect = titleRect.AlignLeft(30).AlignCenter(20, 20); EditorIcons.List.Draw(titleDragDropRect); TreeValuesHolder treedragdrop = (TreeValuesHolder) DragAndDropUtilities.DragAndDropZone(titleDragDropRect, holder, typeof(TreeValuesHolder), false, false); if (treedragdrop != holder) { int treeDragDropIndex = _properties.IndexOf(treedragdrop); Swap(_properties, treeDragDropIndex, i); } if (holder.Tree.UnitySerializedObject?.targetObject == null) { EditorGUILayout.LabelField($"This component is no longer valid in the current context (loaded different scene?)", SirenixGUIStyles.MultiLineLabel); } else { InspectorUtilities.BeginDrawPropertyTree(holder.Tree, true); for (int index = 0; index < holder.ValuePaths.Count; index++) { string path = holder.ValuePaths[index]; GUILayout.BeginHorizontal(); Rect rect1 = GUILayoutUtility.GetRect(EditorGUIUtility.singleLineHeight + 5, EditorGUIUtility.singleLineHeight + 3, GUILayout.ExpandWidth(false)).AlignRight(EditorGUIUtility.singleLineHeight + 2); EditorIcons.List.Draw(rect1); ValueDragDropHolder dragdrop = (ValueDragDropHolder) DragAndDropUtilities.DragAndDropZone(rect1, new ValueDragDropHolder(holder, index), typeof(ValueDragDropHolder), false, false); if (dragdrop.TreeValuesHolder == holder && dragdrop.Index != index) { string ptemp = holder.ValuePaths[index]; holder.ValuePaths[index] = holder.ValuePaths[dragdrop.Index]; holder.ValuePaths[dragdrop.Index] = ptemp; } InspectorProperty propertyAtPath = holder.Tree.GetPropertyAtPath(path); if (propertyAtPath == null) propertyAtPath = holder.Tree.GetPropertyAtUnityPath(path); if (propertyAtPath != null) { propertyAtPath.Draw(); } else EditorGUILayout.LabelField($"Could not find property ({path})"); if (SirenixEditorGUI.IconButton(EditorIcons.X)) { holder.ValuePaths.RemoveAt(index--); if (holder.ValuePaths.Count == 0) _properties.RemoveAt(i--); } GUILayout.Space(3); GUILayout.EndHorizontal(); } InspectorUtilities.EndDrawPropertyTree(holder.Tree); } SirenixEditorGUI.EndBox(); } GUIHelper.PopLabelWidth(); } [Serializable] private class TreeValuesHolder { [NonSerialized] public PropertyTree Tree; [NonSerialized] public Object Target; public int InstanceID; public List ValuePaths = new List(); [NonSerialized] public Object ParentObject; public TreeValuesHolder(PropertyTree tree) { Tree = tree; Target = (Object) tree.WeakTargets[0]; InstanceID = Target.GetInstanceID(); GetParentObject(); } public bool CheckRefresh() { if (Tree == null ||Tree.SecretRootProperty == null || Target == null) { Target = EditorUtility.InstanceIDToObject(InstanceID); if (Target == null) return false; GetParentObject(); Tree = Sirenix.OdinInspector.Editor.PropertyTree.Create(new List { Target }, ParentObject != null ? new SerializedObject(ParentObject) : null); } return true; } private void GetParentObject() { ParentObject = (Target as MonoBehaviour); if (ParentObject == null) ParentObject = (Target as Component); if (ParentObject == null) ParentObject = Target as ScriptableObject; } } private struct ValueDragDropHolder { public TreeValuesHolder TreeValuesHolder; public int Index; public ValueDragDropHolder(TreeValuesHolder treeValuesHolder, int index) { TreeValuesHolder = treeValuesHolder; Index = index; } } private void Swap(IList list, int indexA, int indexB) { T tmp = list[indexA]; list[indexA] = list[indexB]; list[indexB] = tmp; } } [DrawerPriority(100, 0, 0)] public class OdinWatchWindowContextMenuDrawer : OdinValueDrawer, IDefinesGenericMenuItems { public void PopulateGenericMenu(InspectorProperty property, GenericMenu genericMenu) { genericMenu.AddItem(new GUIContent("Watch"), false, () => OdinWatchWindow.AddWatch(property)); } protected override void DrawPropertyLayout(GUIContent label) { this.CallNextDrawer(label); } } #endif