using System; using System.Text; using UnityEngine; using UnityEditor; using UnityEditorInternal; namespace Normal.Realtime { [InitializeOnLoad] [CustomEditor(typeof(RealtimeView))] public class RealtimeViewEditor : Editor { // Class private static bool __showAdvancedSettings = false; // Instance private RealtimeView realtimeView { get { return (RealtimeView)target; } } // TODO: Realtime should expose a state enum instead of having to hit the room directly I think. Or at least a bool for if we're connected or not private bool isOnline { get { if (realtimeView.realtime == null) return false; if (realtimeView.realtime.room == null) return false; return realtimeView.realtime.room.connectionState == Room.ConnectionState.Ready; } } private SerializedProperty realtimeProperty { get { return serializedObject.FindProperty("_realtime"); } } private SerializedProperty sceneViewUUIDProperty { get { return serializedObject.FindProperty("_sceneViewUUID"); } } private SerializedProperty sceneViewOwnedByCreatingClientDProperty { get { return serializedObject.FindProperty("_sceneViewOwnedByCreatingClient"); } } private SerializedProperty sceneViewPreventOwnershipTakeoverProperty { get { return serializedObject.FindProperty("_sceneViewPreventOwnershipTakeover"); } } private SerializedProperty sceneViewDestroyWhenOwnerOrLastClientLeavesProperty { get { return serializedObject.FindProperty("_sceneViewDestroyWhenOwnerOrLastClientLeaves"); } } // GUI private ReorderableList _components; private ReorderableList _childViews; static RealtimeViewEditor() { #if UNITY_2018_1_OR_NEWER EditorApplication.hierarchyChanged += RealtimeViewConfiguration._HierarchyDidChange; #else EditorApplication.hierarchyWindowChanged += RealtimeViewConfiguration._HierarchyDidChange; #endif } private void OnEnable() { __showAdvancedSettings = EditorPrefs.GetBool("Normal.RealtimeViewEditor.ShowAdvancedSettings"); _components = new ReorderableList(serializedObject, serializedObject.FindProperty("_components")); _components.displayAdd = false; _components.displayRemove = false; _components.draggable = false; _components.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, "Components"); }; _components.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { SerializedProperty componentProperty = _components.serializedProperty.GetArrayElementAtIndex(index); SerializedProperty componentComponentIDProperty = componentProperty.FindPropertyRelative("componentID"); SerializedProperty componentComponentProperty = componentProperty.FindPropertyRelative("component"); SerializedProperty componentComponentIDHasBeenUsedProperty = componentProperty.FindPropertyRelative("componentIDHasBeenUsed"); rect.y += 2; EditorGUI.BeginDisabledGroup(true); if (componentComponentProperty.objectReferenceValue == null && componentComponentIDProperty.boolValue) { // This field has been used before. Show that the component ID is deprecated. EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width - 30, EditorGUIUtility.singleLineHeight), "Deprecated View"); EditorGUI.LabelField(new Rect(rect.x + (rect.width - 24), rect.y, 20, EditorGUIUtility.singleLineHeight), "" + componentComponentIDProperty.intValue); } else { EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 30, EditorGUIUtility.singleLineHeight), componentComponentProperty, GUIContent.none); EditorGUI.PropertyField(new Rect(rect.x + (rect.width - 24), rect.y, 20, EditorGUIUtility.singleLineHeight), componentComponentIDProperty, GUIContent.none); } EditorGUI.EndDisabledGroup(); if (!componentComponentIDHasBeenUsedProperty.boolValue && componentComponentProperty.objectReferenceValue != null) { componentComponentIDHasBeenUsedProperty.boolValue = true; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } }; _components.onAddCallback = (ReorderableList list) => { int numberOfComponents = list.serializedProperty.arraySize; int nextAvailableComponentID = 1; if (numberOfComponents > 0) { int largestComponentID = 0; for (int i = 0; i < numberOfComponents; i++) { SerializedProperty component = list.serializedProperty.GetArrayElementAtIndex(i); int componentID = component.FindPropertyRelative("componentID").intValue; if (componentID > largestComponentID) largestComponentID = componentID; } nextAvailableComponentID = largestComponentID + 1; } list.serializedProperty.arraySize = numberOfComponents + 1; list.index = numberOfComponents; SerializedProperty newComponent = list.serializedProperty.GetArrayElementAtIndex(numberOfComponents); newComponent.FindPropertyRelative("componentID").intValue = nextAvailableComponentID; newComponent.FindPropertyRelative("component").objectReferenceValue = null; }; _childViews = new ReorderableList(serializedObject, serializedObject.FindProperty("_childViews")); _childViews.displayAdd = false; _childViews.displayRemove = false; _childViews.draggable = false; _childViews.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, "Children"); }; _childViews.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { SerializedProperty viewProperty = _childViews.serializedProperty.GetArrayElementAtIndex(index); SerializedProperty viewViewIDProperty = viewProperty.FindPropertyRelative("viewID"); SerializedProperty viewViewProperty = viewProperty.FindPropertyRelative("view"); SerializedProperty viewViewIDHasBeenUsedProperty = viewProperty.FindPropertyRelative("viewIDHasBeenUsed"); rect.y += 2; EditorGUI.BeginDisabledGroup(true); if (viewViewProperty.objectReferenceValue == null && viewViewIDHasBeenUsedProperty.boolValue) { // This field has been used before. Show that the view ID is deprecated. EditorGUI.LabelField(new Rect(rect.x, rect.y, rect.width - 30, EditorGUIUtility.singleLineHeight), "Deprecated View"); EditorGUI.LabelField(new Rect(rect.x + (rect.width - 24), rect.y, 20, EditorGUIUtility.singleLineHeight), "" + viewViewIDProperty.intValue); } else { EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 30, EditorGUIUtility.singleLineHeight), viewViewProperty, GUIContent.none); EditorGUI.PropertyField(new Rect(rect.x + (rect.width - 24), rect.y, 20, EditorGUIUtility.singleLineHeight), viewViewIDProperty, GUIContent.none); } EditorGUI.EndDisabledGroup(); if (!viewViewIDHasBeenUsedProperty.boolValue && viewViewProperty.objectReferenceValue != null) { viewViewIDHasBeenUsedProperty.boolValue = true; serializedObject.ApplyModifiedPropertiesWithoutUndo(); } }; _childViews.onAddCallback = (ReorderableList list) => { int numberOfChildViews = list.serializedProperty.arraySize; int nextAvailableChildViewID = 1; if (numberOfChildViews > 0) { int largestChildViewID = 0; for (int i = 0; i < numberOfChildViews; i++) { SerializedProperty view = list.serializedProperty.GetArrayElementAtIndex(i); int viewID = view.FindPropertyRelative("viewID").intValue; if (viewID > largestChildViewID) largestChildViewID = viewID; } nextAvailableChildViewID = largestChildViewID + 1; } list.serializedProperty.arraySize = numberOfChildViews + 1; list.index = numberOfChildViews; SerializedProperty newView = list.serializedProperty.GetArrayElementAtIndex(numberOfChildViews); newView.FindPropertyRelative("viewID").intValue = nextAvailableChildViewID; newView.FindPropertyRelative("view").objectReferenceValue = null; }; } public override void OnInspectorGUI() { GUILayout.Space(12); // Properties Start GUI.enabled = !Application.isPlaying; serializedObject.Update(); // Scene View bool isRootSceneView = realtimeView.isRootSceneView; byte[] sceneViewUUID = RealtimeViewConfiguration.GetSceneViewUUIDAsByteArray(sceneViewUUIDProperty); if (isRootSceneView) { // UUID string sceneViewUUIDString = ""; if (sceneViewUUID.Length == 16) { sceneViewUUIDString = new Guid(sceneViewUUID).ToString(); } else { StringBuilder sceneViewUUIDHexString = new StringBuilder(sceneViewUUID.Length * 2); foreach (byte value in sceneViewUUID) sceneViewUUIDHexString.AppendFormat("{0:x2}", value); sceneViewUUIDString = sceneViewUUIDHexString.ToString(); } GUILayout.Label("Scene View UUID: " + sceneViewUUIDString); GUILayout.Space(4); } // Components _components.DoLayoutList(); GUILayout.Space(4); // Child Views _childViews.DoLayoutList(); // Right now all advanced settings are scene view specific. If that changes, remove this if statement. if (isRootSceneView) { // Advanced Settings GUIStyle foldoutStyle = EditorStyles.foldout; FontStyle previousFoldoutFontStyle = foldoutStyle.fontStyle; //foldoutStyle.fontStyle = FontStyle.Bold; __showAdvancedSettings = EditorGUILayout.Foldout(__showAdvancedSettings, "Advanced Settings", foldoutStyle); foldoutStyle.fontStyle = previousFoldoutFontStyle; EditorPrefs.SetBool("Normal.RealtimeViewEditor.ShowAdvancedSettings", __showAdvancedSettings); if (__showAdvancedSettings) { if (isRootSceneView) { // Realtime realtimeProperty.objectReferenceValue = EditorGUILayout.ObjectField("Realtime Instance", realtimeProperty.objectReferenceValue, typeof(Realtime), true); // Ownership & lifetime sceneViewOwnedByCreatingClientDProperty.boolValue = EditorGUILayout.ToggleLeft("Owned By Creating Client", sceneViewOwnedByCreatingClientDProperty.boolValue); sceneViewPreventOwnershipTakeoverProperty.boolValue = EditorGUILayout.ToggleLeft("Prevent Ownership Takeover", sceneViewPreventOwnershipTakeoverProperty.boolValue); sceneViewDestroyWhenOwnerOrLastClientLeavesProperty.boolValue = EditorGUILayout.ToggleLeft("Destroy When Owner Or Last Client Leaves", sceneViewDestroyWhenOwnerOrLastClientLeavesProperty.boolValue); // Reset UUID if (GUILayout.Button("Reset View UUID")) { if (EditorUtility.DisplayDialog("Reset RealtimeView UUID", "YO!!! This is a dangerous operation! Be careful!\n\nThis RealtimeView will no longer be able to retrieve persistent state that was stored under the previous UUID.\n\nAre you sure you want to reset it?", "OK", "Cancel")) { sceneViewUUID = RealtimeViewConfiguration.SetSceneViewUUIDUsingByteArray(sceneViewUUIDProperty, Guid.NewGuid().ToByteArray()); // Update the map RealtimeViewConfiguration._UpdateRealtimeViewSceneViewUUID(realtimeView, sceneViewUUID); } } } GUILayout.Space(2); } } // Properties End serializedObject.ApplyModifiedProperties(); GUILayout.Space(2); // Ownership GUI.enabled = isOnline; GUILayout.Label("Owner: " + GetOwner()); GUILayout.BeginHorizontal(); if (GUILayout.Button("Request Ownership")) RequestOwnership(); if (GUILayout.Button("Clear Ownership")) ClearOwnership(); GUILayout.EndHorizontal(); GUILayout.Space(4); // Reset GUI.enabled = true; } string GetOwner() { if (!isOnline) return "Offline"; int ownerID = realtimeView.model.ownerID; // No owner if (ownerID == -1) return "None"; // Owned by the local client if (ownerID == realtimeView.realtime.clientID) return "Local client (clientID: " + ownerID + ")"; // Owned by a remote client return "Remote client (clientID: " + ownerID + ")"; } void RequestOwnership() { realtimeView.RequestOwnership(); } void ClearOwnership() { realtimeView.ClearOwnership(); } } }